3 * Iter Vehemens ad Necem (IVAN)
4 * Copyright (C) Timo Kiviluoto
5 * Released under the GNU General
8 * See LICENSING which should be included
9 * along with this file for more details
12 /* Compiled through charset.cpp */
15 #include <sys/types.h>
19 /* These statedata structs contain functions and values used for handling
20 * states. Remember to update them. All normal states must have
21 * PrintBeginMessage and PrintEndMessage functions and a Description string.
22 * BeginHandler, EndHandler, Handler (called each tick) and IsAllowed are
23 * optional, enter zero if the state doesn't need one. If the SECRET flag
24 * is set, Description is not shown in the panel without magical means.
25 * You can also set some source (SRC_*) and duration (DUR_*) flags, which
26 * control whether the state can be randomly activated in certain situations.
27 * These flags can be found in ivandef.h. RANDOMIZABLE sets all source
28 * & duration flags at once. */
30 const char *Description
;
32 void (character::*PrintBeginMessage
) () const;
33 void (character::*PrintEndMessage
) () const;
34 void (character::*BeginHandler
) ();
35 void (character::*EndHandler
) ();
36 void (character::*Handler
) ();
37 truth (character::*IsAllowed
) () const;
38 void (character::*SituationDangerModifier
) (double &) const;
42 const statedata StateData
[STATES
] =
50 &character::EndPolymorph
,
56 RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
),
57 &character::PrintBeginHasteMessage
,
58 &character::PrintEndHasteMessage
,
66 RANDOMIZABLE
&~SRC_GOOD
,
67 &character::PrintBeginSlowMessage
,
68 &character::PrintEndSlowMessage
,
76 RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
|SRC_GOOD
),
77 &character::PrintBeginPolymorphControlMessage
,
78 &character::PrintEndPolymorphControlMessage
,
87 &character::PrintBeginLifeSaveMessage
,
88 &character::PrintEndLifeSaveMessage
,
96 SECRET
|SRC_FOUNTAIN
|SRC_CONFUSE_READ
|DUR_FLAGS
,
97 &character::PrintBeginLycanthropyMessage
,
98 &character::PrintEndLycanthropyMessage
,
101 &character::LycanthropyHandler
,
103 &character::LycanthropySituationDangerModifier
106 RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
),
107 &character::PrintBeginInvisibilityMessage
,
108 &character::PrintEndInvisibilityMessage
,
109 &character::BeginInvisibility
, &character::EndInvisibility
,
115 RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
),
116 &character::PrintBeginInfraVisionMessage
,
117 &character::PrintEndInfraVisionMessage
,
118 &character::BeginInfraVision
,
119 &character::EndInfraVision
,
125 RANDOMIZABLE
&~SRC_EVIL
,
126 &character::PrintBeginESPMessage
,
127 &character::PrintEndESPMessage
,
128 &character::BeginESP
,
136 &character::PrintBeginPoisonedMessage
,
137 &character::PrintEndPoisonedMessage
,
140 &character::PoisonedHandler
,
141 &character::CanBePoisoned
,
142 &character::PoisonedSituationDangerModifier
145 SECRET
|(RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_GOOD
)),
146 &character::PrintBeginTeleportMessage
,
147 &character::PrintEndTeleportMessage
,
150 &character::TeleportHandler
,
155 SECRET
|(RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_GOOD
)),
156 &character::PrintBeginPolymorphMessage
,
157 &character::PrintEndPolymorphMessage
,
160 &character::PolymorphHandler
,
162 &character::PolymorphingSituationDangerModifier
165 RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
),
166 &character::PrintBeginTeleportControlMessage
,
167 &character::PrintEndTeleportControlMessage
,
176 &character::PrintBeginPanicMessage
,
177 &character::PrintEndPanicMessage
,
178 &character::BeginPanic
,
179 &character::EndPanic
,
181 &character::CanPanic
,
182 &character::PanicSituationDangerModifier
185 SECRET
|(RANDOMIZABLE
&~(DUR_PERMANENT
|SRC_GOOD
)),
186 &character::PrintBeginConfuseMessage
,
187 &character::PrintEndConfuseMessage
,
191 &character::CanBeConfused
,
192 &character::ConfusedSituationDangerModifier
195 SECRET
|(RANDOMIZABLE
&~DUR_TEMPORARY
),
196 &character::PrintBeginParasitizedMessage
,
197 &character::PrintEndParasitizedMessage
,
200 &character::ParasitizedHandler
,
201 &character::CanBeParasitized
,
202 &character::ParasitizedSituationDangerModifier
206 &character::PrintBeginSearchingMessage
,
207 &character::PrintEndSearchingMessage
,
210 &character::SearchingHandler
,
215 SECRET
|(RANDOMIZABLE
&~(SRC_GOOD
|SRC_EVIL
)),
216 &character::PrintBeginGasImmunityMessage
,
217 &character::PrintEndGasImmunityMessage
,
225 RANDOMIZABLE
&~SRC_EVIL
,
226 &character::PrintBeginLevitationMessage
,
227 &character::PrintEndLevitationMessage
,
229 &character::EndLevitation
,
235 SECRET
|(RANDOMIZABLE
&~DUR_TEMPORARY
),
236 &character::PrintBeginLeprosyMessage
,
237 &character::PrintEndLeprosyMessage
,
238 &character::BeginLeprosy
,
239 &character::EndLeprosy
,
240 &character::LeprosyHandler
,
242 &character::LeprosySituationDangerModifier
245 SRC_FOUNTAIN
|SRC_CONFUSE_READ
|DUR_FLAGS
,
246 &character::PrintBeginHiccupsMessage
,
247 &character::PrintEndHiccupsMessage
,
250 &character::HiccupsHandler
,
252 &character::HiccupsSituationDangerModifier
255 DUR_FLAGS
, //perhaps no fountain, no secret and no confuse read either: SECRET|SRC_FOUNTAIN|SRC_CONFUSE_READ|
256 &character::PrintBeginVampirismMessage
,
257 &character::PrintEndVampirismMessage
,
260 &character::VampirismHandler
,
262 &character::VampirismSituationDangerModifier
265 SECRET
|(RANDOMIZABLE
&~(SRC_MUSHROOM
|SRC_EVIL
)),
266 &character::PrintBeginDetectMessage
,
267 &character::PrintEndDetectMessage
,
270 &character::DetectHandler
,
277 characterprototype::characterprototype (const characterprototype
*Base
, characterspawner Spawner
,
278 charactercloner Cloner
, cchar
*ClassID
)
284 Index
= protocontainer
<character
>::Add(this);
288 std::list
<character
*>::iterator
character::GetTeamIterator () { return TeamIterator
; }
289 void character::SetTeamIterator (std::list
<character
*>::iterator What
) { TeamIterator
= What
; }
290 void character::CreateInitialEquipment (int SpecialFlags
) { AddToInventory(DataBase
->Inventory
, SpecialFlags
); }
291 void character::EditAP (sLong What
) { AP
= Limit
<sLong
>(AP
+What
, -12000, 1200); }
292 int character::GetRandomStepperBodyPart () const { return TORSO_INDEX
; }
293 void character::GainIntrinsic (sLong What
) { BeginTemporaryState(What
, PERMANENT
); }
294 truth
character::IsUsingArms () const { return GetAttackStyle() & USE_ARMS
; }
295 truth
character::IsUsingLegs () const { return GetAttackStyle() & USE_LEGS
; }
296 truth
character::IsUsingHead () const { return GetAttackStyle() & USE_HEAD
; }
297 void character::CalculateAllowedWeaponSkillCategories () { AllowedWeaponSkillCategories
= MARTIAL_SKILL_CATEGORIES
; }
298 festring
character::GetBeVerb () const { return IsPlayer() ? CONST_S("are") : CONST_S("is"); }
299 void character::SetEndurance (int What
) { BaseExperience
[ENDURANCE
] = What
* EXP_MULTIPLIER
; }
300 void character::SetPerception (int What
) { BaseExperience
[PERCEPTION
] = What
* EXP_MULTIPLIER
; }
301 void character::SetIntelligence (int What
) { BaseExperience
[INTELLIGENCE
] = What
* EXP_MULTIPLIER
; }
302 void character::SetWisdom (int What
) { BaseExperience
[WISDOM
] = What
* EXP_MULTIPLIER
; }
303 void character::SetWillPower (int What
) { BaseExperience
[WILL_POWER
] = What
* EXP_MULTIPLIER
; }
304 void character::SetCharisma (int What
) { BaseExperience
[CHARISMA
] = What
* EXP_MULTIPLIER
; }
305 void character::SetMana (int What
) { BaseExperience
[MANA
] = What
* EXP_MULTIPLIER
; }
306 truth
character::IsOnGround () const { return MotherEntity
&& MotherEntity
->IsOnGround(); }
307 truth
character::LeftOversAreUnique () const { return GetArticleMode() || AssignedName
.GetSize(); }
308 truth
character::HomeDataIsValid () const { return (HomeData
&& HomeData
->Level
== GetLSquareUnder()->GetLevelIndex() && HomeData
->Dungeon
== GetLSquareUnder()->GetDungeonIndex()); }
309 void character::SetHomePos (v2 Pos
) { HomeData
->Pos
= Pos
; }
310 cchar
*character::FirstPersonUnarmedHitVerb () const { return "hit"; }
311 cchar
*character::FirstPersonCriticalUnarmedHitVerb () const { return "critically hit"; }
312 cchar
*character::ThirdPersonUnarmedHitVerb () const { return "hits"; }
313 cchar
*character::ThirdPersonCriticalUnarmedHitVerb () const { return "critically hits"; }
314 cchar
*character::FirstPersonKickVerb () const { return "kick"; }
315 cchar
*character::FirstPersonCriticalKickVerb () const { return "critically kick"; }
316 cchar
*character::ThirdPersonKickVerb () const { return "kicks"; }
317 cchar
*character::ThirdPersonCriticalKickVerb () const { return "critically kicks"; }
318 cchar
*character::FirstPersonBiteVerb () const { return "bite"; }
319 cchar
*character::FirstPersonCriticalBiteVerb () const { return "critically bite"; }
320 cchar
*character::ThirdPersonBiteVerb () const { return "bites"; }
321 cchar
*character::ThirdPersonCriticalBiteVerb () const { return "critically bites"; }
322 cchar
*character::UnarmedHitNoun () const { return "attack"; }
323 cchar
*character::KickNoun () const { return "kick"; }
324 cchar
*character::BiteNoun () const { return "attack"; }
325 cchar
*character::GetEquipmentName (int) const { return ""; }
326 const std::list
<feuLong
> &character::GetOriginalBodyPartID (int I
) const { return OriginalBodyPartID
[I
]; }
327 square
*character::GetNeighbourSquare (int I
) const { return GetSquareUnder()->GetNeighbourSquare(I
); }
328 lsquare
*character::GetNeighbourLSquare (int I
) const { return static_cast<lsquare
*>(GetSquareUnder())->GetNeighbourLSquare(I
); }
329 wsquare
*character::GetNeighbourWSquare (int I
) const { return static_cast<wsquare
*>(GetSquareUnder())->GetNeighbourWSquare(I
); }
330 god
*character::GetMasterGod () const { return game::GetGod(GetConfig()); }
331 col16
character::GetBodyPartColorA (int, truth
) const { return GetSkinColor(); }
332 col16
character::GetBodyPartColorB (int, truth
) const { return GetTorsoMainColor(); }
333 col16
character::GetBodyPartColorC (int, truth
) const { return GetBeltColor(); } // sorry...
334 col16
character::GetBodyPartColorD (int, truth
) const { return GetTorsoSpecialColor(); }
335 int character::GetRandomApplyBodyPart () const { return TORSO_INDEX
; }
336 truth
character::MustBeRemovedFromBone () const { return IsUnique() && !CanBeGenerated(); }
337 truth
character::IsPet () const { return GetTeam()->GetID() == PLAYER_TEAM
; }
338 character
* character::GetLeader () const { return GetTeam()->GetLeader(); }
339 int character::GetMoveType () const { return (!StateIsActivated(LEVITATION
) ? DataBase
->MoveType
: DataBase
->MoveType
| FLY
); }
340 festring
character::GetZombieDescription () const { return " of "+GetName(INDEFINITE
); }
341 truth
character::BodyPartCanBeSevered (int I
) const { return I
; }
342 truth
character::HasBeenSeen () const { return DataBase
->Flags
& HAS_BEEN_SEEN
; }
343 truth
character::IsTemporary () const { return GetTorso()->GetLifeExpectancy(); }
344 cchar
*character::GetNormalDeathMessage () const { return "killed @k"; }
347 int characterdatabase::*ExpPtr
[ATTRIBUTES
] = {
348 &characterdatabase::DefaultEndurance
,
349 &characterdatabase::DefaultPerception
,
350 &characterdatabase::DefaultIntelligence
,
351 &characterdatabase::DefaultWisdom
,
352 &characterdatabase::DefaultWillPower
,
353 &characterdatabase::DefaultCharisma
,
354 &characterdatabase::DefaultMana
,
355 &characterdatabase::DefaultArmStrength
,
356 &characterdatabase::DefaultLegStrength
,
357 &characterdatabase::DefaultDexterity
,
358 &characterdatabase::DefaultAgility
362 contentscript
<item
> characterdatabase::*EquipmentDataPtr
[EQUIPMENT_DATAS
] = {
363 &characterdatabase::Helmet
,
364 &characterdatabase::Amulet
,
365 &characterdatabase::Cloak
,
366 &characterdatabase::BodyArmor
,
367 &characterdatabase::Belt
,
368 &characterdatabase::RightWielded
,
369 &characterdatabase::LeftWielded
,
370 &characterdatabase::RightRing
,
371 &characterdatabase::LeftRing
,
372 &characterdatabase::RightGauntlet
,
373 &characterdatabase::LeftGauntlet
,
374 &characterdatabase::RightBoot
,
375 &characterdatabase::LeftBoot
379 character::character (ccharacter
&Char
) :
380 entity(Char
), id(Char
), NP(Char
.NP
), AP(Char
.AP
),
381 TemporaryState(Char
.TemporaryState
&~POLYMORPHED
),
382 Team(Char
.Team
), GoingTo(ERROR_V2
), Money(0),
383 AssignedName(Char
.AssignedName
), Action(0),
384 DataBase(Char
.DataBase
), MotherEntity(0),
385 PolymorphBackup(0), EquipmentState(0), SquareUnder(0),
386 AllowedWeaponSkillCategories(Char
.AllowedWeaponSkillCategories
),
387 BodyParts(Char
.BodyParts
),
388 RegenerationCounter(Char
.RegenerationCounter
),
389 SquaresUnder(Char
.SquaresUnder
), LastAcidMsgMin(0),
390 Stamina(Char
.Stamina
), MaxStamina(Char
.MaxStamina
),
391 BlocksSinceLastTurn(0), GenerationDanger(Char
.GenerationDanger
),
392 CommandFlags(Char
.CommandFlags
), WarnFlags(0),
393 ScienceTalks(Char
.ScienceTalks
), TrapData(0), CounterToMindWormHatch(0)
397 Flags
|= C_INITIALIZING
|C_IN_NO_MSG_MODE
;
398 Stack
= new stack(0, this, HIDDEN
);
399 for (c
= 0; c
< STATES
; ++c
) TemporaryStateCounter
[c
] = Char
.TemporaryStateCounter
[c
];
400 if (Team
) TeamIterator
= Team
->Add(this);
401 for (c
= 0; c
< BASE_ATTRIBUTES
; ++c
) BaseExperience
[c
] = Char
.BaseExperience
[c
];
402 BodyPartSlot
= new bodypartslot
[BodyParts
];
403 OriginalBodyPartID
= new std::list
<feuLong
>[BodyParts
];
404 CWeaponSkill
= new cweaponskill
[AllowedWeaponSkillCategories
];
405 SquareUnder
= new square
*[SquaresUnder
];
406 if (SquaresUnder
== 1) *SquareUnder
= 0; else memset(SquareUnder
, 0, SquaresUnder
*sizeof(square
*));
407 for (c
= 0; c
< BodyParts
; ++c
) {
408 BodyPartSlot
[c
].SetMaster(this);
409 bodypart
*CharBodyPart
= Char
.GetBodyPart(c
);
410 OriginalBodyPartID
[c
] = Char
.OriginalBodyPartID
[c
];
412 bodypart
*BodyPart
= static_cast<bodypart
*>(CharBodyPart
->Duplicate());
413 SetBodyPart(c
, BodyPart
);
414 BodyPart
->CalculateEmitation();
417 for (c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) CWeaponSkill
[c
] = Char
.CWeaponSkill
[c
];
418 HomeData
= Char
.HomeData
? new homedata(*Char
.HomeData
) : 0;
419 ID
= game::CreateNewCharacterID(this);
423 character::character () :
424 entity(HAS_BE
), NP(50000), AP(0), TemporaryState(0), Team(0),
425 GoingTo(ERROR_V2
), Money(0), Action(0), MotherEntity(0),
426 PolymorphBackup(0), EquipmentState(0), SquareUnder(0),
427 RegenerationCounter(0), HomeData(0), LastAcidMsgMin(0),
428 BlocksSinceLastTurn(0), GenerationDanger(DEFAULT_GENERATION_DANGER
),
429 WarnFlags(0), ScienceTalks(0), TrapData(0), CounterToMindWormHatch(0)
431 Stack
= new stack(0, this, HIDDEN
);
435 character::~character () {
436 if (Action
) delete Action
;
437 if (Team
) Team
->Remove(GetTeamIterator());
439 for (int c
= 0; c
< BodyParts
; ++c
) delete GetBodyPart(c
);
440 delete [] BodyPartSlot
;
441 delete [] OriginalBodyPartID
;
442 delete PolymorphBackup
;
443 delete [] SquareUnder
;
444 delete [] CWeaponSkill
;
446 for (trapdata
*T
= TrapData
; T
;) {
451 game::RemoveCharacterID(ID
);
455 void character::Hunger () {
456 switch (GetBurdenState()) {
460 EditExperience(LEG_STRENGTH
, 150, 1 << 2);
461 EditExperience(AGILITY
, -50, 1 << 2);
465 EditExperience(LEG_STRENGTH
, 75, 1 << 1);
466 EditExperience(AGILITY
, -25, 1 << 1);
473 switch (GetHungerState()) {
475 EditExperience(ARM_STRENGTH
, -75, 1 << 3);
476 EditExperience(LEG_STRENGTH
, -75, 1 << 3);
479 EditExperience(ARM_STRENGTH
, -50, 1 << 2);
480 EditExperience(LEG_STRENGTH
, -50, 1 << 2);
483 EditExperience(ARM_STRENGTH
, -25, 1 << 1);
484 EditExperience(LEG_STRENGTH
, -25, 1 << 1);
487 EditExperience(AGILITY
, -25, 1 << 1);
490 EditExperience(AGILITY
, -50, 1 << 2);
493 EditExperience(AGILITY
, -75, 1 << 3);
496 CheckStarvationDeath(CONST_S("starved to death"));
500 int character::TakeHit (character
*Enemy
, item
*Weapon
, bodypart
*EnemyBodyPart
, v2 HitPos
, double Damage
,
501 double ToHitValue
, int Success
, int Type
, int GivenDir
, truth Critical
, truth ForceHit
)
504 game::ClearEventData();
505 game::mActor
= Enemy
;
506 game::mResult
= DID_NO_DAMAGE
;
507 if (game::RunOnCharEvent(this, CONST_S("take_hit"))) { game::ClearEventData(); return game::mResult
; }
508 game::ClearEventData();
509 int Dir
= Type
== BITE_ATTACK
? YOURSELF
: GivenDir
;
510 double DodgeValue
= GetDodgeValue();
511 if (!Enemy
->IsPlayer() && GetAttackWisdomLimit() != NO_LIMIT
) Enemy
->EditExperience(WISDOM
, 75, 1 << 13);
512 if (!Enemy
->CanBeSeenBy(this)) ToHitValue
*= 2;
513 if (!CanBeSeenBy(Enemy
)) DodgeValue
*= 2;
514 if (Enemy
->StateIsActivated(CONFUSED
)) ToHitValue
*= 0.75;
516 switch (Enemy
->GetTirednessState()) {
522 switch (GetTirednessState()) {
530 if (!IsRetreating()) SetGoingTo(Enemy
->GetPos());
531 else SetGoingTo(GetPos()-((Enemy
->GetPos()-GetPos())<<4));
532 if (!Enemy
->IsRetreating()) Enemy
->SetGoingTo(GetPos());
533 else Enemy
->SetGoingTo(Enemy
->GetPos()-((GetPos()-Enemy
->GetPos())<<4));
536 /* Effectively, the average chance to hit is 100% / (DV/THV + 1). */
537 if (RAND() % int(100+ToHitValue
/DodgeValue
*(100+Success
)) < 100 && !Critical
&& !ForceHit
) {
538 Enemy
->AddMissMessage(this);
539 EditExperience(AGILITY
, 150, 1 << 7);
540 EditExperience(PERCEPTION
, 75, 1 << 7);
541 if (Enemy
->CanBeSeenByPlayer())
542 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
544 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
548 int TrueDamage
= int(Damage
*(100+Success
)/100)+(RAND()%3 ? 1 : 0);
550 TrueDamage
+= TrueDamage
>> 1;
554 int BodyPart
= ChooseBodyPartToReceiveHit(ToHitValue
, DodgeValue
);
558 Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonCriticalUnarmedHitVerb(), Enemy
->ThirdPersonCriticalUnarmedHitVerb(), BodyPart
);
561 Enemy
->AddWeaponHitMessage(this, Weapon
, BodyPart
, true);
564 Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonCriticalKickVerb(), Enemy
->ThirdPersonCriticalKickVerb(), BodyPart
);
567 Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonCriticalBiteVerb(), Enemy
->ThirdPersonCriticalBiteVerb(), BodyPart
);
573 Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonUnarmedHitVerb(), Enemy
->ThirdPersonUnarmedHitVerb(), BodyPart
);
576 Enemy
->AddWeaponHitMessage(this, Weapon
, BodyPart
, false);
579 Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonKickVerb(), Enemy
->ThirdPersonKickVerb(), BodyPart
);
582 Enemy
->AddPrimitiveHitMessage(this, Enemy
->FirstPersonBiteVerb(), Enemy
->ThirdPersonBiteVerb(), BodyPart
);
587 if (!Critical
&& TrueDamage
&& Enemy
->AttackIsBlockable(Type
)) {
588 TrueDamage
= CheckForBlock(Enemy
, Weapon
, ToHitValue
, TrueDamage
, Success
, Type
);
589 if (!TrueDamage
|| (Weapon
&& !Weapon
->Exists())) {
590 if (Enemy
->CanBeSeenByPlayer())
591 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
593 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
598 int WeaponSkillHits
= CalculateWeaponSkillHits(Enemy
);
599 int DoneDamage
= ReceiveBodyPartDamage(Enemy
, TrueDamage
, PHYSICAL_DAMAGE
, BodyPart
, Dir
, false, Critical
, true, Type
== BITE_ATTACK
&& Enemy
->BiteCapturesBodyPart());
600 truth Succeeded
= (GetBodyPart(BodyPart
) && HitEffect(Enemy
, Weapon
, HitPos
, Type
, BodyPart
, Dir
, !DoneDamage
)) || DoneDamage
;
601 if (Succeeded
) Enemy
->WeaponSkillHit(Weapon
, Type
, WeaponSkillHits
);
604 if (Weapon
->Exists() && DoneDamage
< TrueDamage
) Weapon
->ReceiveDamage(Enemy
, TrueDamage
-DoneDamage
, PHYSICAL_DAMAGE
);
605 if (Weapon
->Exists() && DoneDamage
&& SpillsBlood() && GetBodyPart(BodyPart
) &&
606 (GetBodyPart(BodyPart
)->IsAlive() || GetBodyPart(BodyPart
)->GetMainMaterial()->IsLiquid()))
607 Weapon
->SpillFluid(0, CreateBlood(15+RAND()%15));
610 if (Enemy
->AttackIsBlockable(Type
)) SpecialBodyDefenceEffect(Enemy
, EnemyBodyPart
, Type
);
613 if (Enemy
->CanBeSeenByPlayer())
614 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
616 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
618 return DID_NO_DAMAGE
;
621 if (CheckDeath(GetNormalDeathMessage(), Enemy
, Enemy
->IsPlayer() ? FORCE_MSG
: 0)) return HAS_DIED
;
623 if (Enemy
->CanBeSeenByPlayer())
624 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
626 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
632 struct svpriorityelement
{
633 svpriorityelement (int BodyPart
, int StrengthValue
) : BodyPart(BodyPart
), StrengthValue(StrengthValue
) {}
634 bool operator < (const svpriorityelement
&AnotherPair
) const { return StrengthValue
> AnotherPair
.StrengthValue
; }
640 int character::ChooseBodyPartToReceiveHit (double ToHitValue
, double DodgeValue
) {
641 if (BodyParts
== 1) return 0;
642 std::priority_queue
<svpriorityelement
> SVQueue
;
643 for (int c
= 0; c
< BodyParts
; ++c
) {
644 bodypart
*BodyPart
= GetBodyPart(c
);
645 if (BodyPart
&& (BodyPart
->GetHP() != 1 || BodyPart
->CanBeSevered(PHYSICAL_DAMAGE
)))
646 SVQueue
.push(svpriorityelement(c
, ModifyBodyPartHitPreference(c
, BodyPart
->GetStrengthValue()+BodyPart
->GetHP())));
648 while (SVQueue
.size()) {
649 svpriorityelement E
= SVQueue
.top();
650 int ToHitPercentage
= int(GLOBAL_WEAK_BODYPART_HIT_MODIFIER
*ToHitValue
*GetBodyPart(E
.BodyPart
)->GetBodyPartVolume()/(DodgeValue
*GetBodyVolume()));
651 ToHitPercentage
= ModifyBodyPartToHitChance(E
.BodyPart
, ToHitPercentage
);
652 if (ToHitPercentage
< 1) ToHitPercentage
= 1;
653 else if (ToHitPercentage
> 95) ToHitPercentage
= 95;
654 if (ToHitPercentage
> RAND()%100) return E
.BodyPart
;
661 void character::Be () {
662 if (game::ForceJumpToPlayerBe()) {
663 if (!IsPlayer()) return;
664 game::SetForceJumpToPlayerBe(false);
666 truth ForceBe
= HP
!= MaxHP
|| AllowSpoil();
667 for (int c
= 0; c
< BodyParts
; ++c
) {
668 bodypart
*BodyPart
= GetBodyPart(c
);
669 if (BodyPart
&& (ForceBe
|| BodyPart
->NeedsBe())) BodyPart
->Be();
672 if (!IsEnabled()) return;
673 if (GetTeam() == PLAYER
->GetTeam()) {
674 for (int c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) {
675 if (CWeaponSkill
[c
].Tick() && IsPlayer()) CWeaponSkill
[c
].AddLevelDownMessage(c
);
680 if (GetHungerState() == STARVING
&& !(RAND()%50)) LoseConsciousness(250+RAND_N(250), true);
681 if (!Action
|| Action
->AllowFoodConsumption()) Hunger();
683 if (Stamina
!= MaxStamina
) RegenerateStamina();
684 if (HP
!= MaxHP
) Regenerate();
685 if (Action
&& AP
>= 1000) ActionAutoTermination();
686 if (Action
&& AP
>= 1000) {
688 if (!IsEnabled()) return;
690 EditAP(GetStateAPGain(100));
694 SpecialTurnHandler();
695 BlocksSinceLastTurn
= 0;
697 static int Timer
= 0;
699 if (ivanconfig::GetAutoSaveInterval() && !GetAction() && ++Timer
>= ivanconfig::GetAutoSaveInterval()) {
700 //fprintf(stderr, "autosaving..."); fflush(stderr);
701 game::Save(game::GetAutoSaveFileName());
702 //fprintf(stderr, "done\n"); fflush(stderr);
705 game::CalculateNextDanger();
706 if (!StateIsActivated(POLYMORPHED
)) game::UpdatePlayerAttributeAverage();
707 if (!game::IsInWilderness()) Search(GetAttribute(PERCEPTION
));
711 if (Action
->ShowEnvironment()) {
712 static int Counter
= 0;
713 if (++Counter
== 10) {
714 game::DrawEverything();
718 msgsystem::ThyMessagesAreNowOld();
719 if (Action
->IsVoluntary() && READ_KEY()) Action
->Terminate(false);
722 if (!Action
&& !game::IsInWilderness()) GetAICommand();
728 void character::Move (v2 MoveTo
, truth TeleportMove
, truth Run
) {
729 if (!IsEnabled()) return;
730 /* Test whether the player is stuck to something */
731 if (!TeleportMove
&& !TryToUnStickTraps(MoveTo
-GetPos())) return;
732 if (Run
&& !IsPlayer() && TorsoIsAlive() &&
733 (Stamina
<= 10000/Max(GetAttribute(LEG_STRENGTH
), 1) || (!StateIsActivated(PANIC
) && Stamina
< MaxStamina
>>2))) {
737 if (GetBurdenState() != OVER_LOADED
|| TeleportMove
) {
738 lsquare
*OldSquareUnder
[MAX_SQUARES_UNDER
];
740 if (!game::IsInWilderness()) {
743 area
*ca
= GetSquareUnder()->GetArea();
745 for (int f
= 0; f
< 8; ++f
) {
746 v2 np
= GetPos()+game::GetMoveVector(f
);
748 if (np
.X
>= 0 && np
.Y
>= 0 && np
.X
< ca
->GetXSize() && np
.Y
< ca
->GetYSize()) {
749 lsquare
*sq
= static_cast<lsquare
*>(ca
->GetSquare(np
.X
, np
.Y
));
756 for (int c
= 0; c
< GetSquaresUnder(); ++c
) OldSquareUnder
[c
] = GetLSquareUnder(c
);
761 /* Multitiled creatures should behave differently, maybe? */
763 int ED
= GetSquareUnder()->GetEntryDifficulty(), Base
= 10000;
765 EditAP(-GetMoveAPRequirement(ED
)>>1);
767 EditExperience(AGILITY
, 125, ED
<<7);
769 switch (GetHungerState()) {
770 case SATIATED
: Base
= 11000; break;
771 case BLOATED
: Base
= 12500; break;
772 case OVER_FED
: Base
= 15000; break;
775 EditStamina(-Base
/Max(GetAttribute(LEG_STRENGTH
), 1), true);
777 int ED
= GetSquareUnder()->GetEntryDifficulty();
779 EditAP(-GetMoveAPRequirement(ED
));
781 EditExperience(AGILITY
, 75, ED
<<7);
784 if (IsPlayer()) ShowNewPosInfo();
785 if (IsPlayer() && !game::IsInWilderness()) GetStackUnder()->SetSteppedOn(true);
786 if (!game::IsInWilderness()) SignalStepFrom(OldSquareUnder
);
789 cchar
*CrawlVerb
= StateIsActivated(LEVITATION
) ? "float" : "crawl";
791 ADD_MESSAGE("You try very hard to %s forward. But your load is too heavy.", CrawlVerb
);
798 void character::GetAICommand () {
799 SeekLeader(GetLeader());
800 if (FollowLeader(GetLeader())) return;
801 if (CheckForEnemies(true, true, true)) return;
802 if (CheckForUsefulItemsOnGround()) return;
803 if (CheckForDoors()) return;
804 if (CheckSadism()) return;
805 if (MoveRandomly()) return;
810 truth
character::MoveTowardsTarget (truth Run
) {
815 if (!Route
.empty()) {
818 } else TPos
= GoingTo
;
820 MoveTo
[0] = v2(0, 0);
821 MoveTo
[1] = v2(0, 0);
822 MoveTo
[2] = v2(0, 0);
824 if (TPos
.X
< Pos
.X
) {
825 if (TPos
.Y
< Pos
.Y
) {
826 MoveTo
[0] = v2(-1, -1);
827 MoveTo
[1] = v2(-1, 0);
828 MoveTo
[2] = v2( 0, -1);
829 } else if (TPos
.Y
== Pos
.Y
) {
830 MoveTo
[0] = v2(-1, 0);
831 MoveTo
[1] = v2(-1, -1);
832 MoveTo
[2] = v2(-1, 1);
833 } else if (TPos
.Y
> Pos
.Y
) {
834 MoveTo
[0] = v2(-1, 1);
835 MoveTo
[1] = v2(-1, 0);
836 MoveTo
[2] = v2( 0, 1);
838 } else if (TPos
.X
== Pos
.X
) {
839 if (TPos
.Y
< Pos
.Y
) {
840 MoveTo
[0] = v2( 0, -1);
841 MoveTo
[1] = v2(-1, -1);
842 MoveTo
[2] = v2( 1, -1);
843 } else if (TPos
.Y
== Pos
.Y
) {
846 } else if (TPos
.Y
> Pos
.Y
) {
847 MoveTo
[0] = v2( 0, 1);
848 MoveTo
[1] = v2(-1, 1);
849 MoveTo
[2] = v2( 1, 1);
851 } else if (TPos
.X
> Pos
.X
) {
852 if (TPos
.Y
< Pos
.Y
) {
853 MoveTo
[0] = v2(1, -1);
854 MoveTo
[1] = v2(1, 0);
855 MoveTo
[2] = v2(0, -1);
856 } else if (TPos
.Y
== Pos
.Y
) {
857 MoveTo
[0] = v2(1, 0);
858 MoveTo
[1] = v2(1, -1);
859 MoveTo
[2] = v2(1, 1);
860 } else if (TPos
.Y
> Pos
.Y
) {
861 MoveTo
[0] = v2(1, 1);
862 MoveTo
[1] = v2(1, 0);
863 MoveTo
[2] = v2(0, 1);
867 v2 ModifiedMoveTo
= ApplyStateModification(MoveTo
[0]);
869 if (TryMove(ModifiedMoveTo
, true, Run
)) return true;
871 int L
= (Pos
-TPos
).GetManhattanLength();
873 if (RAND()&1) Swap(MoveTo
[1], MoveTo
[2]);
875 if (Pos
.IsAdjacent(TPos
)) {
880 if ((Pos
+MoveTo
[1]-TPos
).GetManhattanLength() <= L
&& TryMove(ApplyStateModification(MoveTo
[1]), true, Run
)) return true;
881 if ((Pos
+MoveTo
[2]-TPos
).GetManhattanLength() <= L
&& TryMove(ApplyStateModification(MoveTo
[2]), true, Run
)) return true;
882 Illegal
.insert(Pos
+ModifiedMoveTo
);
883 if (CreateRoute()) return true;
888 int character::CalculateNewSquaresUnder (lsquare
**NewSquare
, v2 Pos
) const {
889 if (GetLevel()->IsValidPos(Pos
)) {
890 *NewSquare
= GetNearLSquare(Pos
);
897 truth
character::TryMove (v2 MoveVector
, truth Important
, truth Run
) {
898 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
899 character
*Pet
[MAX_SQUARES_UNDER
];
900 character
*Neutral
[MAX_SQUARES_UNDER
];
901 character
*Hostile
[MAX_SQUARES_UNDER
];
902 v2 PetPos
[MAX_SQUARES_UNDER
];
903 v2 NeutralPos
[MAX_SQUARES_UNDER
];
904 v2 HostilePos
[MAX_SQUARES_UNDER
];
905 v2 MoveTo
= GetPos()+MoveVector
;
906 int Direction
= game::GetDirectionForVector(MoveVector
);
907 if (Direction
== DIR_ERROR
) ABORT("Direction fault.");
908 if (!game::IsInWilderness()) {
909 int Squares
= CalculateNewSquaresUnder(MoveToSquare
, MoveTo
);
914 for (int c
= 0; c
< Squares
; ++c
) {
915 character
* Char
= MoveToSquare
[c
]->GetCharacter();
916 if (Char
&& Char
!= this) {
917 v2 Pos
= MoveToSquare
[c
]->GetPos();
920 PetPos
[Pets
++] = Pos
;
921 } else if (Char
->GetRelation(this) != HOSTILE
) {
922 Neutral
[Neutrals
] = Char
;
923 NeutralPos
[Neutrals
++] = Pos
;
925 Hostile
[Hostiles
] = Char
;
926 HostilePos
[Hostiles
++] = Pos
;
931 if (Hostiles
== 1) return Hit(Hostile
[0], HostilePos
[0], Direction
);
933 int Index
= RAND() % Hostiles
;
934 return Hit(Hostile
[Index
], HostilePos
[Index
], Direction
);
938 if (!IsPlayer() && !Pets
&& Important
&& CanMoveOn(MoveToSquare
[0]))
939 return HandleCharacterBlockingTheWay(Neutral
[0], NeutralPos
[0], Direction
);
941 return IsPlayer() && Hit(Neutral
[0], NeutralPos
[0], Direction
);
942 } else if (Neutrals
) {
944 int Index
= RAND() % Neutrals
;
945 return Hit(Neutral
[Index
], NeutralPos
[Index
], Direction
);
951 for (int c
= 0; c
< Squares
; ++c
) if (MoveToSquare
[c
]->IsScary(this)) return false;
955 if (IsPlayer() && !ivanconfig::GetBeNice() && Pet
[0]->IsMasochist() && HasSadistAttackMode() &&
956 game::TruthQuestion("Do you want to punish " + Pet
[0]->GetObjectPronoun() + "? [y/N]"))
957 return Hit(Pet
[0], PetPos
[0], Direction
, SADIST_HIT
);
959 return (Important
&& (CanMoveOn(MoveToSquare
[0]) ||
960 (IsPlayer() && game::GoThroughWallsCheatIsActive())) && Displace(Pet
[0]));
961 } else if (Pets
) return false;
963 if ((CanMove() && CanMoveOn(MoveToSquare
[0])) || ((game::GoThroughWallsCheatIsActive() && IsPlayer()))) {
964 Move(MoveTo
, false, Run
);
965 if (IsEnabled() && GetPos() == GoingTo
) TerminateGoingTo();
968 for (int c
= 0; c
< Squares
; ++c
) {
969 olterrain
*Terrain
= MoveToSquare
[c
]->GetOLTerrain();
970 if (Terrain
&& Terrain
->CanBeOpened()) {
972 if (Terrain
->IsLocked()) {
975 if (ivanconfig::GetKickDownDoors()) {
976 if (game::TruthQuestion(CONST_S("Locked! Do you want to kick ")+Terrain
->GetName(DEFINITE
)+"? [Y/n]", true, game::GetMoveCommandKeyBetweenPoints(PLAYER
->GetPos(), MoveToSquare
[0]->GetPos()))) {
977 Kick(MoveToSquare
[c
], Direction
);
984 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 */
986 } else if (Important
&& CheckKick()) {
987 room
*Room
= MoveToSquare
[c
]->GetRoom();
988 if (!Room
|| Room
->AllowKick(this, MoveToSquare
[c
])) {
989 int HP
= Terrain
->GetHP();
990 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s kicks %s.", CHAR_NAME(DEFINITE
), Terrain
->CHAR_NAME(DEFINITE
));
991 Kick(MoveToSquare
[c
], Direction
);
992 olterrain
*NewTerrain
= MoveToSquare
[c
]->GetOLTerrain();
993 if (NewTerrain
== Terrain
&& Terrain
->GetHP() == HP
) { // BUG!
994 Illegal
.insert(MoveTo
);
1000 } else { /* if (Terrain->IsLocked()) */
1001 /*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);*/
1002 /* Non-players always try to open it */
1003 if (!IsPlayer()) return MoveToSquare
[c
]->Open(this);
1004 if (game::TruthQuestion(CONST_S("Do you want to ")+(ivanconfig::GetKickDownDoors()?"kick ":"open ")+
1005 Terrain
->GetName(DEFINITE
)+"? [y/N]", false, game::GetMoveCommandKeyBetweenPoints(PLAYER
->GetPos(),
1006 MoveToSquare
[0]->GetPos()))) {
1007 if (ivanconfig::GetKickDownDoors()) {
1008 Kick(MoveToSquare
[c
], Direction
);
1011 return MoveToSquare
[c
]->Open(this);
1014 } /* if (Terrain->IsLocked()) */
1015 } else { /* if (CanOpen()) */
1017 ADD_MESSAGE("This monster type cannot open doors.");
1021 Illegal
.insert(MoveTo
);
1022 return CreateRoute();
1024 } /* if (CanOpen()) */
1025 } /* if (Terrain && Terrain->CanBeOpened()) */
1030 if (IsPlayer() && !IsStuck() && GetLevel()->IsOnGround() && game::TruthQuestion(CONST_S("Do you want to leave ")+game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex())+"? [y/N]")) {
1031 if (HasPetrussNut() && !HasGoldenEagleShirt()) {
1032 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!"));
1033 game::GetCurrentArea()->SendNewDrawRequest();
1034 game::DrawEverything();
1035 ShowAdventureInfo();
1036 festring Msg
= CONST_S("killed Petrus and became the Avatar of Chaos");
1037 PLAYER
->AddScoreEntry(Msg
, 3, false);
1041 if (game::TryTravel(WORLD_MAP
, WORLD_MAP
, game::GetCurrentDungeonIndex())) return true;
1046 /** No multitile support */
1047 if (CanMove() && GetArea()->IsValidPos(MoveTo
) && (CanMoveOn(GetNearWSquare(MoveTo
)) || game::GoThroughWallsCheatIsActive())) {
1048 if (!game::GoThroughWallsCheatIsActive()) {
1049 charactervector
&V
= game::GetWorldMap()->GetPlayerGroup();
1050 truth Discard
= false;
1051 for (uInt c
= 0; c
< V
.size(); ++c
) {
1052 if (!V
[c
]->CanMoveOn(GetNearWSquare(MoveTo
))) {
1054 ADD_MESSAGE("One or more of your team members cannot cross this terrain.");
1055 if (!game::TruthQuestion("Discard them? [y/N]")) return false;
1058 if (Discard
) delete V
[c
];
1059 V
.erase(V
.begin() + c
--);
1063 Move(MoveTo
, false);
1072 void character::CreateCorpse (lsquare
*Square
) {
1073 if (!BodyPartsDisappearWhenSevered() && !game::AllBodyPartsVanish()) {
1074 corpse
*Corpse
= corpse::Spawn(0, NO_MATERIALS
);
1075 Corpse
->SetDeceased(this);
1076 Square
->AddItem(Corpse
);
1084 void character::Die (ccharacter
*Killer
, cfestring
&Msg
, feuLong DeathFlags
) {
1085 /* Note: This function musn't delete any objects, since one of these may be
1086 the one currently processed by pool::Be()! */
1087 if (!IsEnabled()) return;
1088 game::ClearEventData();
1089 game::mActor
= Killer
;
1090 if (game::RunOnCharEvent(this, CONST_S("die"))) { game::ClearEventData(); RemoveTraps(); return; }
1091 game::ClearEventData();
1094 ADD_MESSAGE("You die.");
1095 game::DrawEverything();
1096 if (game::TruthQuestion(CONST_S("Do you want to save screenshot? [y/n]"), REQUIRES_ANSWER
)) {
1099 dir
<< ivanconfig::GetMyDir() << "/save";
1100 mkdir(dir
.CStr(), 0755);
1102 dir
<< getenv("HOME") << "/.ivan-save";
1103 mkdir(dir
.CStr(), 0755);
1105 dir
<< "/deathshots";
1106 mkdir(dir
.CStr(), 0755);
1108 time_t t
= time(NULL
);
1109 struct tm
*ts
= localtime(&t
);
1111 timestr
<< (int)(ts
->tm_year
%100);
1112 int t
= ts
->tm_mon
+1;
1113 if (t
< 10) timestr
<< '0'; timestr
<< t
;
1114 t
= ts
->tm_mday
; if (t
< 10) timestr
<< '0'; timestr
<< t
;
1116 t
= ts
->tm_hour
; if (t
< 10) timestr
<< '0'; timestr
<< t
;
1117 t
= ts
->tm_min
; if (t
< 10) timestr
<< '0'; timestr
<< t
;
1118 t
= ts
->tm_sec
; if (t
< 10) timestr
<< '0'; timestr
<< t
;
1122 #if defined(HAVE_IMLIB2) || defined(HAVE_LIBPNG)
1123 festring ext
= ".png";
1125 festring ext
= ".bmp";
1127 festring fname
= dir
+"/deathshot_"+timestr
;
1128 if (inputfile::fileExists(fname
+ext
)) {
1129 for (int f
= 0; f
< 1000; f
++) {
1131 sprintf(buf
, "%03d", f
);
1132 festring fn
= fname
+buf
;
1133 if (!inputfile::fileExists(fn
+ext
)) {
1140 fprintf(stderr
, "deathshot: %s\n", fname
.CStr());
1141 #if defined(HAVE_IMLIB2) || defined(HAVE_LIBPNG)
1142 DOUBLE_BUFFER
->SavePNG(fname
);
1144 DOUBLE_BUFFER
->SaveBMP(fname
);
1147 if (game::WizardModeIsActive()) {
1148 game::DrawEverything();
1149 if (!game::TruthQuestion(CONST_S("Do you want to do this, cheater? [y/n]"), REQUIRES_ANSWER
)) {
1155 SetNP(SATIATED_LEVEL
);
1156 SendNewDrawRequest();
1160 } else if (CanBeSeenByPlayer() && !(DeathFlags
& DISALLOW_MSG
)) {
1161 ProcessAndAddMessage(GetDeathMessage());
1162 } else if (DeathFlags
& FORCE_MSG
) {
1163 ADD_MESSAGE("You sense the death of something.");
1166 if (!(DeathFlags
& FORBID_REINCARNATION
)) {
1167 if (StateIsActivated(LIFE_SAVED
) && CanMoveOn(!game::IsInWilderness() ? GetSquareUnder() : PLAYER
->GetSquareUnder())) {
1171 if (SpecialSaveLife()) return;
1172 } else if (StateIsActivated(LIFE_SAVED
)) {
1176 Flags
|= C_IN_NO_MSG_MODE
;
1177 character
*Ghost
= 0;
1179 game::RemoveSaves();
1180 if (!game::IsInWilderness()) {
1181 Ghost
= game::CreateGhost();
1186 square
*SquareUnder
[MAX_SQUARES_UNDER
];
1187 memset(SquareUnder
, 0, sizeof(SquareUnder
));
1189 if (IsPlayer() || !game::IsInWilderness()) {
1190 for (int c
= 0; c
< SquaresUnder
; ++c
) SquareUnder
[c
] = GetSquareUnder(c
);
1193 charactervector
& V
= game::GetWorldMap()->GetPlayerGroup();
1194 V
.erase(std::find(V
.begin(), V
.end(), this));
1196 //lsquare **LSquareUnder = reinterpret_cast<lsquare **>(SquareUnder); /* warning; wtf? */
1197 lsquare
*LSquareUnder
[MAX_SQUARES_UNDER
];
1198 memmove(LSquareUnder
, SquareUnder
, sizeof(SquareUnder
));
1200 if (!game::IsInWilderness()) {
1201 if (!StateIsActivated(POLYMORPHED
)) {
1202 if (!IsPlayer() && !IsTemporary() && !Msg
.IsEmpty()) game::SignalDeath(this, Killer
, Msg
);
1203 if (!(DeathFlags
& DISALLOW_CORPSE
)) CreateCorpse(LSquareUnder
[0]); else SendToHell();
1205 if (!IsPlayer() && !IsTemporary() && !Msg
.IsEmpty()) game::SignalDeath(GetPolymorphBackup(), Killer
, Msg
);
1206 GetPolymorphBackup()->CreateCorpse(LSquareUnder
[0]);
1207 GetPolymorphBackup()->Flags
&= ~C_POLYMORPHED
;
1208 SetPolymorphBackup(0);
1212 if (!IsPlayer() && !IsTemporary() && !Msg
.IsEmpty()) game::SignalDeath(this, Killer
, Msg
);
1217 if (!game::IsInWilderness()) {
1218 for (int c
= 0; c
< GetSquaresUnder(); ++c
) LSquareUnder
[c
]->SetTemporaryEmitation(GetEmitation());
1220 ShowAdventureInfo();
1221 if (!game::IsInWilderness()) {
1222 for(int c
= 0; c
< GetSquaresUnder(); ++c
) LSquareUnder
[c
]->SetTemporaryEmitation(0);
1226 if (!game::IsInWilderness()) {
1227 if (GetSquaresUnder() == 1) {
1228 stack
*StackUnder
= LSquareUnder
[0]->GetStack();
1229 GetStack()->MoveItemsTo(StackUnder
);
1230 doforbodypartswithparam
<stack
*>()(this, &bodypart::DropEquipment
, StackUnder
);
1232 while (GetStack()->GetItems()) GetStack()->GetBottom()->MoveTo(LSquareUnder
[RAND_N(GetSquaresUnder())]->GetStack());
1233 for (int c
= 0; c
< BodyParts
; ++c
) {
1234 bodypart
*BodyPart
= GetBodyPart(c
);
1235 if (BodyPart
) BodyPart
->DropEquipment(LSquareUnder
[RAND_N(GetSquaresUnder())]->GetStack());
1240 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(0);
1242 Flags
&= ~C_IN_NO_MSG_MODE
;
1246 if (!game::IsInWilderness()) {
1247 Ghost
->PutTo(LSquareUnder
[0]->GetPos());
1251 game::TextScreen(CONST_S("Unfortunately you died."), ZERO_V2
, WHITE
, true, true, &game::ShowDeathSmiley
);
1257 void character::AddMissMessage (ccharacter
*Enemy
) const {
1259 if (Enemy
->IsPlayer()) Msg
= GetDescription(DEFINITE
)+" misses you!";
1260 else if (IsPlayer()) Msg
= CONST_S("You miss ")+Enemy
->GetDescription(DEFINITE
)+'!';
1261 else if (CanBeSeenByPlayer() || Enemy
->CanBeSeenByPlayer()) Msg
= GetDescription(DEFINITE
)+" misses "+Enemy
->GetDescription(DEFINITE
)+'!';
1263 ADD_MESSAGE("%s", Msg
.CStr());
1267 void character::AddBlockMessage (ccharacter
*Enemy
, citem
*Blocker
, cfestring
&HitNoun
, truth Partial
) const {
1269 festring BlockVerb
= (Partial
? " to partially block the " : " to block the ")+HitNoun
;
1271 Msg
<< "You manage" << BlockVerb
<< " with your " << Blocker
->GetName(UNARTICLED
) << '!';
1272 } else if (Enemy
->IsPlayer() || Enemy
->CanBeSeenByPlayer()) {
1273 if (CanBeSeenByPlayer())
1274 Msg
<< GetName(DEFINITE
) << " manages" << BlockVerb
<< " with " << GetPossessivePronoun() << ' ' << Blocker
->GetName(UNARTICLED
) << '!';
1276 Msg
<< "Something manages" << BlockVerb
<< " with something!";
1280 ADD_MESSAGE("%s", Msg
.CStr());
1284 void character::AddPrimitiveHitMessage (ccharacter
*Enemy
, cfestring
&FirstPersonHitVerb
,
1285 cfestring
&ThirdPersonHitVerb
, int BodyPart
) const
1288 festring BodyPartDescription
;
1289 if (BodyPart
&& (Enemy
->CanBeSeenByPlayer() || Enemy
->IsPlayer()))
1290 BodyPartDescription
<< " in the " << Enemy
->GetBodyPartName(BodyPart
);
1291 if (Enemy
->IsPlayer())
1292 Msg
<< GetDescription(DEFINITE
) << ' ' << ThirdPersonHitVerb
<< " you" << BodyPartDescription
<< '!';
1293 else if (IsPlayer())
1294 Msg
<< "You " << FirstPersonHitVerb
<< ' ' << Enemy
->GetDescription(DEFINITE
) << BodyPartDescription
<< '!';
1295 else if (CanBeSeenByPlayer() || Enemy
->CanBeSeenByPlayer())
1296 Msg
<< GetDescription(DEFINITE
) << ' ' << ThirdPersonHitVerb
<< ' ' << Enemy
->GetDescription(DEFINITE
) + BodyPartDescription
<< '!';
1299 ADD_MESSAGE("%s", Msg
.CStr());
1303 cchar
*const HitVerb
[] = { "strike", "slash", "stab" };
1304 cchar
*const HitVerb3rdPersonEnd
[] = { "s", "es", "s" };
1307 void character::AddWeaponHitMessage (ccharacter
*Enemy
, citem
*Weapon
, int BodyPart
, truth Critical
) const {
1309 festring BodyPartDescription
;
1311 if (BodyPart
&& (Enemy
->CanBeSeenByPlayer() || Enemy
->IsPlayer()))
1312 BodyPartDescription
<< " in the " << Enemy
->GetBodyPartName(BodyPart
);
1314 int FittingTypes
= 0;
1315 int DamageFlags
= Weapon
->GetDamageFlags();
1318 for (int c
= 0; c
< DAMAGE_TYPES
; ++c
) {
1319 if (1 << c
& DamageFlags
) {
1320 if (!FittingTypes
|| !RAND_N(FittingTypes
+1)) DamageType
= c
;
1325 if (!FittingTypes
) ABORT("No damage flags specified for %s!", Weapon
->CHAR_NAME(UNARTICLED
));
1327 festring NewHitVerb
= Critical
? " critically " : " ";
1328 NewHitVerb
<< HitVerb
[DamageType
];
1329 cchar
*const E
= HitVerb3rdPersonEnd
[DamageType
];
1331 if (Enemy
->IsPlayer()) {
1332 Msg
<< GetDescription(DEFINITE
) << NewHitVerb
<< E
<< " you" << BodyPartDescription
;
1333 if (CanBeSeenByPlayer()) Msg
<< " with " << GetPossessivePronoun() << ' ' << Weapon
->GetName(UNARTICLED
);
1335 } else if (IsPlayer()) {
1336 Msg
<< "You" << NewHitVerb
<< ' ' << Enemy
->GetDescription(DEFINITE
) << BodyPartDescription
<< '!';
1337 } else if(CanBeSeenByPlayer() || Enemy
->CanBeSeenByPlayer()) {
1338 Msg
<< GetDescription(DEFINITE
) << NewHitVerb
<< E
<< ' ' << Enemy
->GetDescription(DEFINITE
) << BodyPartDescription
;
1339 if (CanBeSeenByPlayer()) Msg
<< " with " << GetPossessivePronoun() << ' ' << Weapon
->GetName(UNARTICLED
);
1344 ADD_MESSAGE("%s", Msg
.CStr());
1348 item
*character::GeneralFindItem (ItemCheckerCB chk
) const {
1349 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1351 if (it
&& chk(it
)) return it
;
1357 static truth
isElpuriHead (item
*i
) { return i
->IsHeadOfElpuri(); }
1358 truth
character::HasHeadOfElpuri () const {
1359 if (GeneralFindItem(::isElpuriHead
)) return true;
1360 return combineequipmentpredicates()(this, &item::IsHeadOfElpuri
, 1);
1364 static truth
isPetrussNut (item
*i
) { return i
->IsPetrussNut(); }
1365 truth
character::HasPetrussNut () const {
1366 if (GeneralFindItem(::isPetrussNut
)) return true;
1367 return combineequipmentpredicates()(this, &item::IsPetrussNut
, 1);
1371 static truth
isGoldenEagleShirt (item
*i
) { return i
->IsGoldenEagleShirt(); }
1372 truth
character::HasGoldenEagleShirt () const {
1373 if (GeneralFindItem(::isGoldenEagleShirt
)) return true;
1374 return combineequipmentpredicates()(this, &item::IsGoldenEagleShirt
, 1);
1378 truth
character::HasOmmelBlood () const {
1379 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
)
1380 if (i
->IsKleinBottle() && i
->GetSecondaryMaterial() && i
->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD
) return true;
1382 for (int c
= 0; c
< GetEquipments(); ++c
) {
1383 item
*Item
= GetEquipment(c
);
1385 if (Item
&& Item
->IsKleinBottle() && Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD
) return true;
1387 return false; //combineequipmentpredicates()(this, &item::IsKleinBottle, 1);
1391 truth
character::HasCurdledBlood () const {
1392 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
)
1393 if (i
->IsKleinBottle() && i
->GetSecondaryMaterial() && i
->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD
) return true;
1395 for (int c
= 0; c
< GetEquipments(); ++c
) {
1396 item
*Item
= GetEquipment(c
);
1398 if (Item
&& Item
->IsKleinBottle() && Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD
) return true;
1400 return false; //combineequipmentpredicates()(this, &item::IsKleinBottle, 1);
1404 truth
character::CurdleOmmelBlood () const {
1405 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1406 if (i
->IsKleinBottle() && i
->GetSecondaryMaterial() && i
->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD
) {
1407 i
->ChangeSecondaryMaterial(MAKE_MATERIAL(CURDLED_OMMEL_BLOOD
));
1412 for (int c
= 0; c
< GetEquipments(); ++c
) {
1413 item
*Item
= GetEquipment(c
);
1415 if (Item
&& Item
->IsKleinBottle() && Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD
) {
1416 Item
->ChangeSecondaryMaterial(MAKE_MATERIAL(CURDLED_OMMEL_BLOOD
));
1420 return false; //combineequipmentpredicates()(this, &item::IsKleinBottle, 1);
1424 truth
character::RemoveCurdledOmmelBlood () {
1425 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1426 if (i
->IsKleinBottle() && i
->GetSecondaryMaterial() && i
->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD
) {
1427 (*i
)->RemoveFromSlot();
1433 for (int c
= 0; c
< GetEquipments(); ++c
) {
1434 item
*Item
= GetEquipment(c
);
1436 if (Item
&& Item
->IsKleinBottle() && Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD
) {
1437 Item
->RemoveFromSlot();
1446 int character::GeneralRemoveItem (ItemCheckerCB chk
, truth allItems
) {
1452 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1454 if (Item
&& chk(Item
)) {
1455 Item
->RemoveFromSlot();
1458 if (!allItems
) return cnt
;
1467 for (int c
= 0; c
< GetEquipments(); ++c
) {
1468 item
*Item
= GetEquipment(c
);
1469 if (Item
&& chk(Item
)) {
1470 Item
->RemoveFromSlot();
1473 if (!allItems
) return cnt
;
1483 static truth
isEncryptedScroll (item
*i
) { return i
->IsEncryptedScroll(); }
1484 truth
character::RemoveEncryptedScroll () { return GeneralRemoveItem(::isEncryptedScroll
) != 0; }
1487 static truth
isMondedrPass (item
*i
) { return i
->IsMondedrPass(); }
1488 truth
character::RemoveMondedrPass () { return GeneralRemoveItem(::isMondedrPass
) != 0; }
1491 static truth
isRingOfThieves (item
*i
) { return i
->IsRingOfThieves(); }
1492 truth
character::RemoveRingOfThieves () { return GeneralRemoveItem(::isRingOfThieves
) != 0; }
1495 truth
character::ReadItem (item
*ToBeRead
) {
1496 if (!ToBeRead
->CanBeRead(this)) {
1497 if (IsPlayer()) ADD_MESSAGE("You can't read this.");
1500 if (!GetLSquareUnder()->IsDark() || game::GetSeeWholeMapCheatMode()) {
1501 if (StateIsActivated(CONFUSED
) && !(RAND()&7)) {
1502 if (!ToBeRead
->IsDestroyable(this)) {
1503 ADD_MESSAGE("You read some words of %s and understand exactly nothing.", ToBeRead
->CHAR_NAME(DEFINITE
));
1505 ADD_MESSAGE("%s is very confusing. Or perhaps you are just too confused?", ToBeRead
->CHAR_NAME(DEFINITE
));
1506 ActivateRandomState(SRC_CONFUSE_READ
, 1000+RAND()%1500);
1507 ToBeRead
->RemoveFromSlot();
1508 ToBeRead
->SendToHell();
1513 if (ToBeRead
->Read(this)) {
1514 if (!game::WizardModeIsActive()) {
1515 /* This AP is used to take the stuff out of backpack */
1522 if (IsPlayer()) ADD_MESSAGE("It's too dark here to read.");
1527 void character::CalculateBurdenState () {
1528 int OldBurdenState
= BurdenState
;
1529 sLong SumOfMasses
= GetCarriedWeight();
1530 sLong CarryingStrengthUnits
= sLong(GetCarryingStrength())*2500;
1531 if (SumOfMasses
> (CarryingStrengthUnits
<< 1) + CarryingStrengthUnits
) BurdenState
= OVER_LOADED
;
1532 else if (SumOfMasses
> CarryingStrengthUnits
<< 1) BurdenState
= STRESSED
;
1533 else if (SumOfMasses
> CarryingStrengthUnits
) BurdenState
= BURDENED
;
1534 else BurdenState
= UNBURDENED
;
1535 if (!IsInitializing() && BurdenState
!= OldBurdenState
) CalculateBattleInfo();
1539 void character::Save (outputfile
&SaveFile
) const {
1540 SaveFile
<< (uShort
)GetType();
1541 Stack
->Save(SaveFile
);
1543 for (int c
= 0; c
< BASE_ATTRIBUTES
; ++c
) SaveFile
<< BaseExperience
[c
];
1545 SaveFile
<< ExpModifierMap
;
1546 SaveFile
<< NP
<< AP
<< Stamina
<< GenerationDanger
<< ScienceTalks
<< CounterToMindWormHatch
;
1547 SaveFile
<< TemporaryState
<< EquipmentState
<< Money
<< GoingTo
<< RegenerationCounter
<< Route
<< Illegal
;
1548 SaveFile
<< CurrentSweatMaterial
;
1549 SaveFile
.Put(!!IsEnabled());
1550 SaveFile
<< HomeData
<< BlocksSinceLastTurn
<< CommandFlags
;
1551 SaveFile
<< WarnFlags
<< (uShort
)Flags
;
1553 for (int c
= 0; c
< BodyParts
; ++c
) SaveFile
<< BodyPartSlot
[c
] << OriginalBodyPartID
[c
];
1555 SaveLinkedList(SaveFile
, TrapData
);
1558 for (int c
= 0; c
< STATES
; ++c
) SaveFile
<< TemporaryStateCounter
[c
];
1562 SaveFile
<< Team
->GetID(); // feuLong
1564 SaveFile
.Put(false);
1567 if (GetTeam() && GetTeam()->GetLeader() == this) SaveFile
.Put(true); else SaveFile
.Put(false);
1569 SaveFile
<< AssignedName
<< PolymorphBackup
;
1571 for (int c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) SaveFile
<< CWeaponSkill
[c
];
1573 SaveFile
<< (uShort
)GetConfig();
1577 void character::Load (inputfile
&SaveFile
) {
1579 Stack
->Load(SaveFile
);
1581 game::AddCharacterID(this, ID
);
1583 for (int c
= 0; c
< BASE_ATTRIBUTES
; ++c
) SaveFile
>> BaseExperience
[c
];
1585 SaveFile
>> ExpModifierMap
;
1586 SaveFile
>> NP
>> AP
>> Stamina
>> GenerationDanger
>> ScienceTalks
>> CounterToMindWormHatch
;
1587 SaveFile
>> TemporaryState
>> EquipmentState
>> Money
>> GoingTo
>> RegenerationCounter
>> Route
>> Illegal
;
1588 SaveFile
>> CurrentSweatMaterial
;
1590 if (!SaveFile
.Get()) Disable();
1592 SaveFile
>> HomeData
>> BlocksSinceLastTurn
>> CommandFlags
;
1593 SaveFile
>> WarnFlags
;
1594 WarnFlags
&= ~WARNED
;
1595 Flags
|= ReadType(uShort
, SaveFile
) & ~ENTITY_FLAGS
;
1597 for (int c
= 0; c
< BodyParts
; ++c
) {
1598 SaveFile
>> BodyPartSlot
[c
] >> OriginalBodyPartID
[c
];
1599 item
*BodyPart
= *BodyPartSlot
[c
];
1600 if (BodyPart
) BodyPart
->Disable();
1603 LoadLinkedList(SaveFile
, TrapData
);
1606 if (Action
) Action
->SetActor(this);
1608 for (int c
= 0; c
< STATES
; ++c
) SaveFile
>> TemporaryStateCounter
[c
];
1610 if (SaveFile
.Get()) SetTeam(game::GetTeam(ReadType(feuLong
, SaveFile
)));
1612 if (SaveFile
.Get()) GetTeam()->SetLeader(this);
1614 SaveFile
>> AssignedName
>> PolymorphBackup
;
1616 for (int c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) SaveFile
>> CWeaponSkill
[c
];
1618 databasecreator
<character
>::InstallDataBase(this, ReadType(uShort
, SaveFile
));
1620 if (IsEnabled() && !game::IsInWilderness()) {
1621 for (int c
= 1; c
< GetSquaresUnder(); ++c
) GetSquareUnder(c
)->SetCharacter(this);
1625 const fearray<festring> < = GetLevelTags();
1627 fprintf(stderr, "====\n");
1628 for (uInt f = 0; f < lt.Size; ++f) fprintf(stderr, " %u: [%s]\n", f, lt[f].CStr());
1634 truth
character::Engrave (cfestring
&What
) {
1635 GetLSquareUnder()->Engrave(What
);
1639 truth
character::MoveRandomly () {
1640 if (!IsEnabled()) return false;
1641 for (int c
= 0; c
< 10; ++c
) {
1642 v2 ToTry
= game::GetMoveVector(RAND()&7);
1643 if (GetLevel()->IsValidPos(GetPos()+ToTry
)) {
1644 lsquare
*Square
= GetNearLSquare(GetPos()+ToTry
);
1645 if (!Square
->IsDangerous(this) && !Square
->IsScary(this) && TryMove(ToTry
, false, false)) return true;
1652 truth
character::TestForPickup (item
*ToBeTested
) const {
1653 if (MakesBurdened(ToBeTested
->GetWeight()+GetCarriedWeight())) return false;
1658 void character::AddScoreEntry (cfestring
&Description
, double Multiplier
, truth AddEndLevel
) const {
1659 if (!game::WizardModeIsReallyActive()) {
1661 if (!HScore
.CheckVersion()) {
1662 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;
1665 festring Desc
= game::GetPlayerName();
1666 Desc
<< ", " << Description
;
1667 if (AddEndLevel
) Desc
<< " in "+(game::IsInWilderness() ? "the world map" : game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex()));
1668 HScore
.Add(sLong(game::GetScore()*Multiplier
), Desc
);
1674 truth
character::CheckDeath (cfestring
&Msg
, ccharacter
*Murderer
, feuLong DeathFlags
) {
1675 if (!IsEnabled()) return true;
1676 if (game::IsSumoWrestling() && IsDead()) {
1677 game::EndSumoWrestling(!!IsPlayer());
1680 if (DeathFlags
& FORCE_DEATH
|| IsDead()) {
1681 if (Murderer
&& Murderer
->IsPlayer() && GetTeam()->GetKillEvilness()) game::DoEvilDeed(GetTeam()->GetKillEvilness());
1682 festring SpecifierMsg
;
1683 int SpecifierParts
= 0;
1684 if (GetPolymorphBackup()) {
1685 SpecifierMsg
<< " polymorphed into ";
1686 id::AddName(SpecifierMsg
, INDEFINITE
);
1689 if (!(DeathFlags
& IGNORE_TRAPS
) && IsStuck()) {
1690 if (SpecifierParts
++) SpecifierMsg
<< " and";
1691 SpecifierMsg
<< " caught in " << GetTrapDescription();
1693 if (GetAction() && !(DeathFlags
& IGNORE_UNCONSCIOUSNESS
&& GetAction()->IsUnconsciousness())) {
1694 festring ActionMsg
= GetAction()->GetDeathExplanation();
1695 if (!ActionMsg
.IsEmpty()) {
1696 if (SpecifierParts
> 1) {
1697 SpecifierMsg
= ActionMsg
<< ',' << SpecifierMsg
;
1699 if (SpecifierParts
) SpecifierMsg
<< " and";
1700 SpecifierMsg
<< ActionMsg
;
1705 festring NewMsg
= Msg
;
1706 if (Murderer
== this) {
1707 SEARCH_N_REPLACE(NewMsg
, "@bkp", CONST_S("by ") + GetPossessivePronoun(false) + " own");
1708 SEARCH_N_REPLACE(NewMsg
, "@bk", CONST_S("by ") + GetObjectPronoun(false) + "self");
1709 SEARCH_N_REPLACE(NewMsg
, "@k", GetObjectPronoun(false) + "self");
1711 SEARCH_N_REPLACE(NewMsg
, "@bkp", CONST_S("by ") + Murderer
->GetName(INDEFINITE
) + "'s");
1712 SEARCH_N_REPLACE(NewMsg
, "@bk", CONST_S("by ") + Murderer
->GetName(INDEFINITE
));
1713 SEARCH_N_REPLACE(NewMsg
, "@k", CONST_S("by ") + Murderer
->GetName(INDEFINITE
));
1715 if (SpecifierParts
) NewMsg
<< " while" << SpecifierMsg
;
1716 if (IsPlayer() && game::WizardModeIsActive()) ADD_MESSAGE("Death message: %s. Score: %d.", NewMsg
.CStr(), game::GetScore());
1717 Die(Murderer
, NewMsg
, DeathFlags
);
1724 truth
character::CheckStarvationDeath (cfestring
&Msg
) {
1725 if (GetNP() < 1 && UsesNutrition()) return CheckDeath(Msg
, 0, FORCE_DEATH
);
1730 void character::ThrowItem (int Direction
, item
*ToBeThrown
) {
1731 if (Direction
> 7) ABORT("Throw in TOO odd direction...");
1732 ToBeThrown
->Fly(this, Direction
, GetAttribute(ARM_STRENGTH
));
1736 void character::HasBeenHitByItem (character
*Thrower
, item
*Thingy
, int Damage
, double ToHitValue
, int Direction
) {
1737 if (IsPlayer()) ADD_MESSAGE("%s hits you.", Thingy
->CHAR_NAME(DEFINITE
));
1738 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s hits %s.", Thingy
->CHAR_NAME(DEFINITE
), CHAR_NAME(DEFINITE
));
1739 int BodyPart
= ChooseBodyPartToReceiveHit(ToHitValue
, DodgeValue
);
1740 int WeaponSkillHits
= Thrower
? CalculateWeaponSkillHits(Thrower
) : 0;
1741 int DoneDamage
= ReceiveBodyPartDamage(Thrower
, Damage
, PHYSICAL_DAMAGE
, BodyPart
, Direction
);
1742 truth Succeeded
= (GetBodyPart(BodyPart
) && HitEffect(Thrower
, Thingy
, Thingy
->GetPos(), THROW_ATTACK
, BodyPart
, Direction
, !DoneDamage
)) || DoneDamage
;
1743 if (Succeeded
&& Thrower
) Thrower
->WeaponSkillHit(Thingy
, THROW_ATTACK
, WeaponSkillHits
);
1744 festring DeathMsg
= CONST_S("killed by a flying ")+Thingy
->GetName(UNARTICLED
);
1745 if (CheckDeath(DeathMsg
, Thrower
)) return;
1747 if (Thrower
->CanBeSeenByPlayer())
1748 DeActivateVoluntaryAction(CONST_S("The attack of ")+Thrower
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
1750 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
1752 DeActivateVoluntaryAction(CONST_S("The hit interrupts you."));
1757 truth
character::DodgesFlyingItem (item
*Item
, double ToHitValue
) {
1758 return !Item
->EffectIsGood() && RAND() % int(100+ToHitValue
/DodgeValue
*100) < 100;
1762 void character::GetPlayerCommand () {
1764 truth HasActed
= false;
1766 game::DrawEverything();
1767 if (game::GetDangerFound()) {
1768 if (game::GetDangerFound() > 500.) {
1769 if (game::GetCausePanicFlag()) {
1770 game::SetCausePanicFlag(false);
1771 BeginTemporaryState(PANIC
, 500+RAND_N(500));
1773 game::AskForEscPress(CONST_S("You are horrified by your situation!"));
1774 } else if (ivanconfig::GetWarnAboutDanger()) {
1775 if (game::GetDangerFound() > 50.) game::AskForEscPress(CONST_S("You sense great danger!"));
1776 else game::AskForEscPress(CONST_S("You sense danger!"));
1778 game::SetDangerFound(0);
1780 game::SetIsInGetCommand(true);
1781 int Key
= GET_KEY();
1782 game::SetIsInGetCommand(false);
1783 if (Key
!= '+' && Key
!= '-' && Key
!= 'M') msgsystem::ThyMessagesAreNowOld(); // gum
1784 truth ValidKeyPressed
= false;
1786 for (int c
= 0; c
< DIRECTION_COMMAND_KEYS
; ++c
) {
1787 if (Key
== game::GetMoveCommandKey(c
)) {
1788 HasActed
= TryMove(ApplyStateModification(game::GetMoveVector(c
)), true, game::PlayerIsRunning());
1789 ValidKeyPressed
= true;
1793 if (!ValidKeyPressed
) {
1794 for (int c
= 0; (cmd
= commandsystem::GetCommand(c
)); ++c
) {
1796 /* Numpad aliases for most commonly used commands */
1797 if (Key
== KEY_DEL
&& cmd
->GetName() == "Eat") Key
= cmd
->GetKey();
1798 if (Key
== KEY_INS
&& cmd
->GetName() == "PickUp") Key
= cmd
->GetKey();
1799 if (Key
== KEY_PLUS
&& cmd
->GetName() == "EquipmentScreen") Key
= cmd
->GetKey();
1801 if (Key
== cmd
->GetKey()) {
1802 if (game::IsInWilderness() && !commandsystem::GetCommand(c
)->IsUsableInWilderness()) {
1803 ADD_MESSAGE("This function cannot be used while in wilderness.");
1804 } else if (!game::WizardModeIsActive() && commandsystem::GetCommand(c
)->IsWizardModeFunction()) {
1805 ADD_MESSAGE("Activate wizardmode to use this function.");
1807 HasActed
= commandsystem::GetCommand(c
)->GetLinkedFunction()(this);
1809 ValidKeyPressed
= true;
1814 if (!ValidKeyPressed
) ADD_MESSAGE("Unknown key. Press '?' for a list of commands.");
1816 game::IncreaseTurn();
1820 void character::Vomit (v2 Pos
, int Amount
, truth ShowMsg
) {
1821 if (!CanVomit()) return;
1823 if (IsPlayer()) ADD_MESSAGE("You vomit.");
1824 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s vomits.", CHAR_NAME(DEFINITE
));
1826 if (VomittingIsUnhealthy()) {
1827 EditExperience(ARM_STRENGTH
, -75, 1 << 9);
1828 EditExperience(LEG_STRENGTH
, -75, 1 << 9);
1831 EditNP(-2500-RAND()%2501);
1832 CheckStarvationDeath(CONST_S("vomited himself to death"));
1834 if (StateIsActivated(PARASITIZED
) && !(RAND() & 7)) {
1835 if (IsPlayer()) ADD_MESSAGE("You notice a dead broad tapeworm among your former stomach contents.");
1836 DeActivateTemporaryState(PARASITIZED
);
1838 if (!game::IsInWilderness()) {
1839 GetNearLSquare(Pos
)->ReceiveVomit(this, liquid::Spawn(GetVomitMaterial(), sLong(sqrt(GetBodyVolume())*Amount
/1000)));
1844 truth
character::Polymorph (character
*NewForm
, int Counter
) {
1845 if (!IsPolymorphable() || (!IsPlayer() && game::IsInWilderness())) {
1850 if (GetAction()) GetAction()->Terminate(false);
1851 NewForm
->SetAssignedName("");
1853 ADD_MESSAGE("Your body glows in a crimson light. You transform into %s!", NewForm
->CHAR_NAME(INDEFINITE
));
1854 else if (CanBeSeenByPlayer())
1855 ADD_MESSAGE("%s glows in a crimson light and %s transforms into %s!", CHAR_NAME(DEFINITE
), GetPersonalPronoun().CStr(), NewForm
->CHAR_NAME(INDEFINITE
));
1857 Flags
|= C_IN_NO_MSG_MODE
;
1858 NewForm
->Flags
|= C_IN_NO_MSG_MODE
;
1859 NewForm
->ChangeTeam(GetTeam());
1860 NewForm
->GenerationDanger
= GenerationDanger
;
1861 NewForm
->mOnEvents
= this->mOnEvents
;
1863 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(NewForm
);
1867 NewForm
->PutToOrNear(Pos
);
1868 NewForm
->SetAssignedName(GetAssignedName());
1869 NewForm
->ActivateTemporaryState(POLYMORPHED
);
1870 NewForm
->SetTemporaryStateCounter(POLYMORPHED
, Counter
);
1872 if (TemporaryStateIsActivated(POLYMORPHED
)) {
1873 NewForm
->SetPolymorphBackup(GetPolymorphBackup());
1874 SetPolymorphBackup(0);
1877 NewForm
->SetPolymorphBackup(this);
1878 Flags
|= C_POLYMORPHED
;
1882 GetStack()->MoveItemsTo(NewForm
->GetStack());
1883 NewForm
->SetMoney(GetMoney());
1884 DonateEquipmentTo(NewForm
);
1885 Flags
&= ~C_IN_NO_MSG_MODE
;
1886 NewForm
->Flags
&= ~C_IN_NO_MSG_MODE
;
1887 NewForm
->CalculateAll();
1891 game::SetPlayer(NewForm
);
1892 game::SendLOSUpdateRequest();
1896 NewForm
->TestWalkability();
1901 void character::BeKicked (character
*Kicker
, item
*Boot
, bodypart
*Leg
, v2 HitPos
, double KickDamage
,
1902 double ToHitValue
, int Success
, int Direction
, truth Critical
, truth ForceHit
)
1905 game::ClearEventData();
1906 game::mActor
= Kicker
;
1907 if (game::RunOnCharEvent(this, CONST_S("before_be_kicked"))) { game::ClearEventData(); return; }
1908 game::ClearEventData();
1910 switch (TakeHit(Kicker
, Boot
, Leg
, HitPos
, KickDamage
, ToHitValue
, Success
, KICK_ATTACK
, Direction
, Critical
, ForceHit
)) {
1914 if (IsEnabled() && !CheckBalance(KickDamage
)) {
1915 if (IsPlayer()) ADD_MESSAGE("The kick throws you off balance.");
1916 else if (Kicker
->IsPlayer()) ADD_MESSAGE("The kick throws %s off balance.", CHAR_DESCRIPTION(DEFINITE
));
1917 v2 FallToPos
= GetPos()+game::GetMoveVector(Direction
);
1918 FallTo(Kicker
, FallToPos
);
1924 /* Return true if still in balance */
1925 truth
character::CheckBalance (double KickDamage
) {
1926 return !CanMove() || IsStuck() || !KickDamage
|| (!IsFlying() && KickDamage
*5 < RAND()%GetSize());
1930 void character::FallTo (character
*GuiltyGuy
, v2 Where
) {
1932 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
1933 int Squares
= CalculateNewSquaresUnder(MoveToSquare
, Where
);
1935 truth NoRoom
= false;
1936 for (int c
= 0; c
< Squares
; ++c
) {
1937 olterrain
*Terrain
= MoveToSquare
[c
]->GetOLTerrain();
1938 if (Terrain
&& !CanMoveOn(Terrain
)) { NoRoom
= true; break; }
1942 if (IsPlayer()) ADD_MESSAGE("You hit your head on the wall.");
1943 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s hits %s head on the wall.", CHAR_NAME(DEFINITE
), GetPossessivePronoun().CStr());
1945 ReceiveDamage(GuiltyGuy
, 1+RAND()%5, PHYSICAL_DAMAGE
, HEAD
);
1946 CheckDeath(CONST_S("killed by hitting a wall due to being kicked @bk"), GuiltyGuy
);
1948 if (IsFreeForMe(MoveToSquare
[0])) Move(Where
, true);
1949 // Place code that handles characters bouncing to each other here
1955 truth
character::CheckCannibalism (cmaterial
*What
) const {
1956 return GetTorso()->GetMainMaterial()->IsSameAs(What
);
1960 void character::StandIdleAI () {
1961 SeekLeader(GetLeader());
1962 if (CheckForEnemies(true, true, true)) return;
1963 if (CheckForUsefulItemsOnGround()) return;
1964 if (FollowLeader(GetLeader())) return;
1965 if (CheckForDoors()) return;
1966 if (MoveTowardsHomePos()) return;
1967 if (CheckSadism()) return;
1972 truth
character::LoseConsciousness (int Counter
, truth HungerFaint
) {
1973 if (!AllowUnconsciousness()) return false;
1974 action
*Action
= GetAction();
1976 if (HungerFaint
&& !Action
->AllowUnconsciousness()) return false;
1977 if (Action
->IsUnconsciousness()) {
1978 static_cast<unconsciousness
*>(Action
)->RaiseCounterTo(Counter
);
1981 Action
->Terminate(false);
1983 if (IsPlayer()) ADD_MESSAGE("You lose consciousness.");
1984 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s loses consciousness.", CHAR_NAME(DEFINITE
));
1985 unconsciousness
*Unconsciousness
= unconsciousness::Spawn(this);
1986 Unconsciousness
->SetCounter(Counter
);
1987 SetAction(Unconsciousness
);
1992 void character::DeActivateVoluntaryAction (cfestring
&Reason
) {
1993 if (GetAction() && GetAction()->IsVoluntary()) {
1995 if (Reason
.GetSize()) ADD_MESSAGE("%s", Reason
.CStr());
1996 if (game::TruthQuestion(CONST_S("Continue ") + GetAction()->GetDescription()+"? [y/N]")) GetAction()->ActivateInDNDMode();
1997 else GetAction()->Terminate(false);
2000 GetAction()->Terminate(false);
2006 void character::ActionAutoTermination () {
2007 if (!GetAction() || !GetAction()->IsVoluntary() || GetAction()->InDNDMode()) return;
2009 for (int c
= 0; c
< game::GetTeams(); ++c
) {
2010 if (GetTeam()->GetRelation(game::GetTeam(c
)) == HOSTILE
) {
2011 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
2013 if (ch
->IsEnabled() && ch
->CanBeSeenBy(this, false, true) && (ch
->CanMove() || ch
->GetPos().IsAdjacent(Pos
)) && ch
->CanAttack()) {
2015 ADD_MESSAGE("%s seems to be hostile.", ch
->CHAR_NAME(DEFINITE
));
2016 if (game::TruthQuestion(CONST_S("Continue ")+GetAction()->GetDescription()+"? [y/N]")) GetAction()->ActivateInDNDMode();
2017 else GetAction()->Terminate(false);
2019 GetAction()->Terminate(false);
2029 truth
character::CheckForEnemies (truth CheckDoors
, truth CheckGround
, truth MayMoveRandomly
, truth RunTowardsTarget
) {
2030 if (!IsEnabled()) return false;
2031 truth HostileCharsNear
= false;
2032 character
*NearestChar
= 0;
2033 sLong NearestDistance
= 0x7FFFFFFF;
2035 for (int c
= 0; c
< game::GetTeams(); ++c
) {
2036 if (GetTeam()->GetRelation(game::GetTeam(c
)) == HOSTILE
) {
2037 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
2039 if (ch
->IsEnabled() && GetAttribute(WISDOM
) < ch
->GetAttackWisdomLimit()) {
2040 sLong ThisDistance
= Max
<sLong
>(abs(ch
->GetPos().X
- Pos
.X
), abs(ch
->GetPos().Y
- Pos
.Y
));
2041 if (ThisDistance
<= GetLOSRangeSquare()) HostileCharsNear
= true;
2042 if ((ThisDistance
< NearestDistance
|| (ThisDistance
== NearestDistance
&& !(RAND() % 3))) &&
2043 ch
->CanBeSeenBy(this, false, IsGoingSomeWhere()) &&
2044 (!IsGoingSomeWhere() || HasClearRouteTo(ch
->GetPos()))) {
2046 NearestDistance
= ThisDistance
;
2054 if (GetAttribute(INTELLIGENCE
) >= 10 || IsSpy()) game::CallForAttention(GetPos(), 100);
2055 if (SpecialEnemySightedReaction(NearestChar
)) return true;
2056 if (IsExtraCoward() && !StateIsActivated(PANIC
) && NearestChar
->GetRelativeDanger(this) >= 0.5) {
2057 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s sees %s.", CHAR_NAME(DEFINITE
), NearestChar
->CHAR_DESCRIPTION(DEFINITE
));
2058 BeginTemporaryState(PANIC
, 500+RAND()%500);
2060 if (!IsRetreating()) {
2061 if (CheckGround
&& NearestDistance
> 2 && CheckForUsefulItemsOnGround(false)) return true;
2062 SetGoingTo(NearestChar
->GetPos());
2064 SetGoingTo(Pos
-((NearestChar
->GetPos()-Pos
)<<4));
2066 return MoveTowardsTarget(true);
2068 character
*Leader
= GetLeader();
2069 if (Leader
== this) Leader
= 0;
2070 if (!Leader
&& IsGoingSomeWhere()) {
2071 if (!MoveTowardsTarget(RunTowardsTarget
)) {
2075 if (!IsEnabled()) return true;
2076 if (GetPos() == GoingTo
) TerminateGoingTo();
2080 if ((!Leader
|| (Leader
&& !IsGoingSomeWhere())) && HostileCharsNear
) {
2081 if (CheckDoors
&& CheckForDoors()) return true;
2082 if (CheckGround
&& CheckForUsefulItemsOnGround()) return true;
2083 if (MayMoveRandomly
&& MoveRandomly()) return true; // one has heard that an enemy is near but doesn't know where
2091 truth
character::CheckForDoors () {
2092 if (!CanOpen() || !IsEnabled()) return false;
2093 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
2094 lsquare
*Square
= GetNeighbourLSquare(d
);
2095 if (Square
&& Square
->GetOLTerrain() && Square
->GetOLTerrain()->Open(this)) return true;
2101 truth
character::CheckForUsefulItemsOnGround (truth CheckFood
) {
2102 if (StateIsActivated(PANIC
) || !IsEnabled()) return false;
2103 itemvector ItemVector
;
2104 GetStackUnder()->FillItemVector(ItemVector
);
2105 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) {
2106 if (ItemVector
[c
]->CanBeSeenBy(this) && ItemVector
[c
]->IsPickable(this)) {
2107 if (!(CommandFlags
& DONT_CHANGE_EQUIPMENT
) && TryToEquip(ItemVector
[c
])) return true;
2108 if (CheckFood
&& UsesNutrition() && !CheckIfSatiated() && TryToConsume(ItemVector
[c
])) return true;
2109 if (IsRangedAttacker() && (ItemVector
[c
])->GetThrowItemTypes() && TryToAddToInventory(ItemVector
[c
])) return true;
2116 truth
character::TryToAddToInventory (item
*Item
) {
2117 if (!(GetBurdenState() > STRESSED
) || !CanUseEquipment() || Item
->GetSquaresUnder() != 1) return false;
2118 room
*Room
= GetRoom();
2119 if (!Room
|| Room
->PickupItem(this, Item
, 1)) {
2120 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s picks up %s from the ground.", CHAR_NAME(DEFINITE
), Item
->CHAR_NAME(INDEFINITE
));
2121 Item
->MoveTo(GetStack());
2129 truth
character::CheckInventoryForItemToThrow (item
*ToBeChecked
) {
2130 return (ToBeChecked
->GetThrowItemTypes() & GetWhatThrowItemTypesToThrow()) ? true : false; //hehe
2134 truth
character::CheckThrowItemOpportunity () {
2135 if (!IsRangedAttacker() || !CanThrow() || !IsHumanoid() || !IsSmall() || !IsEnabled()) return false; // total gum
2136 //fprintf(stderr, "character::CheckThrowItemOpportunity...\n");
2138 // (1) - Acquire target as nearest enemy
2139 // (2) - Check that this enemy is in range, and is in appropriate direction; no friendly fire!
2140 // (3) - check inventory for throwing weapon, select this weapon
2141 // (4) - throw item in direction where the enemy is
2143 //Check the visible area for hostiles
2144 int ThrowDirection
= 0;
2145 int TargetFound
= 0;
2148 int RangeMax
= GetLOSRange();
2149 int CandidateDirections
[7] = {0, 0, 0, 0, 0, 0, 0};
2150 int HostileFound
= 0;
2151 item
*ToBeThrown
= 0;
2152 level
*Level
= GetLevel();
2154 for (int r
= 1; r
<= RangeMax
; ++r
) {
2155 for (int dir
= 0; dir
< 8; ++dir
) {
2158 case 0: TestPos
= v2(Pos
.X
-r
, Pos
.Y
-r
); break;
2159 case 1: TestPos
= v2(Pos
.X
, Pos
.Y
-r
); break;
2160 case 2: TestPos
= v2(Pos
.X
+r
, Pos
.Y
-r
); break;
2161 case 3: TestPos
= v2(Pos
.X
-r
, Pos
.Y
); break;
2162 case 4: TestPos
= v2(Pos
.X
+r
, Pos
.Y
); break;
2163 case 5: TestPos
= v2(Pos
.X
-r
, Pos
.Y
+r
); break;
2164 case 6: TestPos
= v2(Pos
.X
, Pos
.Y
+r
); break;
2165 case 7: TestPos
= v2(Pos
.X
+r
, Pos
.Y
+r
); break;
2167 if (Level
->IsValidPos(TestPos
)) {
2168 square
*TestSquare
= GetNearSquare(TestPos
);
2169 character
*Dude
= TestSquare
->GetCharacter();
2171 if (Dude
&& Dude
->IsEnabled() && Dude
->CanBeSeenBy(this, false, true)) {
2172 if (GetRelation(Dude
) != HOSTILE
) CandidateDirections
[dir
] = BLOCKED
;
2173 else if (GetRelation(Dude
) == HOSTILE
&& CandidateDirections
[dir
] != BLOCKED
) {
2174 //then load this candidate position direction into the vector of possible throw directions
2175 CandidateDirections
[dir
] = SUCCESS
;
2184 for (int dir
= 0; dir
< 8; ++dir
) {
2185 if (CandidateDirections
[dir
] == SUCCESS
&& !TargetFound
) {
2186 ThrowDirection
= dir
;
2191 if (!TargetFound
) return false;
2195 //fprintf(stderr, "throw: has target.\n");
2196 // check inventory for throwing weapon
2197 itemvector ItemVector
;
2198 GetStack()->FillItemVector(ItemVector
);
2199 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) {
2200 if (ItemVector
[c
]->IsThrowingWeapon()) {
2201 ToBeThrown
= ItemVector
[c
];
2205 if (!ToBeThrown
) return false;
2206 //fprintf(stderr, "throw: has throwing weapon.\n");
2207 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s throws %s.", CHAR_NAME(DEFINITE
), ToBeThrown
->CHAR_NAME(INDEFINITE
));
2208 ThrowItem(ThrowDirection
, ToBeThrown
);
2209 EditExperience(ARM_STRENGTH
, 75, 1<<8);
2210 EditExperience(DEXTERITY
, 75, 1<<8);
2211 EditExperience(PERCEPTION
, 75, 1<<8);
2219 truth
character::CheckAIZapOpportunity () {
2220 if (/*!IsRangedAttacker() || */ !CanZap() || !IsHumanoid() || !IsSmall() || !IsEnabled()) return false; // total gum
2222 // (1) - Acquire target as nearest enemy
2223 // (2) - Check that this enemy is in range, and is in appropriate direction; no friendly fire!
2224 // (3) - check inventory for zappable item
2225 // (4) - zap item in direction where the enemy is
2226 //Check the rest of the visible area for hostiles
2229 int SensibleRange
= 5;
2230 int RangeMax
= GetLOSRange();
2231 if (RangeMax
< SensibleRange
) SensibleRange
= RangeMax
;
2232 int CandidateDirections
[7] = {0, 0, 0, 0, 0, 0, 0};
2233 int HostileFound
= 0;
2234 int ZapDirection
= 0;
2235 int TargetFound
= 0;
2236 item
*ToBeZapped
= 0;
2237 level
*Level
= GetLevel();
2239 for (int r
= 2; r
<= SensibleRange
; ++r
) {
2240 for (int dir
= 0; dir
< 8; ++dir
) {
2242 case 0: TestPos
= v2(Pos
.X
-r
, Pos
.Y
-r
); break;
2243 case 1: TestPos
= v2(Pos
.X
, Pos
.Y
-r
); break;
2244 case 2: TestPos
= v2(Pos
.X
+r
, Pos
.Y
-r
); break;
2245 case 3: TestPos
= v2(Pos
.X
-r
, Pos
.Y
); break;
2246 case 4: TestPos
= v2(Pos
.X
+r
, Pos
.Y
); break;
2247 case 5: TestPos
= v2(Pos
.X
-r
, Pos
.Y
+r
); break;
2248 case 6: TestPos
= v2(Pos
.X
, Pos
.Y
+r
); break;
2249 case 7: TestPos
= v2(Pos
.X
+r
, Pos
.Y
+r
); break;
2251 if (Level
->IsValidPos(TestPos
)) {
2252 square
*TestSquare
= GetNearSquare(TestPos
);
2253 character
*Dude
= TestSquare
->GetCharacter();
2255 if (Dude
&& Dude
->IsEnabled() && Dude
->CanBeSeenBy(this, false, true)) {
2256 if (GetRelation(Dude
) != HOSTILE
) CandidateDirections
[dir
] = BLOCKED
;
2257 else if (GetRelation(Dude
) == HOSTILE
&& CandidateDirections
[dir
] != BLOCKED
) {
2258 //then load this candidate position direction into the vector of possible zap directions
2259 CandidateDirections
[dir
] = SUCCESS
;
2268 for (int dir
= 0; dir
< 8; ++dir
) {
2269 if (CandidateDirections
[dir
] == SUCCESS
&& !TargetFound
) {
2275 if (!TargetFound
) return false;
2279 // check inventory for zappable item
2280 itemvector ItemVector
;
2281 GetStack()->FillItemVector(ItemVector
);
2282 for (unsigned int c
= 0; c
< ItemVector
.size(); ++c
) {
2283 if (ItemVector
[c
]->GetMinCharges() > 0 && ItemVector
[c
]->GetPrice()) {
2284 // bald-faced gum solution for choosing zappables that have shots left.
2285 // MinCharges needs to be replaced. Empty wands have zero price!
2286 ToBeZapped
= ItemVector
[c
];
2290 if (!ToBeZapped
) return false;
2291 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s zaps %s.", CHAR_NAME(DEFINITE
), ToBeZapped
->CHAR_NAME(INDEFINITE
));
2292 if (ToBeZapped
->Zap(this, GetPos(), ZapDirection
)) {
2293 EditAP(-100000/APBonus(GetAttribute(PERCEPTION
)));
2303 truth
character::FollowLeader (character
*Leader
) {
2304 if (!Leader
|| Leader
== this || !IsEnabled()) return false;
2305 if (CommandFlags
& FOLLOW_LEADER
&& Leader
->CanBeSeenBy(this) && Leader
->SquareUnderCanBeSeenBy(this, true)) {
2306 v2 Distance
= GetPos()-GoingTo
;
2307 if (abs(Distance
.X
) <= 2 && abs(Distance
.Y
) <= 2) return false;
2308 return MoveTowardsTarget(false);
2310 if (IsGoingSomeWhere()) {
2311 if (!MoveTowardsTarget(true)) {
2321 void character::SeekLeader (ccharacter
*Leader
) {
2322 if (Leader
&& Leader
!= this) {
2323 if (Leader
->CanBeSeenBy(this) && (Leader
->SquareUnderCanBeSeenBy(this, true) || !IsGoingSomeWhere())) {
2324 if (CommandFlags
& FOLLOW_LEADER
) SetGoingTo(Leader
->GetPos());
2325 } else if (!IsGoingSomeWhere()) {
2326 team
*Team
= GetTeam();
2327 for (std::list
<character
*>::const_iterator i
= Team
->GetMember().begin(); i
!= Team
->GetMember().end(); ++i
) {
2329 if (ch
->IsEnabled() && ch
->GetID() != GetID() &&
2330 (CommandFlags
& FOLLOW_LEADER
) == (ch
->CommandFlags
& FOLLOW_LEADER
) && ch
->CanBeSeenBy(this)) {
2331 v2 Pos
= ch
->GetPos();
2332 v2 Distance
= GetPos()-Pos
;
2333 if (abs(Distance
.X
) > 2 && abs(Distance
.Y
) > 2) {
2344 int character::GetMoveEase () const {
2345 switch (BurdenState
) {
2347 case STRESSED
: return 50;
2348 case BURDENED
: return 75;
2349 case UNBURDENED
: return 100;
2355 int character::GetLOSRange () const {
2356 if (!game::IsInWilderness()) return GetAttribute(PERCEPTION
)*GetLevel()->GetLOSModifier()/48;
2361 truth
character::Displace (character
*Who
, truth Forced
) {
2362 if (GetBurdenState() == OVER_LOADED
) {
2364 cchar
*CrawlVerb
= StateIsActivated(LEVITATION
) ? "float" : "crawl";
2365 ADD_MESSAGE("You try very hard to %s forward. But your load is too heavy.", CrawlVerb
);
2372 double Danger
= GetRelativeDanger(Who
);
2373 int PriorityDifference
= Limit(GetDisplacePriority()-Who
->GetDisplacePriority(), -31, 31);
2375 if (IsPlayer()) ++PriorityDifference
;
2376 else if (Who
->IsPlayer()) --PriorityDifference
;
2378 if (PriorityDifference
>= 0) Danger
*= 1 << PriorityDifference
;
2379 else Danger
/= 1 << -PriorityDifference
;
2381 if (IsSmall() && Who
->IsSmall() &&
2382 (Forced
|| Danger
> 1.0 || !(Who
->IsPlayer() || Who
->IsBadPath(GetPos()))) &&
2383 !IsStuck() && !Who
->IsStuck() && (!Who
->GetAction() || Who
->GetAction()->TryDisplace()) &&
2384 CanMove() && Who
->CanMove() && Who
->CanMoveOn(GetLSquareUnder())) {
2385 if (IsPlayer()) ADD_MESSAGE("You displace %s!", Who
->CHAR_DESCRIPTION(DEFINITE
));
2386 else if (Who
->IsPlayer()) ADD_MESSAGE("%s displaces you!", CHAR_DESCRIPTION(DEFINITE
));
2387 else if (CanBeSeenByPlayer() || Who
->CanBeSeenByPlayer()) ADD_MESSAGE("%s displaces %s!", CHAR_DESCRIPTION(DEFINITE
), Who
->CHAR_DESCRIPTION(DEFINITE
));
2388 lsquare
*OldSquareUnder1
[MAX_SQUARES_UNDER
];
2389 lsquare
*OldSquareUnder2
[MAX_SQUARES_UNDER
];
2390 for (int c
= 0; c
< GetSquaresUnder(); ++c
) OldSquareUnder1
[c
] = GetLSquareUnder(c
);
2391 for (int c
= 0; c
< Who
->GetSquaresUnder(); ++c
) OldSquareUnder2
[c
] = Who
->GetLSquareUnder(c
);
2393 v2 WhoPos
= Who
->GetPos();
2398 EditAP(-GetMoveAPRequirement(GetSquareUnder()->GetEntryDifficulty()) - 500);
2399 EditNP(-12*GetSquareUnder()->GetEntryDifficulty());
2400 EditExperience(AGILITY
, 75, GetSquareUnder()->GetEntryDifficulty() << 7);
2401 if (IsPlayer()) ShowNewPosInfo();
2402 if (Who
->IsPlayer()) Who
->ShowNewPosInfo();
2403 SignalStepFrom(OldSquareUnder1
);
2404 Who
->SignalStepFrom(OldSquareUnder2
);
2408 ADD_MESSAGE("%s resists!", Who
->CHAR_DESCRIPTION(DEFINITE
));
2417 void character::SetNP (sLong What
) {
2418 int OldState
= GetHungerState();
2421 int NewState
= GetHungerState();
2422 if (NewState
== STARVING
&& OldState
> STARVING
) DeActivateVoluntaryAction(CONST_S("You are getting really hungry."));
2423 else if (NewState
== VERY_HUNGRY
&& OldState
> VERY_HUNGRY
) DeActivateVoluntaryAction(CONST_S("You are getting very hungry."));
2424 else if (NewState
== HUNGRY
&& OldState
> HUNGRY
) DeActivateVoluntaryAction(CONST_S("You are getting hungry."));
2429 void character::ShowNewPosInfo () const {
2430 msgsystem::EnterBigMessageMode();
2433 if (ivanconfig::GetAutoCenterMap()) {
2434 game::UpdateCameraX();
2435 game::UpdateCameraY();
2437 if (Pos
.X
< game::GetCamera().X
+3 || Pos
.X
>= game::GetCamera().X
+game::GetScreenXSize()-3) game::UpdateCameraX();
2438 if (Pos
.Y
< game::GetCamera().Y
+3 || Pos
.Y
>= game::GetCamera().Y
+game::GetScreenYSize()-3) game::UpdateCameraY();
2441 game::SendLOSUpdateRequest();
2442 game::DrawEverythingNoBlit();
2445 if (!game::IsInWilderness()) {
2446 if (GetLSquareUnder()->IsDark() && !game::GetSeeWholeMapCheatMode()) ADD_MESSAGE("It's dark in here!");
2448 GetLSquareUnder()->ShowSmokeMessage();
2449 itemvectorvector PileVector
;
2450 GetStackUnder()->Pile(PileVector
, this, CENTER
);
2452 if (PileVector
.size()) {
2453 truth Feel
= !GetLSquareUnder()->IsTransparent() || GetLSquareUnder()->IsDark();
2455 if (PileVector
.size() == 1) {
2457 ADD_MESSAGE("You feel %s lying here.", PileVector
[0][0]->GetName(INDEFINITE
, PileVector
[0].size()).CStr());
2459 if (ivanconfig::GetShowFullItemDesc() && PileVector
[0][0]->AllowDetailedDescription()) {
2462 PileVector
[0][0]->AddInventoryEntry(PLAYER
, text
, PileVector
[0].size(), true);
2463 //fprintf(stderr, "invdsc : [%s]\n", text.CStr());
2464 ADD_MESSAGE("%s %s lying here.", text
.CStr(), PileVector
[0].size() == 1 ? "is" : "are");
2466 ADD_MESSAGE("%s %s lying here.", PileVector
[0][0]->GetName(INDEFINITE
, PileVector
[0].size()).CStr(), PileVector
[0].size() == 1 ? "is" : "are");
2469 fprintf(stderr, "description: [%s]\n", PileVector[0][0]->GetDescription(INDEFINITE).CStr());
2470 fprintf(stderr, "strength : [%s]\n", PileVector[0][0]->GetStrengthValueDescription());
2471 fprintf(stderr, "basetohit : [%s]\n", PileVector[0][0]->GetBaseToHitValueDescription());
2472 fprintf(stderr, "baseblock : [%s]\n", PileVector[0][0]->GetBaseBlockValueDescription());
2473 fprintf(stderr, "extdsc : [%s]\n", PileVector[0][0]->GetExtendedDescription().CStr());
2478 for (uInt c
= 0; c
< PileVector
.size(); ++c
) {
2479 if ((Items
+= PileVector
[c
].size()) > 3) break;
2482 if (Feel
) ADD_MESSAGE("You feel several items lying here.");
2483 else ADD_MESSAGE("Several items are lying here.");
2485 if (Feel
) ADD_MESSAGE("You feel a few items lying here.");
2486 else ADD_MESSAGE("A few items are lying here.");
2492 GetLSquareUnder()->GetSideItemDescription(SideItems
);
2494 if (!SideItems
.IsEmpty()) ADD_MESSAGE("There is %s.", SideItems
.CStr());
2496 if (GetLSquareUnder()->HasEngravings()) {
2497 if (CanRead()) ADD_MESSAGE("Something has been engraved here: \"%s\"", GetLSquareUnder()->GetEngraved());
2498 else ADD_MESSAGE("Something has been engraved here.");
2502 msgsystem::LeaveBigMessageMode();
2506 void character::Hostility (character
*Enemy
) {
2507 if (Enemy
== this || !Enemy
|| !Team
|| !Enemy
->Team
) return;
2508 if (Enemy
->IsMasochist() && GetRelation(Enemy
) == FRIEND
) return;
2509 if (!IsAlly(Enemy
)) {
2510 GetTeam()->Hostility(Enemy
->GetTeam());
2511 } else if (IsPlayer() && !Enemy
->IsPlayer()) {
2512 // I believe both may be players due to polymorph feature...
2513 if (Enemy
->CanBeSeenByPlayer()) ADD_MESSAGE("%s becomes enraged.", Enemy
->CHAR_NAME(DEFINITE
));
2514 Enemy
->ChangeTeam(game::GetTeam(BETRAYED_TEAM
));
2519 stack
*character::GetGiftStack () const {
2520 if (GetLSquareUnder()->GetRoomIndex() && !GetLSquareUnder()->GetRoom()->AllowDropGifts()) return GetStack();
2521 return GetStackUnder();
2525 truth
character::MoveRandomlyInRoom () {
2526 for (int c
= 0; c
< 10; ++c
) {
2527 v2 ToTry
= game::GetMoveVector(RAND()&7);
2528 if (GetLevel()->IsValidPos(GetPos()+ToTry
)) {
2529 lsquare
*Square
= GetNearLSquare(GetPos()+ToTry
);
2530 if (!Square
->IsDangerous(this) && !Square
->IsScary(this) &&
2531 (!Square
->GetOLTerrain() || !Square
->GetOLTerrain()->IsDoor()) &&
2532 TryMove(ToTry
, false, false)) return true;
2539 //#define dirlogf(...) do { fprintf(stderr, __VA_ARGS__); } while (0)
2540 #define dirlogf(...) ((void)0)
2567 static const int revDir
[8] = { MDIR_DOWN_RIGHT
, MDIR_DOWN
, MDIR_DOWN_LEFT
, MDIR_RIGHT
, MDIR_LEFT
, MDIR_UP_RIGHT
, MDIR_UP
, MDIR_UP_LEFT
};
2568 static const bool orthoDir
[8] = { false, true, false, true, true, false, true, false };
2571 // only for ortho moveDir
2572 static inline truth
IsDirExcluded (int moveDir
, int dir
) {
2573 if (moveDir
== dir
) return true;
2575 case MDIR_UP
: return (dir
== MDIR_UP_LEFT
|| dir
== MDIR_UP_RIGHT
);
2576 case MDIR_LEFT
: return (dir
== MDIR_UP_LEFT
|| dir
== MDIR_DOWN_LEFT
);
2577 case MDIR_RIGHT
: return (dir
== MDIR_UP_RIGHT
|| dir
== MDIR_DOWN_RIGHT
);
2578 case MDIR_DOWN
: return (dir
== MDIR_DOWN_LEFT
|| dir
== MDIR_DOWN_RIGHT
);
2584 truth
character::IsPassableSquare (int x
, int y
) const {
2585 if (x
>= 0 && y
>= 0) {
2586 area
*ca
= GetSquareUnder()->GetArea();
2589 if (x
>= ca
->GetXSize() || y
>= ca
->GetYSize()) return false;
2590 sq
= static_cast<lsquare
*>(ca
->GetSquare(x
, y
));
2591 return sq
&& CanMoveOn(sq
);
2597 void character::CountPossibleMoveDirs (cv2 pos
, int *odirs
, int *ndirs
, int exclideDir
) const {
2598 if (odirs
) *odirs
= 0;
2599 if (ndirs
) *ndirs
= 0;
2600 for (int f
= 0; f
< 8; ++f
) {
2601 if (!IsDirExcluded(exclideDir
, f
)) {
2602 if (IsPassableSquare(pos
+game::GetMoveVector(f
))) {
2604 if (odirs
) ++(*odirs
);
2606 if (ndirs
) ++(*ndirs
);
2615 * in corridor (for orto-dirs):
2616 * count dirs excluding ortho-dir we going:
2617 * if there is one or less ortho-dirs and one or less non-ortho-dirs, we are in corridor
2619 // only for ortho-dirs
2620 truth
character::IsInCorridor (int x
, int y
, int moveDir
) const {
2623 dirlogf("IsInCorridor(%d,%d,%d)\n", x
, y
, moveDir
);
2625 moveDir
= (moveDir
>= 0 && moveDir
< MDIR_STAND
? revDir
[moveDir
] : -1);
2626 dirlogf(" reversedDir: %d\n", moveDir
);
2627 CountPossibleMoveDirs(v2(x
, y
), &od
, &nd
, moveDir
);
2628 dirlogf(" possibleDirs: (%d:%d)\n", od
, nd
);
2629 dirlogf(" IsInCorridor: %s\n", ((od
<= 1 && nd
<= 1) ? "yes" : "no"));
2630 return (od
<= 1 && nd
<= 1);
2634 cv2
character::GetDiagonalForDirs (int moveDir
, int newDir
) const {
2638 case MDIR_LEFT
: return game::GetMoveVector(MDIR_UP_LEFT
);
2639 case MDIR_RIGHT
: return game::GetMoveVector(MDIR_UP_RIGHT
);
2644 case MDIR_LEFT
: return game::GetMoveVector(MDIR_DOWN_LEFT
);
2645 case MDIR_RIGHT
: return game::GetMoveVector(MDIR_DOWN_RIGHT
);
2650 case MDIR_UP
: return game::GetMoveVector(MDIR_UP_LEFT
);
2651 case MDIR_DOWN
: return game::GetMoveVector(MDIR_DOWN_LEFT
);
2656 case MDIR_UP
: return game::GetMoveVector(MDIR_UP_RIGHT
);
2657 case MDIR_DOWN
: return game::GetMoveVector(MDIR_DOWN_RIGHT
);
2661 ABORT("wtf in character::GetDiagonalForDirs()");
2665 truth
character::IsInTunnelDeadEnd () const {
2668 CountPossibleMoveDirs(GetPos(), &od
, &nd
, -1);
2669 return (od
<= 1 && nd
== 0);
2674 * try to walk in the given dir
2675 * can do two steps without a turn and still in corridor?
2679 * go in non-ortho dir, set prevdir to last ortho-dir from corridor tracing
2681 // only for ortho-dirs; assume that the char is in corridor
2682 int character::CheckCorridorMove (v2
&moveVector
, cv2 pos
, int moveDir
, truth
*markAsTurn
) const {
2683 v2
ps1(pos
+(moveVector
= game::GetMoveVector(moveDir
)));
2685 if (markAsTurn
) *markAsTurn
= true;
2687 if (IsPassableSquare(ps1
)) {
2688 // we can do first step in the given dir
2689 // check if we will be in corridor after it
2690 dirlogf("CheckCorridorMove: can do first step\n");
2691 if (IsInCorridor(ps1
, moveDir
)) {
2692 // check second step
2693 v2
ps2(ps1
+moveVector
);
2695 dirlogf("CheckCorridorMove: still in corridor after the first step\n");
2696 if (IsPassableSquare(ps2
)) {
2697 // can do second step
2698 dirlogf("CheckCorridorMove: can do second step\n");
2701 // can't do second step; but we still in corridor, so we should make a turn
2702 int newDir
= -1; // direction to turn
2704 for (int f
= 0; f
< MDIR_STAND
; ++f
) {
2705 if (f
!= moveDir
&& orthoDir
[f
] && f
!= revDir
[moveDir
] && IsPassableSquare(ps1
+game::GetMoveVector(f
))) {
2710 dirlogf("CheckCorridorMove: can't do second step; moveDir=%d; newDir=%d\n", moveDir
, newDir
);
2712 // dead end, will stop
2713 //ABORT("wtd in character::CheckCorridorMove()");
2716 // we should do diagonal move
2717 moveVector
= GetDiagonalForDirs(moveDir
, newDir
);
2718 // if this is 'one-tile-turn', we should not change the direction to newDir
2719 if (IsPassableSquare(ps1
+game::GetMoveVector(newDir
)+game::GetMoveVector(moveDir
))) {
2720 // yes, this is 'one-tile-turn'
2721 dirlogf("CheckCorridorMove: one-tile-turn, don't change dir\n");
2729 * 'g'o right: should stop at '*', but it just goes right
2731 if (markAsTurn
) *markAsTurn
= IsInCorridor(ps1
+game::GetMoveVector(newDir
), newDir
);
2738 dirlogf("CheckCorridorMove: can do one or two steps; move forward\n");
2739 // can do one or two steps; just move forward
2742 dirlogf("CheckCorridorMove: dead end\n");
2743 // can't go, assume invalid direction
2748 void character::GoOn (go
*Go
, truth FirstStep
) {
2749 dirlogf("=== character::GoOn; dir=%d; pos=(%d,%d) ===\n", Go
->GetDirection(), GetPos().X
, GetPos().Y
);
2751 dirlogf("FirstStep\n");
2752 mPrevMoveDir
= Go
->GetDirection();
2753 Go
->SetIsWalkingInOpen(!IsInCorridor(Go
->GetDirection()));
2756 v2 MoveVector
= ApplyStateModification(game::GetMoveVector(Go
->GetDirection()));
2757 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
2758 int Squares
= CalculateNewSquaresUnder(MoveToSquare
, GetPos()+MoveVector
);
2759 int moveDir
= game::MoveVectorToDirection(MoveVector
);
2761 if (!Squares
|| !CanMoveOn(MoveToSquare
[0])) {
2762 dirlogf("just can't move\n");
2763 Go
->Terminate(false);
2768 if (!Go
->GetPrevWasTurn() && Go
->IsWalkingInOpen() != !IsInCorridor(GetPos(), moveDir
)) {
2769 dirlogf("moved to/from open place\n");
2770 Go
->Terminate(false);
2774 uInt OldRoomIndex
= GetLSquareUnder()->GetRoomIndex();
2775 uInt CurrentRoomIndex
= MoveToSquare
[0]->GetRoomIndex();
2777 if (OldRoomIndex
&& (CurrentRoomIndex
!= OldRoomIndex
)) {
2778 // room about to be changed, stop here
2779 dirlogf("room about to be changed\n");
2780 Go
->Terminate(false);
2784 // stop near the dangerous square
2785 for (int c
= 0; c
< Squares
; ++c
) {
2786 if ((MoveToSquare
[c
]->GetCharacter() && GetTeam() != MoveToSquare
[c
]->GetCharacter()->GetTeam()) ||
2787 MoveToSquare
[c
]->IsDangerous(this)) {
2788 dirlogf("sense the danger\n");
2789 Go
->Terminate(false);
2795 if (moveDir
!= Go
->GetDirection()) {
2796 // state modified the direction, move and stop
2797 dirlogf("move affected by state\n");
2798 if (TryMove(MoveVector
, true, game::PlayerIsRunning())) {
2799 game::DrawEverything();
2800 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
2802 Go
->Terminate(false);
2806 truth doStop
= false, markAsTurn
= false;
2809 // continuous walking
2810 if (Go
->IsWalkingInOpen() || !orthoDir
[moveDir
]) {
2811 // walking in open space or diagonal walking
2812 v2
newPos(GetPos()+MoveVector
);
2813 int ood
, ond
, nod
, nnd
;
2815 * open: stop if # of possible dirs in next step != # of possible dirs in current step
2816 * (or next step is in corridor)
2818 dirlogf("open walking\n");
2819 if (IsInCorridor(newPos
, moveDir
)) {
2820 // trying to enter the corridor, stop right here
2821 dirlogf("entering the corridor\n");
2822 Go
->Terminate(false);
2825 CountPossibleMoveDirs(GetPos(), &ood
, &ond
);
2826 CountPossibleMoveDirs(newPos
, &nod
, &nnd
);
2827 if (ood
!= nod
|| ond
!= nnd
) {
2828 // # of directions to walk to changed, stop right here
2829 dirlogf("# of directions changed from (%d:%d) to (%d:%d)\n", ood
, ond
, nod
, nnd
);
2830 //Go->Terminate(false);
2834 // ok, we can do this move
2836 // ortho-walking thru the corridor
2837 int newDir
= CheckCorridorMove(MoveVector
, GetPos(), moveDir
, &markAsTurn
);
2840 // ah, something weird; stop right here
2841 Go
->Terminate(false);
2844 Go
->SetDirection(newDir
); // perform possible turn
2847 // first step, just do it
2849 // now try to perform the move
2851 dirlogf("trying to make the move\n");
2852 // stop near the dangerous square (fuckin' copypasta)
2853 Squares
= CalculateNewSquaresUnder(MoveToSquare
, GetPos()+MoveVector
);
2855 for (int c
= 0; c
< Squares
; ++c
) {
2856 if ((MoveToSquare
[c
]->GetCharacter() && GetTeam() != MoveToSquare
[c
]->GetCharacter()->GetTeam()) ||
2857 MoveToSquare
[c
]->IsDangerous(this)) {
2858 dirlogf(" danger!\n");
2859 Go
->Terminate(false);
2865 square
*BeginSquare
= GetSquareUnder();
2866 uInt OldRoomIndex
= GetLSquareUnder()->GetRoomIndex();
2867 uInt CurrentRoomIndex
= MoveToSquare
[0]->GetRoomIndex();
2869 // stop on the square with something interesting
2872 area
*ca
= GetSquareUnder()->GetArea();
2873 v2 npos
= GetPos()+MoveVector
;
2875 for (int f
= 0; f
< MDIR_STAND
; ++f
) {
2876 v2 np
= npos
+game::GetMoveVector(f
);
2878 if (np
.X
>= 0 && np
.Y
>= 0 && np
.X
< ca
->GetXSize() && np
.Y
< ca
->GetYSize()) {
2879 lsquare
*sq
= static_cast<lsquare
*>(ca
->GetSquare(np
.X
, np
.Y
));
2880 olterrain
*terra
= sq
->GetOLTerrain();
2883 dirlogf("** OK terra at %d; door: %s; seen: %s\n", f
, (terra
->IsDoor() ? "yes" : "no"), (sq
->IsGoSeen() ? "yes" : "no"));
2884 if (terra
->IsDoor()) {
2885 if (ivanconfig::GetStopOnSeenDoors() || !sq
->IsGoSeen()) {
2886 dirlogf(" *** stop near the door\n");
2896 for (int c
= 0; c
< Squares
; ++c
) {
2897 if (MoveToSquare
[c
]->GetStack()->HasSomethingFunny(this, ivanconfig::GetStopOnCorpses(), ivanconfig::GetStopOnSeenItems())) {
2898 dirlogf(" stepped near something interesting\n");
2906 Go
->SetPrevWasTurn(markAsTurn
&& MoveVector
.X
&& MoveVector
.Y
); // diagonal move?
2908 truth moveOk
= TryMove(MoveVector
, true, game::PlayerIsRunning());
2910 if (!moveOk
|| BeginSquare
== GetSquareUnder() || (CurrentRoomIndex
&& (OldRoomIndex
!= CurrentRoomIndex
))) {
2911 dirlogf(" stopped\n");
2913 game::DrawEverything();
2914 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
2916 Go
->Terminate(false);
2921 mPrevMoveDir
= Go
->GetDirection();
2922 Go
->SetIsWalkingInOpen(!IsInCorridor(moveDir
));
2925 game::DrawEverything();
2926 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
2927 if (doStop
) Go
->Terminate(false);
2931 void character::SetTeam (team
*What
) {
2932 /*k8 if(Team) int esko = esko = 2; */
2934 SetTeamIterator(What
->Add(this));
2938 void character::ChangeTeam (team
*What
) {
2939 if (Team
) Team
->Remove(GetTeamIterator());
2941 SendNewDrawRequest();
2942 if (Team
) SetTeamIterator(Team
->Add(this));
2946 truth
character::ChangeRandomAttribute (int HowMuch
) {
2947 for (int c
= 0; c
< 50; ++c
) {
2948 int AttribID
= RAND()%ATTRIBUTES
;
2949 if (EditAttribute(AttribID
, HowMuch
)) return true;
2955 int character::RandomizeReply (sLong
&Said
, int Replies
) {
2956 truth NotSaid
= false;
2957 for (int c
= 0; c
< Replies
; ++c
) {
2958 if (!(Said
& (1 << c
))) {
2963 if (!NotSaid
) Said
= 0;
2965 while (Said
& 1 << (ToSay
= RAND() % Replies
));
2971 void character::DisplayInfo (festring
&Msg
) {
2973 Msg
<< " You are " << GetStandVerb() << " here.";
2975 Msg
<< ' ' << GetName(INDEFINITE
).CapitalizeCopy() << " is " << GetStandVerb() << " here. " << GetPersonalPronoun().CapitalizeCopy();
2976 cchar
*Separator1
= GetAction() ? "," : " and";
2977 cchar
*Separator2
= " and";
2978 if (GetTeam() == PLAYER
->GetTeam()) {
2981 int Relation
= GetRelation(PLAYER
);
2982 if (Relation
== HOSTILE
) Msg
<< " is hostile";
2983 else if (Relation
== UNCARING
) {
2984 Msg
<< " does not care about you";
2985 Separator1
= Separator2
= " and is";
2987 Msg
<< " is friendly";
2990 if (StateIsActivated(PANIC
)) {
2991 Msg
<< Separator1
<< " panicked";
2992 Separator2
= " and";
2994 if (GetAction()) Msg
<< Separator2
<< ' ' << GetAction()->GetDescription();
3000 void character::TestWalkability () {
3001 if (!IsEnabled()) return;
3002 square
*SquareUnder
= !game::IsInWilderness() ? GetSquareUnder() : PLAYER
->GetSquareUnder();
3003 if (SquareUnder
->IsFatalToStay() && !CanMoveOn(SquareUnder
)) {
3004 truth Alive
= false;
3005 if (!game::IsInWilderness() || IsPlayer()) {
3006 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
3007 square
*Square
= GetNeighbourSquare(d
);
3008 if (Square
&& CanMoveOn(Square
) && IsFreeForMe(Square
)) {
3009 if (IsPlayer()) ADD_MESSAGE("%s.", SquareUnder
->SurviveMessage(this));
3010 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE
), SquareUnder
->MonsterSurviveMessage(this));
3011 Move(Square
->GetPos(), true); // actually, this shouldn't be a teleport move
3012 SquareUnder
->SurviveEffect(this);
3022 festring DeathMsg
= festring(SquareUnder
->DeathMessage(this));
3023 game::AskForEscPress(DeathMsg
+".");
3024 festring Msg
= SquareUnder
->ScoreEntry(this);
3025 PLAYER
->AddScoreEntry(Msg
);
3028 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE
), SquareUnder
->MonsterDeathVerb(this));
3029 Die(0, SquareUnder
->ScoreEntry(this), DISALLOW_MSG
);
3036 int character::GetSize () const {
3037 return GetTorso()->GetSize();
3041 void character::SetMainMaterial (material
*NewMaterial
, int SpecialFlags
) {
3042 NewMaterial
->SetVolume(GetBodyPart(0)->GetMainMaterial()->GetVolume());
3043 GetBodyPart(0)->SetMainMaterial(NewMaterial
, SpecialFlags
);
3044 for (int c
= 1; c
< BodyParts
; ++c
) {
3045 NewMaterial
= NewMaterial
->SpawnMore(GetBodyPart(c
)->GetMainMaterial()->GetVolume());
3046 GetBodyPart(c
)->SetMainMaterial(NewMaterial
, SpecialFlags
);
3051 void character::ChangeMainMaterial (material
*NewMaterial
, int SpecialFlags
) {
3052 NewMaterial
->SetVolume(GetBodyPart(0)->GetMainMaterial()->GetVolume());
3053 GetBodyPart(0)->ChangeMainMaterial(NewMaterial
, SpecialFlags
);
3054 for (int c
= 1; c
< BodyParts
; ++c
) {
3055 NewMaterial
= NewMaterial
->SpawnMore(GetBodyPart(c
)->GetMainMaterial()->GetVolume());
3056 GetBodyPart(c
)->ChangeMainMaterial(NewMaterial
, SpecialFlags
);
3061 void character::SetSecondaryMaterial (material
*, int) {
3062 ABORT("Illegal character::SetSecondaryMaterial call!");
3066 void character::ChangeSecondaryMaterial (material
*, int) {
3067 ABORT("Illegal character::ChangeSecondaryMaterial call!");
3071 void character::TeleportRandomly (truth Intentional
) {
3072 v2 TelePos
= ERROR_V2
;
3073 if (StateIsActivated(TELEPORT_CONTROL
)) {
3075 v2 Input
= game::PositionQuestion(CONST_S("Where do you wish to teleport? [direction keys move cursor, space accepts]"), GetPos(), &game::TeleportHandler
, 0, false);
3076 if (Input
== ERROR_V2
) Input
= GetPos(); // esc pressed
3077 lsquare
*Square
= GetNearLSquare(Input
);
3078 if (CanMoveOn(Square
) || game::GoThroughWallsCheatIsActive()) {
3079 if (Square
->GetPos() == GetPos()) {
3080 ADD_MESSAGE("You disappear and reappear.");
3083 if (IsFreeForMe(Square
)) {
3084 if ((Input
-GetPos()).GetLengthSquare() <= GetTeleportRangeSquare()) {
3085 EditExperience(INTELLIGENCE
, 100, 1 << 10);
3088 ADD_MESSAGE("You cannot concentrate yourself enough to control a teleport that far.");
3091 character
*C
= Square
->GetCharacter();
3092 if (C
) ADD_MESSAGE("For a moment you feel very much like %s.", C
->CHAR_NAME(INDEFINITE
));
3093 else ADD_MESSAGE("You feel that something weird has happened, but can't really tell what exactly.");
3096 ADD_MESSAGE("You feel like having been hit by something really hard from the inside.");
3098 } else if (!Intentional
) {
3099 if (IsGoingSomeWhere() && GetLevel()->IsValidPos(GoingTo
)) {
3100 v2 Where
= GetLevel()->GetNearestFreeSquare(this, GoingTo
);
3101 if (Where
!= ERROR_V2
&& (Where
-GetPos()).GetLengthSquare() <= GetTeleportRangeSquare()) {
3102 EditExperience(INTELLIGENCE
, 100, 1 << 10);
3110 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.");
3113 //if (TelePos != ERROR_V2) Move(TelePos, true);
3114 //else Move(GetLevel()->GetRandomSquare(this), true);
3115 //if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE));
3116 //if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
3118 if (TelePos
== ERROR_V2
) TelePos
= GetLevel()->GetRandomSquare(this);
3120 room
*PossibleRoom
= game::GetCurrentLevel()->GetLSquare(TelePos
)->GetRoom();
3122 if (!PossibleRoom
) {
3123 //if it's outside of a room
3124 if (TelePos
!= ERROR_V2
) Move(TelePos
, true);
3125 else Move(GetLevel()->GetRandomSquare(this), true);
3126 if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE
));
3127 if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
3128 } else if (PossibleRoom
&& PossibleRoom
->IsOKToTeleportInto()) {
3129 // If it's inside of a room, check whether a ward is active that might impede the player
3130 if (TelePos
!= ERROR_V2
) Move(TelePos
, true);
3131 else Move(GetLevel()->GetRandomSquare(this), true);
3132 if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE
));
3133 if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
3136 ADD_MESSAGE("A mighty force blasts you back to where you were standing. A ward prevents you from teleporting.");
3138 game::GetCurrentLevel()->Explosion(this, CONST_S("killed by an explosion triggered when attempting to teleport into room protected by a ward"), PLAYER
->GetPos(), 300 >> 3, false);
3143 CONST_S("killed by an explosion triggered when attempting to teleport into room protected by a ward"),
3147 lsquare* Square = GetNearLSquare(GetPos());
3148 Square->DrawParticles(RED);
3149 Square->FireBall(Beam);*/
3154 void character::DoDetecting () {
3155 material
*TempMaterial
;
3158 festring Temp
= game::DefaultQuestion(CONST_S("What material do you want to detect?"), game::GetDefaultDetectMaterial());
3159 TempMaterial
= protosystem::CreateMaterial(Temp
);
3160 if (TempMaterial
) break;
3161 game::DrawEverythingNoBlit();
3164 level
*Level
= GetLevel();
3165 int Squares
= Level
->DetectMaterial(TempMaterial
);
3167 if (Squares
> GetAttribute(INTELLIGENCE
) * (25+RAND()%51)) {
3168 ADD_MESSAGE("An enormous burst of geographical information overwhelms your consciousness. Your mind cannot cope with it and your memories blur.");
3169 Level
->BlurMemory();
3170 BeginTemporaryState(CONFUSED
, 1000 + RAND() % 1000);
3171 EditExperience(INTELLIGENCE
, -100, 1 << 12);
3172 } else if (!Squares
) {
3173 ADD_MESSAGE("You feel a sudden urge to imagine the dark void of a starless night sky.");
3174 EditExperience(INTELLIGENCE
, 200, 1 << 12);
3176 ADD_MESSAGE("You feel attracted to all things made of %s.", TempMaterial
->GetName(false, false).CStr());
3177 game::PositionQuestion(CONST_S("Detecting material [direction keys move cursor, space exits]"), GetPos(), 0, 0, false);
3178 EditExperience(INTELLIGENCE
, 300, 1 << 12);
3181 delete TempMaterial
;
3182 Level
->CalculateLuminances();
3183 game::SendLOSUpdateRequest();
3187 void character::RestoreHP () {
3188 doforbodyparts()(this, &bodypart::FastRestoreHP
);
3193 void character::RestoreLivingHP () {
3195 for (int c
= 0; c
< BodyParts
; ++c
) {
3196 bodypart
*BodyPart
= GetBodyPart(c
);
3197 if (BodyPart
&& BodyPart
->CanRegenerate()) {
3198 BodyPart
->FastRestoreHP();
3199 HP
+= BodyPart
->GetHP();
3205 truth
character::AllowDamageTypeBloodSpill (int Type
) {
3206 switch (Type
&0xFFF) {
3207 case PHYSICAL_DAMAGE
:
3216 case MUSTARD_GAS_DAMAGE
:
3220 ABORT("Unknown blood effect destroyed the dungeon!");
3225 /* Returns truly done damage */
3226 int character::ReceiveBodyPartDamage (character
*Damager
, int Damage
, int Type
, int BodyPartIndex
,
3227 int Direction
, truth PenetrateResistance
, truth Critical
, truth ShowNoDamageMsg
, truth CaptureBodyPart
)
3229 bodypart
*BodyPart
= GetBodyPart(BodyPartIndex
);
3230 if (!Damager
|| Damager
->AttackMayDamageArmor()) BodyPart
->DamageArmor(Damager
, Damage
, Type
);
3231 if (!PenetrateResistance
) {
3232 Damage
-= (BodyPart
->GetTotalResistance(Type
)>>1)+RAND()%((BodyPart
->GetTotalResistance(Type
)>>1)+1);
3234 if (int(Damage
) < 1) {
3238 if (ShowNoDamageMsg
) {
3239 if (IsPlayer()) ADD_MESSAGE("You are not hurt.");
3240 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s is not hurt.", GetPersonalPronoun().CStr());
3246 if (Critical
&& AllowDamageTypeBloodSpill(Type
) && !game::IsInWilderness()) {
3247 BodyPart
->SpillBlood(2+(RAND()&1));
3248 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
3249 lsquare
*Square
= GetNeighbourLSquare(d
);
3250 if (Square
&& Square
->IsFlyable()) BodyPart
->SpillBlood(1, Square
->GetPos());
3254 if (BodyPart
->ReceiveDamage(Damager
, Damage
, Type
, Direction
) && BodyPartCanBeSevered(BodyPartIndex
)) {
3255 if (DamageTypeDestroysBodyPart(Type
)) {
3256 if (IsPlayer()) ADD_MESSAGE("Your %s is destroyed!", BodyPart
->GetBodyPartName().CStr());
3257 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s is destroyed!", GetPossessivePronoun().CStr(), BodyPart
->GetBodyPartName().CStr());
3258 GetBodyPart(BodyPartIndex
)->DropEquipment();
3259 item
*Severed
= SevereBodyPart(BodyPartIndex
);
3260 if (Severed
) Severed
->DestroyBodyPart(!game::IsInWilderness() ? GetStackUnder() : GetStack());
3261 SendNewDrawRequest();
3262 if (IsPlayer()) game::AskForEscPress(CONST_S("Bodypart destroyed!"));
3264 if (IsPlayer()) ADD_MESSAGE("Your %s is severed off!", BodyPart
->GetBodyPartName().CStr());
3265 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s is severed off!", GetPossessivePronoun().CStr(), BodyPart
->GetBodyPartName().CStr());
3266 item
*Severed
= SevereBodyPart(BodyPartIndex
);
3267 SendNewDrawRequest();
3269 if (CaptureBodyPart
) {
3270 Damager
->GetLSquareUnder()->AddItem(Severed
);
3271 } else if (!game::IsInWilderness()) {
3272 /** No multi-tile humanoid support! */
3273 GetStackUnder()->AddItem(Severed
);
3274 if (Direction
!= YOURSELF
) Severed
->Fly(0, Direction
, Damage
);
3276 GetStack()->AddItem(Severed
);
3278 Severed
->DropEquipment();
3279 } else if (IsPlayer() || CanBeSeenByPlayer()) {
3280 ADD_MESSAGE("It vanishes.");
3282 if (IsPlayer()) game::AskForEscPress(CONST_S("Bodypart severed!"));
3284 if (CanPanicFromSeveredBodyPart() && RAND()%100 < GetPanicLevel() && !StateIsActivated(PANIC
) && !IsDead()) {
3285 BeginTemporaryState(PANIC
, 1000+RAND()%1001);
3287 SpecialBodyPartSeverReaction();
3290 if (!IsDead()) CheckPanic(500);
3296 /* Returns 0 if bodypart disappears */
3297 item
*character::SevereBodyPart (int BodyPartIndex
, truth ForceDisappearance
, stack
*EquipmentDropStack
) {
3298 bodypart
*BodyPart
= GetBodyPart(BodyPartIndex
);
3299 if (StateIsActivated(LEPROSY
)) BodyPart
->GetMainMaterial()->SetIsInfectedByLeprosy(true);
3300 if (ForceDisappearance
|| BodyPartsDisappearWhenSevered() || StateIsActivated(POLYMORPHED
) || game::AllBodyPartsVanish()) {
3301 BodyPart
->DropEquipment(EquipmentDropStack
);
3302 BodyPart
->RemoveFromSlot();
3303 CalculateAttributeBonuses();
3304 CalculateBattleInfo();
3305 BodyPart
->SendToHell();
3306 SignalPossibleTransparencyChange();
3307 RemoveTraps(BodyPartIndex
);
3310 BodyPart
->SetOwnerDescription("of " + GetName(INDEFINITE
));
3311 BodyPart
->SetIsUnique(LeftOversAreUnique());
3312 UpdateBodyPartPicture(BodyPartIndex
, true);
3313 BodyPart
->RemoveFromSlot();
3314 BodyPart
->RandomizePosition();
3315 CalculateAttributeBonuses();
3316 CalculateBattleInfo();
3318 SignalPossibleTransparencyChange();
3319 RemoveTraps(BodyPartIndex
);
3324 /* The second int is actually TargetFlags, which is not used here, but seems to be used in humanoid::ReceiveDamage.
3325 * Returns true if the character really receives damage */
3326 truth
character::ReceiveDamage (character
*Damager
, int Damage
, int Type
, int, int Direction
,
3327 truth
, truth PenetrateArmor
, truth Critical
, truth ShowMsg
)
3329 truth Affected
= ReceiveBodyPartDamage(Damager
, Damage
, Type
, 0, Direction
, PenetrateArmor
, Critical
, ShowMsg
);
3330 if (DamageTypeAffectsInventory(Type
)) {
3331 for (int c
= 0; c
< GetEquipments(); ++c
) {
3332 item
*Equipment
= GetEquipment(c
);
3333 if (Equipment
) Equipment
->ReceiveDamage(Damager
, Damage
, Type
);
3335 GetStack()->ReceiveDamage(Damager
, Damage
, Type
);
3341 festring
character::GetDescription (int Case
) const {
3342 if (IsPlayer()) return CONST_S("you");
3343 if (CanBeSeenByPlayer()) return GetName(Case
);
3344 return CONST_S("something");
3348 festring
character::GetPersonalPronoun (truth PlayersView
) const {
3349 if (IsPlayer() && PlayersView
) return CONST_S("you");
3350 if (GetSex() == UNDEFINED
|| (PlayersView
&& !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("it");
3351 if (GetSex() == MALE
) return CONST_S("he");
3352 return CONST_S("she");
3356 festring
character::GetPossessivePronoun (truth PlayersView
) const {
3357 if (IsPlayer() && PlayersView
) return CONST_S("your");
3358 if (GetSex() == UNDEFINED
|| (PlayersView
&& !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("its");
3359 if (GetSex() == MALE
) return CONST_S("his");
3360 return CONST_S("her");
3364 festring
character::GetObjectPronoun (truth PlayersView
) const {
3365 if (IsPlayer() && PlayersView
) return CONST_S("you");
3366 if (GetSex() == UNDEFINED
|| (PlayersView
&& !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("it");
3367 if (GetSex() == MALE
) return CONST_S("him");
3368 return CONST_S("her");
3372 void character::AddName (festring
&String
, int Case
) const {
3373 if (AssignedName
.IsEmpty()) {
3374 id::AddName(String
, Case
);
3375 } else if (!(Case
& PLURAL
)) {
3376 if (!ShowClassDescription()) {
3377 String
<< AssignedName
;
3379 String
<< AssignedName
<< ' ';
3380 id::AddName(String
, (Case
|ARTICLE_BIT
)&~INDEFINE_BIT
);
3383 id::AddName(String
, Case
);
3384 String
<< " named " << AssignedName
;
3389 int character::GetHungerState () const {
3390 if (!UsesNutrition()) return NOT_HUNGRY
;
3391 if (GetNP() > OVER_FED_LEVEL
) return OVER_FED
;
3392 if (GetNP() > BLOATED_LEVEL
) return BLOATED
;
3393 if (GetNP() > SATIATED_LEVEL
) return SATIATED
;
3394 if (GetNP() > NOT_HUNGER_LEVEL
) return NOT_HUNGRY
;
3395 if (GetNP() > HUNGER_LEVEL
) return HUNGRY
;
3396 if (GetNP() > VERY_HUNGER_LEVEL
) return VERY_HUNGRY
;
3401 truth
character::CanConsume (material
*Material
) const {
3402 return GetConsumeFlags() & Material
->GetConsumeType();
3406 void character::SetTemporaryStateCounter (sLong State
, int What
) {
3407 for (int c
= 0; c
< STATES
; ++c
) {
3408 if ((1 << c
) & State
) TemporaryStateCounter
[c
] = What
;
3413 void character::EditTemporaryStateCounter (sLong State
, int What
) {
3414 for (int c
= 0; c
< STATES
; ++c
) {
3415 if ((1 << c
) & State
) TemporaryStateCounter
[c
] += What
;
3420 int character::GetTemporaryStateCounter (sLong State
) const {
3421 for (int c
= 0; c
< STATES
; ++c
) {
3422 if ((1 << c
) & State
) return TemporaryStateCounter
[c
];
3424 ABORT("Illegal GetTemporaryStateCounter request!");
3429 truth
character::CheckKick () const {
3431 if (IsPlayer()) ADD_MESSAGE("This race can't kick.");
3438 int character::GetResistance (int Type
) const {
3439 switch (Type
&0xFFF) {
3440 case PHYSICAL_DAMAGE
:
3443 case MUSTARD_GAS_DAMAGE
:
3446 case ENERGY
: return GetEnergyResistance();
3447 case FIRE
: return GetFireResistance();
3448 case POISON
: return GetPoisonResistance();
3449 case ELECTRICITY
: return GetElectricityResistance();
3450 case ACID
: return GetAcidResistance();
3452 ABORT("Resistance lack detected!");
3457 void character::Regenerate () {
3458 if (HP
== MaxHP
) return;
3459 sLong RegenerationBonus
= 0;
3460 truth NoHealableBodyParts
= true;
3461 for (int c
= 0; c
< BodyParts
; ++c
) {
3462 bodypart
*BodyPart
= GetBodyPart(c
);
3463 if (BodyPart
&& BodyPart
->CanRegenerate()) {
3464 RegenerationBonus
+= BodyPart
->GetMaxHP();
3465 if (NoHealableBodyParts
&& BodyPart
->GetHP() < BodyPart
->GetMaxHP()) NoHealableBodyParts
= false;
3468 if (!RegenerationBonus
|| NoHealableBodyParts
) return;
3469 RegenerationBonus
*= (50+GetAttribute(ENDURANCE
));
3471 if (Action
&& Action
->IsRest()) {
3472 if (SquaresUnder
== 1) RegenerationBonus
*= GetSquareUnder()->GetRestModifier() << 1;
3474 int Lowest
= GetSquareUnder(0)->GetRestModifier();
3475 for (int c
= 1; c
< GetSquaresUnder(); ++c
) {
3476 int Mod
= GetSquareUnder(c
)->GetRestModifier();
3477 if (Mod
< Lowest
) Lowest
= Mod
;
3479 RegenerationBonus
*= Lowest
<< 1;
3483 RegenerationCounter
+= RegenerationBonus
;
3485 while (RegenerationCounter
> 1250000) {
3486 bodypart
*BodyPart
= HealHitPoint();
3487 if (!BodyPart
) break;
3488 EditNP(-Max(7500/MaxHP
, 1));
3489 RegenerationCounter
-= 1250000;
3490 int HP
= BodyPart
->GetHP();
3491 EditExperience(ENDURANCE
, Min(1000*BodyPart
->GetMaxHP()/(HP
*HP
), 300), 1000);
3496 void character::PrintInfo () const {
3497 felist
Info(CONST_S("Information about ")+GetName(DEFINITE
));
3498 for (int c
= 0; c
< GetEquipments(); ++c
) {
3499 item
*Equipment
= GetEquipment(c
);
3500 if ((EquipmentEasilyRecognized(c
) || game::WizardModeIsActive()) && Equipment
) {
3501 int ImageKey
= game::AddToItemDrawVector(itemvector(1, Equipment
));
3502 Info
.AddEntry(festring(GetEquipmentName(c
))+": "+Equipment
->GetName(INDEFINITE
), LIGHT_GRAY
, 0, ImageKey
, true);
3505 if (Info
.IsEmpty()) {
3506 ADD_MESSAGE("There's nothing special to tell about %s.", CHAR_NAME(DEFINITE
));
3508 game::SetStandardListAttributes(Info
);
3509 Info
.SetEntryDrawer(game::ItemEntryDrawer
);
3512 game::ClearItemDrawVector();
3516 truth
character::TryToRiseFromTheDead () {
3517 for (int c
= 0; c
< BodyParts
; ++c
) {
3518 bodypart
*BodyPart
= GetBodyPart(c
);
3520 BodyPart
->ResetSpoiling();
3521 if (BodyPart
->CanRegenerate() || BodyPart
->GetHP() < 1) BodyPart
->SetHP(1);
3529 truth
character::RaiseTheDead (character
*) {
3530 truth Useful
= false;
3531 for (int c
= 0; c
< BodyParts
; ++c
) {
3532 bodypart
*BodyPart
= GetBodyPart(c
);
3533 if (!BodyPart
&& CanCreateBodyPart(c
)) {
3534 CreateBodyPart(c
)->SetHP(1);
3535 if (IsPlayer()) ADD_MESSAGE("Suddenly you grow a new %s.", GetBodyPartName(c
).CStr());
3536 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE
), GetBodyPartName(c
).CStr());
3538 } else if (BodyPart
&& BodyPart
->CanRegenerate() && BodyPart
->GetHP() < 1) {
3543 if (IsPlayer()) ADD_MESSAGE("You shudder.");
3544 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s shudders.", CHAR_NAME(DEFINITE
));
3550 void character::SetSize (int Size
) {
3551 for (int c
= 0; c
< BodyParts
; ++c
) {
3552 bodypart
*BodyPart
= GetBodyPart(c
);
3553 if (BodyPart
) BodyPart
->SetSize(GetBodyPartSize(c
, Size
));
3558 sLong
character::GetBodyPartSize (int I
, int TotalSize
) const {
3559 if (I
== TORSO_INDEX
) return TotalSize
;
3560 ABORT("Weird bodypart size request for a character!");
3565 sLong
character::GetBodyPartVolume (int I
) const {
3566 if (I
== TORSO_INDEX
) return GetTotalVolume();
3567 ABORT("Weird bodypart volume request for a character!");
3572 void character::CreateBodyParts (int SpecialFlags
) {
3573 for (int c
= 0; c
< BodyParts
; ++c
) if (CanCreateBodyPart(c
)) CreateBodyPart(c
, SpecialFlags
);
3577 void character::RestoreBodyParts () {
3578 for (int c
= 0; c
< BodyParts
; ++c
) if (!GetBodyPart(c
) && CanCreateBodyPart(c
)) CreateBodyPart(c
);
3582 void character::UpdatePictures () {
3583 if (!PictureUpdatesAreForbidden()) for (int c
= 0; c
< BodyParts
; ++c
) UpdateBodyPartPicture(c
, false);
3587 bodypart
*character::MakeBodyPart (int I
) const {
3588 if (I
== TORSO_INDEX
) return normaltorso::Spawn(0, NO_MATERIALS
);
3589 ABORT("Weird bodypart to make for a character!");
3594 bodypart
*character::CreateBodyPart (int I
, int SpecialFlags
) {
3595 bodypart
*BodyPart
= MakeBodyPart(I
);
3596 material
*Material
= CreateBodyPartMaterial(I
, GetBodyPartVolume(I
));
3597 BodyPart
->InitMaterials(Material
, false);
3598 BodyPart
->SetSize(GetBodyPartSize(I
, GetTotalSize()));
3599 BodyPart
->SetBloodMaterial(GetBloodMaterial());
3600 BodyPart
->SetNormalMaterial(Material
->GetConfig());
3602 SetBodyPart(I
, BodyPart
);
3603 BodyPart
->InitSpecialAttributes();
3604 if (!(SpecialFlags
& NO_PIC_UPDATE
)) UpdateBodyPartPicture(I
, false);
3605 if (!IsInitializing()) {
3606 CalculateBattleInfo();
3607 SendNewDrawRequest();
3608 SignalPossibleTransparencyChange();
3614 v2
character::GetBodyPartBitmapPos (int I
, truth
) const {
3615 if (I
== TORSO_INDEX
) return GetTorsoBitmapPos();
3616 ABORT("Weird bodypart BitmapPos request for a character!");
3621 void character::UpdateBodyPartPicture (int I
, truth Severed
) {
3622 bodypart
*BP
= GetBodyPart(I
);
3624 BP
->SetBitmapPos(GetBodyPartBitmapPos(I
, Severed
));
3625 BP
->GetMainMaterial()->SetSkinColor(GetBodyPartColorA(I
, Severed
));
3626 BP
->GetMainMaterial()->SetSkinColorIsSparkling(GetBodyPartSparkleFlags(I
) & SPARKLING_A
);
3627 BP
->SetMaterialColorB(GetBodyPartColorB(I
, Severed
));
3628 BP
->SetMaterialColorC(GetBodyPartColorC(I
, Severed
));
3629 BP
->SetMaterialColorD(GetBodyPartColorD(I
, Severed
));
3630 BP
->SetSparkleFlags(GetBodyPartSparkleFlags(I
));
3631 BP
->SetSpecialFlags(GetSpecialBodyPartFlags(I
));
3632 BP
->SetWobbleData(GetBodyPartWobbleData(I
));
3633 BP
->UpdatePictures();
3638 void character::LoadDataBaseStats () {
3639 for (int c
= 0; c
< BASE_ATTRIBUTES
; ++c
) {
3640 BaseExperience
[c
] = DataBase
->NaturalExperience
[c
];
3641 if (BaseExperience
[c
]) LimitRef(BaseExperience
[c
], MIN_EXP
, MAX_EXP
);
3643 SetMoney(GetDefaultMoney());
3644 SetInitialSweatMaterial(GetSweatMaterial());
3645 const fearray
<sLong
> &Skills
= GetKnownCWeaponSkills();
3647 const fearray
<sLong
> &Hits
= GetCWeaponSkillHits();
3648 if (Hits
.Size
== 1) {
3649 for (uInt c
= 0; c
< Skills
.Size
; ++c
) {
3650 if (Skills
[c
] < AllowedWeaponSkillCategories
) CWeaponSkill
[Skills
[c
]].AddHit(Hits
[0]*100);
3652 } else if (Hits
.Size
== Skills
.Size
) {
3653 for (uInt c
= 0; c
< Skills
.Size
; ++c
) {
3654 if (Skills
[c
] < AllowedWeaponSkillCategories
) CWeaponSkill
[Skills
[c
]].AddHit(Hits
[c
]*100);
3657 ABORT("Illegal weapon skill hit array size detected!");
3663 character
*characterprototype::SpawnAndLoad (inputfile
&SaveFile
) const {
3664 character
*Char
= Spawner(0, LOAD
);
3665 Char
->Load(SaveFile
);
3666 Char
->CalculateAll();
3671 void character::Initialize (int NewConfig
, int SpecialFlags
) {
3672 Flags
|= C_INITIALIZING
|C_IN_NO_MSG_MODE
;
3673 CalculateBodyParts();
3674 CalculateAllowedWeaponSkillCategories();
3675 CalculateSquaresUnder();
3676 BodyPartSlot
= new bodypartslot
[BodyParts
];
3677 OriginalBodyPartID
= new std::list
<feuLong
>[BodyParts
];
3678 CWeaponSkill
= new cweaponskill
[AllowedWeaponSkillCategories
];
3679 SquareUnder
= new square
*[SquaresUnder
];
3681 if (SquaresUnder
== 1) *SquareUnder
= 0; else memset(SquareUnder
, 0, SquaresUnder
*sizeof(square
*));
3683 for (int c
= 0; c
< BodyParts
; ++c
) BodyPartSlot
[c
].SetMaster(this);
3685 if (!(SpecialFlags
& LOAD
)) {
3686 ID
= game::CreateNewCharacterID(this);
3687 databasecreator
<character
>::InstallDataBase(this, NewConfig
);
3688 LoadDataBaseStats();
3689 TemporaryState
|= GetClassStates();
3690 if (TemporaryState
) {
3691 for (int c
= 0; c
< STATES
; ++c
) if (TemporaryState
& (1 << c
)) TemporaryStateCounter
[c
] = PERMANENT
;
3694 CreateBodyParts(SpecialFlags
| NO_PIC_UPDATE
);
3695 InitSpecialAttributes();
3696 CommandFlags
= GetDefaultCommandFlags();
3698 if (GetAttribute(INTELLIGENCE
, false) < 8) CommandFlags
&= ~DONT_CONSUME_ANYTHING_VALUABLE
; // gum
3699 if (!GetDefaultName().IsEmpty()) SetAssignedName(GetDefaultName());
3702 if (!(SpecialFlags
& LOAD
)) PostConstruct();
3704 if (!(SpecialFlags
& LOAD
)) {
3705 if (!(SpecialFlags
& NO_EQUIPMENT
)) CreateInitialEquipment((SpecialFlags
& NO_EQUIPMENT_PIC_UPDATE
) >> 1);
3706 if (!(SpecialFlags
& NO_PIC_UPDATE
)) UpdatePictures();
3712 Flags
&= ~(C_INITIALIZING
|C_IN_NO_MSG_MODE
);
3716 truth
character::TeleportNear (character
*Caller
) {
3717 v2 Where
= GetLevel()->GetNearestFreeSquare(this, Caller
->GetPos());
3718 if (Where
== ERROR_V2
) return false;
3724 void character::ReceiveHeal (sLong Amount
) {
3726 for (c
= 0; c
< Amount
/ 10; ++c
) if (!HealHitPoint()) break;
3728 if (RAND()%10 < Amount
) HealHitPoint();
3729 if (Amount
>= 250 || RAND()%250 < Amount
) {
3730 bodypart
*NewBodyPart
= GenerateRandomBodyPart();
3731 if (!NewBodyPart
) return;
3732 NewBodyPart
->SetHP(1);
3733 if (IsPlayer()) ADD_MESSAGE("You grow a new %s.", NewBodyPart
->GetBodyPartName().CStr());
3734 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE
), NewBodyPart
->GetBodyPartName().CStr());
3739 void character::AddHealingLiquidConsumeEndMessage () const {
3740 if (IsPlayer()) ADD_MESSAGE("You feel better.");
3741 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks healthier.", CHAR_NAME(DEFINITE
));
3745 void character::ReceiveSchoolFood (sLong SizeOfEffect
) {
3746 SizeOfEffect
+= RAND()%SizeOfEffect
;
3747 if (SizeOfEffect
>= 250) VomitAtRandomDirection(SizeOfEffect
);
3748 if (!(RAND() % 3) && SizeOfEffect
>= 500 && EditAttribute(ENDURANCE
, SizeOfEffect
/500)) {
3749 if (IsPlayer()) ADD_MESSAGE("You gain a little bit of toughness for surviving this stuff.");
3750 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks tougher.", CHAR_NAME(DEFINITE
));
3752 BeginTemporaryState(POISONED
, (SizeOfEffect
>>1));
3756 void character::AddSchoolFoodConsumeEndMessage () const {
3757 if (IsPlayer()) ADD_MESSAGE("Yuck! This stuff tasted like vomit and old mousepads.");
3761 void character::AddSchoolFoodHitMessage () const {
3762 if (IsPlayer()) ADD_MESSAGE("Yuck! This stuff feels like vomit and old mousepads.");
3766 void character::ReceiveNutrition (sLong SizeOfEffect
) {
3767 EditNP(SizeOfEffect
);
3771 void character::ReceiveOmmelUrine (sLong Amount
) {
3772 EditExperience(ARM_STRENGTH
, 500, Amount
<<4);
3773 EditExperience(LEG_STRENGTH
, 500, Amount
<<4);
3774 if (IsPlayer()) game::DoEvilDeed(Amount
/25);
3778 void character::ReceiveOmmelCerumen (sLong Amount
) {
3779 EditExperience(INTELLIGENCE
, 500, Amount
<< 5);
3780 EditExperience(WISDOM
, 500, Amount
<< 5);
3781 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3785 void character::ReceiveOmmelSweat (sLong Amount
) {
3786 EditExperience(AGILITY
, 500, Amount
<< 4);
3787 EditExperience(DEXTERITY
, 500, Amount
<< 4);
3789 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3793 void character::ReceiveOmmelTears (sLong Amount
) {
3794 EditExperience(PERCEPTION
, 500, Amount
<< 4);
3795 EditExperience(CHARISMA
, 500, Amount
<< 4);
3796 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3800 void character::ReceiveOmmelSnot (sLong Amount
) {
3801 EditExperience(ENDURANCE
, 500, Amount
<< 5);
3803 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3807 void character::ReceiveOmmelBone (sLong Amount
) {
3808 EditExperience(ARM_STRENGTH
, 500, Amount
<< 6);
3809 EditExperience(LEG_STRENGTH
, 500, Amount
<< 6);
3810 EditExperience(DEXTERITY
, 500, Amount
<< 6);
3811 EditExperience(AGILITY
, 500, Amount
<< 6);
3812 EditExperience(ENDURANCE
, 500, Amount
<< 6);
3813 EditExperience(PERCEPTION
, 500, Amount
<< 6);
3814 EditExperience(INTELLIGENCE
, 500, Amount
<< 6);
3815 EditExperience(WISDOM
, 500, Amount
<< 6);
3816 EditExperience(CHARISMA
, 500, Amount
<< 6);
3819 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3823 void character::AddOmmelConsumeEndMessage () const {
3824 if (IsPlayer()) ADD_MESSAGE("You feel a primitive force coursing through your veins.");
3825 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks more powerful.", CHAR_NAME(DEFINITE
));
3829 void character::ReceivePepsi (sLong Amount
) {
3830 ReceiveDamage(0, Amount
/ 100, POISON
, TORSO
);
3831 EditExperience(PERCEPTION
, Amount
, 1 << 14);
3832 if (CheckDeath(CONST_S("was poisoned by pepsi"), 0)) return;
3833 if (IsPlayer()) game::DoEvilDeed(Amount
/ 10);
3837 void character::AddPepsiConsumeEndMessage () const {
3838 if (IsPlayer()) ADD_MESSAGE("Urgh. You feel your guruism fading away.");
3839 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks very lame.", CHAR_NAME(DEFINITE
));
3843 void character::ReceiveDarkness (sLong Amount
) {
3844 EditExperience(INTELLIGENCE
, -Amount
/ 5, 1 << 13);
3845 EditExperience(WISDOM
, -Amount
/ 5, 1 << 13);
3846 EditExperience(CHARISMA
, -Amount
/ 5, 1 << 13);
3847 if (IsPlayer()) game::DoEvilDeed(int(Amount
/ 50));
3851 void character::AddFrogFleshConsumeEndMessage () const {
3852 if (IsPlayer()) ADD_MESSAGE("Arg. You feel the fate of a navastater placed upon you...");
3853 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks like a navastater.", CHAR_NAME(DEFINITE
));
3857 void character::ReceiveKoboldFlesh (sLong
) {
3858 /* As it is commonly known, the possibility of fainting per 500 cubic
3859 centimeters of kobold flesh is exactly 5%. */
3860 if (!(RAND() % 20)) {
3861 if (IsPlayer()) ADD_MESSAGE("You lose control of your legs and fall down.");
3862 LoseConsciousness(250 + RAND_N(250));
3867 void character::AddKoboldFleshConsumeEndMessage () const {
3868 if (IsPlayer()) ADD_MESSAGE("This stuff tasted really funny.");
3872 void character::AddKoboldFleshHitMessage () const {
3873 if (IsPlayer()) ADD_MESSAGE("You feel very funny.");
3877 void character::AddBoneConsumeEndMessage () const {
3878 if (IsPlayer()) ADD_MESSAGE("You feel like a hippie.");
3879 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s barks happily.", CHAR_NAME(DEFINITE
)); // this suspects that nobody except dogs can eat bones
3882 truth
character::RawEditAttribute (double &Experience
, int Amount
) const {
3883 /* Check if the attribute is disabled for creature */
3884 if (!Experience
) return false;
3885 if ((Amount
< 0 && Experience
< 2 * EXP_MULTIPLIER
) || (Amount
> 0 && Experience
> 999 * EXP_MULTIPLIER
)) return false;
3886 Experience
+= Amount
* EXP_MULTIPLIER
;
3887 LimitRef
<double>(Experience
, MIN_EXP
, MAX_EXP
);
3892 void character::DrawPanel (truth AnimationDraw
) const {
3893 if (AnimationDraw
) { DrawStats(true); return; }
3894 igraph::BlitBackGround(v2(19 + (game::GetScreenXSize() << 4), 0), v2(RES
.X
- 19 - (game::GetScreenXSize() << 4), RES
.Y
));
3895 igraph::BlitBackGround(v2(16, 45 + (game::GetScreenYSize() << 4)), v2(game::GetScreenXSize() << 4, 9));
3896 FONT
->Printf(DOUBLE_BUFFER
, v2(16, 45 + (game::GetScreenYSize() << 4)), WHITE
, "%s", GetPanelName().CStr());
3897 game::UpdateAttributeMemory();
3898 int PanelPosX
= RES
.X
- 96;
3899 int PanelPosY
= DrawStats(false);
3900 PrintAttribute("End", ENDURANCE
, PanelPosX
, PanelPosY
++);
3901 PrintAttribute("Per", PERCEPTION
, PanelPosX
, PanelPosY
++);
3902 PrintAttribute("Int", INTELLIGENCE
, PanelPosX
, PanelPosY
++);
3903 PrintAttribute("Wis", WISDOM
, PanelPosX
, PanelPosY
++);
3904 PrintAttribute("Wil", WILL_POWER
, PanelPosX
, PanelPosY
++);
3905 PrintAttribute("Cha", CHARISMA
, PanelPosX
, PanelPosY
++);
3906 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Siz %d", GetSize());
3907 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), IsInBadCondition() ? RED
: WHITE
, "HP %d/%d", GetHP(), GetMaxHP());
3909 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Gold: %d", GetMoney());
3912 if (game::IsInWilderness())
3913 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Worldmap");
3915 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", game::GetCurrentDungeon()->GetShortLevelDescription(game::GetCurrentLevelIndex()).CapitalizeCopy().CStr());
3918 game::GetTime(Time
);
3919 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Day %d", Time
.Day
);
3920 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Time %d:%s%d", Time
.Hour
, Time
.Min
< 10 ? "0" : "", Time
.Min
);
3921 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Turn %d", game::GetTurn());
3926 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", festring(GetAction()->GetDescription()).CapitalizeCopy().CStr());
3928 for (int c
= 0; c
< STATES
; ++c
)
3929 if (!(StateData
[c
].Flags
& SECRET
) && StateIsActivated(1 << c
) && (1 << c
!= HASTE
|| !StateIsActivated(SLOW
)) && (1 << c
!= SLOW
|| !StateIsActivated(HASTE
)))
3930 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), (1 << c
) & EquipmentState
|| TemporaryStateCounter
[c
] == PERMANENT
? BLUE
: WHITE
, "%s", StateData
[c
].Description
);
3932 /* Make this more elegant!!! */
3933 if (GetHungerState() == STARVING
)
3934 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Starving");
3935 else if (GetHungerState() == VERY_HUNGRY
)
3936 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), BLUE
, "Very hungry");
3937 else if (GetHungerState() == HUNGRY
)
3938 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), BLUE
, "Hungry");
3939 else if (GetHungerState() == SATIATED
)
3940 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Satiated");
3941 else if (GetHungerState() == BLOATED
)
3942 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Bloated");
3943 else if (GetHungerState() == OVER_FED
)
3944 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Overfed!");
3946 switch (GetBurdenState()) {
3948 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Overload!");
3951 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), BLUE
, "Stressed");
3954 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), BLUE
, "Burdened");
3958 switch (GetTirednessState()) {
3960 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Fainting");
3963 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Exhausted");
3967 if (game::PlayerIsRunning()) {
3968 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", GetRunDescriptionLine(0));
3969 cchar
*SecondLine
= GetRunDescriptionLine(1);
3970 if (strlen(SecondLine
)) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", SecondLine
);
3975 void character::CalculateDodgeValue () {
3976 DodgeValue
= 0.05 * GetMoveEase() * GetAttribute(AGILITY
) / sqrt(GetSize());
3977 if (IsFlying()) DodgeValue
*= 2;
3978 if (DodgeValue
< 1) DodgeValue
= 1;
3982 truth
character::DamageTypeAffectsInventory (int Type
) {
3983 switch (Type
&0xFFF) {
3990 case PHYSICAL_DAMAGE
:
3993 case MUSTARD_GAS_DAMAGE
:
3997 ABORT("Unknown reaping effect destroyed dungeon!");
4002 int character::CheckForBlockWithArm (character
*Enemy
, item
*Weapon
, arm
*Arm
,
4003 double WeaponToHitValue
, int Damage
, int Success
, int Type
)
4005 int BlockStrength
= Arm
->GetBlockCapability();
4006 double BlockValue
= Arm
->GetBlockValue();
4007 if (BlockStrength
&& BlockValue
) {
4008 item
*Blocker
= Arm
->GetWielded();
4009 if (RAND() % int(100+WeaponToHitValue
/BlockValue
/(1<<BlocksSinceLastTurn
)*(100+Success
)) < 100) {
4010 int NewDamage
= BlockStrength
< Damage
? Damage
-BlockStrength
: 0;
4012 case UNARMED_ATTACK
: AddBlockMessage(Enemy
, Blocker
, Enemy
->UnarmedHitNoun(), NewDamage
); break;
4013 case WEAPON_ATTACK
: AddBlockMessage(Enemy
, Blocker
, "attack", NewDamage
); break;
4014 case KICK_ATTACK
: AddBlockMessage(Enemy
, Blocker
, Enemy
->KickNoun(), NewDamage
); break;
4015 case BITE_ATTACK
: AddBlockMessage(Enemy
, Blocker
, Enemy
->BiteNoun(), NewDamage
); break;
4017 sLong Weight
= Blocker
->GetWeight();
4018 sLong StrExp
= Limit(15 * Weight
/ 200, 75, 300);
4019 sLong DexExp
= Weight
? Limit(75000 / Weight
, 75, 300) : 300;
4020 Arm
->EditExperience(ARM_STRENGTH
, StrExp
, 1 << 8);
4021 Arm
->EditExperience(DEXTERITY
, DexExp
, 1 << 8);
4022 EditStamina(-10000 / GetAttribute(ARM_STRENGTH
), false);
4023 if (Arm
->TwoHandWieldIsActive()) {
4024 arm
*PairArm
= Arm
->GetPairArm();
4025 PairArm
->EditExperience(ARM_STRENGTH
, StrExp
, 1 << 8);
4026 PairArm
->EditExperience(DEXTERITY
, DexExp
, 1 << 8);
4028 Blocker
->WeaponSkillHit(Enemy
->CalculateWeaponSkillHits(this));
4029 Blocker
->ReceiveDamage(this, Damage
, PHYSICAL_DAMAGE
);
4030 Blocker
->BlockEffect(this, Enemy
, Weapon
, Type
);
4031 if (Weapon
) Weapon
->ReceiveDamage(Enemy
, Damage
- NewDamage
, PHYSICAL_DAMAGE
);
4032 if (BlocksSinceLastTurn
< 16) ++BlocksSinceLastTurn
;
4040 sLong
character::GetStateAPGain (sLong BaseAPGain
) const {
4041 if (!StateIsActivated(HASTE
) == !StateIsActivated(SLOW
)) return BaseAPGain
;
4042 if (StateIsActivated(HASTE
)) return (BaseAPGain
* 5) >> 2;
4043 return (BaseAPGain
<< 2) / 5;
4047 void character::SignalEquipmentAdd (int EquipmentIndex
) {
4048 item
*Equipment
= GetEquipment(EquipmentIndex
);
4049 if (Equipment
->IsInCorrectSlot(EquipmentIndex
)) {
4050 sLong AddedStates
= Equipment
->GetGearStates();
4052 for (int c
= 0; c
< STATES
; ++c
) {
4053 if (AddedStates
& (1 << c
)) {
4054 if (!StateIsActivated(1 << c
)) {
4055 if (!IsInNoMsgMode()) (this->*StateData
[c
].PrintBeginMessage
)();
4056 EquipmentState
|= 1 << c
;
4057 if (StateData
[c
].BeginHandler
) (this->*StateData
[c
].BeginHandler
)();
4059 EquipmentState
|= 1 << c
;
4065 if (!IsInitializing() && Equipment
->IsInCorrectSlot(EquipmentIndex
)) ApplyEquipmentAttributeBonuses(Equipment
);
4069 void character::SignalEquipmentRemoval (int, citem
*Item
) {
4070 CalculateEquipmentState();
4071 if (CalculateAttributeBonuses()) CheckDeath(festring("lost ")+GetPossessivePronoun(false)+" vital "+Item
->GetName(INDEFINITE
));
4075 void character::CalculateEquipmentState () {
4076 sLong Back
= EquipmentState
;
4078 for (int c
= 0; c
< GetEquipments(); ++c
) {
4079 item
*Equipment
= GetEquipment(c
);
4080 if (Equipment
&& Equipment
->IsInCorrectSlot(c
)) EquipmentState
|= Equipment
->GetGearStates();
4082 for (int c
= 0; c
< STATES
; ++c
) {
4083 if (Back
& (1 << c
) && !StateIsActivated(1 << c
)) {
4084 if (StateData
[c
].EndHandler
) {
4085 (this->*StateData
[c
].EndHandler
)();
4086 if (!IsEnabled()) return;
4088 if (!IsInNoMsgMode()) (this->*StateData
[c
].PrintEndMessage
)();
4094 /* Counter = duration in ticks */
4095 void character::BeginTemporaryState (sLong State
, int Counter
) {
4096 if (!Counter
) return;
4098 if (State
== POLYMORPHED
) ABORT("No Polymorphing with BeginTemporaryState!");
4099 for (Index
= 0; Index
< STATES
; ++Index
) if (1 << Index
== State
) break;
4100 if (Index
== STATES
) ABORT("BeginTemporaryState works only when State == 2^n!");
4101 if (TemporaryStateIsActivated(State
)) {
4102 int OldCounter
= GetTemporaryStateCounter(State
);
4103 if (OldCounter
!= PERMANENT
) EditTemporaryStateCounter(State
, Max(Counter
, 50-OldCounter
));
4104 } else if (StateData
[Index
].IsAllowed
== 0 || (this->*StateData
[Index
].IsAllowed
)()) {
4105 SetTemporaryStateCounter(State
, Max(Counter
, 50));
4106 if (!EquipmentStateIsActivated(State
)) {
4107 if (!IsInNoMsgMode()) (this->*StateData
[Index
].PrintBeginMessage
)();
4108 ActivateTemporaryState(State
);
4109 if (StateData
[Index
].BeginHandler
) (this->*StateData
[Index
].BeginHandler
)();
4111 ActivateTemporaryState(State
);
4117 void character::HandleStates () {
4118 if (!TemporaryState
&& !EquipmentState
) return;
4119 for (int c
= 0; c
< STATES
; ++c
) {
4120 if (TemporaryState
& (1 << c
) && TemporaryStateCounter
[c
] != PERMANENT
) {
4121 if (!--TemporaryStateCounter
[c
]) {
4122 TemporaryState
&= ~(1 << c
);
4123 if (!(EquipmentState
& (1 << c
))) {
4124 if (StateData
[c
].EndHandler
) {
4125 (this->*StateData
[c
].EndHandler
)();
4126 if (!IsEnabled()) return;
4128 if (!TemporaryStateCounter
[c
]) (this->*StateData
[c
].PrintEndMessage
)();
4132 if (StateIsActivated(1 << c
)) {
4133 if (StateData
[c
].Handler
) (this->*StateData
[c
].Handler
)();
4135 if (!IsEnabled()) return;
4140 void character::PrintBeginPolymorphControlMessage () const {
4141 if (IsPlayer()) ADD_MESSAGE("You feel your mind has total control over your body.");
4145 void character::PrintEndPolymorphControlMessage () const {
4146 if (IsPlayer()) ADD_MESSAGE("You are somehow uncertain of your willpower.");
4150 void character::PrintBeginLifeSaveMessage () const {
4151 if (IsPlayer()) ADD_MESSAGE("You hear Hell's gates being locked just now.");
4155 void character::PrintEndLifeSaveMessage () const {
4156 if (IsPlayer()) ADD_MESSAGE("You feel the Afterlife is welcoming you once again.");
4160 void character::PrintBeginLycanthropyMessage () const {
4161 if (IsPlayer()) ADD_MESSAGE("You suddenly notice you've always loved full moons.");
4165 void character::PrintEndLycanthropyMessage () const {
4166 if (IsPlayer()) ADD_MESSAGE("You feel the wolf inside you has had enough of your bad habits.");
4170 void character::PrintBeginVampirismMessage () const {
4171 if (IsPlayer()) ADD_MESSAGE("You suddenly decide you have always hated garlic.");
4175 void character::PrintEndVampirismMessage () const {
4176 if (IsPlayer()) ADD_MESSAGE("You recall your delight of the morning sunshine back in New Attnam. You are a vampire no longer.");
4180 void character::PrintBeginInvisibilityMessage () const {
4181 if ((PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm()) || (PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5)) {
4182 if (IsPlayer()) ADD_MESSAGE("You seem somehow transparent.");
4183 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s seems somehow transparent.", CHAR_NAME(DEFINITE
));
4185 if (IsPlayer()) ADD_MESSAGE("You fade away.");
4186 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s disappears!", CHAR_NAME(DEFINITE
));
4191 void character::PrintEndInvisibilityMessage () const {
4192 if ((PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm()) || (PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5)) {
4193 if (IsPlayer()) ADD_MESSAGE("Your notice your transparency has ended.");
4194 else if (CanBeSeenByPlayer()) ADD_MESSAGE("The appearance of %s seems far more solid now.", CHAR_NAME(INDEFINITE
));
4196 if (IsPlayer()) ADD_MESSAGE("You reappear.");
4197 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s appears from nowhere!", CHAR_NAME(INDEFINITE
));
4202 void character::PrintBeginInfraVisionMessage () const {
4204 if (StateIsActivated(INVISIBLE
) && IsWarm() && !(StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5))
4205 ADD_MESSAGE("You reappear.");
4207 ADD_MESSAGE("You feel your perception being magically altered.");
4212 void character::PrintEndInfraVisionMessage () const {
4214 if (StateIsActivated(INVISIBLE
) && IsWarm() && !(StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5))
4215 ADD_MESSAGE("You disappear.");
4217 ADD_MESSAGE("You feel your perception returning to normal.");
4222 void character::PrintBeginESPMessage () const {
4223 if (IsPlayer()) ADD_MESSAGE("You suddenly feel like being only a tiny part of a great network of intelligent minds.");
4227 void character::PrintEndESPMessage () const {
4228 if (IsPlayer()) ADD_MESSAGE("You are filled with desire to be just yourself from now on.");
4232 void character::PrintBeginHasteMessage () const {
4233 if (IsPlayer()) ADD_MESSAGE("Time slows down to a crawl.");
4234 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks faster!", CHAR_NAME(DEFINITE
));
4238 void character::PrintEndHasteMessage () const {
4239 if (IsPlayer()) ADD_MESSAGE("Everything seems to move much faster now.");
4240 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks slower!", CHAR_NAME(DEFINITE
));
4244 void character::PrintBeginSlowMessage () const {
4245 if (IsPlayer()) ADD_MESSAGE("Everything seems to move much faster now.");
4246 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks slower!", CHAR_NAME(DEFINITE
));
4250 void character::PrintEndSlowMessage () const {
4251 if (IsPlayer()) ADD_MESSAGE("Time slows down to a crawl.");
4252 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks faster!", CHAR_NAME(DEFINITE
));
4256 void character::EndPolymorph () {
4257 ForceEndPolymorph();
4261 character
*character::ForceEndPolymorph () {
4263 ADD_MESSAGE("You return to your true form.");
4264 } else if (game::IsInWilderness()) {
4265 ActivateTemporaryState(POLYMORPHED
);
4266 SetTemporaryStateCounter(POLYMORPHED
, 10);
4267 return this; // fast gum solution, state ends when the player enters a dungeon
4269 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s returns to %s true form.", CHAR_NAME(DEFINITE
), GetPossessivePronoun().CStr());
4271 if (GetAction()) GetAction()->Terminate(false);
4275 character
*Char
= GetPolymorphBackup();
4276 Flags
|= C_IN_NO_MSG_MODE
;
4277 Char
->Flags
|= C_IN_NO_MSG_MODE
;
4278 Char
->ChangeTeam(GetTeam());
4279 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(Char
);
4280 SetPolymorphBackup(0);
4281 Char
->PutToOrNear(Pos
);
4283 Char
->Flags
&= ~C_POLYMORPHED
;
4284 GetStack()->MoveItemsTo(Char
->GetStack());
4285 DonateEquipmentTo(Char
);
4286 Char
->SetMoney(GetMoney());
4287 Flags
&= ~C_IN_NO_MSG_MODE
;
4288 Char
->Flags
&= ~C_IN_NO_MSG_MODE
;
4289 Char
->CalculateAll();
4290 Char
->SetAssignedName(GetAssignedName());
4293 game::SetPlayer(Char
);
4294 game::SendLOSUpdateRequest();
4297 Char
->TestWalkability();
4302 void character::LycanthropyHandler () {
4303 if (!(RAND() % 2000)) {
4304 if (StateIsActivated(POLYMORPH_CONTROL
) && !game::TruthQuestion(CONST_S("Do you wish to change into a werewolf? [y/N]"))) return;
4305 Polymorph(werewolfwolf::Spawn(), 1000 + RAND() % 2000);
4310 void character::SaveLife () {
4311 if (TemporaryStateIsActivated(LIFE_SAVED
)) {
4313 ADD_MESSAGE("But wait! You glow briefly red and seem to be in a better shape!");
4314 else if (CanBeSeenByPlayer())
4315 ADD_MESSAGE("But wait, suddenly %s glows briefly red and seems to be in a better shape!", GetPersonalPronoun().CStr());
4316 DeActivateTemporaryState(LIFE_SAVED
);
4318 item
*LifeSaver
= 0;
4319 for (int c
= 0; c
< GetEquipments(); ++c
) {
4320 item
*Equipment
= GetEquipment(c
);
4321 if (Equipment
&& Equipment
->IsInCorrectSlot(c
) && Equipment
->GetGearStates() & LIFE_SAVED
) LifeSaver
= Equipment
;
4323 if (!LifeSaver
) ABORT("The Universe can only kill you once!");
4325 ADD_MESSAGE("But wait! Your %s glows briefly red and disappears and you seem to be in a better shape!", LifeSaver
->CHAR_NAME(UNARTICLED
));
4326 else if (CanBeSeenByPlayer())
4327 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());
4328 LifeSaver
->RemoveFromSlot();
4329 LifeSaver
->SendToHell();
4332 if (IsPlayer()) game::AskForEscPress(CONST_S("Life saved!"));
4340 if (GetNP() < SATIATED_LEVEL
) SetNP(SATIATED_LEVEL
);
4342 SendNewDrawRequest();
4344 if (GetAction()) GetAction()->Terminate(false);
4348 character
*character::PolymorphRandomly (int MinDanger
, int MaxDanger
, int Time
) {
4349 character
*NewForm
= 0;
4350 if (StateIsActivated(POLYMORPH_CONTROL
)) {
4352 if (!GetNewFormForPolymorphWithControl(NewForm
)) return NewForm
;
4354 NewForm
= protosystem::CreateMonster(MinDanger
*10, MaxDanger
*10, NO_EQUIPMENT
);
4357 NewForm
= protosystem::CreateMonster(MinDanger
, MaxDanger
, NO_EQUIPMENT
);
4359 Polymorph(NewForm
, Time
);
4364 /* In reality, the reading takes Time / (Intelligence * 10) turns */
4365 void character::StartReading (item
*Item
, sLong Time
) {
4366 study
*Read
= study::Spawn(this);
4367 Read
->SetLiteratureID(Item
->GetID());
4368 if (game::WizardModeIsActive()) Time
= 1;
4369 Read
->SetCounter(Time
);
4371 if (IsPlayer()) ADD_MESSAGE("You start reading %s.", Item
->CHAR_NAME(DEFINITE
));
4372 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s starts reading %s.", CHAR_NAME(DEFINITE
), Item
->CHAR_NAME(DEFINITE
));
4376 /* Call when one makes something with his/her/its hands.
4377 * Difficulty of 5 takes about one turn, so it's the most common to use. */
4378 void character::DexterityAction (int Difficulty
) {
4379 EditAP(-20000 * Difficulty
/ APBonus(GetAttribute(DEXTERITY
)));
4380 EditExperience(DEXTERITY
, Difficulty
* 15, 1 << 7);
4384 /* If Theoretically != false, range is not a factor. */
4385 truth
character::CanBeSeenByPlayer (truth Theoretically
, truth IgnoreESP
) const {
4386 if (IsEnabled() && !game::IsGenerating() && (Theoretically
|| GetSquareUnder())) {
4387 truth MayBeESPSeen
= PLAYER
->IsEnabled() && !IgnoreESP
&& PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5;
4388 truth MayBeInfraSeen
= PLAYER
->IsEnabled() && PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm();
4389 truth Visible
= !StateIsActivated(INVISIBLE
) || MayBeESPSeen
|| MayBeInfraSeen
;
4390 if (game::IsInWilderness()) return Visible
;
4391 if (MayBeESPSeen
&& (Theoretically
|| GetDistanceSquareFrom(PLAYER
) <= PLAYER
->GetESPRangeSquare())) return true;
4392 if (!Visible
) return false;
4393 return Theoretically
|| SquareUnderCanBeSeenByPlayer(MayBeInfraSeen
);
4399 truth
character::CanBeSeenBy (ccharacter
*Who
, truth Theoretically
, truth IgnoreESP
) const {
4400 if (Who
->IsPlayer()) return CanBeSeenByPlayer(Theoretically
, IgnoreESP
);
4401 if (IsEnabled() && !game::IsGenerating() && (Theoretically
|| GetSquareUnder())) {
4402 truth MayBeESPSeen
= Who
->IsEnabled() && !IgnoreESP
&& Who
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5;
4403 truth MayBeInfraSeen
= Who
->IsEnabled() && Who
->StateIsActivated(INFRA_VISION
) && IsWarm();
4404 truth Visible
= !StateIsActivated(INVISIBLE
) || MayBeESPSeen
|| MayBeInfraSeen
;
4405 if (game::IsInWilderness()) return Visible
;
4406 if (MayBeESPSeen
&& (Theoretically
|| GetDistanceSquareFrom(Who
) <= Who
->GetESPRangeSquare())) return true;
4407 if (!Visible
) return false;
4408 return Theoretically
|| SquareUnderCanBeSeenBy(Who
, MayBeInfraSeen
);
4414 truth
character::SquareUnderCanBeSeenByPlayer (truth IgnoreDarkness
) const {
4415 if (!GetSquareUnder()) return false;
4416 int S1
= SquaresUnder
, S2
= PLAYER
->SquaresUnder
;
4417 if (S1
== 1 && S2
== 1) {
4418 if (GetSquareUnder()->CanBeSeenByPlayer(IgnoreDarkness
)) return true;
4419 if (IgnoreDarkness
) {
4420 int LOSRangeSquare
= PLAYER
->GetLOSRangeSquare();
4421 if ((GetPos() - PLAYER
->GetPos()).GetLengthSquare() <= LOSRangeSquare
) {
4422 eyecontroller::Map
= GetLevel()->GetMap();
4423 return mapmath
<eyecontroller
>::DoLine(PLAYER
->GetPos().X
, PLAYER
->GetPos().Y
, GetPos().X
, GetPos().Y
, SKIP_FIRST
);
4428 for (int c1
= 0; c1
< S1
; ++c1
) {
4429 lsquare
*Square
= GetLSquareUnder(c1
);
4430 if (Square
->CanBeSeenByPlayer(IgnoreDarkness
)) return true;
4431 else if (IgnoreDarkness
) {
4432 v2 Pos
= Square
->GetPos();
4433 int LOSRangeSquare
= PLAYER
->GetLOSRangeSquare();
4434 for (int c2
= 0; c2
< S2
; ++c2
) {
4435 v2 PlayerPos
= PLAYER
->GetPos(c2
);
4436 if ((Pos
-PlayerPos
).GetLengthSquare() <= LOSRangeSquare
) {
4437 eyecontroller::Map
= GetLevel()->GetMap();
4438 if (mapmath
<eyecontroller
>::DoLine(PlayerPos
.X
, PlayerPos
.Y
, Pos
.X
, Pos
.Y
, SKIP_FIRST
)) return true;
4448 truth
character::SquareUnderCanBeSeenBy (ccharacter
*Who
, truth IgnoreDarkness
) const {
4449 int S1
= SquaresUnder
, S2
= Who
->SquaresUnder
;
4450 int LOSRangeSquare
= Who
->GetLOSRangeSquare();
4451 if (S1
== 1 && S2
== 1) return GetSquareUnder()->CanBeSeenFrom(Who
->GetPos(), LOSRangeSquare
, IgnoreDarkness
);
4452 for (int c1
= 0; c1
< S1
; ++c1
) {
4453 lsquare
*Square
= GetLSquareUnder(c1
);
4454 for (int c2
= 0; c2
< S2
; ++c2
) if (Square
->CanBeSeenFrom(Who
->GetPos(c2
), LOSRangeSquare
, IgnoreDarkness
)) return true;
4460 int character::GetDistanceSquareFrom (ccharacter
*Who
) const {
4461 int S1
= SquaresUnder
, S2
= Who
->SquaresUnder
;
4462 if (S1
== 1 && S2
== 1) return (GetPos() - Who
->GetPos()).GetLengthSquare();
4463 v2
MinDist(0x7FFF, 0x7FFF);
4464 int MinLength
= 0xFFFF;
4465 for (int c1
= 0; c1
< S1
; ++c1
) {
4466 for (int c2
= 0; c2
< S2
; ++c2
) {
4467 v2 Dist
= GetPos(c1
)-Who
->GetPos(c2
);
4468 if (Dist
.X
< 0) Dist
.X
= -Dist
.X
;
4469 if (Dist
.Y
< 0) Dist
.Y
= -Dist
.Y
;
4470 if (Dist
.X
<= MinDist
.X
&& Dist
.Y
<= MinDist
.Y
) {
4472 MinLength
= Dist
.GetLengthSquare();
4473 } else if (Dist
.X
< MinDist
.X
|| Dist
.Y
< MinDist
.Y
) {
4474 int Length
= Dist
.GetLengthSquare();
4475 if (Length
< MinLength
) {
4486 void character::AttachBodyPart (bodypart
*BodyPart
) {
4487 SetBodyPart(BodyPart
->GetBodyPartIndex(), BodyPart
);
4488 if (!AllowSpoil()) BodyPart
->ResetSpoiling();
4489 BodyPart
->ResetPosition();
4490 BodyPart
->UpdatePictures();
4491 CalculateAttributeBonuses();
4492 CalculateBattleInfo();
4493 SendNewDrawRequest();
4494 SignalPossibleTransparencyChange();
4498 /* Returns true if the character has all bodyparts, false if not. */
4499 truth
character::HasAllBodyParts () const {
4500 for (int c
= 0; c
< BodyParts
; ++c
) if (!GetBodyPart(c
) && CanCreateBodyPart(c
)) return false;
4505 bodypart
*character::GenerateRandomBodyPart () {
4506 int NeededBodyPart
[MAX_BODYPARTS
];
4508 for (int c
= 0; c
< BodyParts
; ++c
) if (!GetBodyPart(c
) && CanCreateBodyPart(c
)) NeededBodyPart
[Index
++] = c
;
4509 return Index
? CreateBodyPart(NeededBodyPart
[RAND() % Index
]) : 0;
4513 /* Searches the character's Stack and if it find some bodyparts there that are the character's
4514 * old bodyparts returns a stackiterator to one of them (choosen in random).
4515 * If no fitting bodyparts are found the function returns 0 */
4516 bodypart
*character::FindRandomOwnBodyPart (truth AllowNonLiving
) const {
4517 itemvector LostAndFound
;
4518 for (int c
= 0; c
< BodyParts
; ++c
) {
4519 if (!GetBodyPart(c
)) {
4520 for (std::list
<feuLong
>::iterator i
= OriginalBodyPartID
[c
].begin(); i
!= OriginalBodyPartID
[c
].end(); ++i
) {
4521 bodypart
*Found
= static_cast<bodypart
*>(SearchForItem(*i
));
4522 if (Found
&& (AllowNonLiving
|| Found
->CanRegenerate())) LostAndFound
.push_back(Found
);
4526 if (LostAndFound
.empty()) return 0;
4527 return static_cast<bodypart
*>(LostAndFound
[RAND() % LostAndFound
.size()]);
4531 void character::PrintBeginPoisonedMessage () const {
4532 if (IsPlayer()) ADD_MESSAGE("You seem to be very ill.");
4533 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks very ill.", CHAR_NAME(DEFINITE
));
4537 void character::PrintEndPoisonedMessage () const {
4538 if (IsPlayer()) ADD_MESSAGE("You feel better again.");
4539 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks better.", CHAR_NAME(DEFINITE
));
4543 void character::PoisonedHandler () {
4544 if (!(RAND() % 100)) VomitAtRandomDirection(500 + RAND_N(250));
4546 for (int Used
= 0; Used
< GetTemporaryStateCounter(POISONED
); Used
+= 100) if (!(RAND() % 100)) ++Damage
;
4548 ReceiveDamage(0, Damage
, POISON
, ALL
, 8, false, false, false, false);
4549 CheckDeath(CONST_S("died of acute poisoning"), 0);
4554 truth
character::IsWarm () const {
4555 return combinebodypartpredicates()(this, &bodypart::IsWarm
, 1);
4559 void character::BeginInvisibility () {
4561 SendNewDrawRequest();
4562 SignalPossibleTransparencyChange();
4566 void character::BeginInfraVision () {
4567 if (IsPlayer()) GetArea()->SendNewDrawRequest();
4571 void character::BeginESP () {
4572 if (IsPlayer()) GetArea()->SendNewDrawRequest();
4576 void character::EndInvisibility () {
4578 SendNewDrawRequest();
4579 SignalPossibleTransparencyChange();
4583 void character::EndInfraVision () {
4584 if (IsPlayer() && IsEnabled()) GetArea()->SendNewDrawRequest();
4588 void character::EndESP () {
4589 if (IsPlayer() && IsEnabled()) GetArea()->SendNewDrawRequest();
4593 void character::Draw (blitdata
&BlitData
) const {
4594 col24 L
= BlitData
.Luminance
;
4595 if (PLAYER
->IsEnabled() &&
4596 ((PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5 &&
4597 (PLAYER
->GetPos() - GetPos()).GetLengthSquare() <= PLAYER
->GetESPRangeSquare()) ||
4598 (PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm())))
4599 BlitData
.Luminance
= ivanconfig::GetContrastLuminance();
4601 DrawBodyParts(BlitData
);
4602 BlitData
.Luminance
= ivanconfig::GetContrastLuminance();
4603 BlitData
.Src
.Y
= 16;
4604 cint SquareIndex
= BlitData
.CustomData
& SQUARE_INDEX_MASK
;
4606 if (GetTeam() == PLAYER
->GetTeam() && !IsPlayer() && SquareIndex
== GetTameSymbolSquareIndex()) {
4607 BlitData
.Src
.X
= 32;
4608 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4611 if (IsFlying() && SquareIndex
== GetFlySymbolSquareIndex()) {
4612 BlitData
.Src
.X
= 128;
4613 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4616 if (IsSwimming() && SquareIndex
== GetSwimmingSymbolSquareIndex()) {
4617 BlitData
.Src
.X
= 240;
4618 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4621 if (GetAction() && GetAction()->IsUnconsciousness() && SquareIndex
== GetUnconsciousSymbolSquareIndex()) {
4622 BlitData
.Src
.X
= 224;
4623 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4626 BlitData
.Src
.X
= BlitData
.Src
.Y
= 0;
4627 BlitData
.Luminance
= L
;
4631 void character::DrawBodyParts (blitdata
&BlitData
) const {
4632 GetTorso()->Draw(BlitData
);
4636 void character::PrintBeginTeleportMessage () const {
4637 if (IsPlayer()) ADD_MESSAGE("You feel jumpy.");
4641 void character::PrintEndTeleportMessage () const {
4642 if (IsPlayer()) ADD_MESSAGE("You suddenly realize you've always preferred walking to jumping.");
4646 void character::PrintBeginDetectMessage () const {
4647 if (IsPlayer()) ADD_MESSAGE("You feel curious about your surroundings.");
4651 void character::PrintEndDetectMessage () const {
4652 if (IsPlayer()) ADD_MESSAGE("You decide to rely on your intuition from now on.");
4656 void character::TeleportHandler () {
4657 if (!(RAND() % 1500) && !game::IsInWilderness()) {
4658 if (IsPlayer()) ADD_MESSAGE("You feel an urgent spatial relocation is now appropriate.");
4659 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s disappears.", CHAR_NAME(DEFINITE
));
4665 void character::DetectHandler () {
4667 //the AI can't be asked position questions! So only the player can hav this state really :/ a bit daft of me
4668 if (!(RAND()%3000) && !game::IsInWilderness()) {
4669 ADD_MESSAGE("Your mind wanders in search of something.");
4670 DoDetecting(); //in fact, who knows what would happen if a dark frog had the detecting state?
4676 void character::PrintBeginPolymorphMessage () const {
4677 if (IsPlayer()) ADD_MESSAGE("An unconfortable uncertainty of who you really are overwhelms you.");
4681 void character::PrintEndPolymorphMessage () const {
4682 if (IsPlayer()) ADD_MESSAGE("You feel you are you and no one else.");
4686 void character::PolymorphHandler () {
4687 if (!(RAND() % 1500)) PolymorphRandomly(1, 999999, 200 + RAND() % 800);
4690 void character::PrintBeginTeleportControlMessage () const {
4691 if (IsPlayer()) ADD_MESSAGE("You feel very controlled.");
4695 void character::PrintEndTeleportControlMessage () const {
4696 if (IsPlayer()) ADD_MESSAGE("You feel your control slipping.");
4700 void character::DisplayStethoscopeInfo (character
*) const {
4701 felist
Info(CONST_S("Information about ") + GetDescription(DEFINITE
));
4702 AddSpecialStethoscopeInfo(Info
);
4703 Info
.AddEntry(CONST_S("Endurance: ") + GetAttribute(ENDURANCE
), LIGHT_GRAY
);
4704 Info
.AddEntry(CONST_S("Perception: ") + GetAttribute(PERCEPTION
), LIGHT_GRAY
);
4705 Info
.AddEntry(CONST_S("Intelligence: ") + GetAttribute(INTELLIGENCE
), LIGHT_GRAY
);
4706 Info
.AddEntry(CONST_S("Wisdom: ") + GetAttribute(WISDOM
), LIGHT_GRAY
);
4707 //Info.AddEntry(CONST_S("Willpower: ") + GetAttribute(WILL_POWER), LIGHT_GRAY);
4708 Info
.AddEntry(CONST_S("Charisma: ") + GetAttribute(CHARISMA
), LIGHT_GRAY
);
4709 Info
.AddEntry(CONST_S("HP: ") + GetHP() + "/" + GetMaxHP(), IsInBadCondition() ? RED
: LIGHT_GRAY
);
4710 if (GetAction()) Info
.AddEntry(festring(GetAction()->GetDescription()).CapitalizeCopy(), LIGHT_GRAY
);
4711 for (int c
= 0; c
< STATES
; ++c
) {
4712 if (StateIsActivated(1 << c
) && (1 << c
!= HASTE
|| !StateIsActivated(SLOW
)) && (1 << c
!= SLOW
|| !StateIsActivated(HASTE
)))
4713 Info
.AddEntry(StateData
[c
].Description
, LIGHT_GRAY
);
4715 switch (GetTirednessState()) {
4716 case FAINTING
: Info
.AddEntry("Fainting", RED
); break;
4717 case EXHAUSTED
: Info
.AddEntry("Exhausted", LIGHT_GRAY
); break;
4719 game::SetStandardListAttributes(Info
);
4724 truth
character::CanUseStethoscope (truth PrintReason
) const {
4725 if (PrintReason
) ADD_MESSAGE("This type of monster can't use a stethoscope.");
4730 /* Effect used by at least Sophos.
4731 * NOTICE: Doesn't check for death! */
4732 void character::TeleportSomePartsAway (int NumberToTeleport
) {
4733 for (int c
= 0; c
< NumberToTeleport
; ++c
) {
4734 int RandomBodyPart
= GetRandomNonVitalBodyPart();
4735 if (RandomBodyPart
== NONE_INDEX
) {
4736 for (; c
< NumberToTeleport
; ++c
) {
4737 GetTorso()->SetHP((GetTorso()->GetHP() << 2) / 5);
4738 sLong TorsosVolume
= GetTorso()->GetMainMaterial()->GetVolume() / 10;
4739 if (!TorsosVolume
) break;
4740 sLong Amount
= (RAND() % TorsosVolume
)+1;
4741 item
*Lump
= GetTorso()->GetMainMaterial()->CreateNaturalForm(Amount
);
4742 GetTorso()->GetMainMaterial()->EditVolume(-Amount
);
4743 Lump
->MoveTo(GetNearLSquare(GetLevel()->GetRandomSquare())->GetStack());
4744 if (IsPlayer()) ADD_MESSAGE("Parts of you teleport away.");
4745 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Parts of %s teleport away.", CHAR_NAME(DEFINITE
));
4748 item
*SeveredBodyPart
= SevereBodyPart(RandomBodyPart
);
4749 if (SeveredBodyPart
) {
4750 GetNearLSquare(GetLevel()->GetRandomSquare())->AddItem(SeveredBodyPart
);
4751 SeveredBodyPart
->DropEquipment();
4752 if (IsPlayer()) ADD_MESSAGE("Your %s teleports away.", GetBodyPartName(RandomBodyPart
).CStr());
4753 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s teleports away.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart
).CStr());
4755 if (IsPlayer()) ADD_MESSAGE("Your %s disappears.", GetBodyPartName(RandomBodyPart
).CStr());
4756 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s disappears.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart
).CStr());
4763 /* Returns an index of a random bodypart that is not vital. If no non-vital bodypart is found returns NONE_INDEX */
4764 int character::GetRandomNonVitalBodyPart () const {
4765 int OKBodyPart
[MAX_BODYPARTS
];
4766 int OKBodyParts
= 0;
4767 for (int c
= 0; c
< BodyParts
; ++c
) if (GetBodyPart(c
) && !BodyPartIsVital(c
)) OKBodyPart
[OKBodyParts
++] = c
;
4768 return OKBodyParts
? OKBodyPart
[RAND() % OKBodyParts
] : NONE_INDEX
;
4772 void character::CalculateVolumeAndWeight () {
4773 Volume
= Stack
->GetVolume();
4774 Weight
= Stack
->GetWeight();
4776 CarriedWeight
= Weight
;
4777 for (int c
= 0; c
< BodyParts
; ++c
) {
4778 bodypart
*BodyPart
= GetBodyPart(c
);
4780 BodyVolume
+= BodyPart
->GetBodyPartVolume();
4781 Volume
+= BodyPart
->GetVolume();
4782 CarriedWeight
+= BodyPart
->GetCarriedWeight();
4783 Weight
+= BodyPart
->GetWeight();
4789 void character::SignalVolumeAndWeightChange () {
4790 if (!IsInitializing()) {
4791 CalculateVolumeAndWeight();
4792 if (IsEnabled()) CalculateBurdenState();
4793 if (MotherEntity
) MotherEntity
->SignalVolumeAndWeightChange();
4798 void character::SignalEmitationIncrease (col24 EmitationUpdate
) {
4799 if (game::CompareLights(EmitationUpdate
, Emitation
) > 0) {
4800 game::CombineLights(Emitation
, EmitationUpdate
);
4801 if (MotherEntity
) MotherEntity
->SignalEmitationIncrease(EmitationUpdate
);
4802 else if (SquareUnder
[0] && !game::IsInWilderness()) {
4803 for(int c
= 0; c
< GetSquaresUnder(); ++c
) GetLSquareUnder()->SignalEmitationIncrease(EmitationUpdate
);
4809 void character::SignalEmitationDecrease (col24 EmitationUpdate
) {
4810 if (game::CompareLights(EmitationUpdate
, Emitation
) >= 0 && Emitation
) {
4811 col24 Backup
= Emitation
;
4812 CalculateEmitation();
4813 if (Backup
!= Emitation
) {
4814 if (MotherEntity
) MotherEntity
->SignalEmitationDecrease(EmitationUpdate
);
4815 else if (SquareUnder
[0] && !game::IsInWilderness()) {
4816 for (int c
= 0; c
< GetSquaresUnder(); ++c
) GetLSquareUnder(c
)->SignalEmitationDecrease(EmitationUpdate
);
4823 void character::CalculateEmitation () {
4824 Emitation
= GetBaseEmitation();
4825 for (int c
= 0; c
< BodyParts
; ++c
) {
4826 bodypart
*BodyPart
= GetBodyPart(c
);
4827 if (BodyPart
) game::CombineLights(Emitation
, BodyPart
->GetEmitation());
4829 game::CombineLights(Emitation
, Stack
->GetEmitation());
4833 void character::CalculateAll () {
4834 Flags
|= C_INITIALIZING
;
4835 CalculateAttributeBonuses();
4836 CalculateVolumeAndWeight();
4837 CalculateEmitation();
4838 CalculateBodyPartMaxHPs(0);
4839 CalculateMaxStamina();
4840 CalculateBurdenState();
4841 CalculateBattleInfo();
4842 Flags
&= ~C_INITIALIZING
;
4846 void character::CalculateHP () {
4847 HP
= sumbodypartproperties()(this, &bodypart::GetHP
);
4851 void character::CalculateMaxHP () {
4852 MaxHP
= sumbodypartproperties()(this, &bodypart::GetMaxHP
);
4856 void character::CalculateBodyPartMaxHPs (feuLong Flags
) {
4857 doforbodypartswithparam
<feuLong
>()(this, &bodypart::CalculateMaxHP
, Flags
);
4863 truth
character::EditAttribute (int Identifier
, int Value
) {
4864 if (Identifier
== ENDURANCE
&& UseMaterialAttributes()) return false;
4865 if (RawEditAttribute(BaseExperience
[Identifier
], Value
)) {
4866 if (!IsInitializing()) {
4867 if (Identifier
== LEG_STRENGTH
) CalculateBurdenState();
4868 else if (Identifier
== ENDURANCE
) CalculateBodyPartMaxHPs();
4869 else if (IsPlayer() && Identifier
== PERCEPTION
) game::SendLOSUpdateRequest();
4870 else if (IsPlayerKind() && (Identifier
== INTELLIGENCE
|| Identifier
== WISDOM
|| Identifier
== CHARISMA
)) UpdatePictures();
4871 CalculateBattleInfo();
4879 truth
character::ActivateRandomState (int Flags
, int Time
, sLong Seed
) {
4881 if (Seed
) femath::SetSeed(Seed
);
4882 sLong ToBeActivated
= GetRandomState(Flags
|DUR_TEMPORARY
);
4884 if (!ToBeActivated
) return false;
4885 BeginTemporaryState(ToBeActivated
, Time
);
4890 truth
character::GainRandomIntrinsic (int Flags
) {
4891 sLong ToBeActivated
= GetRandomState(Flags
|DUR_PERMANENT
);
4892 if (!ToBeActivated
) return false;
4893 GainIntrinsic(ToBeActivated
);
4898 /* Returns 0 if state not found */
4899 sLong
character::GetRandomState (int Flags
) const {
4900 sLong OKStates
[STATES
];
4901 int NumberOfOKStates
= 0;
4902 for (int c
= 0; c
< STATES
; ++c
) {
4903 if (StateData
[c
].Flags
& Flags
& DUR_FLAGS
&& StateData
[c
].Flags
& Flags
& SRC_FLAGS
) OKStates
[NumberOfOKStates
++] = 1 << c
;
4905 return NumberOfOKStates
? OKStates
[RAND() % NumberOfOKStates
] : 0;
4909 int characterprototype::CreateSpecialConfigurations (characterdatabase
**TempConfig
, int Configs
, int Level
) {
4910 if (Level
== 0 && TempConfig
[0]->CreateDivineConfigurations
) {
4911 Configs
= databasecreator
<character
>::CreateDivineConfigurations(this, TempConfig
, Configs
);
4913 if (Level
== 1 && TempConfig
[0]->CreateUndeadConfigurations
) {
4914 for (int c
= 1; c
< protocontainer
<character
>::GetSize(); ++c
) {
4915 const character::prototype
*Proto
= protocontainer
<character
>::GetProto(c
);
4916 const character::database
*const *CharacterConfigData
= Proto
->GetConfigData();
4917 if (!CharacterConfigData
) ABORT("No database entry for character <%s>!", Proto
->GetClassID());
4918 const character::database
*const* End
= CharacterConfigData
+ Proto
->GetConfigSize();
4919 for (++CharacterConfigData
; CharacterConfigData
!= End
; ++CharacterConfigData
) {
4920 const character::database
*CharacterDataBase
= *CharacterConfigData
;
4921 if (CharacterDataBase
->UndeadVersions
) {
4922 character::database
* ConfigDataBase
= new character::database(**TempConfig
);
4923 ConfigDataBase
->InitDefaults(this, (c
<< 8) | CharacterDataBase
->Config
);
4924 ConfigDataBase
->PostFix
<< "of ";
4925 if (CharacterDataBase
->Adjective
.GetSize()) {
4926 if (CharacterDataBase
->UsesLongAdjectiveArticle
) ConfigDataBase
->PostFix
<< "an ";
4927 else ConfigDataBase
->PostFix
<< "a ";
4928 ConfigDataBase
->PostFix
<< CharacterDataBase
->Adjective
<< ' ';
4930 if (CharacterDataBase
->UsesLongArticle
) ConfigDataBase
->PostFix
<< "an ";
4931 else ConfigDataBase
->PostFix
<< "a ";
4933 ConfigDataBase
->PostFix
<< CharacterDataBase
->NameSingular
;
4934 if (CharacterDataBase
->PostFix
.GetSize()) ConfigDataBase
->PostFix
<< ' ' << CharacterDataBase
->PostFix
;
4935 int P1
= TempConfig
[0]->UndeadAttributeModifier
;
4936 int P2
= TempConfig
[0]->UndeadVolumeModifier
;
4938 for (c2
= 0; c2
< ATTRIBUTES
; ++c2
) ConfigDataBase
->*ExpPtr
[c2
] = CharacterDataBase
->*ExpPtr
[c2
] * P1
/ 100;
4939 for (c2
= 0; c2
< EQUIPMENT_DATAS
; ++c2
) ConfigDataBase
->*EquipmentDataPtr
[c2
] = contentscript
<item
>();
4940 ConfigDataBase
->DefaultIntelligence
= 5;
4941 ConfigDataBase
->DefaultWisdom
= 5;
4942 ConfigDataBase
->DefaultCharisma
= 5;
4943 ConfigDataBase
->TotalSize
= CharacterDataBase
->TotalSize
;
4944 ConfigDataBase
->Sex
= CharacterDataBase
->Sex
;
4945 ConfigDataBase
->AttributeBonus
= CharacterDataBase
->AttributeBonus
;
4946 ConfigDataBase
->TotalVolume
= CharacterDataBase
->TotalVolume
* P2
/ 100;
4947 if (TempConfig
[0]->UndeadCopyMaterials
) {
4948 ConfigDataBase
->HeadBitmapPos
= CharacterDataBase
->HeadBitmapPos
;
4949 ConfigDataBase
->HairColor
= CharacterDataBase
->HairColor
;
4950 ConfigDataBase
->EyeColor
= CharacterDataBase
->EyeColor
;
4951 ConfigDataBase
->CapColor
= CharacterDataBase
->CapColor
;
4952 ConfigDataBase
->FleshMaterial
= CharacterDataBase
->FleshMaterial
;
4953 ConfigDataBase
->BloodMaterial
= CharacterDataBase
->BloodMaterial
;
4954 ConfigDataBase
->VomitMaterial
= CharacterDataBase
->VomitMaterial
;
4955 ConfigDataBase
->SweatMaterial
= CharacterDataBase
->SweatMaterial
;
4957 ConfigDataBase
->KnownCWeaponSkills
= CharacterDataBase
->KnownCWeaponSkills
;
4958 ConfigDataBase
->CWeaponSkillHits
= CharacterDataBase
->CWeaponSkillHits
;
4959 ConfigDataBase
->PostProcess();
4960 TempConfig
[Configs
++] = ConfigDataBase
;
4965 if (Level
== 0 && TempConfig
[0]->CreateGolemMaterialConfigurations
) {
4966 for (int c
= 1; c
< protocontainer
<material
>::GetSize(); ++c
) {
4967 const material::prototype
* Proto
= protocontainer
<material
>::GetProto(c
);
4968 const material::database
*const* MaterialConfigData
= Proto
->GetConfigData();
4969 const material::database
*const* End
= MaterialConfigData
+ Proto
->GetConfigSize();
4970 for (++MaterialConfigData
; MaterialConfigData
!= End
; ++MaterialConfigData
) {
4971 const material::database
* MaterialDataBase
= *MaterialConfigData
;
4972 if (MaterialDataBase
->CategoryFlags
& IS_GOLEM_MATERIAL
) {
4973 character::database
* ConfigDataBase
= new character::database(**TempConfig
);
4974 ConfigDataBase
->InitDefaults(this, MaterialDataBase
->Config
);
4975 ConfigDataBase
->Adjective
= MaterialDataBase
->NameStem
;
4976 ConfigDataBase
->UsesLongAdjectiveArticle
= MaterialDataBase
->NameFlags
& USE_AN
;
4977 ConfigDataBase
->AttachedGod
= MaterialDataBase
->AttachedGod
;
4978 TempConfig
[Configs
++] = ConfigDataBase
;
4987 double character::GetTimeToDie (ccharacter
*Enemy
, int Damage
, double ToHitValue
, truth AttackIsBlockable
, truth UseMaxHP
) const {
4988 double DodgeValue
= GetDodgeValue();
4989 if (!Enemy
->CanBeSeenBy(this, true)) ToHitValue
*= 2;
4990 if (!CanBeSeenBy(Enemy
, true)) DodgeValue
*= 2;
4991 double MinHits
= 1000;
4993 for (int c
= 0; c
< BodyParts
; ++c
) {
4994 if (BodyPartIsVital(c
) && GetBodyPart(c
)) {
4995 double Hits
= GetBodyPart(c
)->GetTimeToDie(Damage
, ToHitValue
, DodgeValue
, AttackIsBlockable
, UseMaxHP
);
4996 if (First
) { MinHits
= Hits
; First
= false; } else MinHits
= 1 / (1 / MinHits
+ 1 / Hits
);
5003 double character::GetRelativeDanger (ccharacter
*Enemy
, truth UseMaxHP
) const {
5004 double Danger
= Enemy
->GetTimeToKill(this, UseMaxHP
) / GetTimeToKill(Enemy
, UseMaxHP
);
5005 int EnemyAP
= Enemy
->GetMoveAPRequirement(1);
5006 int ThisAP
= GetMoveAPRequirement(1);
5007 if (EnemyAP
> ThisAP
) Danger
*= 1.25;
5008 else if (ThisAP
> EnemyAP
) Danger
*= 0.80;
5009 if (!Enemy
->CanBeSeenBy(this, true)) Danger
*= Enemy
->IsPlayer() ? 0.2 : 0.5;
5010 if (!CanBeSeenBy(Enemy
, true)) Danger
*= IsPlayer() ? 5. : 2.;
5011 if (GetAttribute(INTELLIGENCE
) < 10 && !IsPlayer()) Danger
*= 0.80;
5012 if (Enemy
->GetAttribute(INTELLIGENCE
) < 10 && !Enemy
->IsPlayer()) Danger
*= 1.25;
5013 return Limit(Danger
, 0.001, 1000.0);
5017 festring
character::GetBodyPartName (int I
, truth Articled
) const {
5018 if (I
== TORSO_INDEX
) return Articled
? CONST_S("a torso") : CONST_S("torso");
5019 ABORT("Illegal character bodypart name request!");
5024 item
*character::SearchForItem(feuLong ID
) const {
5025 item
*Equipment
= findequipment
<feuLong
>()(this, &item::HasID
, ID
);
5026 if (Equipment
) return Equipment
;
5027 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (i
->GetID() == ID
) return *i
;
5032 truth
character::ContentsCanBeSeenBy (ccharacter
*Viewer
) const {
5033 return Viewer
== this;
5037 truth
character::HitEffect (character
*Enemy
, item
* Weapon
, v2 HitPos
, int Type
, int BodyPartIndex
,
5038 int Direction
, truth BlockedByArmour
)
5040 if (Weapon
) return Weapon
->HitEffect(this, Enemy
, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
5042 case UNARMED_ATTACK
: return Enemy
->SpecialUnarmedEffect(this, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
5043 case KICK_ATTACK
: return Enemy
->SpecialKickEffect(this, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
5044 case BITE_ATTACK
: return Enemy
->SpecialBiteEffect(this, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
5050 void character::WeaponSkillHit (item
*Weapon
, int Type
, int Hits
) {
5053 case UNARMED_ATTACK
: Category
= UNARMED
; break;
5054 case WEAPON_ATTACK
: Weapon
->WeaponSkillHit(Hits
); return;
5055 case KICK_ATTACK
: Category
= KICK
; break;
5056 case BITE_ATTACK
: Category
= BITE
; break;
5058 if (!IsHumanoid()) return;
5059 Category
= Weapon
->GetWeaponCategory();
5062 ABORT("Illegal Type %d passed to character::WeaponSkillHit()!", Type
);
5065 if (GetCWeaponSkill(Category
)->AddHit(Hits
)) {
5066 CalculateBattleInfo();
5067 if (IsPlayer()) GetCWeaponSkill(Category
)->AddLevelUpMessage(Category
);
5072 /* Returns 0 if character cannot be duplicated */
5073 character
*character::Duplicate (feuLong Flags
) {
5074 if (!(Flags
& IGNORE_PROHIBITIONS
) && !CanBeCloned()) return 0;
5075 character
*Char
= GetProtoType()->Clone(this);
5076 if (Flags
& MIRROR_IMAGE
) {
5077 DuplicateEquipment(Char
, Flags
& ~IGNORE_PROHIBITIONS
);
5078 Char
->SetLifeExpectancy(Flags
>> LE_BASE_SHIFT
& LE_BASE_RANGE
, Flags
>> LE_RAND_SHIFT
& LE_RAND_RANGE
);
5080 Char
->CalculateAll();
5081 Char
->CalculateEmitation();
5082 Char
->UpdatePictures();
5083 Char
->Flags
&= ~(C_INITIALIZING
|C_IN_NO_MSG_MODE
);
5088 truth
character::TryToEquip (item
*Item
) {
5089 if (!Item
->AllowEquip() || !CanUseEquipment() || GetAttribute(WISDOM
) >= Item
->GetWearWisdomLimit() || Item
->GetSquaresUnder() != 1)
5091 for (int e
= 0; e
< GetEquipments(); ++e
) {
5092 if (GetBodyPartOfEquipment(e
) && EquipmentIsAllowed(e
)) {
5093 sorter Sorter
= EquipmentSorter(e
);
5094 if ((Sorter
== 0 || (Item
->*Sorter
)(this)) &&
5095 ((e
!= RIGHT_WIELDED_INDEX
&& e
!= LEFT_WIELDED_INDEX
) ||
5096 Item
->IsWeapon(this) || Item
->IsShield(this)) && AllowEquipment(Item
, e
)) {
5097 item
*OldEquipment
= GetEquipment(e
);
5098 if (BoundToUse(OldEquipment
, e
)) continue;
5099 lsquare
*LSquareUnder
= GetLSquareUnder();
5100 stack
*StackUnder
= LSquareUnder
->GetStack();
5101 msgsystem::DisableMessages();
5102 Flags
|= C_PICTURE_UPDATES_FORBIDDEN
;
5103 LSquareUnder
->Freeze();
5104 StackUnder
->Freeze();
5105 double Danger
= GetRelativeDanger(PLAYER
);
5106 if (OldEquipment
) OldEquipment
->RemoveFromSlot();
5107 Item
->RemoveFromSlot();
5108 SetEquipment(e
, Item
);
5109 double NewDanger
= GetRelativeDanger(PLAYER
);
5110 Item
->RemoveFromSlot();
5111 StackUnder
->AddItem(Item
);
5112 if (OldEquipment
) SetEquipment(e
, OldEquipment
);
5113 msgsystem::EnableMessages();
5114 Flags
&= ~C_PICTURE_UPDATES_FORBIDDEN
;
5115 LSquareUnder
->UnFreeze();
5116 StackUnder
->UnFreeze();
5118 if (NewDanger
> Danger
|| BoundToUse(Item
, e
)) {
5119 room
*Room
= GetRoom();
5120 if (!Room
|| Room
->PickupItem(this, Item
, 1)) {
5121 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
));
5122 if (Room
) Room
->DropItem(this, OldEquipment
, 1);
5123 OldEquipment
->MoveTo(StackUnder
);
5124 Item
->RemoveFromSlot();
5125 SetEquipment(e
, Item
);
5131 if (NewDanger
> Danger
|| (NewDanger
== Danger
&& e
!= RIGHT_WIELDED_INDEX
&& e
!= LEFT_WIELDED_INDEX
) || BoundToUse(Item
, e
)) {
5132 room
*Room
= GetRoom();
5133 if (!Room
|| Room
->PickupItem(this, Item
, 1)) {
5134 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s picks up and equips %s.", CHAR_NAME(DEFINITE
), Item
->CHAR_NAME(INDEFINITE
));
5135 Item
->RemoveFromSlot();
5136 SetEquipment(e
, Item
);
5149 truth
character::TryToConsume (item
*Item
) {
5150 return Item
->CanBeEatenByAI(this) && ConsumeItem(Item
, Item
->GetConsumeMaterial(this)->GetConsumeVerb());
5154 void character::UpdateESPLOS () const {
5155 if (StateIsActivated(ESP
) && !game::IsInWilderness()) {
5156 for (int c
= 0; c
< game::GetTeams(); ++c
) {
5157 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
5158 const character
*ch
= *i
;
5159 if (ch
->IsEnabled()) ch
->SendNewDrawRequest();
5166 int character::GetCWeaponSkillLevel (citem
*Item
) const {
5167 if (Item
->GetWeaponCategory() < GetAllowedWeaponSkillCategories()) return GetCWeaponSkill(Item
->GetWeaponCategory())->GetLevel();
5172 void character::PrintBeginPanicMessage () const {
5173 if (IsPlayer()) ADD_MESSAGE("You panic!");
5174 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s panics.", CHAR_NAME(DEFINITE
));
5178 void character::PrintEndPanicMessage () const {
5179 if (IsPlayer()) ADD_MESSAGE("You finally calm down.");
5180 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s calms down.", CHAR_NAME(DEFINITE
));
5184 void character::CheckPanic (int Ticks
) {
5185 if (GetPanicLevel() > 1 && !StateIsActivated(PANIC
) && GetHP() * 100 < RAND() % (GetPanicLevel() * GetMaxHP() << 1))
5186 BeginTemporaryState(PANIC
, ((Ticks
* 3) >> 2) + RAND() % ((Ticks
>> 1) + 1)); // 25% randomness to ticks...
5190 /* returns 0 if fails else the newly created character */
5191 character
*character::DuplicateToNearestSquare (character
*Cloner
, feuLong Flags
) {
5192 character
*NewlyCreated
= Duplicate(Flags
);
5193 if (!NewlyCreated
) return 0;
5194 if (Flags
& CHANGE_TEAM
&& Cloner
) NewlyCreated
->ChangeTeam(Cloner
->GetTeam());
5195 NewlyCreated
->PutNear(GetPos());
5196 return NewlyCreated
;
5200 void character::SignalSpoil (material
*m
) {
5201 if (GetMotherEntity()) GetMotherEntity()->SignalSpoil(m
);
5202 else Disappear(0, "spoil", &item::IsVeryCloseToSpoiling
);
5206 truth
character::CanHeal () const {
5207 for (int c
= 0; c
< BodyParts
; ++c
) {
5208 bodypart
*BodyPart
= GetBodyPart(c
);
5209 if (BodyPart
&& BodyPart
->CanRegenerate() && BodyPart
->GetHP() < BodyPart
->GetMaxHP()) return true;
5215 int character::GetRelation (ccharacter
*Who
) const {
5216 return GetTeam()->GetRelation(Who
->GetTeam());
5220 truth (item::*AffectTest
[BASE_ATTRIBUTES
])() const = {
5221 &item::AffectsEndurance
,
5222 &item::AffectsPerception
,
5223 &item::AffectsIntelligence
,
5224 &item::AffectsWisdom
,
5225 &item::AffectsWillPower
,
5226 &item::AffectsCharisma
,
5231 /* Returns nonzero if endurance has decreased and death may occur */
5232 truth
character::CalculateAttributeBonuses () {
5233 doforbodyparts()(this, &bodypart::CalculateAttributeBonuses
);
5234 int BackupBonus
[BASE_ATTRIBUTES
];
5235 int BackupCarryingBonus
= CarryingBonus
;
5238 for (c1
= 0; c1
< BASE_ATTRIBUTES
; ++c1
) {
5239 BackupBonus
[c1
] = AttributeBonus
[c1
];
5240 AttributeBonus
[c1
] = 0;
5242 for (c1
= 0; c1
< GetEquipments(); ++c1
) {
5243 item
*Equipment
= GetEquipment(c1
);
5244 if (!Equipment
|| !Equipment
->IsInCorrectSlot(c1
)) continue;
5245 for (int c2
= 0; c2
< BASE_ATTRIBUTES
; ++c2
) {
5246 if ((Equipment
->*AffectTest
[c2
])()) AttributeBonus
[c2
] += Equipment
->GetEnchantment();
5248 if (Equipment
->AffectsCarryingCapacity()) CarryingBonus
+= Equipment
->GetCarryingBonus();
5251 ApplySpecialAttributeBonuses();
5253 if (IsPlayer() && !IsInitializing() && AttributeBonus
[PERCEPTION
] != BackupBonus
[PERCEPTION
]) game::SendLOSUpdateRequest();
5254 if (IsPlayer() && !IsInitializing() && AttributeBonus
[INTELLIGENCE
] != BackupBonus
[INTELLIGENCE
]) UpdateESPLOS();
5256 if (!IsInitializing() && CarryingBonus
!= BackupCarryingBonus
) CalculateBurdenState();
5258 if (!IsInitializing() && AttributeBonus
[ENDURANCE
] != BackupBonus
[ENDURANCE
]) {
5259 CalculateBodyPartMaxHPs();
5260 CalculateMaxStamina();
5261 return AttributeBonus
[ENDURANCE
] < BackupBonus
[ENDURANCE
];
5268 void character::ApplyEquipmentAttributeBonuses (item
*Equipment
) {
5269 if (Equipment
->AffectsEndurance()) {
5270 AttributeBonus
[ENDURANCE
] += Equipment
->GetEnchantment();
5271 CalculateBodyPartMaxHPs();
5272 CalculateMaxStamina();
5274 if (Equipment
->AffectsPerception()) {
5275 AttributeBonus
[PERCEPTION
] += Equipment
->GetEnchantment();
5276 if (IsPlayer()) game::SendLOSUpdateRequest();
5278 if (Equipment
->AffectsIntelligence()) {
5279 AttributeBonus
[INTELLIGENCE
] += Equipment
->GetEnchantment();
5280 if (IsPlayer()) UpdateESPLOS();
5282 if (Equipment
->AffectsWisdom()) AttributeBonus
[WISDOM
] += Equipment
->GetEnchantment();
5283 if (Equipment
->AffectsWillPower()) AttributeBonus
[WILL_POWER
] += Equipment
->GetEnchantment();
5284 if (Equipment
->AffectsCharisma()) AttributeBonus
[CHARISMA
] += Equipment
->GetEnchantment();
5285 if (Equipment
->AffectsMana()) AttributeBonus
[MANA
] += Equipment
->GetEnchantment();
5286 if (Equipment
->AffectsCarryingCapacity()) {
5287 CarryingBonus
+= Equipment
->GetCarryingBonus();
5288 CalculateBurdenState();
5293 void character::ReceiveAntidote (sLong Amount
) {
5294 if (StateIsActivated(POISONED
)) {
5295 if (GetTemporaryStateCounter(POISONED
) > Amount
) {
5296 EditTemporaryStateCounter(POISONED
, -Amount
);
5299 if (IsPlayer()) ADD_MESSAGE("Aaaah... You feel much better.");
5300 Amount
-= GetTemporaryStateCounter(POISONED
);
5301 DeActivateTemporaryState(POISONED
);
5304 if ((Amount
>= 100 || RAND_N(100) < Amount
) && StateIsActivated(PARASITIZED
)) {
5305 if (IsPlayer()) ADD_MESSAGE("Something in your belly didn't seem to like this stuff.");
5306 DeActivateTemporaryState(PARASITIZED
);
5307 Amount
-= Min(100, Amount
);
5309 if ((Amount
>= 100 || RAND_N(100) < Amount
) && StateIsActivated(LEPROSY
)) {
5310 if (IsPlayer()) ADD_MESSAGE("You are not falling to pieces anymore.");
5311 DeActivateTemporaryState(LEPROSY
);
5312 Amount
-= Min(100, Amount
);
5317 void character::AddAntidoteConsumeEndMessage () const {
5318 if (StateIsActivated(POISONED
)) {
5319 // true only if the antidote didn't cure the poison completely
5320 if (IsPlayer()) ADD_MESSAGE("Your body processes the poison in your veins with rapid speed.");
5325 truth
character::IsDead () const {
5326 for (int c
= 0; c
< BodyParts
; ++c
) {
5327 bodypart
*BodyPart
= GetBodyPart(c
);
5328 if (BodyPartIsVital(c
) && (!BodyPart
|| BodyPart
->GetHP() < 1)) return true;
5334 void character::SignalSpoilLevelChange (material
*m
) {
5335 if (GetMotherEntity()) GetMotherEntity()->SignalSpoilLevelChange(m
); else UpdatePictures();
5339 void character::AddOriginalBodyPartID (int I
, feuLong What
) {
5340 if (std::find(OriginalBodyPartID
[I
].begin(), OriginalBodyPartID
[I
].end(), What
) == OriginalBodyPartID
[I
].end()) {
5341 OriginalBodyPartID
[I
].push_back(What
);
5342 if (OriginalBodyPartID
[I
].size() > 100) OriginalBodyPartID
[I
].erase(OriginalBodyPartID
[I
].begin());
5347 void character::AddToInventory (const fearray
<contentscript
<item
> > &ItemArray
, int SpecialFlags
) {
5348 for (uInt c1
= 0; c1
< ItemArray
.Size
; ++c1
) {
5349 if (ItemArray
[c1
].IsValid()) {
5350 const interval
*TimesPtr
= ItemArray
[c1
].GetTimes();
5351 int Times
= TimesPtr
? TimesPtr
->Randomize() : 1;
5352 for (int c2
= 0; c2
< Times
; ++c2
) {
5353 item
*Item
= ItemArray
[c1
].Instantiate(SpecialFlags
);
5355 Stack
->AddItem(Item
);
5356 Item
->SpecialGenerationHandler();
5364 truth
character::HasHadBodyPart (citem
*Item
) const {
5365 for (int c
= 0; c
< BodyParts
; ++c
)
5366 if (std::find(OriginalBodyPartID
[c
].begin(), OriginalBodyPartID
[c
].end(), Item
->GetID()) != OriginalBodyPartID
[c
].end())
5368 return GetPolymorphBackup() && GetPolymorphBackup()->HasHadBodyPart(Item
);
5372 festring
&character::ProcessMessage (festring
&Msg
) const {
5373 SEARCH_N_REPLACE(Msg
, "@nu", GetName(UNARTICLED
));
5374 SEARCH_N_REPLACE(Msg
, "@ni", GetName(INDEFINITE
));
5375 SEARCH_N_REPLACE(Msg
, "@nd", GetName(DEFINITE
));
5376 SEARCH_N_REPLACE(Msg
, "@du", GetDescription(UNARTICLED
));
5377 SEARCH_N_REPLACE(Msg
, "@di", GetDescription(INDEFINITE
));
5378 SEARCH_N_REPLACE(Msg
, "@dd", GetDescription(DEFINITE
));
5379 SEARCH_N_REPLACE(Msg
, "@pp", GetPersonalPronoun());
5380 SEARCH_N_REPLACE(Msg
, "@sp", GetPossessivePronoun());
5381 SEARCH_N_REPLACE(Msg
, "@op", GetObjectPronoun());
5382 SEARCH_N_REPLACE(Msg
, "@Nu", GetName(UNARTICLED
).CapitalizeCopy());
5383 SEARCH_N_REPLACE(Msg
, "@Ni", GetName(INDEFINITE
).CapitalizeCopy());
5384 SEARCH_N_REPLACE(Msg
, "@Nd", GetName(DEFINITE
).CapitalizeCopy());
5385 SEARCH_N_REPLACE(Msg
, "@Du", GetDescription(UNARTICLED
).CapitalizeCopy());
5386 SEARCH_N_REPLACE(Msg
, "@Di", GetDescription(INDEFINITE
).CapitalizeCopy());
5387 SEARCH_N_REPLACE(Msg
, "@Dd", GetDescription(DEFINITE
).CapitalizeCopy());
5388 SEARCH_N_REPLACE(Msg
, "@Pp", GetPersonalPronoun().CapitalizeCopy());
5389 SEARCH_N_REPLACE(Msg
, "@Sp", GetPossessivePronoun().CapitalizeCopy());
5390 SEARCH_N_REPLACE(Msg
, "@Op", GetObjectPronoun().CapitalizeCopy());
5391 SEARCH_N_REPLACE(Msg
, "@Gd", GetMasterGod()->GetName());
5396 void character::ProcessAndAddMessage (festring Msg
) const {
5397 ADD_MESSAGE("%s", ProcessMessage(Msg
).CStr());
5401 void character::BeTalkedTo () {
5403 if (GetRelation(PLAYER
) == HOSTILE
)
5404 ProcessAndAddMessage(GetHostileReplies()[RandomizeReply(Said
, GetHostileReplies().Size
)]);
5406 ProcessAndAddMessage(GetFriendlyReplies()[RandomizeReply(Said
, GetFriendlyReplies().Size
)]);
5410 truth
character::CheckZap () {
5412 ADD_MESSAGE("This monster type can't zap.");
5419 void character::DamageAllItems (character
*Damager
, int Damage
, int Type
) {
5420 GetStack()->ReceiveDamage(Damager
, Damage
, Type
);
5421 for (int c
= 0; c
< GetEquipments(); ++c
) {
5422 item
*Equipment
= GetEquipment(c
);
5423 if (Equipment
) Equipment
->ReceiveDamage(Damager
, Damage
, Type
);
5428 truth
character::Equips (citem
*Item
) const {
5429 return combineequipmentpredicateswithparam
<feuLong
>()(this, &item::HasID
, Item
->GetID(), 1);
5433 void character::PrintBeginConfuseMessage () const {
5434 if (IsPlayer()) ADD_MESSAGE("You feel quite happy.");
5438 void character::PrintEndConfuseMessage () const {
5439 if (IsPlayer()) ADD_MESSAGE("The world is boring again.");
5443 v2
character::ApplyStateModification (v2 TryDirection
) const {
5444 if (!StateIsActivated(CONFUSED
) || RAND() & 15 || game::IsInWilderness()) return TryDirection
;
5445 v2 To
= GetLevel()->GetFreeAdjacentSquare(this, GetPos(), true);
5446 if (To
== ERROR_V2
) return TryDirection
;
5448 if (To
!= TryDirection
&& IsPlayer()) ADD_MESSAGE("Whoa! You somehow don't manage to walk straight.");
5453 void character::AddConfuseHitMessage () const {
5454 if (IsPlayer()) ADD_MESSAGE("This stuff is confusing.");
5458 item
*character::SelectFromPossessions (cfestring
&Topic
, sorter Sorter
) {
5459 itemvector ReturnVector
;
5460 SelectFromPossessions(ReturnVector
, Topic
, NO_MULTI_SELECT
, Sorter
);
5461 return !ReturnVector
.empty() ? ReturnVector
[0] : 0;
5465 truth
character::SelectFromPossessions (itemvector
&ReturnVector
, cfestring
&Topic
, int Flags
, sorter Sorter
) {
5467 truth InventoryPossible
= GetStack()->SortedItems(this, Sorter
);
5468 if (InventoryPossible
) List
.AddEntry(CONST_S("choose from inventory"), LIGHT_GRAY
, 20, game::AddToItemDrawVector(itemvector()));
5473 for (c
= 0; c
< BodyParts
; ++c
) {
5474 bodypart
*BodyPart
= GetBodyPart(c
);
5475 if (BodyPart
&& (Sorter
== 0 || (BodyPart
->*Sorter
)(this))) {
5476 Item
.push_back(BodyPart
);
5478 BodyPart
->AddName(Entry
, UNARTICLED
);
5479 int ImageKey
= game::AddToItemDrawVector(itemvector(1, BodyPart
));
5480 List
.AddEntry(Entry
, LIGHT_GRAY
, 20, ImageKey
, true);
5484 for (c
= 0; c
< GetEquipments(); ++c
) {
5485 item
*Equipment
= GetEquipment(c
);
5486 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) {
5487 Item
.push_back(Equipment
);
5488 Entry
= GetEquipmentName(c
);
5491 Equipment
->AddInventoryEntry(this, Entry
, 1, true);
5492 AddSpecialEquipmentInfo(Entry
, c
);
5493 int ImageKey
= game::AddToItemDrawVector(itemvector(1, Equipment
));
5494 List
.AddEntry(Entry
, LIGHT_GRAY
, 20, ImageKey
, true);
5499 game::SetStandardListAttributes(List
);
5500 List
.SetFlags(SELECTABLE
|DRAW_BACKGROUND_AFTERWARDS
);
5501 List
.SetEntryDrawer(game::ItemEntryDrawer
);
5502 game::DrawEverythingNoBlit();
5503 int Chosen
= List
.Draw();
5504 game::ClearItemDrawVector();
5505 if (Chosen
!= ESCAPED
) {
5506 if ((InventoryPossible
&& !Chosen
) || Chosen
& FELIST_ERROR_BIT
) {
5507 GetStack()->DrawContents(ReturnVector
, this, Topic
, Flags
, Sorter
);
5509 ReturnVector
.push_back(Item
[InventoryPossible
? Chosen
- 1 : Chosen
]);
5510 if (Flags
& SELECT_PAIR
&& ReturnVector
[0]->HandleInPairs()) {
5511 item
*PairEquipment
= GetPairEquipment(ReturnVector
[0]->GetEquipmentIndex());
5512 if (PairEquipment
&& PairEquipment
->CanBePiledWith(ReturnVector
[0], this)) ReturnVector
.push_back(PairEquipment
);
5517 if (!GetStack()->SortedItems(this, Sorter
)) return false;
5518 game::ClearItemDrawVector();
5519 GetStack()->DrawContents(ReturnVector
, this, Topic
, Flags
, Sorter
);
5525 truth
character::EquipsSomething (sorter Sorter
) {
5526 for (int c
= 0; c
< GetEquipments(); ++c
) {
5527 item
*Equipment
= GetEquipment(c
);
5528 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) return true;
5534 material
*character::CreateBodyPartMaterial (int, sLong Volume
) const {
5535 return MAKE_MATERIAL(GetFleshMaterial(), Volume
);
5539 truth
character::CheckTalk () {
5541 ADD_MESSAGE("This monster does not know the art of talking.");
5548 truth
character::MoveTowardsHomePos () {
5549 if (HomeDataIsValid() && IsEnabled()) {
5550 SetGoingTo(HomeData
->Pos
);
5551 return MoveTowardsTarget(false) || (!GetPos().IsAdjacent(HomeData
->Pos
) && MoveRandomly());
5557 truth
character::TryToChangeEquipment (stack
*MainStack
, stack
*SecStack
, int Chosen
) {
5558 if (!GetBodyPartOfEquipment(Chosen
)) {
5559 ADD_MESSAGE("Bodypart missing!");
5562 item
*OldEquipment
= GetEquipment(Chosen
);
5563 if (!IsPlayer() && BoundToUse(OldEquipment
, Chosen
)) {
5564 ADD_MESSAGE("%s refuses to unequip %s.", CHAR_DESCRIPTION(DEFINITE
), OldEquipment
->CHAR_NAME(DEFINITE
));
5567 if (OldEquipment
) OldEquipment
->MoveTo(MainStack
);
5568 sorter Sorter
= EquipmentSorter(Chosen
);
5569 if (!MainStack
->SortedItems(this, Sorter
) && (!SecStack
|| !SecStack
->SortedItems(this, Sorter
))) {
5570 ADD_MESSAGE("You haven't got any item that could be used for this purpose.");
5573 game::DrawEverythingNoBlit();
5574 itemvector ItemVector
;
5575 int Return
= MainStack
->DrawContents(ItemVector
, SecStack
, this,
5576 CONST_S("Choose ") + GetEquipmentName(Chosen
) + ':',
5577 SecStack
? CONST_S("Items in your inventory") : CONST_S(""),
5578 SecStack
? festring(CONST_S("Items in ") + GetPossessivePronoun() + " inventory") : CONST_S(""),
5579 SecStack
? festring(GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState()) : CONST_S(""),
5580 GetVerbalBurdenStateColor(),
5581 NONE_AS_CHOICE
|NO_MULTI_SELECT
,
5583 if (Return
== ESCAPED
) {
5585 OldEquipment
->RemoveFromSlot();
5586 SetEquipment(Chosen
, OldEquipment
);
5590 item
*Item
= ItemVector
.empty() ? 0 : ItemVector
[0];
5592 if (!IsPlayer() && !AllowEquipment(Item
, Chosen
)) {
5593 ADD_MESSAGE("%s refuses to equip %s.", CHAR_DESCRIPTION(DEFINITE
), Item
->CHAR_NAME(DEFINITE
));
5596 Item
->RemoveFromSlot();
5597 SetEquipment(Chosen
, Item
);
5598 if (CheckIfEquipmentIsNotUsable(Chosen
)) Item
->MoveTo(MainStack
); // small bug?
5600 return Item
!= OldEquipment
;
5604 void character::PrintBeginParasitizedMessage () const {
5605 if (IsPlayer()) ADD_MESSAGE("You feel you are no longer alone.");
5609 void character::PrintEndParasitizedMessage () const {
5610 if (IsPlayer()) ADD_MESSAGE("A feeling of sLong welcome emptiness overwhelms you.");
5614 void character::ParasitizedHandler () {
5616 if (!(RAND() % 250)) {
5617 if (IsPlayer()) ADD_MESSAGE("Ugh. You feel something violently carving its way through your intestines.");
5618 ReceiveDamage(0, 1, POISON
, TORSO
, 8, false, false, false, false);
5619 CheckDeath(CONST_S("killed by a vile parasite"), 0);
5624 truth
character::CanFollow () const {
5625 return CanMove() && !StateIsActivated(PANIC
) && !IsStuck();
5629 festring
character::GetKillName () const {
5630 if (!GetPolymorphBackup()) return GetName(INDEFINITE
);
5632 GetPolymorphBackup()->AddName(KillName
, INDEFINITE
);
5633 KillName
<< " polymorphed into ";
5634 id::AddName(KillName
, INDEFINITE
);
5639 festring
character::GetPanelName () const {
5641 Name
<< AssignedName
<< " the " << game::GetVerbalPlayerAlignment() << ' ';
5642 id::AddName(Name
, UNARTICLED
);
5647 sLong
character::GetMoveAPRequirement (int Difficulty
) const {
5648 return (!StateIsActivated(PANIC
) ? 10000000 : 8000000) * Difficulty
/ (APBonus(GetAttribute(AGILITY
)) * GetMoveEase());
5652 bodypart
*character::HealHitPoint() {
5653 int NeedHeal
= 0, NeedHealIndex
[MAX_BODYPARTS
];
5654 for (int c
= 0; c
< BodyParts
; ++c
) {
5655 bodypart
*BodyPart
= GetBodyPart(c
);
5656 if (BodyPart
&& BodyPart
->CanRegenerate() && BodyPart
->GetHP() < BodyPart
->GetMaxHP()) NeedHealIndex
[NeedHeal
++] = c
;
5659 bodypart
*BodyPart
= GetBodyPart(NeedHealIndex
[RAND() % NeedHeal
]);
5660 BodyPart
->IncreaseHP();
5668 void character::CreateHomeData () {
5669 HomeData
= new homedata
;
5670 lsquare
*Square
= GetLSquareUnder();
5671 HomeData
->Pos
= Square
->GetPos();
5672 HomeData
->Dungeon
= Square
->GetDungeonIndex();
5673 HomeData
->Level
= Square
->GetLevelIndex();
5674 HomeData
->Room
= Square
->GetRoomIndex();
5678 room
*character::GetHomeRoom() const {
5679 if (HomeDataIsValid() && HomeData
->Room
) return GetLevel()->GetRoom(HomeData
->Room
);
5684 void character::RemoveHomeData () {
5690 void character::AddESPConsumeMessage () const {
5691 if (IsPlayer()) ADD_MESSAGE("You feel a strange mental activity.");
5695 void character::SetBodyPart (int I
, bodypart
*What
) {
5696 BodyPartSlot
[I
].PutInItem(What
);
5698 What
->SignalPossibleUsabilityChange();
5700 AddOriginalBodyPartID(I
, What
->GetID());
5701 if (What
->GetMainMaterial()->IsInfectedByLeprosy()) GainIntrinsic(LEPROSY
);
5702 else if (StateIsActivated(LEPROSY
)) What
->GetMainMaterial()->SetIsInfectedByLeprosy(true);
5707 truth
character::ConsumeItem (item
*Item
, cfestring
&ConsumeVerb
) {
5708 if (IsPlayer() && HasHadBodyPart(Item
) && !game::TruthQuestion(CONST_S("Are you sure? You may be able to put it back... [y/N]")))
5710 if (Item
->IsOnGround() && GetRoom() && !GetRoom()->ConsumeItem(this, Item
, 1))
5712 if (IsPlayer()) ADD_MESSAGE("You begin %s %s.", ConsumeVerb
.CStr(), Item
->CHAR_NAME(DEFINITE
));
5713 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s begins %s %s.", CHAR_NAME(DEFINITE
), ConsumeVerb
.CStr(), Item
->CHAR_NAME(DEFINITE
));
5714 consume
*Consume
= consume::Spawn(this);
5715 Consume
->SetDescription(ConsumeVerb
);
5716 Consume
->SetConsumingID(Item
->GetID());
5723 truth
character::CheckThrow () const {
5725 ADD_MESSAGE("This monster type cannot throw.");
5732 void character::GetHitByExplosion (const explosion
*Explosion
, int Damage
) {
5733 int DamageDirection
= GetPos() == Explosion
->Pos
? RANDOM_DIR
: game::CalculateRoughDirection(GetPos() - Explosion
->Pos
);
5734 if (!IsPet() && Explosion
->Terrorist
&& Explosion
->Terrorist
->IsPet()) Explosion
->Terrorist
->Hostility(this);
5735 GetTorso()->SpillBlood((8 - Explosion
->Size
+ RAND() % (8 - Explosion
->Size
)) >> 1);
5736 v2 SpillPos
= GetPos() + game::GetMoveVector(DamageDirection
);
5737 if (GetArea()->IsValidPos(SpillPos
)) GetTorso()->SpillBlood((8-Explosion
->Size
+RAND()%(8-Explosion
->Size
))>>1, SpillPos
);
5738 if (IsPlayer()) ADD_MESSAGE("You are hit by the explosion!");
5739 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s is hit by the explosion.", CHAR_NAME(DEFINITE
));
5740 truth WasUnconscious
= GetAction() && GetAction()->IsUnconsciousness();
5741 ReceiveDamage(Explosion
->Terrorist
, Damage
>> 1, FIRE
, ALL
, DamageDirection
, true, false, false, false);
5743 ReceiveDamage(Explosion
->Terrorist
, Damage
>> 1, PHYSICAL_DAMAGE
, ALL
, DamageDirection
, true, false, false, false);
5744 CheckDeath(Explosion
->DeathMsg
, Explosion
->Terrorist
, !WasUnconscious
? IGNORE_UNCONSCIOUSNESS
: 0);
5749 void character::SortAllItems (const sortdata
&SortData
) {
5750 GetStack()->SortAllItems(SortData
);
5751 doforequipmentswithparam
<const sortdata
&>()(this, &item::SortAllItems
, SortData
);
5755 void character::PrintBeginSearchingMessage () const {
5756 if (IsPlayer()) ADD_MESSAGE("You feel you can now notice even the very smallest details around you.");
5760 void character::PrintEndSearchingMessage () const {
5761 if (IsPlayer()) ADD_MESSAGE("You feel less perceptive.");
5765 void character::SearchingHandler () {
5766 if (!game::IsInWilderness()) Search(15);
5770 void character::Search (int Perception
) {
5771 for (int d
= 0; d
< GetExtendedNeighbourSquares(); ++d
) {
5772 lsquare
*LSquare
= GetNeighbourLSquare(d
);
5773 if (LSquare
) LSquare
->GetStack()->Search(this, Min(Perception
, 200));
5778 // surprisingly returns 0 if fails
5779 character
*character::GetRandomNeighbour (int RelationFlags
) const {
5780 character
*Chars
[MAX_NEIGHBOUR_SQUARES
];
5782 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
5783 lsquare
*LSquare
= GetNeighbourLSquare(d
);
5785 character
*Char
= LSquare
->GetCharacter();
5786 if (Char
&& (GetRelation(Char
) & RelationFlags
)) Chars
[Index
++] = Char
;
5789 return Index
? Chars
[RAND() % Index
] : 0;
5793 void character::ResetStates () {
5794 for (int c
= 0; c
< STATES
; ++c
) {
5795 if (1 << c
!= POLYMORPHED
&& TemporaryStateIsActivated(1 << c
) && TemporaryStateCounter
[c
] != PERMANENT
) {
5796 TemporaryState
&= ~(1 << c
);
5797 if (StateData
[c
].EndHandler
) {
5798 (this->*StateData
[c
].EndHandler
)();
5799 if (!IsEnabled())return;
5806 void characterdatabase::InitDefaults (const characterprototype
*NewProtoType
, int NewConfig
) {
5808 ProtoType
= NewProtoType
;
5814 void character::PrintBeginGasImmunityMessage () const {
5815 if (IsPlayer()) ADD_MESSAGE("All smells fade away.");
5819 void character::PrintEndGasImmunityMessage () const {
5820 if (IsPlayer()) ADD_MESSAGE("Yuck! The world smells bad again.");
5824 void character::ShowAdventureInfo () const {
5825 static const char *lists
[4][4] = {
5826 { "Show massacre history",
5828 "Show message history",
5831 "Show message history",
5834 { "Show message history",
5838 { "Show massacre history",
5839 "Show message history",
5843 // massacre, inventory, messages
5844 static const int nums
[4][3] = {
5851 if (GetStack()->GetItems()) {
5852 idx
= game::MassacreListsEmpty() ? 1 : 0;
5854 idx
= game::MassacreListsEmpty() ? 2 : 3;
5858 sel
= game::ListSelectorArray(sel
, CONST_S("Do you want to see some funny history?"), lists
[idx
]);
5860 if (sel
== nums
[idx
][0] && !game::MassacreListsEmpty()) {
5861 game::DisplayMassacreLists();
5863 if (sel
== nums
[idx
][1] && GetStack()->GetItems()) {
5864 GetStack()->DrawContents(this, CONST_S("Your inventory"), NO_SELECT
);
5865 for(stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) i
->DrawContents(this);
5866 doforequipmentswithparam
<ccharacter
*>()(this, &item::DrawContents
, this);
5868 if (sel
== nums
[idx
][2]) {
5869 msgsystem::DrawMessageHistory();
5875 truth
character::EditAllAttributes (int Amount
) {
5876 if (!Amount
) return true;
5878 truth MayEditMore
= false;
5879 for (c
= 0; c
< BodyParts
; ++c
) {
5880 bodypart
*BodyPart
= GetBodyPart(c
);
5881 if (BodyPart
&& BodyPart
->EditAllAttributes(Amount
)) MayEditMore
= true;
5883 for (c
= 0; c
< BASE_ATTRIBUTES
; ++c
) {
5884 if (BaseExperience
[c
]) {
5885 BaseExperience
[c
] += Amount
* EXP_MULTIPLIER
;
5886 LimitRef(BaseExperience
[c
], MIN_EXP
, MAX_EXP
);
5887 if ((Amount
< 0 && BaseExperience
[c
] != MIN_EXP
) || (Amount
> 0 && BaseExperience
[c
] != MAX_EXP
)) MayEditMore
= true;
5894 game::SendLOSUpdateRequest();
5897 if (IsPlayerKind()) UpdatePictures();
5903 void character::AddAttributeInfo (festring
&Entry
) const {
5905 Entry
<< GetAttribute(ENDURANCE
);
5907 Entry
<< GetAttribute(PERCEPTION
);
5909 Entry
<< GetAttribute(INTELLIGENCE
);
5911 Entry
<< GetAttribute(WISDOM
);
5913 Entry
<< GetAttribute(CHARISMA
);
5915 Entry
<< GetAttribute(MANA
);
5919 void character::AddDefenceInfo (felist
&List
) const {
5921 for (int c
= 0; c
< BodyParts
; ++c
) {
5922 bodypart
*BodyPart
= GetBodyPart(c
);
5924 Entry
= CONST_S(" ");
5925 BodyPart
->AddName(Entry
, UNARTICLED
);
5927 Entry
<< BodyPart
->GetMaxHP();
5929 Entry
<< BodyPart
->GetTotalResistance(PHYSICAL_DAMAGE
);
5930 List
.AddEntry(Entry
, LIGHT_GRAY
);
5936 void character::DetachBodyPart () {
5937 ADD_MESSAGE("You haven't got any extra bodyparts.");
5942 void character::ReceiveHolyBanana (sLong Amount
) {
5944 EditExperience(ARM_STRENGTH
, Amount
, 1 << 13);
5945 EditExperience(LEG_STRENGTH
, Amount
, 1 << 13);
5946 EditExperience(DEXTERITY
, Amount
, 1 << 13);
5947 EditExperience(AGILITY
, Amount
, 1 << 13);
5948 EditExperience(ENDURANCE
, Amount
, 1 << 13);
5949 EditExperience(PERCEPTION
, Amount
, 1 << 13);
5950 EditExperience(INTELLIGENCE
, Amount
, 1 << 13);
5951 EditExperience(WISDOM
, Amount
, 1 << 13);
5952 EditExperience(CHARISMA
, Amount
, 1 << 13);
5957 void character::AddHolyBananaConsumeEndMessage () const {
5958 if (IsPlayer()) ADD_MESSAGE("You feel a mysterious strengthening fire coursing through your body.");
5959 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s is surrounded by a swirling fire aura.", CHAR_NAME(DEFINITE
));
5963 void character::ReceiveHolyMango (sLong Amount
) {
5965 EditExperience(ARM_STRENGTH
, Amount
, 1 << 13);
5966 EditExperience(LEG_STRENGTH
, Amount
, 1 << 13);
5967 EditExperience(DEXTERITY
, Amount
, 1 << 13);
5968 EditExperience(AGILITY
, Amount
, 1 << 13);
5969 EditExperience(ENDURANCE
, Amount
, 1 << 13);
5970 EditExperience(PERCEPTION
, Amount
, 1 << 13);
5971 EditExperience(INTELLIGENCE
, Amount
, 1 << 13);
5972 EditExperience(WISDOM
, Amount
, 1 << 13);
5973 EditExperience(CHARISMA
, Amount
, 1 << 13);
5978 void character::AddHolyMangoConsumeEndMessage () const {
5979 if (IsPlayer()) ADD_MESSAGE("You feel a mysterious strengthening fire coursing through your body.");
5980 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s is surrounded by a swirling fire aura.", CHAR_NAME(DEFINITE
));
5984 truth
character::PreProcessForBone () {
5985 if (IsPet() && IsEnabled()) {
5986 Die(0, CONST_S(""), FORBID_REINCARNATION
);
5989 if (GetAction()) GetAction()->Terminate(false);
5990 if (TemporaryStateIsActivated(POLYMORPHED
)) {
5991 character
*PolymorphBackup
= GetPolymorphBackup();
5993 PolymorphBackup
->PreProcessForBone();
5996 if (MustBeRemovedFromBone()) return false;
5997 if (IsUnique() && !CanBeGenerated()) game::SignalQuestMonsterFound();
6001 GetStack()->PreProcessForBone();
6002 doforequipments()(this, &item::PreProcessForBone
);
6003 doforbodyparts()(this, &bodypart::PreProcessForBone
);
6004 game::RemoveCharacterID(ID
);
6006 game::AddCharacterID(this, ID
);
6011 truth
character::PostProcessForBone (double &DangerSum
, int& Enemies
) {
6012 if (PostProcessForBone()) {
6013 if (GetRelation(PLAYER
) == HOSTILE
) {
6014 double Danger
= GetRelativeDanger(PLAYER
, true);
6015 if (Danger
> 99.0) game::SetTooGreatDangerFound(true);
6016 else if (!IsUnique() && !IgnoreDanger()) {
6017 DangerSum
+= Danger
;
6027 truth
character::PostProcessForBone () {
6028 feuLong NewID
= game::CreateNewCharacterID(this);
6029 game::GetBoneCharacterIDMap().insert(std::make_pair(-ID
, NewID
));
6030 game::RemoveCharacterID(ID
);
6032 if (IsUnique() && CanBeGenerated()) {
6033 if (DataBase
->Flags
& HAS_BEEN_GENERATED
) return false;
6036 GetStack()->PostProcessForBone();
6037 doforequipments()(this, &item::PostProcessForBone
);
6038 doforbodyparts()(this, &bodypart::PostProcessForBone
);
6043 void character::FinalProcessForBone () {
6045 GetStack()->FinalProcessForBone();
6046 doforequipments()(this, &item::FinalProcessForBone
);
6048 for (c
= 0; c
< BodyParts
; ++c
) {
6049 for (std::list
<feuLong
>::iterator i
= OriginalBodyPartID
[c
].begin(); i
!= OriginalBodyPartID
[c
].end();) {
6050 boneidmap::iterator BI
= game::GetBoneItemIDMap().find(*i
);
6051 if (BI
== game::GetBoneItemIDMap().end()) {
6052 std::list
<feuLong
>::iterator Dirt
= i
++;
6053 OriginalBodyPartID
[c
].erase(Dirt
);
6063 void character::SetSoulID (feuLong What
) {
6064 if (GetPolymorphBackup()) GetPolymorphBackup()->SetSoulID(What
);
6068 truth
character::SearchForItem (citem
*Item
) const {
6069 if (combineequipmentpredicateswithparam
<feuLong
>()(this, &item::HasID
, Item
->GetID(), 1)) return true;
6070 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (*i
== Item
) return true;
6075 item
*character::SearchForItem (const sweaponskill
*SWeaponSkill
) const {
6076 for (int c
= 0; c
< GetEquipments(); ++c
) {
6077 item
*Equipment
= GetEquipment(c
);
6078 if (Equipment
&& SWeaponSkill
->IsSkillOf(Equipment
)) return Equipment
;
6080 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (SWeaponSkill
->IsSkillOf(*i
)) return *i
;
6085 void character::PutNear (v2 Pos
) {
6086 v2 NewPos
= game::GetCurrentLevel()->GetNearestFreeSquare(this, Pos
, false);
6087 if (NewPos
== ERROR_V2
) {
6088 do { NewPos
= game::GetCurrentLevel()->GetRandomSquare(this); } while(NewPos
== Pos
);
6094 void character::PutToOrNear (v2 Pos
) {
6095 if (game::IsInWilderness() || (CanMoveOn(game::GetCurrentLevel()->GetLSquare(Pos
)) && IsFreeForMe(game::GetCurrentLevel()->GetLSquare(Pos
))))
6102 void character::PutTo (v2 Pos
) {
6103 SquareUnder
[0] = game::GetCurrentArea()->GetSquare(Pos
);
6104 SquareUnder
[0]->AddCharacter(this);
6108 void character::Remove () {
6109 SquareUnder
[0]->RemoveCharacter();
6114 void character::SendNewDrawRequest () const {
6115 for (int c
= 0; c
< SquaresUnder
; ++c
) {
6116 square
*Square
= GetSquareUnder(c
);
6117 if (Square
) Square
->SendNewDrawRequest();
6122 truth
character::IsOver (v2 Pos
) const {
6123 for (int c
= 0; c
< SquaresUnder
; ++c
) {
6124 square
*Square
= GetSquareUnder(c
);
6125 if (Square
&& Square
->GetPos() == Pos
) return true;
6131 truth
character::CanTheoreticallyMoveOn (const lsquare
*LSquare
) const { return GetMoveType() & LSquare
->GetTheoreticalWalkability(); }
6132 truth
character::CanMoveOn (const lsquare
*LSquare
) const { return GetMoveType() & LSquare
->GetWalkability(); }
6133 truth
character::CanMoveOn (const square
*Square
) const { return GetMoveType() & Square
->GetSquareWalkability(); }
6134 truth
character::CanMoveOn (const olterrain
*OLTerrain
) const { return GetMoveType() & OLTerrain
->GetWalkability(); }
6135 truth
character::CanMoveOn (const oterrain
*OTerrain
) const { return GetMoveType() & OTerrain
->GetWalkability(); }
6136 truth
character::IsFreeForMe(square
*Square
) const { return !Square
->GetCharacter() || Square
->GetCharacter() == this; }
6137 void character::LoadSquaresUnder () { SquareUnder
[0] = game::GetSquareInLoad(); }
6139 truth
character::AttackAdjacentEnemyAI () {
6140 if (!IsEnabled()) return false;
6141 character
*Char
[MAX_NEIGHBOUR_SQUARES
];
6142 v2 Pos
[MAX_NEIGHBOUR_SQUARES
];
6143 int Dir
[MAX_NEIGHBOUR_SQUARES
];
6145 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
6146 square
*Square
= GetNeighbourSquare(d
);
6148 character
*Enemy
= Square
->GetCharacter();
6149 if (Enemy
&& (GetRelation(Enemy
) == HOSTILE
|| StateIsActivated(CONFUSED
))) {
6151 Pos
[Index
] = Square
->GetPos();
6152 Char
[Index
++] = Enemy
;
6157 int ChosenIndex
= RAND() % Index
;
6158 Hit(Char
[ChosenIndex
], Pos
[ChosenIndex
], Dir
[ChosenIndex
]);
6165 void character::SignalStepFrom (lsquare
**OldSquareUnder
) {
6167 lsquare
*NewSquareUnder
[MAX_SQUARES_UNDER
];
6168 for (c
= 0; c
< GetSquaresUnder(); ++c
) NewSquareUnder
[c
] = GetLSquareUnder(c
);
6169 for (c
= 0; c
< GetSquaresUnder(); ++c
) {
6170 if (IsEnabled() && GetLSquareUnder(c
) == NewSquareUnder
[c
]) NewSquareUnder
[c
]->StepOn(this, OldSquareUnder
);
6175 int character::GetSumOfAttributes () const {
6176 return GetAttribute(ENDURANCE
) + GetAttribute(PERCEPTION
) + GetAttribute(INTELLIGENCE
) + GetAttribute(WISDOM
) + GetAttribute(CHARISMA
) + GetAttribute(ARM_STRENGTH
) + GetAttribute(AGILITY
);
6180 void character::IntelligenceAction (int Difficulty
) {
6181 EditAP(-20000 * Difficulty
/ APBonus(GetAttribute(INTELLIGENCE
)));
6182 EditExperience(INTELLIGENCE
, Difficulty
* 15, 1 << 7);
6186 struct walkabilitycontroller
{
6187 static truth
Handler (int x
, int y
) {
6188 return x
>= 0 && y
>= 0 && x
< LevelXSize
&& y
< LevelYSize
&& Map
[x
][y
]->GetTheoreticalWalkability() & MoveType
;
6190 static lsquare
***Map
;
6191 static int LevelXSize
, LevelYSize
;
6192 static int MoveType
;
6196 lsquare
***walkabilitycontroller::Map
;
6197 int walkabilitycontroller::LevelXSize
, walkabilitycontroller::LevelYSize
;
6198 int walkabilitycontroller::MoveType
;
6201 truth
character::CreateRoute () {
6203 if (GetAttribute(INTELLIGENCE
) >= 10 && !StateIsActivated(CONFUSED
)) {
6205 walkabilitycontroller::Map
= GetLevel()->GetMap();
6206 walkabilitycontroller::LevelXSize
= GetLevel()->GetXSize();
6207 walkabilitycontroller::LevelYSize
= GetLevel()->GetYSize();
6208 walkabilitycontroller::MoveType
= GetMoveType();
6210 for (int c
= 0; c
< game::GetTeams(); ++c
)
6211 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
6212 character
*Char
= *i
;
6213 if (Char
->IsEnabled() && !Char
->Route
.empty() && (Char
->GetMoveType()&GetMoveType()) == Char
->GetMoveType()) {
6214 v2 CharGoingTo
= Char
->Route
[0];
6215 v2 iPos
= Char
->Route
.back();
6216 if ((GoingTo
-CharGoingTo
).GetLengthSquare() <= 100 && (Pos
- iPos
).GetLengthSquare() <= 100 &&
6217 mapmath
<walkabilitycontroller
>::DoLine(CharGoingTo
.X
, CharGoingTo
.Y
, GoingTo
.X
, GoingTo
.Y
, SKIP_FIRST
) &&
6218 mapmath
<walkabilitycontroller
>::DoLine(Pos
.X
, Pos
.Y
, iPos
.X
, iPos
.Y
, SKIP_FIRST
)) {
6219 if (!Illegal
.empty() && Illegal
.find(Char
->Route
.back()) != Illegal
.end()) continue;
6220 Node
= GetLevel()->FindRoute(CharGoingTo
, GoingTo
, Illegal
, GetMoveType());
6221 if (Node
) { while(Node
->Last
) { Route
.push_back(Node
->Pos
); Node
= Node
->Last
; } }
6222 else { Route
.clear(); continue; }
6223 Route
.insert(Route
.end(), Char
->Route
.begin(), Char
->Route
.end());
6224 Node
= GetLevel()->FindRoute(Pos
, iPos
, Illegal
, GetMoveType());
6225 if (Node
) { while (Node
->Last
) { Route
.push_back(Node
->Pos
); Node
= Node
->Last
; } }
6226 else { Route
.clear(); continue; }
6227 IntelligenceAction(1);
6232 Node
= GetLevel()->FindRoute(Pos
, GoingTo
, Illegal
, GetMoveType());
6233 if (Node
) { while(Node
->Last
) { Route
.push_back(Node
->Pos
); Node
= Node
->Last
; } }
6234 else TerminateGoingTo();
6235 IntelligenceAction(5);
6242 void character::SetGoingTo (v2 What
) {
6243 if (GoingTo
!= What
) {
6251 void character::TerminateGoingTo () {
6258 truth
character::CheckForFood (int Radius
) {
6259 if (StateIsActivated(PANIC
) || !UsesNutrition() || !IsEnabled()) return false;
6262 for (int r
= 1; r
<= Radius
; ++r
) {
6265 for (y
= Pos
.Y
-r
; y
<= Pos
.Y
+r
; ++y
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
6268 if (x
< GetLevel()->GetXSize()) {
6269 for (y
= Pos
.Y
-r
; y
<= Pos
.Y
+r
; ++y
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
6273 for (x
= Pos
.X
-r
; x
<= Pos
.X
+r
; ++x
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
6276 if (y
< GetLevel()->GetYSize()) {
6277 for (x
= Pos
.X
-r
; x
<= Pos
.X
+r
; ++x
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
6284 truth
character::CheckForFoodInSquare (v2 Pos
) {
6285 level
*Level
= GetLevel();
6286 if (Level
->IsValidPos(Pos
)) {
6287 lsquare
*Square
= Level
->GetLSquare(Pos
);
6288 stack
*Stack
= Square
->GetStack();
6289 if (Stack
->GetItems()) {
6290 for (stackiterator i
= Stack
->GetBottom(); i
.HasItem(); ++i
) {
6291 if (i
->IsPickable(this) && i
->CanBeSeenBy(this) && i
->CanBeEatenByAI(this) && (!Square
->GetRoomIndex() || Square
->GetRoom()->AllowFoodSearch())) {
6293 return MoveTowardsTarget(false);
6302 void character::SetConfig (int NewConfig
, int SpecialFlags
) {
6303 databasecreator
<character
>::InstallDataBase(this, NewConfig
);
6306 if (!(SpecialFlags
& NO_PIC_UPDATE
)) UpdatePictures();
6310 truth
character::IsOver (citem
*Item
) const {
6311 for (int c1
= 0; c1
< Item
->GetSquaresUnder(); ++c1
)
6312 for (int c2
= 0; c2
< SquaresUnder
; ++c2
)
6313 if (Item
->GetPos(c1
) == GetPos(c2
)) return true;
6318 truth
character::CheckConsume (cfestring
&Verb
) const {
6319 if (!UsesNutrition()) {
6320 if (IsPlayer()) ADD_MESSAGE("In this form you can't and don't need to %s.", Verb
.CStr());
6327 void character::PutTo (lsquare
*To
) {
6328 PutTo(To
->GetPos());
6332 double character::RandomizeBabyExperience (double SumE
) {
6333 if (!SumE
) return 0;
6334 double E
= (SumE
/ 4) - (SumE
/ 32) + (double(RAND()) / MAX_RAND
) * (SumE
/ 16 + 1);
6335 return Limit(E
, MIN_EXP
, MAX_EXP
);
6339 liquid
*character::CreateBlood (sLong Volume
) const {
6340 return liquid::Spawn(GetBloodMaterial(), Volume
);
6344 void character::SpillFluid (character
*Spiller
, liquid
*Liquid
, int SquareIndex
) {
6345 sLong ReserveVolume
= Liquid
->GetVolume() >> 1;
6346 Liquid
->EditVolume(-ReserveVolume
);
6347 GetStack()->SpillFluid(Spiller
, Liquid
, sLong(Liquid
->GetVolume() * sqrt(double(GetStack()->GetVolume()) / GetVolume())));
6348 Liquid
->EditVolume(ReserveVolume
);
6350 sLong Modifier
[MAX_BODYPARTS
], ModifierSum
= 0;
6351 for (c
= 0; c
< BodyParts
; ++c
) {
6352 if (GetBodyPart(c
)) {
6353 Modifier
[c
] = sLong(sqrt(GetBodyPart(c
)->GetVolume()));
6354 if (Modifier
[c
]) Modifier
[c
] *= 1 + (RAND() & 3);
6355 ModifierSum
+= Modifier
[c
];
6360 for (c
= 1; c
< GetBodyParts(); ++c
) {
6361 if (GetBodyPart(c
) && IsEnabled())
6362 GetBodyPart(c
)->SpillFluid(Spiller
, Liquid
->SpawnMoreLiquid(Liquid
->GetVolume() * Modifier
[c
] / ModifierSum
), SquareIndex
);
6365 Liquid
->SetVolume(Liquid
->GetVolume() * Modifier
[TORSO_INDEX
] / ModifierSum
);
6366 GetTorso()->SpillFluid(Spiller
, Liquid
, SquareIndex
);
6371 void character::StayOn (liquid
*Liquid
) {
6372 Liquid
->TouchEffect(this, TORSO_INDEX
);
6376 truth
character::IsAlly (ccharacter
*Char
) const {
6377 return Char
->GetTeam()->GetID() == GetTeam()->GetID();
6381 void character::ResetSpoiling () {
6382 doforbodyparts()(this, &bodypart::ResetSpoiling
);
6386 item
*character::SearchForItem (ccharacter
*Char
, sorter Sorter
) const {
6387 item
*Equipment
= findequipment
<ccharacter
*>()(this, Sorter
, Char
);
6388 if (Equipment
) return Equipment
;
6389 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (((*i
)->*Sorter
)(Char
)) return *i
;
6394 truth
character::DetectMaterial (cmaterial
*Material
) const {
6395 return GetStack()->DetectMaterial(Material
) ||
6396 combinebodypartpredicateswithparam
<cmaterial
*>()(this, &bodypart::DetectMaterial
, Material
, 1) ||
6397 combineequipmentpredicateswithparam
<cmaterial
*>()(this, &item::DetectMaterial
, Material
, 1);
6401 truth
character::DamageTypeDestroysBodyPart (int Type
) {
6402 return (Type
&0xFFF) != PHYSICAL_DAMAGE
;
6406 truth
character::CheckIfTooScaredToHit (ccharacter
*Enemy
) const {
6407 if (IsPlayer() && StateIsActivated(PANIC
)) {
6408 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
6409 square
*Square
= GetNeighbourSquare(d
);
6411 if(CanMoveOn(Square
) && (!Square
->GetCharacter() || Square
->GetCharacter()->IsPet())) {
6412 ADD_MESSAGE("You are too scared to attack %s.", Enemy
->CHAR_DESCRIPTION(DEFINITE
));
6422 void character::PrintBeginLevitationMessage () const {
6424 if (IsPlayer()) ADD_MESSAGE("You rise into the air like a small hot-air balloon.");
6425 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s begins to float.", CHAR_NAME(DEFINITE
));
6430 void character::PrintEndLevitationMessage () const {
6432 if (IsPlayer()) ADD_MESSAGE("You descend gently onto the ground.");
6433 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s drops onto the ground.", CHAR_NAME(DEFINITE
));
6438 truth
character::IsLimbIndex (int I
) {
6440 case RIGHT_ARM_INDEX
:
6441 case LEFT_ARM_INDEX
:
6442 case RIGHT_LEG_INDEX
:
6443 case LEFT_LEG_INDEX
:
6450 void character::EditExperience (int Identifier
, double Value
, double Speed
) {
6451 if (!AllowExperience() || (Identifier
== ENDURANCE
&& UseMaterialAttributes())) return;
6452 int Change
= RawEditExperience(BaseExperience
[Identifier
], GetNaturalExperience(Identifier
), Value
, Speed
);
6453 if (!Change
) return;
6454 cchar
*PlayerMsg
= 0, *NPCMsg
= 0;
6455 switch (Identifier
) {
6458 PlayerMsg
= "You feel tougher than anything!";
6459 if (IsPet()) NPCMsg
= "Suddenly %s looks tougher.";
6461 PlayerMsg
= "You feel less healthy.";
6462 if (IsPet()) NPCMsg
= "Suddenly %s looks less healthy.";
6464 CalculateBodyPartMaxHPs();
6465 CalculateMaxStamina();
6470 PlayerMsg
= "You now see the world in much better detail than before.";
6472 PlayerMsg
= "You feel very guru.";
6473 game::GetGod(VALPURUS
)->AdjustRelation(100);
6475 game::SendLOSUpdateRequest();
6480 if (Change
> 0) PlayerMsg
= "Suddenly the inner structure of the Multiverse around you looks quite simple.";
6481 else PlayerMsg
= "It surely is hard to think today.";
6484 if (IsPlayerKind()) UpdatePictures();
6488 if (Change
> 0) PlayerMsg
= "You feel your life experience increasing all the time.";
6489 else PlayerMsg
= "You feel like having done something unwise.";
6491 if (IsPlayerKind()) UpdatePictures();
6495 PlayerMsg
= "You feel very confident of your social skills.";
6497 if (GetAttribute(CHARISMA
) <= 15) NPCMsg
= "%s looks less ugly.";
6498 else NPCMsg
= "%s looks more attractive.";
6501 PlayerMsg
= "You feel somehow disliked.";
6503 if (GetAttribute(CHARISMA
) < 15) NPCMsg
= "%s looks more ugly.";
6504 else NPCMsg
= "%s looks less attractive.";
6507 if (IsPlayerKind()) UpdatePictures();
6511 PlayerMsg
= "You feel magical forces coursing through your body!";
6512 NPCMsg
= "You notice an odd glow around %s.";
6514 PlayerMsg
= "You feel your magical abilities withering slowly.";
6515 NPCMsg
= "You notice strange vibrations in the air around %s. But they disappear rapidly.";
6520 if (IsPlayer()) ADD_MESSAGE("%s", PlayerMsg
);
6521 else if (NPCMsg
&& CanBeSeenByPlayer()) ADD_MESSAGE(NPCMsg
, CHAR_NAME(DEFINITE
));
6523 CalculateBattleInfo();
6527 int character::RawEditExperience (double &Exp
, double NaturalExp
, double Value
, double Speed
) const {
6528 double OldExp
= Exp
;
6533 if(!OldExp
|| !Value
|| (Value
> 0 && OldExp
>= NaturalExp
* (100 + Value
) / 100) ||
6534 (Value
< 0 && OldExp
<= NaturalExp
* (100 + Value
) / 100)) return 0;
6535 if (!IsPlayer()) Speed
*= 1.5;
6536 Exp
+= (NaturalExp
* (100 + Value
) - 100 * OldExp
) * Speed
* EXP_DIVISOR
;
6537 LimitRef(Exp
, MIN_EXP
, MAX_EXP
);
6538 int NewA
= int(Exp
* EXP_DIVISOR
);
6539 int OldA
= int(OldExp
* EXP_DIVISOR
);
6540 int Delta
= NewA
- OldA
;
6541 if (Delta
> 0) Exp
= Max(Exp
, (NewA
+ 0.05) * EXP_MULTIPLIER
);
6542 else if (Delta
< 0) Exp
= Min(Exp
, (NewA
+ 0.95) * EXP_MULTIPLIER
);
6543 LimitRef(Exp
, MIN_EXP
, MAX_EXP
);
6548 int character::GetAttribute (int Identifier
, truth AllowBonus
) const {
6549 int A
= int(BaseExperience
[Identifier
] * EXP_DIVISOR
);
6550 if (AllowBonus
&& Identifier
== INTELLIGENCE
&& BrainsHurt()) return Max((A
+ AttributeBonus
[INTELLIGENCE
]) / 3, 1);
6551 return A
&& AllowBonus
? Max(A
+ AttributeBonus
[Identifier
], 1) : A
;
6555 void characterdatabase::PostProcess () {
6556 double AM
= (100 + AttributeBonus
) * EXP_MULTIPLIER
/ 100;
6557 for (int c
= 0; c
< ATTRIBUTES
; ++c
) NaturalExperience
[c
] = this->*ExpPtr
[c
] * AM
;
6561 void character::EditDealExperience (sLong Price
) {
6562 EditExperience(CHARISMA
, sqrt(Price
) / 5, 1 << 9);
6566 void character::PrintBeginLeprosyMessage () const {
6567 if (IsPlayer()) ADD_MESSAGE("You feel you're falling in pieces.");
6571 void character::PrintEndLeprosyMessage () const {
6572 if (IsPlayer()) ADD_MESSAGE("You feel your limbs are stuck in place tightly."); // CHANGE OR DIE
6576 void character::TryToInfectWithLeprosy (ccharacter
*Infector
) {
6577 if (!IsImmuneToLeprosy() &&
6578 ((GetRelation(Infector
) == HOSTILE
&& !RAND_N(50 * GetAttribute(ENDURANCE
))) ||
6579 !RAND_N(500 * GetAttribute(ENDURANCE
)))) GainIntrinsic(LEPROSY
);
6583 void character::SignalGeneration () {
6584 const_cast<database
*>(DataBase
)->Flags
|= HAS_BEEN_GENERATED
;
6588 void character::CheckIfSeen () {
6589 if (IsPlayer() || CanBeSeenByPlayer()) SignalSeen();
6593 void character::SignalSeen () {
6594 if (!(WarnFlags
& WARNED
) && GetRelation(PLAYER
) == HOSTILE
) {
6595 double Danger
= GetRelativeDanger(PLAYER
);
6597 game::SetDangerFound(Max(game::GetDangerFound(), Danger
));
6598 if (Danger
> 500.0 && !(WarnFlags
& HAS_CAUSED_PANIC
)) {
6599 WarnFlags
|= HAS_CAUSED_PANIC
;
6600 game::SetCausePanicFlag(true);
6602 WarnFlags
|= WARNED
;
6605 const_cast<database
*>(DataBase
)->Flags
|= HAS_BEEN_SEEN
;
6609 int character::GetPolymorphIntelligenceRequirement () const {
6610 if (DataBase
->PolymorphIntelligenceRequirement
== DEPENDS_ON_ATTRIBUTES
) return Max(GetAttributeAverage() - 5, 0);
6611 return DataBase
->PolymorphIntelligenceRequirement
;
6615 void character::RemoveAllItems () {
6616 GetStack()->Clean();
6617 for (int c
= 0; c
< GetEquipments(); ++c
) {
6618 item
*Equipment
= GetEquipment(c
);
6620 Equipment
->RemoveFromSlot();
6621 Equipment
->SendToHell();
6627 int character::CalculateWeaponSkillHits (ccharacter
*Enemy
) const {
6628 if (Enemy
->IsPlayer()) {
6629 configid
ConfigID(GetType(), GetConfig());
6630 const dangerid
& DangerID
= game::GetDangerMap().find(ConfigID
)->second
;
6631 return Min(int(DangerID
.EquippedDanger
* 2000), 1000);
6633 return Min(int(GetRelativeDanger(Enemy
, true) * 2000), 1000);
6637 truth
character::CanUseEquipment (int I
) const {
6638 return CanUseEquipment() && I
< GetEquipments() && GetBodyPartOfEquipment(I
) && EquipmentIsAllowed(I
);
6642 /* Target mustn't have any equipment */
6643 void character::DonateEquipmentTo (character
*Character
) {
6645 feuLong
*EquipmentMemory
= game::GetEquipmentMemory();
6646 for (int c
= 0; c
< MAX_EQUIPMENT_SLOTS
; ++c
) {
6647 item
*Item
= GetEquipment(c
);
6649 if (Character
->CanUseEquipment(c
)) {
6650 Item
->RemoveFromSlot();
6651 Character
->SetEquipment(c
, Item
);
6653 EquipmentMemory
[c
] = Item
->GetID();
6654 Item
->MoveTo(Character
->GetStack());
6656 } else if (CanUseEquipment(c
)) {
6657 EquipmentMemory
[c
] = 0;
6658 } else if (EquipmentMemory
[c
] && Character
->CanUseEquipment(c
)) {
6659 for (stackiterator i
= Character
->GetStack()->GetBottom(); i
.HasItem(); ++i
) {
6660 if (i
->GetID() == EquipmentMemory
[c
]) {
6662 Item
->RemoveFromSlot();
6663 Character
->SetEquipment(c
, Item
);
6667 EquipmentMemory
[c
] = 0;
6671 for (int c
= 0; c
< GetEquipments(); ++c
) {
6672 item
*Item
= GetEquipment(c
);
6674 if (Character
->CanUseEquipment(c
)) {
6675 Item
->RemoveFromSlot();
6676 Character
->SetEquipment(c
, Item
);
6678 Item
->MoveTo(Character
->GetStackUnder());
6686 void character::ReceivePeaSoup (sLong
) {
6687 if (!game::IsInWilderness()) {
6688 lsquare
*Square
= GetLSquareUnder();
6689 if (Square
->IsFlyable()) Square
->AddSmoke(gas::Spawn(FART
, 250));
6694 void character::AddPeaSoupConsumeEndMessage () const {
6696 if (CanHear()) ADD_MESSAGE("Mmmh! The soup is very tasty. You hear a small puff.");
6697 else ADD_MESSAGE("Mmmh! The soup is very tasty.");
6698 } else if (CanBeSeenByPlayer() && PLAYER
->CanHear()) {
6700 ADD_MESSAGE("You hear a small puff.");
6705 void character::CalculateMaxStamina () {
6706 MaxStamina
= TorsoIsAlive() ? GetAttribute(ENDURANCE
) * 10000 : 0;
6710 void character::EditStamina (int Amount
, truth CanCauseUnconsciousness
) {
6711 if (!TorsoIsAlive()) return;
6712 int UnconsciousnessStamina
= MaxStamina
>> 3;
6713 if (!CanCauseUnconsciousness
&& Amount
< 0) {
6714 if (Stamina
> UnconsciousnessStamina
) {
6716 if (Stamina
< UnconsciousnessStamina
) Stamina
= UnconsciousnessStamina
;
6720 int OldStamina
= Stamina
;
6722 if (Stamina
> MaxStamina
) {
6723 Stamina
= MaxStamina
;
6724 } else if (Stamina
< 0) {
6726 LoseConsciousness(250 + RAND_N(250));
6727 } else if (IsPlayer()) {
6728 if (OldStamina
>= MaxStamina
>> 2 && Stamina
< MaxStamina
>> 2) {
6729 ADD_MESSAGE("You are getting a little tired.");
6730 } else if(OldStamina
>= UnconsciousnessStamina
&& Stamina
< UnconsciousnessStamina
) {
6731 ADD_MESSAGE("You are seriously out of breath!");
6732 game::SetPlayerIsRunning(false);
6735 if (IsPlayer() && StateIsActivated(PANIC
) && GetTirednessState() != FAINTING
) game::SetPlayerIsRunning(true);
6739 void character::RegenerateStamina () {
6740 if (GetTirednessState() != UNTIRED
) {
6741 EditExperience(ENDURANCE
, 50, 1);
6742 if (Sweats() && TorsoIsAlive() && !RAND_N(30) && !game::IsInWilderness()) {
6743 // Sweat amount proportional to endurance also
6744 //sLong Volume = sLong(0.05 * sqrt(GetBodyVolume()));
6745 sLong Volume
= long(0.05*sqrt(GetBodyVolume()*GetAttribute(ENDURANCE
)/10));
6746 if (GetTirednessState() == FAINTING
) Volume
<<= 1;
6747 for (int c
= 0; c
< SquaresUnder
; ++c
) GetLSquareUnder(c
)->SpillFluid(0, CreateSweat(Volume
), false, false);
6752 if (Action
->IsRest()) {
6753 if (SquaresUnder
== 1) {
6754 Bonus
= GetSquareUnder()->GetRestModifier() << 1;
6756 int Lowest
= GetSquareUnder(0)->GetRestModifier();
6757 for (int c
= 1; c
< GetSquaresUnder(); ++c
) {
6758 int Mod
= GetSquareUnder(c
)->GetRestModifier();
6759 if (Mod
< Lowest
) Lowest
= Mod
;
6761 Bonus
= Lowest
<< 1;
6763 } else if (Action
->IsUnconsciousness()) Bonus
= 2;
6766 switch (GetBurdenState()) {
6767 case OVER_LOADED
: Plus1
= 25; break;
6768 case STRESSED
: Plus1
= 50; break;
6769 case BURDENED
: Plus1
= 75; break;
6773 switch (GetHungerState()) {
6774 case STARVING
: Plus2
= 25; break;
6775 case VERY_HUNGRY
: Plus2
= 50; break;
6776 case HUNGRY
: Plus2
= 75; break;
6779 Stamina
+= Plus1
* Plus2
* Bonus
/ 1000;
6780 if (Stamina
> MaxStamina
) Stamina
= MaxStamina
;
6781 if (IsPlayer() && StateIsActivated(PANIC
) && GetTirednessState() != FAINTING
) game::SetPlayerIsRunning(true);
6785 void character::BeginPanic () {
6786 if (IsPlayer() && GetTirednessState() != FAINTING
) game::SetPlayerIsRunning(true);
6787 DeActivateVoluntaryAction();
6791 void character::EndPanic () {
6792 if (IsPlayer()) game::SetPlayerIsRunning(false);
6796 int character::GetTirednessState () const {
6797 if (Stamina
>= MaxStamina
>> 2) return UNTIRED
;
6798 if (Stamina
>= MaxStamina
>> 3) return EXHAUSTED
;
6803 void character::ReceiveBlackUnicorn (sLong Amount
) {
6804 if (!(RAND() % 160)) game::DoEvilDeed(Amount
/ 50);
6805 BeginTemporaryState(TELEPORT
, Amount
/ 100);
6806 for (int c
= 0; c
< STATES
; ++c
) {
6807 if (StateData
[c
].Flags
& DUR_TEMPORARY
) {
6808 BeginTemporaryState(1 << c
, Amount
/ 100);
6809 if (!IsEnabled()) return;
6810 } else if (StateData
[c
].Flags
& DUR_PERMANENT
) {
6811 GainIntrinsic(1 << c
);
6812 if (!IsEnabled()) return;
6818 void character::ReceiveGrayUnicorn (sLong Amount
) {
6819 if (!(RAND() % 80)) game::DoEvilDeed(Amount
/ 50);
6820 BeginTemporaryState(TELEPORT
, Amount
/ 100);
6821 for (int c
= 0; c
< STATES
; ++c
) {
6822 if (1 << c
!= TELEPORT
) {
6823 DecreaseStateCounter(1 << c
, -Amount
/ 100);
6824 if (!IsEnabled()) return;
6830 void character::ReceiveWhiteUnicorn (sLong Amount
) {
6831 if (!(RAND() % 40)) game::DoEvilDeed(Amount
/ 50);
6832 BeginTemporaryState(TELEPORT
, Amount
/ 100);
6833 DecreaseStateCounter(LYCANTHROPY
, -Amount
/ 100);
6834 DecreaseStateCounter(POISONED
, -Amount
/ 100);
6835 DecreaseStateCounter(PARASITIZED
, -Amount
/ 100);
6836 DecreaseStateCounter(LEPROSY
, -Amount
/ 100);
6837 DecreaseStateCounter(VAMPIRISM
, -Amount
/ 100);
6841 /* Counter should be negative. Removes intrinsics. */
6842 void character::DecreaseStateCounter (sLong State
, int Counter
) {
6844 for (Index
= 0; Index
< STATES
; ++Index
) if (1 << Index
== State
) break;
6845 if (Index
== STATES
) ABORT("DecreaseTemporaryStateCounter works only when State == 2^n!");
6846 if (TemporaryState
& State
) {
6847 if (TemporaryStateCounter
[Index
] == PERMANENT
|| (TemporaryStateCounter
[Index
] += Counter
) <= 0) {
6848 TemporaryState
&= ~State
;
6849 if (!(EquipmentState
& State
)) {
6850 if (StateData
[Index
].EndHandler
) {
6851 (this->*StateData
[Index
].EndHandler
)();
6852 if (!IsEnabled()) return;
6854 (this->*StateData
[Index
].PrintEndMessage
)();
6861 truth
character::IsImmuneToLeprosy () const {
6862 return DataBase
->IsImmuneToLeprosy
|| UseMaterialAttributes();
6866 void character::LeprosyHandler () {
6867 EditExperience(ARM_STRENGTH
, -25, 1 << 1);
6868 EditExperience(LEG_STRENGTH
, -25, 1 << 1);
6869 EditExperience(DEXTERITY
, -25, 1 << 1);
6870 EditExperience(AGILITY
, -25, 1 << 1);
6871 EditExperience(ENDURANCE
, -25, 1 << 1);
6872 EditExperience(CHARISMA
, -25, 1 << 1);
6873 CheckDeath(CONST_S("killed by leprosy"));
6877 bodypart
*character::SearchForOriginalBodyPart (int I
) const {
6878 for (stackiterator i1
= GetStackUnder()->GetBottom(); i1
.HasItem(); ++i1
) {
6879 for (std::list
<feuLong
>::iterator i2
= OriginalBodyPartID
[I
].begin(); i2
!= OriginalBodyPartID
[I
].end(); ++i2
)
6880 if (i1
->GetID() == *i2
) return static_cast<bodypart
*>(*i1
);
6886 void character::SetLifeExpectancy (int Base
, int RandPlus
) {
6888 for (c
= 0; c
< BodyParts
; ++c
) {
6889 bodypart
*BodyPart
= GetBodyPart(c
);
6890 if (BodyPart
) BodyPart
->SetLifeExpectancy(Base
, RandPlus
);
6892 for (c
= 0; c
< GetEquipments(); ++c
) {
6893 item
*Equipment
= GetEquipment(c
);
6894 if (Equipment
) Equipment
->SetLifeExpectancy(Base
, RandPlus
);
6899 /* Receiver should be a fresh duplicate of this */
6900 void character::DuplicateEquipment (character
*Receiver
, feuLong Flags
) {
6901 for (int c
= 0; c
< GetEquipments(); ++c
) {
6902 item
*Equipment
= GetEquipment(c
);
6904 item
*Duplicate
= Equipment
->Duplicate(Flags
);
6905 Receiver
->SetEquipment(c
, Duplicate
);
6911 void character::Disappear (corpse
*Corpse
, cchar
*Verb
, truth (item::*ClosePredicate
)() const) {
6912 truth TorsoDisappeared
= false;
6913 truth CanBeSeen
= Corpse
? Corpse
->CanBeSeenByPlayer() : IsPlayer() || CanBeSeenByPlayer();
6915 if ((GetTorso()->*ClosePredicate
)()) {
6917 if (Corpse
) ADD_MESSAGE("%s %ss.", Corpse
->CHAR_NAME(DEFINITE
), Verb
);
6918 else if (IsPlayer()) ADD_MESSAGE("You %s.", Verb
);
6919 else ADD_MESSAGE("%s %ss.", CHAR_NAME(DEFINITE
), Verb
);
6921 TorsoDisappeared
= true;
6922 for (c
= 0; c
< GetEquipments(); ++c
) {
6923 item
*Equipment
= GetEquipment(c
);
6924 if (Equipment
&& (Equipment
->*ClosePredicate
)()) {
6925 Equipment
->RemoveFromSlot();
6926 Equipment
->SendToHell();
6929 itemvector ItemVector
;
6930 GetStack()->FillItemVector(ItemVector
);
6931 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) {
6932 if (ItemVector
[c
] && (ItemVector
[c
]->*ClosePredicate
)()) {
6933 ItemVector
[c
]->RemoveFromSlot();
6934 ItemVector
[c
]->SendToHell();
6938 for (c
= 1; c
< GetBodyParts(); ++c
) {
6939 bodypart
*BodyPart
= GetBodyPart(c
);
6941 if ((BodyPart
->*ClosePredicate
)()) {
6942 if (!TorsoDisappeared
&& CanBeSeen
) {
6943 if(IsPlayer()) ADD_MESSAGE("Your %s %ss.", GetBodyPartName(c
).CStr(), Verb
);
6944 else ADD_MESSAGE("The %s of %s %ss.", GetBodyPartName(c
).CStr(), CHAR_NAME(DEFINITE
), Verb
);
6946 BodyPart
->DropEquipment();
6947 item
*BodyPart
= SevereBodyPart(c
);
6948 if (BodyPart
) BodyPart
->SendToHell();
6949 } else if (TorsoDisappeared
) {
6950 BodyPart
->DropEquipment();
6951 item
*BodyPart
= SevereBodyPart(c
);
6953 if (Corpse
) Corpse
->GetSlot()->AddFriendItem(BodyPart
);
6954 else if (!game::IsInWilderness()) GetStackUnder()->AddItem(BodyPart
);
6955 else BodyPart
->SendToHell();
6960 if (TorsoDisappeared
) {
6962 Corpse
->RemoveFromSlot();
6963 Corpse
->SendToHell();
6965 CheckDeath(festring(Verb
) + "ed", 0, FORCE_DEATH
|DISALLOW_CORPSE
|DISALLOW_MSG
);
6968 CheckDeath(festring(Verb
) + "ed", 0, DISALLOW_MSG
);
6973 void character::SignalDisappearance () {
6974 if (GetMotherEntity()) GetMotherEntity()->SignalDisappearance();
6975 else Disappear(0, "disappear", &item::IsVeryCloseToDisappearance
);
6979 truth
character::HornOfFearWorks () const {
6980 return CanHear() && GetPanicLevel() > RAND() % 33;
6984 void character::BeginLeprosy () {
6985 doforbodypartswithparam
<truth
>()(this, &bodypart::SetIsInfectedByLeprosy
, true);
6989 void character::EndLeprosy () {
6990 doforbodypartswithparam
<truth
>()(this, &bodypart::SetIsInfectedByLeprosy
, false);
6994 truth
character::IsSameAs (ccharacter
*What
) const {
6995 return What
->GetType() == GetType() && What
->GetConfig() == GetConfig();
6999 feuLong
character::GetCommandFlags () const {
7000 return !StateIsActivated(PANIC
) ? CommandFlags
: CommandFlags
|FLEE_FROM_ENEMIES
;
7004 feuLong
character::GetConstantCommandFlags () const {
7005 return !StateIsActivated(PANIC
) ? DataBase
->ConstantCommandFlags
: DataBase
->ConstantCommandFlags
|FLEE_FROM_ENEMIES
;
7009 feuLong
character::GetPossibleCommandFlags () const {
7010 int Int
= GetAttribute(INTELLIGENCE
);
7011 feuLong Flags
= ALL_COMMAND_FLAGS
;
7012 if (!CanMove() || Int
< 4) Flags
&= ~FOLLOW_LEADER
;
7013 if (!CanMove() || Int
< 6) Flags
&= ~FLEE_FROM_ENEMIES
;
7014 if (!CanUseEquipment() || Int
< 8) Flags
&= ~DONT_CHANGE_EQUIPMENT
;
7015 if (!UsesNutrition() || Int
< 8) Flags
&= ~DONT_CONSUME_ANYTHING_VALUABLE
;
7020 truth
character::IsRetreating () const {
7021 return StateIsActivated(PANIC
) || (CommandFlags
& FLEE_FROM_ENEMIES
&& IsPet());
7025 truth
character::ChatMenu () {
7026 if (GetAction() && !GetAction()->CanBeTalkedTo()) {
7027 ADD_MESSAGE("%s is silent.", CHAR_DESCRIPTION(DEFINITE
));
7028 PLAYER
->EditAP(-200);
7031 feuLong ManagementFlags
= GetManagementFlags();
7032 if (ManagementFlags
== CHAT_IDLY
|| !IsPet()) return ChatIdly();
7033 static cchar
*const ChatMenuEntry
[CHAT_MENU_ENTRIES
] = {
7040 static const petmanagementfunction PMF
[CHAT_MENU_ENTRIES
] = {
7041 &character::ChangePetEquipment
,
7042 &character::TakePetItems
,
7043 &character::GivePetItems
,
7044 &character::IssuePetCommands
,
7045 &character::ChatIdly
7047 felist
List(CONST_S("Choose action:"));
7048 game::SetStandardListAttributes(List
);
7049 List
.AddFlags(SELECTABLE
);
7051 for (c
= 0; c
< CHAT_MENU_ENTRIES
; ++c
) if (1 << c
& ManagementFlags
) List
.AddEntry(ChatMenuEntry
[c
], LIGHT_GRAY
);
7052 int Chosen
= List
.Draw();
7053 if (Chosen
& FELIST_ERROR_BIT
) return false;
7054 for (c
= 0, i
= 0; c
< CHAT_MENU_ENTRIES
; ++c
) {
7055 if (1 << c
& ManagementFlags
&& i
++ == Chosen
) return (this->*PMF
[c
])();
7057 return false; // dummy
7061 truth
character::ChangePetEquipment () {
7062 if (EquipmentScreen(PLAYER
->GetStack(), GetStack())) {
7070 truth
character::TakePetItems () {
7071 truth Success
= false;
7072 stack::SetSelected(0);
7075 game::DrawEverythingNoBlit();
7076 GetStack()->DrawContents(
7080 CONST_S("What do you want to take from ") + CHAR_DESCRIPTION(DEFINITE
) + '?',
7083 GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState(),
7084 GetVerbalBurdenStateColor(),
7086 if (ToTake
.empty()) break;
7087 for (uInt c
= 0; c
< ToTake
.size(); ++c
) ToTake
[c
]->MoveTo(PLAYER
->GetStack());
7088 ADD_MESSAGE("You take %s.", ToTake
[0]->GetName(DEFINITE
, ToTake
.size()).CStr());
7093 PLAYER
->DexterityAction(2);
7099 truth
character::GivePetItems () {
7100 truth Success
= false;
7101 stack::SetSelected(0);
7104 game::DrawEverythingNoBlit();
7105 PLAYER
->GetStack()->DrawContents(
7109 CONST_S("What do you want to give to ") + CHAR_DESCRIPTION(DEFINITE
) + '?',
7112 GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState(),
7113 GetVerbalBurdenStateColor(),
7115 if (ToGive
.empty()) break;
7116 for (uInt c
= 0; c
< ToGive
.size(); ++c
) ToGive
[c
]->MoveTo(GetStack());
7117 ADD_MESSAGE("You give %s to %s.", ToGive
[0]->GetName(DEFINITE
, ToGive
.size()).CStr(), CHAR_DESCRIPTION(DEFINITE
));
7122 PLAYER
->DexterityAction(2);
7128 truth
character::IssuePetCommands () {
7129 if (!IsConscious()) {
7130 ADD_MESSAGE("%s is unconscious.", CHAR_DESCRIPTION(DEFINITE
));
7133 feuLong PossibleC
= GetPossibleCommandFlags();
7135 ADD_MESSAGE("%s cannot be commanded.", CHAR_DESCRIPTION(DEFINITE
));
7138 feuLong OldC
= GetCommandFlags();
7139 feuLong NewC
= OldC
, VaryFlags
= 0;
7140 game::CommandScreen(CONST_S("Issue commands to ")+GetDescription(DEFINITE
), PossibleC
, GetConstantCommandFlags(), VaryFlags
, NewC
);
7141 if (NewC
== OldC
) return false;
7142 SetCommandFlags(NewC
);
7143 PLAYER
->EditAP(-500);
7144 PLAYER
->EditExperience(CHARISMA
, 25, 1 << 7);
7149 truth
character::ChatIdly () {
7150 if (!TryToTalkAboutScience()) {
7152 PLAYER
->EditExperience(CHARISMA
, 75, 1 << 7);
7154 PLAYER
->EditAP(-1000);
7159 truth
character::EquipmentScreen (stack
*MainStack
, stack
*SecStack
) {
7160 if (!CanUseEquipment()) {
7161 ADD_MESSAGE("%s cannot use equipment.", CHAR_DESCRIPTION(DEFINITE
));
7165 truth EquipmentChanged
= false;
7166 felist
List(CONST_S("Equipment menu [ESC exits]"));
7170 List
.EmptyDescription();
7172 List
.AddDescription(CONST_S(""));
7173 List
.AddDescription(festring(GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState()).CapitalizeCopy(), GetVerbalBurdenStateColor());
7175 for (int c
= 0; c
< GetEquipments(); ++c
) {
7176 Entry
= GetEquipmentName(c
);
7179 item
*Equipment
= GetEquipment(c
);
7181 Equipment
->AddInventoryEntry(this, Entry
, 1, true);
7182 AddSpecialEquipmentInfo(Entry
, c
);
7183 int ImageKey
= game::AddToItemDrawVector(itemvector(1, Equipment
));
7184 List
.AddEntry(Entry
, LIGHT_GRAY
, 20, ImageKey
, true);
7186 Entry
<< (GetBodyPartOfEquipment(c
) ? "-" : "can't use");
7187 List
.AddEntry(Entry
, LIGHT_GRAY
, 20, game::AddToItemDrawVector(itemvector()));
7190 game::DrawEverythingNoBlit();
7191 game::SetStandardListAttributes(List
);
7192 List
.SetFlags(SELECTABLE
|DRAW_BACKGROUND_AFTERWARDS
);
7193 List
.SetEntryDrawer(game::ItemEntryDrawer
);
7194 Chosen
= List
.Draw();
7195 game::ClearItemDrawVector();
7196 if (Chosen
>= GetEquipments()) break;
7197 EquipmentChanged
= TryToChangeEquipment(MainStack
, SecStack
, Chosen
);
7199 if (EquipmentChanged
) DexterityAction(5);
7200 return EquipmentChanged
;
7204 feuLong
character::GetManagementFlags () const {
7205 feuLong Flags
= ALL_MANAGEMENT_FLAGS
;
7206 if (!CanUseEquipment() || !AllowPlayerToChangeEquipment()) Flags
&= ~CHANGE_EQUIPMENT
;
7207 if (!GetStack()->GetItems()) Flags
&= ~TAKE_ITEMS
;
7208 if (!WillCarryItems()) Flags
&= ~GIVE_ITEMS
;
7209 if (!GetPossibleCommandFlags()) Flags
&= ~ISSUE_COMMANDS
;
7214 cchar
*VerbalBurdenState
[] = { "overloaded", "stressed", "burdened", "unburdened" };
7215 col16 VerbalBurdenStateColor
[] = { RED
, BLUE
, BLUE
, WHITE
};
7217 cchar
*character::GetVerbalBurdenState () const { return VerbalBurdenState
[BurdenState
]; }
7218 col16
character::GetVerbalBurdenStateColor () const { return VerbalBurdenStateColor
[BurdenState
]; }
7219 int character::GetAttributeAverage () const { return GetSumOfAttributes()/7; }
7221 cfestring
&character::GetStandVerb() const {
7222 if (ForceCustomStandVerb()) return DataBase
->StandVerb
;
7223 static festring Hovering
= "hovering";
7224 static festring Swimming
= "swimming";
7225 if (StateIsActivated(LEVITATION
)) return Hovering
;
7226 if (IsSwimming()) return Swimming
;
7227 return DataBase
->StandVerb
;
7231 truth
character::CheckApply () const {
7233 ADD_MESSAGE("This monster type cannot apply.");
7240 void character::EndLevitation () {
7241 if (!IsFlying() && GetSquareUnder()) {
7242 if (!game::IsInWilderness()) SignalStepFrom(0);
7243 if (game::IsInWilderness() || !GetLSquareUnder()->IsFreezed()) TestWalkability();
7248 truth
character::CanMove () const {
7249 return !IsRooted() || StateIsActivated(LEVITATION
);
7253 void character::CalculateEnchantments () {
7254 doforequipments()(this, &item::CalculateEnchantment
);
7255 GetStack()->CalculateEnchantments();
7259 truth
character::GetNewFormForPolymorphWithControl (character
*&NewForm
) {
7260 festring Topic
, Temp
;
7263 festring Temp
= game::DefaultQuestion(CONST_S("What do you want to become? [press '?' for a list]"), game::GetDefaultPolymorphTo(), &game::PolymorphControlKeyHandler
);
7264 NewForm
= protosystem::CreateMonster(Temp
);
7266 if (NewForm
->IsSameAs(this)) {
7268 ADD_MESSAGE("You choose not to polymorph.");
7272 if (PolymorphBackup
&& NewForm
->IsSameAs(PolymorphBackup
)) {
7274 NewForm
= ForceEndPolymorph();
7277 if (NewForm
->GetPolymorphIntelligenceRequirement() > GetAttribute(INTELLIGENCE
) && !game::WizardModeIsActive()) {
7278 ADD_MESSAGE("You feel your mind isn't yet powerful enough to call forth the form of %s.", NewForm
->CHAR_NAME(INDEFINITE
));
7282 NewForm
->RemoveAllItems();
7290 liquid
*character::CreateSweat(sLong Volume
) const {
7291 //return liquid::Spawn(GetSweatMaterial(), Volume);
7292 return liquid::Spawn(GetCurrentSweatMaterial(), Volume
);
7296 truth
character::TeleportRandomItem (truth TryToHinderVisibility
) {
7297 if (IsImmuneToItemTeleport()) return false;
7298 itemvector ItemVector
;
7299 std::vector
<sLong
> PossibilityVector
;
7300 int TotalPossibility
= 0;
7301 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
7302 ItemVector
.push_back(*i
);
7303 int Possibility
= i
->GetTeleportPriority();
7304 if (TryToHinderVisibility
) Possibility
+= i
->GetHinderVisibilityBonus(this);
7305 PossibilityVector
.push_back(Possibility
);
7306 TotalPossibility
+= Possibility
;
7308 for (int c
= 0; c
< GetEquipments(); ++c
) {
7309 item
*Equipment
= GetEquipment(c
);
7311 ItemVector
.push_back(Equipment
);
7312 int Possibility
= Equipment
->GetTeleportPriority();
7313 if (TryToHinderVisibility
) Possibility
+= Equipment
->GetHinderVisibilityBonus(this);
7314 PossibilityVector
.push_back(Possibility
<<= 1);
7315 TotalPossibility
+= Possibility
;
7318 if (!TotalPossibility
) return false;
7319 int Chosen
= femath::WeightedRand(PossibilityVector
, TotalPossibility
);
7320 item
*Item
= ItemVector
[Chosen
];
7321 truth Equipped
= PLAYER
->Equips(Item
);
7322 truth Seen
= Item
->CanBeSeenByPlayer();
7323 Item
->RemoveFromSlot();
7324 if (Seen
) ADD_MESSAGE("%s disappears.", Item
->CHAR_NAME(DEFINITE
));
7325 if (Equipped
) game::AskForEscPress(CONST_S("Equipment lost!"));
7327 int Range
= Item
->GetEmitation() && TryToHinderVisibility
? 25 : 5;
7328 rect
Border(Pos
+ v2(-Range
, -Range
), Pos
+ v2(Range
, Range
));
7329 Pos
= GetLevel()->GetRandomSquare(this, 0, &Border
);
7330 if (Pos
== ERROR_V2
) Pos
= GetLevel()->GetRandomSquare();
7331 GetNearLSquare(Pos
)->GetStack()->AddItem(Item
);
7332 if (Item
->CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", Item
->CHAR_NAME(INDEFINITE
));
7337 truth
character::HasClearRouteTo (v2 Pos
) const {
7338 pathcontroller::Map
= GetLevel()->GetMap();
7339 pathcontroller::Character
= this;
7340 v2 ThisPos
= GetPos();
7341 return mapmath
<pathcontroller
>::DoLine(ThisPos
.X
, ThisPos
.Y
, Pos
.X
, Pos
.Y
, SKIP_FIRST
);
7345 truth
character::IsTransparent () const {
7346 return !IsEnormous() || GetTorso()->GetMainMaterial()->IsTransparent() || StateIsActivated(INVISIBLE
);
7350 void character::SignalPossibleTransparencyChange () {
7351 if (!game::IsInWilderness()) {
7352 for (int c
= 0; c
< SquaresUnder
; ++c
) {
7353 lsquare
*Square
= GetLSquareUnder(c
);
7354 if (Square
) Square
->SignalPossibleTransparencyChange();
7360 int character::GetCursorData () const {
7362 int Color
= game::PlayerIsRunning() ? BLUE_CURSOR
: DARK_CURSOR
;
7363 for (int c
= 0; c
< BodyParts
; ++c
) {
7364 bodypart
*BodyPart
= GetBodyPart(c
);
7365 if (BodyPart
&& BodyPart
->IsUsable()) {
7366 int ConditionColorIndex
= BodyPart
->GetConditionColorIndex();
7367 if ((BodyPartIsVital(c
) && !ConditionColorIndex
) || (ConditionColorIndex
<= 1 && ++Bad
== 2)) return Color
|CURSOR_FLASH
;
7368 } else if (++Bad
== 2) return Color
|CURSOR_FLASH
;
7370 Color
= game::PlayerIsRunning() ? YELLOW_CURSOR
: RED_CURSOR
;
7371 return Bad
? Color
|CURSOR_FLASH
: Color
;
7375 void character::TryToName () {
7376 if (!IsPet()) ADD_MESSAGE("%s refuses to let YOU decide what %s's called.", CHAR_NAME(DEFINITE
), CHAR_PERSONAL_PRONOUN
);
7377 else if (IsPlayer()) ADD_MESSAGE("You can't rename yourself.");
7378 else if (!IsNameable()) ADD_MESSAGE("%s refuses to be called anything else but %s.", CHAR_NAME(DEFINITE
), CHAR_NAME(DEFINITE
));
7380 festring Topic
= CONST_S("What name will you give to ")+GetName(DEFINITE
)+'?';
7381 festring Name
= game::StringQuestion(Topic
, WHITE
, 0, 80, true);
7382 if (Name
.GetSize()) SetAssignedName(Name
);
7387 double character::GetSituationDanger (ccharacter
*Enemy
, v2 ThisPos
, v2 EnemyPos
, truth SeesEnemy
) const {
7389 if (IgnoreDanger() && !IsPlayer()) {
7390 if (Enemy
->IgnoreDanger() && !Enemy
->IsPlayer()) {
7391 Danger
= double(GetHP())*GetHPRequirementForGeneration()/(Enemy
->GetHP()*Enemy
->GetHPRequirementForGeneration());
7394 Danger
= 0.25*GetHPRequirementForGeneration()/Enemy
->GetHP();
7396 } else if (Enemy
->IgnoreDanger() && !Enemy
->IsPlayer()) {
7397 Danger
= 4.0*GetHP()/Enemy
->GetHPRequirementForGeneration();
7399 Danger
= GetRelativeDanger(Enemy
);
7401 Danger
*= 3.0/((EnemyPos
-ThisPos
).GetManhattanLength()+2);
7402 if (!SeesEnemy
) Danger
*= 0.2;
7403 if (StateIsActivated(PANIC
)) Danger
*= 0.2;
7404 Danger
*= double(GetHP())*Enemy
->GetMaxHP()/(Enemy
->GetHP()*GetMaxHP());
7409 void character::ModifySituationDanger (double &Danger
) const {
7410 switch (GetTirednessState()) {
7411 case FAINTING
: Danger
*= 1.5;
7412 case EXHAUSTED
: Danger
*= 1.25;
7414 for (int c
= 0; c
< STATES
; ++c
) {
7415 if (StateIsActivated(1 << c
) && StateData
[c
].SituationDangerModifier
!= 0) (this->*StateData
[c
].SituationDangerModifier
)(Danger
);
7420 void character::LycanthropySituationDangerModifier (double &Danger
) const {
7421 character
*Wolf
= werewolfwolf::Spawn();
7422 double DangerToWolf
= GetRelativeDanger(Wolf
);
7423 Danger
*= pow(DangerToWolf
, 0.1);
7428 void character::PoisonedSituationDangerModifier (double &Danger
) const {
7429 int C
= GetTemporaryStateCounter(POISONED
);
7430 Danger
*= (1+(C
*C
)/(GetHP()*10000.0*(GetGlobalResistance(POISON
)+1)));
7434 void character::PolymorphingSituationDangerModifier (double &Danger
) const {
7435 if (!StateIsActivated(POLYMORPH_CONTROL
)) Danger
*= 1.5;
7439 void character::PanicSituationDangerModifier (double &Danger
) const {
7444 void character::ConfusedSituationDangerModifier (double &Danger
) const {
7449 void character::ParasitizedSituationDangerModifier (double &Danger
) const {
7454 void character::LeprosySituationDangerModifier (double &Danger
) const {
7459 void character::AddRandomScienceName (festring
&String
) const {
7460 festring Science
= GetScienceTalkName().GetRandomElement().CStr();
7461 if (Science
[0] == '!') {
7462 String
<< Science
.CStr()+1;
7465 festring Attribute
= GetScienceTalkAdjectiveAttribute().GetRandomElement();
7467 truth NoAttrib
= Attribute
.IsEmpty(), NoSecondAdjective
= false;
7468 if (!Attribute
.IsEmpty() && Attribute
[0] == '!') {
7469 NoSecondAdjective
= true;
7470 Attribute
.Erase(0, 1);
7472 if (!Science
.Find("the ")) {
7473 Science
.Erase(0, 4);
7474 if (!Attribute
.Find("the ", 0, 4)) Attribute
<< " the"; else Attribute
.Insert(0, "the ", 4);
7476 if (islower(Science
[0]) && Science
.Find(' ') == festring::NPos
&& Science
.Find('-') == festring::NPos
&&
7477 Science
.Find("phobia") == festring::NPos
) {
7478 Prefix
= GetScienceTalkPrefix().GetRandomElement();
7479 if (!Prefix
.IsEmpty() && Science
.Find(Prefix
) != festring::NPos
) Prefix
.Empty();
7481 int L
= Prefix
.GetSize();
7482 if (L
&& Prefix
[L
-1] == Science
[0]) Science
.Erase(0, 1);
7483 if (!NoAttrib
&& !NoSecondAdjective
== !RAND_GOOD(3)) {
7484 int S1
= NoSecondAdjective
? 0 : GetScienceTalkAdjectiveAttribute().Size
;
7485 int S2
= GetScienceTalkSubstantiveAttribute().Size
;
7486 festring OtherAttribute
;
7487 int Chosen
= RAND_GOOD(S1
+S2
);
7488 if (Chosen
< S1
) OtherAttribute
= GetScienceTalkAdjectiveAttribute()[Chosen
];
7489 else OtherAttribute
= GetScienceTalkSubstantiveAttribute()[Chosen
- S1
];
7490 if (!OtherAttribute
.IsEmpty() && OtherAttribute
.Find("the ", 0, 4) && Attribute
.Find(OtherAttribute
) == festring::NPos
) {
7491 String
<< Attribute
<< ' ' << OtherAttribute
<< ' ' << Prefix
<< Science
;
7495 String
<< Attribute
;
7496 if (!NoAttrib
) String
<< ' ';
7497 String
<< Prefix
<< Science
;
7501 truth
character::TryToTalkAboutScience () {
7502 if (GetRelation(PLAYER
) == HOSTILE
||
7503 GetScienceTalkPossibility() <= RAND_GOOD(100) ||
7504 PLAYER
->GetAttribute(INTELLIGENCE
) < GetScienceTalkIntelligenceRequirement() ||
7505 PLAYER
->GetAttribute(WISDOM
) < GetScienceTalkWisdomRequirement() ||
7506 PLAYER
->GetAttribute(CHARISMA
) < GetScienceTalkCharismaRequirement())
7510 AddRandomScienceName(Science
);
7513 AddRandomScienceName(S1
);
7514 AddRandomScienceName(S2
);
7515 if (S1
.Find(S2
) == festring::NPos
&& S2
.Find(S1
) == festring::NPos
) {
7516 switch (RAND_GOOD(3)) {
7517 case 0: Science
= "the relation of "; break;
7518 case 1: Science
= "the differences of "; break;
7519 case 2: Science
= "the similarities of "; break;
7521 Science
<< S1
<< " and " << S2
;
7524 AddRandomScienceName(Science
);
7527 switch ((RAND() + GET_TICK()) % 10) {
7529 ADD_MESSAGE("You have a rather pleasant chat about %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7532 ADD_MESSAGE("%s explains a few of %s opinions regarding %s to you.", CHAR_DESCRIPTION(DEFINITE
), CHAR_POSSESSIVE_PRONOUN
, Science
.CStr());
7535 ADD_MESSAGE("%s reveals a number of %s insightful views of %s to you.", CHAR_DESCRIPTION(DEFINITE
), CHAR_POSSESSIVE_PRONOUN
, Science
.CStr());
7538 ADD_MESSAGE("You exhange some information pertaining to %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7541 ADD_MESSAGE("You engage in a pretty intriguing conversation about %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7544 ADD_MESSAGE("You discuss at length about %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7547 ADD_MESSAGE("You have a somewhat boring talk concerning %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7550 ADD_MESSAGE("You are drawn into a heated argument regarding %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7553 ADD_MESSAGE("%s delivers a sLong monologue concerning eg. %s.", CHAR_DESCRIPTION(DEFINITE
), Science
.CStr());
7556 ADD_MESSAGE("You dive into a brief but thought-provoking debate over %s with %s", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7559 PLAYER
->EditExperience(INTELLIGENCE
, 1000, 50. * GetScienceTalkIntelligenceModifier() / ++ScienceTalks
);
7560 PLAYER
->EditExperience(WISDOM
, 1000, 50. * GetScienceTalkWisdomModifier() / ++ScienceTalks
);
7561 PLAYER
->EditExperience(CHARISMA
, 1000, 50. * GetScienceTalkCharismaModifier() / ++ScienceTalks
);
7566 truth
character::IsUsingWeaponOfCategory (int Category
) const {
7568 ((GetMainWielded() && GetMainWielded()->GetWeaponCategory() == Category
) ||
7569 (GetSecondaryWielded() && GetSecondaryWielded()->GetWeaponCategory() == Category
));
7573 truth
character::TryToUnStickTraps (v2 Dir
) {
7574 if (!TrapData
) return true;
7575 std::vector
<trapdata
> TrapVector
;
7576 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) TrapVector
.push_back(*TrapData
);
7577 for (uInt c
= 0; c
< TrapVector
.size(); ++c
) {
7579 entity
*Trap
= game::SearchTrap(TrapVector
[c
].TrapID
);
7580 /*k8:??? if(!Trap->Exists()) int esko = esko = 2; */
7581 if (!Trap
->Exists()) continue; /*k8: ??? added by me; what this means? */
7582 if (Trap
->GetVictimID() == GetID() && Trap
->TryToUnStick(this, Dir
)) break;
7585 return !TrapData
&& IsEnabled();
7589 struct trapidcomparer
{
7590 trapidcomparer (feuLong ID
) : ID(ID
) {}
7591 truth
operator () (const trapdata
*T
) const { return T
->TrapID
== ID
; }
7596 void character::RemoveTrap (feuLong ID
) {
7597 trapdata
*&T
= ListFind(TrapData
, trapidcomparer(ID
));
7599 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange
);
7603 void character::AddTrap (feuLong ID
, feuLong BodyParts
) {
7604 trapdata
*&T
= ListFind(TrapData
, trapidcomparer(ID
));
7605 if (T
) T
->BodyParts
|= BodyParts
;
7606 else T
= new trapdata(ID
, GetID(), BodyParts
);
7607 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange
);
7611 truth
character::IsStuckToTrap (feuLong ID
) const {
7612 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) if (T
->TrapID
== ID
) return true;
7617 void character::RemoveTraps () {
7618 for (trapdata
*T
= TrapData
; T
;) {
7619 entity
*Trap
= game::SearchTrap(T
->TrapID
);
7620 if (Trap
) Trap
->UnStick();
7621 trapdata
*ToDel
= T
;
7626 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange
);
7630 void character::RemoveTraps (int BodyPartIndex
) {
7631 feuLong Flag
= 1 << BodyPartIndex
;
7632 for (trapdata
**T
= &TrapData
; *T
;) {
7633 if ((*T
)->BodyParts
& Flag
) {
7634 entity
*Trap
= game::SearchTrap((*T
)->TrapID
);
7635 if (!((*T
)->BodyParts
&= ~Flag
)) {
7636 if (Trap
) Trap
->UnStick();
7637 trapdata
*ToDel
= *T
;
7641 if (Trap
) Trap
->UnStick(BodyPartIndex
);
7649 if (GetBodyPart(BodyPartIndex
)) GetBodyPart(BodyPartIndex
)->SignalPossibleUsabilityChange();
7653 festring
character::GetTrapDescription () const {
7655 std::pair
<entity
*, int> TrapStack
[3];
7657 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) {
7659 entity
*Trap
= game::SearchTrap(T
->TrapID
);
7662 for (c
= 0; c
< Index
; ++c
) if (TrapStack
[c
].first
->GetTrapType() == Trap
->GetTrapType()) ++TrapStack
[c
].second
;
7663 if (c
== Index
) TrapStack
[Index
++] = std::make_pair(Trap
, 1);
7671 TrapStack
[0].first
->AddTrapName(Desc
, TrapStack
[0].second
);
7674 TrapStack
[1].first
->AddTrapName(Desc
, TrapStack
[1].second
);
7675 } else if (Index
== 3) {
7677 TrapStack
[1].first
->AddTrapName(Desc
, TrapStack
[1].second
);
7679 TrapStack
[2].first
->AddTrapName(Desc
, TrapStack
[2].second
);
7682 Desc
<< "lots of traps";
7688 int character::RandomizeHurtBodyPart (feuLong BodyParts
) const {
7689 int BodyPartIndex
[MAX_BODYPARTS
];
7691 for (int c
= 0; c
< GetBodyParts(); ++c
) {
7692 if (1 << c
& BodyParts
) {
7693 /*k8: ??? if(!GetBodyPart(c)) int esko = esko = 2; */
7694 if (!GetBodyPart(c
)) continue;
7695 BodyPartIndex
[Index
++] = c
;
7697 /*k8: ??? if(!Index) int esko = esko = 2;*/
7700 fprintf(stderr
, "FATAL: RandomizeHurtBodyPart -- Index==0\n");
7703 return BodyPartIndex
[RAND_N(Index
)];
7707 truth
character::BodyPartIsStuck (int I
) const {
7708 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) if (1 << I
& T
->BodyParts
) return true;
7713 void character::PrintAttribute (cchar
*Desc
, int I
, int PanelPosX
, int PanelPosY
) const {
7714 int Attribute
= GetAttribute(I
);
7715 int NoBonusAttribute
= GetAttribute(I
, false);
7716 col16 C
= game::GetAttributeColor(I
);
7717 festring String
= Desc
;
7719 String
<< Attribute
;
7721 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
* 10), C
, "%s", String
.CStr());
7722 if (Attribute
!= NoBonusAttribute
) {
7723 int Where
= PanelPosX
+ ((String
.GetSize() + 1) << 3);
7724 FONT
->Printf(DOUBLE_BUFFER
, v2(Where
, PanelPosY
* 10), LIGHT_GRAY
, "%d", NoBonusAttribute
);
7729 truth
character::AllowUnconsciousness () const {
7730 return DataBase
->AllowUnconsciousness
&& TorsoIsAlive();
7734 truth
character::CanPanic () const {
7735 return !Action
|| !Action
->IsUnconsciousness();
7739 int character::GetRandomBodyPart (feuLong Possible
) const {
7740 int OKBodyPart
[MAX_BODYPARTS
];
7741 int OKBodyParts
= 0;
7742 for (int c
= 0; c
< BodyParts
; ++c
) if (1 << c
& Possible
&& GetBodyPart(c
)) OKBodyPart
[OKBodyParts
++] = c
;
7743 return OKBodyParts
? OKBodyPart
[RAND_N(OKBodyParts
)] : NONE_INDEX
;
7747 void character::EditNP (sLong What
) {
7748 int OldState
= GetHungerState();
7750 int NewState
= GetHungerState();
7751 if (OldState
> VERY_HUNGRY
&& NewState
== VERY_HUNGRY
) DeActivateVoluntaryAction(CONST_S("You are getting really hungry."));
7752 if (OldState
> STARVING
&& NewState
== STARVING
) DeActivateVoluntaryAction(CONST_S("You are getting extremely hungry."));
7756 truth
character::IsSwimming () const {
7757 return !IsFlying() && GetSquareUnder() && GetSquareUnder()->GetSquareWalkability() & SWIM
;
7761 void character::AddBlackUnicornConsumeEndMessage () const {
7762 if (IsPlayer()) ADD_MESSAGE("You feel dirty and loathsome.");
7766 void character::AddGrayUnicornConsumeEndMessage () const {
7767 if (IsPlayer()) ADD_MESSAGE("You feel neutralized.");
7771 void character::AddWhiteUnicornConsumeEndMessage () const {
7772 if (IsPlayer()) ADD_MESSAGE("You feel purified.");
7776 void character::AddOmmelBoneConsumeEndMessage () const {
7777 if (IsPlayer()) ADD_MESSAGE("You feel the power of all your canine ancestors combining in your body.");
7778 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s looks extremely ferocious. You shudder.", CHAR_NAME(DEFINITE
));
7782 void character::AddLiquidHorrorConsumeEndMessage () const {
7783 if (IsPlayer()) ADD_MESSAGE("Untold horrors flash before your eyes. The melancholy of the world is on your shoulders!");
7784 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks as if the melancholy of the world is on %s shoulders!.", CHAR_NAME(DEFINITE
), GetPossessivePronoun().CStr());
7788 int character::GetBodyPartSparkleFlags (int) const {
7790 ((GetNaturalSparkleFlags() & SKIN_COLOR
? SPARKLING_A
: 0) |
7791 (GetNaturalSparkleFlags() & TORSO_MAIN_COLOR
? SPARKLING_B
: 0) |
7792 (GetNaturalSparkleFlags() & TORSO_SPECIAL_COLOR
? SPARKLING_D
: 0));
7796 truth
character::IsAnimated () const {
7797 return combinebodypartpredicates()(this, &bodypart::IsAnimated
, 1);
7801 double character::GetNaturalExperience (int Identifier
) const {
7802 return DataBase
->NaturalExperience
[Identifier
];
7806 truth
character::HasBodyPart (sorter Sorter
) const {
7807 if (Sorter
== 0) return true;
7808 return combinebodypartpredicateswithparam
<ccharacter
*>()(this, Sorter
, this, 1);
7812 truth
character::PossessesItem (sorter Sorter
) const {
7813 if (Sorter
== 0) return true;
7815 (GetStack()->SortedItems(this, Sorter
) ||
7816 combinebodypartpredicateswithparam
<ccharacter
*>()(this, Sorter
, this, 1) ||
7817 combineequipmentpredicateswithparam
<ccharacter
*>()(this, Sorter
, this, 1));
7821 truth
character::MoreThanOnePossessesItem (sorter Sorter
) const {
7825 for (int c
= 0; c
< BodyParts
; ++c
) {
7826 bodypart
*BodyPart
= GetBodyPart(c
);
7828 if (BodyPart
&& (Sorter
== 0 || (BodyPart
->*Sorter
)(this))) {
7829 if (++count
> 1) return true;
7832 for (int c
= 0; c
< GetEquipments(); ++c
) {
7833 item
*Equipment
= GetEquipment(c
);
7835 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) {
7836 if (++count
> 1) return true;
7839 for (int c
= 0; c
< GetStack()->GetItems(); ++c
) {
7840 item
*Stk
= GetStack()->GetItem(c
);
7842 if (Stk
&& (Sorter
== 0 || (Stk
->*Sorter
)(this))) {
7843 if (++count
> 1) return true;
7852 item
*character::FirstPossessesItem (sorter Sorter
) const {
7854 for (int c
= 0; c
< BodyParts
; ++c
) {
7855 bodypart
*BodyPart
= GetBodyPart(c
);
7857 if (BodyPart
&& (Sorter
== 0 || (BodyPart
->*Sorter
)(this))) return BodyPart
;
7859 for (int c
= 0; c
< GetEquipments(); ++c
) {
7860 item
*Equipment
= GetEquipment(c
);
7862 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) return Equipment
;
7864 for (int c
= 0; c
< GetStack()->GetItems(); ++c
) {
7865 item
*Stk
= GetStack()->GetItem(c
);
7867 if (Stk
&& (Sorter
== 0 || (Stk
->*Sorter
)(this))) return Stk
;
7875 cchar
*character::GetRunDescriptionLine (int I
) const {
7876 if (!GetRunDescriptionLineOne().IsEmpty()) return !I
? GetRunDescriptionLineOne().CStr() : GetRunDescriptionLineTwo().CStr();
7877 if (IsFlying()) return !I
? "Flying" : "very fast";
7878 if (IsSwimming()) return !I
? "Swimming" : "very fast";
7879 return !I
? "Running" : "";
7883 void character::VomitAtRandomDirection (int Amount
) {
7884 if (game::IsInWilderness()) return;
7885 /* Lacks support of multitile monsters */
7888 for (int d
= 0; d
< 9; ++d
) {
7889 lsquare
*Square
= GetLSquareUnder()->GetNeighbourLSquare(d
);
7890 if (Square
&& !Square
->VomitingIsDangerous(this)) Possible
[Index
++] = Square
->GetPos();
7892 if (Index
) Vomit(Possible
[RAND_N(Index
)], Amount
);
7893 else Vomit(GetPos(), Amount
);
7897 void character::RemoveLifeSavers () {
7898 for (int c
= 0; c
< GetEquipments(); ++c
) {
7899 item
*Equipment
= GetEquipment(c
);
7900 if (Equipment
&& Equipment
->IsInCorrectSlot(c
) && Equipment
->GetGearStates() & LIFE_SAVED
) {
7901 Equipment
->SendToHell();
7902 Equipment
->RemoveFromSlot();
7908 ccharacter
*character::FindCarrier () const {
7909 return this; //check
7913 void character::PrintBeginHiccupsMessage () const {
7914 if (IsPlayer()) ADD_MESSAGE("Your diaphragm is spasming vehemently.");
7918 void character::PrintEndHiccupsMessage () const {
7919 if (IsPlayer()) ADD_MESSAGE("You feel your annoying hiccoughs have finally subsided.");
7923 void character::HiccupsHandler () {
7925 if (!(RAND() % 2000)) {
7926 if (IsPlayer()) ADD_MESSAGE("");
7927 else if (CanBeSeenByPlayer()) ADD_MESSAGE("");
7928 else if ((PLAYER->GetPos()-GetPos()).GetLengthSquare() <= 400) ADD_MESSAGE("");
7929 game::CallForAttention(GetPos(), 400);
7935 void character::VampirismHandler () {
7936 //EditExperience(ARM_STRENGTH, -25, 1 << 1);
7937 //EditExperience(LEG_STRENGTH, -25, 1 << 1);
7938 //EditExperience(DEXTERITY, -25, 1 << 1);
7939 //EditExperience(AGILITY, -25, 1 << 1);
7940 //EditExperience(ENDURANCE, -25, 1 << 1);
7941 EditExperience(CHARISMA
, -25, 1 << 1);
7942 EditExperience(WISDOM
, -25, 1 << 1);
7943 EditExperience(INTELLIGENCE
, -25, 1 << 1);
7944 CheckDeath(CONST_S("killed by vampirism"));
7948 void character::HiccupsSituationDangerModifier (double &Danger
) const {
7953 void character::VampirismSituationDangerModifier (double &Danger
) const {
7954 character
*Vampire
= vampire::Spawn();
7955 double DangerToVampire
= GetRelativeDanger(Vampire
);
7956 Danger
*= pow(DangerToVampire
, 0.1);
7961 bool character::IsConscious () const {
7962 return !Action
|| !Action
->IsUnconsciousness();
7966 wsquare
*character::GetNearWSquare (v2 Pos
) const {
7967 return static_cast<wsquare
*>(GetSquareUnder()->GetArea()->GetSquare(Pos
));
7971 wsquare
*character::GetNearWSquare (int x
, int y
) const {
7972 return static_cast<wsquare
*>(GetSquareUnder()->GetArea()->GetSquare(x
, y
));
7976 void character::ForcePutNear (v2 Pos
) {
7977 /* GUM SOLUTION!!! */
7978 v2 NewPos
= game::GetCurrentLevel()->GetNearestFreeSquare(PLAYER
, Pos
, false);
7979 if (NewPos
== ERROR_V2
) do { NewPos
= game::GetCurrentLevel()->GetRandomSquare(this); } while(NewPos
== Pos
);
7984 void character::ReceiveMustardGas (int BodyPart
, sLong Volume
) {
7985 if (Volume
) GetBodyPart(BodyPart
)->AddFluid(liquid::Spawn(MUSTARD_GAS_LIQUID
, Volume
), CONST_S("skin"), 0, true);
7989 void character::ReceiveMustardGasLiquid (int BodyPartIndex
, sLong Modifier
) {
7990 bodypart
*BodyPart
= GetBodyPart(BodyPartIndex
);
7991 if (BodyPart
->GetMainMaterial()->GetInteractionFlags() & IS_AFFECTED_BY_MUSTARD_GAS
) {
7992 sLong Tries
= Modifier
;
7993 Modifier
-= Tries
; //opt%?
7995 for (sLong c
= 0; c
< Tries
; ++c
) if (!(RAND() % 100)) ++Damage
;
7996 if (Modifier
&& !(RAND() % 1000 / Modifier
)) ++Damage
;
7998 feuLong Minute
= game::GetTotalMinutes();
7999 if (GetLastAcidMsgMin() != Minute
&& (CanBeSeenByPlayer() || IsPlayer())) {
8000 SetLastAcidMsgMin(Minute
);
8001 if (IsPlayer()) ADD_MESSAGE("Mustard gas dissolves the skin of your %s.", BodyPart
->GetBodyPartName().CStr());
8002 else ADD_MESSAGE("Mustard gas dissolves %s.", CHAR_NAME(DEFINITE
));
8004 ReceiveBodyPartDamage(0, Damage
, MUSTARD_GAS_DAMAGE
, BodyPartIndex
, YOURSELF
, false, false, false);
8005 CheckDeath(CONST_S("killed by a fatal exposure to mustard gas"));
8011 truth
character::IsBadPath (v2 Pos
) const {
8012 if (!IsGoingSomeWhere()) return false;
8013 v2 TPos
= !Route
.empty() ? Route
.back() : GoingTo
;
8014 return ((TPos
- Pos
).GetManhattanLength() > (TPos
- GetPos()).GetManhattanLength());
8018 double &character::GetExpModifierRef (expid E
) {
8019 return ExpModifierMap
.insert(std::make_pair(E
, 1.)).first
->second
;
8023 /* Should probably do more. Now only makes Player forget gods */
8024 truth
character::ForgetRandomThing () {
8026 /* hopefully this code isn't some where else */
8027 std::vector
<god
*> Known
;
8028 for (int c
= 1; c
<= GODS
; ++c
) if (game::GetGod(c
)->IsKnown()) Known
.push_back(game::GetGod(c
));
8029 if (Known
.empty()) return false;
8030 int RandomGod
= RAND_N(Known
.size());
8031 Known
.at(RAND_N(Known
.size()))->SetIsKnown(false);
8032 ADD_MESSAGE("You forget how to pray to %s.", Known
.at(RandomGod
)->GetName());
8039 int character::CheckForBlock (character
*Enemy
, item
*Weapon
, double ToHitValue
, int Damage
, int Success
, int Type
) {
8044 void character::ApplyAllGodsKnownBonus () {
8045 stack
*AddPlace
= GetStackUnder();
8046 if (game::IsInWilderness()) AddPlace
= GetStack(); else AddPlace
= GetStackUnder();
8047 pantheonbook
*NewBook
= pantheonbook::Spawn();
8048 AddPlace
->AddItem(NewBook
);
8049 ADD_MESSAGE("\"MORTAL! BEHOLD THE HOLY SAGA\"");
8050 ADD_MESSAGE("%s materializes near your feet.", NewBook
->CHAR_NAME(INDEFINITE
));
8054 void character::ReceiveSirenSong (character
*Siren
) {
8055 if (Siren
->GetTeam() == GetTeam()) return;
8057 if (IsPlayer()) ADD_MESSAGE("The beautiful melody of %s makes you feel sleepy.", Siren
->CHAR_NAME(DEFINITE
));
8058 else if (CanBeSeenByPlayer()) ADD_MESSAGE("The beautiful melody of %s makes %s look sleepy.", Siren
->CHAR_NAME(DEFINITE
), CHAR_NAME(DEFINITE
)); /*k8*/
8059 Stamina
-= (1 + RAND_N(4)) * 10000;
8062 if (!IsPlayer() && IsCharmable() && !RAND_N(5)) {
8063 ChangeTeam(Siren
->GetTeam());
8064 ADD_MESSAGE("%s seems to be totally brainwashed by %s melodies.", CHAR_NAME(DEFINITE
), Siren
->CHAR_NAME(DEFINITE
));
8068 item
*What
= GiveMostExpensiveItem(Siren
);
8071 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
);
8073 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
);
8076 if (IsPlayer()) ADD_MESSAGE("You would like to give something to %s.", Siren
->CHAR_NAME(DEFINITE
));
8083 // return 0, if no item found
8084 item
*character::FindMostExpensiveItem () const {
8086 item
*MostExpensive
= 0;
8087 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
8088 if ((*i
)->GetPrice() > MaxPrice
) {
8089 MaxPrice
= (*i
)->GetPrice();
8090 MostExpensive
= (*i
);
8093 for (int c
= 0; c
< GetEquipments(); ++c
) {
8094 item
*Equipment
= GetEquipment(c
);
8095 if (Equipment
&& Equipment
->GetPrice() > MaxPrice
) {
8096 MaxPrice
= Equipment
->GetPrice();
8097 MostExpensive
= Equipment
;
8100 return MostExpensive
;
8104 // returns 0 if no items available
8105 item
*character::GiveMostExpensiveItem(character
*ToWhom
) {
8106 item
*ToGive
= FindMostExpensiveItem();
8107 if (!ToGive
) return 0;
8108 truth Equipped
= PLAYER
->Equips(ToGive
);
8109 ToGive
->RemoveFromSlot();
8110 if (Equipped
) game::AskForEscPress(CONST_S("Equipment lost!"));
8111 ToWhom
->ReceiveItemAsPresent(ToGive
);
8117 void character::ReceiveItemAsPresent (item
*Present
) {
8118 if (TestForPickup(Present
)) GetStack()->AddItem(Present
); else GetStackUnder()->AddItem(Present
);
8122 /* returns 0 if no enemies in sight */
8123 character
*character::GetNearestEnemy () const {
8124 character
*NearestEnemy
= 0;
8125 sLong NearestEnemyDistance
= 0x7FFFFFFF;
8127 for (int c
= 0; c
< game::GetTeams(); ++c
) {
8128 if (GetTeam()->GetRelation(game::GetTeam(c
)) == HOSTILE
) {
8129 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
8130 if ((*i
)->IsEnabled()) {
8131 sLong ThisDistance
= Max
<sLong
>(abs((*i
)->GetPos().X
- Pos
.X
), abs((*i
)->GetPos().Y
- Pos
.Y
));
8132 if ((ThisDistance
< NearestEnemyDistance
|| (ThisDistance
== NearestEnemyDistance
&& !(RAND() % 3))) && (*i
)->CanBeSeenBy(this)) {
8134 NearestEnemyDistance
= ThisDistance
;
8140 return NearestEnemy
;
8144 truth
character::MindWormCanPenetrateSkull (mindworm
*) const {
8149 truth
character::CanTameWithDulcis (const character
*Tamer
) const {
8150 int TamingDifficulty
= GetTamingDifficulty();
8151 if (TamingDifficulty
== NO_TAMING
) return false;
8152 if (GetAttachedGod() == DULCIS
) return true;
8153 int Modifier
= Tamer
->GetAttribute(WISDOM
) + Tamer
->GetAttribute(CHARISMA
);
8154 if (Tamer
->IsPlayer()) Modifier
+= game::GetGod(DULCIS
)->GetRelation() / 20;
8155 else if (Tamer
->GetAttachedGod() == DULCIS
) Modifier
+= 50;
8156 if (TamingDifficulty
== 0) {
8157 if (!IgnoreDanger()) TamingDifficulty
= int(10 * GetRelativeDanger(Tamer
));
8158 else TamingDifficulty
= 10 * GetHPRequirementForGeneration()/Max(Tamer
->GetHP(), 1);
8160 return Modifier
>= TamingDifficulty
* 3;
8164 truth
character::CanTameWithLyre (const character
*Tamer
) const {
8165 int TamingDifficulty
= GetTamingDifficulty();
8166 if (TamingDifficulty
== NO_TAMING
) return false;
8167 if (TamingDifficulty
== 0) {
8168 if (!IgnoreDanger()) TamingDifficulty
= int(10 * GetRelativeDanger(Tamer
));
8169 else TamingDifficulty
= 10*GetHPRequirementForGeneration()/Max(Tamer
->GetHP(), 1);
8171 return Tamer
->GetAttribute(CHARISMA
) >= TamingDifficulty
;
8175 truth
character::CanTameWithScroll (const character
*Tamer
) const {
8176 int TamingDifficulty
= GetTamingDifficulty();
8178 (TamingDifficulty
!= NO_TAMING
&&
8179 (TamingDifficulty
== 0 ||
8180 Tamer
->GetAttribute(INTELLIGENCE
) * 4 + Tamer
->GetAttribute(CHARISMA
) >= TamingDifficulty
* 5));
8184 truth
character::CheckSadism () {
8185 if (!IsSadist() || !HasSadistAttackMode() || !IsSmall()) return false; // gum
8187 for (int d
= 0; d
< 8; ++d
) {
8188 square
*Square
= GetNeighbourSquare(d
);
8190 character
*Char
= Square
->GetCharacter();
8191 if (Char
&& Char
->IsMasochist() && GetRelation(Char
) == FRIEND
&&
8192 Char
->GetHP() * 3 >= Char
->GetMaxHP() * 2 && Hit(Char
, Square
->GetPos(), d
, SADIST_HIT
)) {
8203 truth
character::CheckForBeverage () {
8204 if (StateIsActivated(PANIC
) || !IsEnabled() || !UsesNutrition() || CheckIfSatiated()) return false;
8205 itemvector ItemVector
;
8206 GetStack()->FillItemVector(ItemVector
);
8207 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) if (ItemVector
[c
]->IsBeverage(this) && TryToConsume(ItemVector
[c
])) return true;
8212 void character::Haste () {
8213 doforbodyparts()(this, &bodypart::Haste
);
8214 doforequipments()(this, &item::Haste
);
8215 BeginTemporaryState(HASTE
, 500 + RAND() % 1000);
8219 void character::Slow () {
8220 doforbodyparts()(this, &bodypart::Slow
);
8221 doforequipments()(this, &item::Slow
);
8222 //BeginTemporaryState(HASTE, 500 + RAND() % 1000); // this seems to be a bug
8223 BeginTemporaryState(SLOW
, 500 + RAND() % 1000);
8227 void character::SurgicallyDetachBodyPart () {
8228 ADD_MESSAGE("You haven't got any extra bodyparts.");
8232 truth
character::CanHear() const
8234 return DataBase
->CanHear
&& HasHead();
8238 truth
character::IsAllowedInDungeon (int dunIndex
) {
8239 const fearray
<int> &dlist
= GetAllowedDungeons();
8241 for (uInt f
= 0; f
< dlist
.Size
; ++f
) {
8242 if (dlist
[f
] == ALL_DUNGEONS
|| dlist
[f
] == dunIndex
) {
8243 fprintf(stderr
, "OK!\n");
8247 fprintf(stderr
, "NO!\n");
8252 truth
character::IsESPBlockedByEquipment () const {
8253 for (int c
= 0; c
< GetEquipments(); ++c
) {
8254 item
*Item
= GetEquipment(c
);
8256 if (Item
&& Item
->IsHelmet(this) &&
8257 ((Item
->GetMainMaterial() && Item
->GetMainMaterial()->BlockESP()) ||
8258 (Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->BlockESP()))) return true;
8264 truth
character::TemporaryStateIsActivated (sLong What
) const {
8265 if ((What
&ESP
) && (TemporaryState
&ESP
) && IsESPBlockedByEquipment()) {
8266 return ((TemporaryState
&What
)&(~ESP
));
8268 return (TemporaryState
& What
);
8272 truth
character::StateIsActivated (sLong What
) const {
8273 if ((What
&ESP
) && ((TemporaryState
|EquipmentState
)&ESP
) && IsESPBlockedByEquipment()) {
8274 return ((TemporaryState
&What
)&(~ESP
)) || ((EquipmentState
&What
)&(~ESP
));
8276 return (TemporaryState
& What
) || (EquipmentState
& What
);