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
)) {
1097 festring dir
= inputfile::GetMyDir()+"/DeathShots";
1099 mkdir(dir
.CStr(), 0755);
1104 time_t t
= time(NULL
);
1105 struct tm
*ts
= localtime(&t
);
1107 timestr
<< (int)(ts
->tm_year
%100);
1108 int t
= ts
->tm_mon
+1;
1109 if (t
< 10) timestr
<< '0'; timestr
<< t
;
1110 t
= ts
->tm_mday
; if (t
< 10) timestr
<< '0'; timestr
<< t
;
1112 t
= ts
->tm_hour
; if (t
< 10) timestr
<< '0'; timestr
<< t
;
1113 t
= ts
->tm_min
; if (t
< 10) timestr
<< '0'; timestr
<< t
;
1114 t
= ts
->tm_sec
; if (t
< 10) timestr
<< '0'; timestr
<< t
;
1118 #if defined(HAVE_IMLIB2) || defined(HAVE_LIBPNG)
1119 festring ext
= ".png";
1121 festring ext
= ".bmp";
1123 festring fname
= dir
+"/deathshot_"+timestr
;
1124 if (inputfile::fileExists(fname
+ext
)) {
1125 for (int f
= 0; f
< 1000; f
++) {
1127 sprintf(buf
, "%03d", f
);
1128 festring fn
= fname
+buf
;
1129 if (!inputfile::fileExists(fn
+ext
)) {
1136 fprintf(stderr
, "deathshot: %s\n", fname
.CStr());
1137 #if defined(HAVE_IMLIB2) || defined(HAVE_LIBPNG)
1138 DOUBLE_BUFFER
->SavePNG(fname
);
1140 DOUBLE_BUFFER
->SaveBMP(fname
);
1143 if (game::WizardModeIsActive()) {
1144 game::DrawEverything();
1145 if (!game::TruthQuestion(CONST_S("Do you want to do this, cheater? [y/n]"), REQUIRES_ANSWER
)) {
1151 SetNP(SATIATED_LEVEL
);
1152 SendNewDrawRequest();
1156 } else if (CanBeSeenByPlayer() && !(DeathFlags
& DISALLOW_MSG
)) {
1157 ProcessAndAddMessage(GetDeathMessage());
1158 } else if (DeathFlags
& FORCE_MSG
) {
1159 ADD_MESSAGE("You sense the death of something.");
1162 if (!(DeathFlags
& FORBID_REINCARNATION
)) {
1163 if (StateIsActivated(LIFE_SAVED
) && CanMoveOn(!game::IsInWilderness() ? GetSquareUnder() : PLAYER
->GetSquareUnder())) {
1167 if (SpecialSaveLife()) return;
1168 } else if (StateIsActivated(LIFE_SAVED
)) {
1172 Flags
|= C_IN_NO_MSG_MODE
;
1173 character
*Ghost
= 0;
1175 game::RemoveSaves();
1176 if (!game::IsInWilderness()) {
1177 Ghost
= game::CreateGhost();
1182 square
*SquareUnder
[MAX_SQUARES_UNDER
];
1183 memset(SquareUnder
, 0, sizeof(SquareUnder
));
1185 if (IsPlayer() || !game::IsInWilderness()) {
1186 for (int c
= 0; c
< SquaresUnder
; ++c
) SquareUnder
[c
] = GetSquareUnder(c
);
1189 charactervector
& V
= game::GetWorldMap()->GetPlayerGroup();
1190 V
.erase(std::find(V
.begin(), V
.end(), this));
1192 //lsquare **LSquareUnder = reinterpret_cast<lsquare **>(SquareUnder); /* warning; wtf? */
1193 lsquare
*LSquareUnder
[MAX_SQUARES_UNDER
];
1194 memmove(LSquareUnder
, SquareUnder
, sizeof(SquareUnder
));
1196 if (!game::IsInWilderness()) {
1197 if (!StateIsActivated(POLYMORPHED
)) {
1198 if (!IsPlayer() && !IsTemporary() && !Msg
.IsEmpty()) game::SignalDeath(this, Killer
, Msg
);
1199 if (!(DeathFlags
& DISALLOW_CORPSE
)) CreateCorpse(LSquareUnder
[0]); else SendToHell();
1201 if (!IsPlayer() && !IsTemporary() && !Msg
.IsEmpty()) game::SignalDeath(GetPolymorphBackup(), Killer
, Msg
);
1202 GetPolymorphBackup()->CreateCorpse(LSquareUnder
[0]);
1203 GetPolymorphBackup()->Flags
&= ~C_POLYMORPHED
;
1204 SetPolymorphBackup(0);
1208 if (!IsPlayer() && !IsTemporary() && !Msg
.IsEmpty()) game::SignalDeath(this, Killer
, Msg
);
1213 if (!game::IsInWilderness()) {
1214 for (int c
= 0; c
< GetSquaresUnder(); ++c
) LSquareUnder
[c
]->SetTemporaryEmitation(GetEmitation());
1216 ShowAdventureInfo();
1217 if (!game::IsInWilderness()) {
1218 for(int c
= 0; c
< GetSquaresUnder(); ++c
) LSquareUnder
[c
]->SetTemporaryEmitation(0);
1222 if (!game::IsInWilderness()) {
1223 if (GetSquaresUnder() == 1) {
1224 stack
*StackUnder
= LSquareUnder
[0]->GetStack();
1225 GetStack()->MoveItemsTo(StackUnder
);
1226 doforbodypartswithparam
<stack
*>()(this, &bodypart::DropEquipment
, StackUnder
);
1228 while (GetStack()->GetItems()) GetStack()->GetBottom()->MoveTo(LSquareUnder
[RAND_N(GetSquaresUnder())]->GetStack());
1229 for (int c
= 0; c
< BodyParts
; ++c
) {
1230 bodypart
*BodyPart
= GetBodyPart(c
);
1231 if (BodyPart
) BodyPart
->DropEquipment(LSquareUnder
[RAND_N(GetSquaresUnder())]->GetStack());
1236 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(0);
1238 Flags
&= ~C_IN_NO_MSG_MODE
;
1242 if (!game::IsInWilderness()) {
1243 Ghost
->PutTo(LSquareUnder
[0]->GetPos());
1247 game::TextScreen(CONST_S("Unfortunately you died."), ZERO_V2
, WHITE
, true, true, &game::ShowDeathSmiley
);
1253 void character::AddMissMessage (ccharacter
*Enemy
) const {
1255 if (Enemy
->IsPlayer()) Msg
= GetDescription(DEFINITE
)+" misses you!";
1256 else if (IsPlayer()) Msg
= CONST_S("You miss ")+Enemy
->GetDescription(DEFINITE
)+'!';
1257 else if (CanBeSeenByPlayer() || Enemy
->CanBeSeenByPlayer()) Msg
= GetDescription(DEFINITE
)+" misses "+Enemy
->GetDescription(DEFINITE
)+'!';
1259 ADD_MESSAGE("%s", Msg
.CStr());
1263 void character::AddBlockMessage (ccharacter
*Enemy
, citem
*Blocker
, cfestring
&HitNoun
, truth Partial
) const {
1265 festring BlockVerb
= (Partial
? " to partially block the " : " to block the ")+HitNoun
;
1267 Msg
<< "You manage" << BlockVerb
<< " with your " << Blocker
->GetName(UNARTICLED
) << '!';
1268 } else if (Enemy
->IsPlayer() || Enemy
->CanBeSeenByPlayer()) {
1269 if (CanBeSeenByPlayer())
1270 Msg
<< GetName(DEFINITE
) << " manages" << BlockVerb
<< " with " << GetPossessivePronoun() << ' ' << Blocker
->GetName(UNARTICLED
) << '!';
1272 Msg
<< "Something manages" << BlockVerb
<< " with something!";
1276 ADD_MESSAGE("%s", Msg
.CStr());
1280 void character::AddPrimitiveHitMessage (ccharacter
*Enemy
, cfestring
&FirstPersonHitVerb
,
1281 cfestring
&ThirdPersonHitVerb
, int BodyPart
) const
1284 festring BodyPartDescription
;
1285 if (BodyPart
&& (Enemy
->CanBeSeenByPlayer() || Enemy
->IsPlayer()))
1286 BodyPartDescription
<< " in the " << Enemy
->GetBodyPartName(BodyPart
);
1287 if (Enemy
->IsPlayer())
1288 Msg
<< GetDescription(DEFINITE
) << ' ' << ThirdPersonHitVerb
<< " you" << BodyPartDescription
<< '!';
1289 else if (IsPlayer())
1290 Msg
<< "You " << FirstPersonHitVerb
<< ' ' << Enemy
->GetDescription(DEFINITE
) << BodyPartDescription
<< '!';
1291 else if (CanBeSeenByPlayer() || Enemy
->CanBeSeenByPlayer())
1292 Msg
<< GetDescription(DEFINITE
) << ' ' << ThirdPersonHitVerb
<< ' ' << Enemy
->GetDescription(DEFINITE
) + BodyPartDescription
<< '!';
1295 ADD_MESSAGE("%s", Msg
.CStr());
1299 cchar
*const HitVerb
[] = { "strike", "slash", "stab" };
1300 cchar
*const HitVerb3rdPersonEnd
[] = { "s", "es", "s" };
1303 void character::AddWeaponHitMessage (ccharacter
*Enemy
, citem
*Weapon
, int BodyPart
, truth Critical
) const {
1305 festring BodyPartDescription
;
1307 if (BodyPart
&& (Enemy
->CanBeSeenByPlayer() || Enemy
->IsPlayer()))
1308 BodyPartDescription
<< " in the " << Enemy
->GetBodyPartName(BodyPart
);
1310 int FittingTypes
= 0;
1311 int DamageFlags
= Weapon
->GetDamageFlags();
1314 for (int c
= 0; c
< DAMAGE_TYPES
; ++c
) {
1315 if (1 << c
& DamageFlags
) {
1316 if (!FittingTypes
|| !RAND_N(FittingTypes
+1)) DamageType
= c
;
1321 if (!FittingTypes
) ABORT("No damage flags specified for %s!", Weapon
->CHAR_NAME(UNARTICLED
));
1323 festring NewHitVerb
= Critical
? " critically " : " ";
1324 NewHitVerb
<< HitVerb
[DamageType
];
1325 cchar
*const E
= HitVerb3rdPersonEnd
[DamageType
];
1327 if (Enemy
->IsPlayer()) {
1328 Msg
<< GetDescription(DEFINITE
) << NewHitVerb
<< E
<< " you" << BodyPartDescription
;
1329 if (CanBeSeenByPlayer()) Msg
<< " with " << GetPossessivePronoun() << ' ' << Weapon
->GetName(UNARTICLED
);
1331 } else if (IsPlayer()) {
1332 Msg
<< "You" << NewHitVerb
<< ' ' << Enemy
->GetDescription(DEFINITE
) << BodyPartDescription
<< '!';
1333 } else if(CanBeSeenByPlayer() || Enemy
->CanBeSeenByPlayer()) {
1334 Msg
<< GetDescription(DEFINITE
) << NewHitVerb
<< E
<< ' ' << Enemy
->GetDescription(DEFINITE
) << BodyPartDescription
;
1335 if (CanBeSeenByPlayer()) Msg
<< " with " << GetPossessivePronoun() << ' ' << Weapon
->GetName(UNARTICLED
);
1340 ADD_MESSAGE("%s", Msg
.CStr());
1344 item
*character::GeneralFindItem (ItemCheckerCB chk
) const {
1345 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1347 if (it
&& chk(it
)) return it
;
1353 static truth
isElpuriHead (item
*i
) { return i
->IsHeadOfElpuri(); }
1354 truth
character::HasHeadOfElpuri () const {
1355 if (GeneralFindItem(::isElpuriHead
)) return true;
1356 return combineequipmentpredicates()(this, &item::IsHeadOfElpuri
, 1);
1360 static truth
isPetrussNut (item
*i
) { return i
->IsPetrussNut(); }
1361 truth
character::HasPetrussNut () const {
1362 if (GeneralFindItem(::isPetrussNut
)) return true;
1363 return combineequipmentpredicates()(this, &item::IsPetrussNut
, 1);
1367 static truth
isGoldenEagleShirt (item
*i
) { return i
->IsGoldenEagleShirt(); }
1368 truth
character::HasGoldenEagleShirt () const {
1369 if (GeneralFindItem(::isGoldenEagleShirt
)) return true;
1370 return combineequipmentpredicates()(this, &item::IsGoldenEagleShirt
, 1);
1374 truth
character::HasOmmelBlood () const {
1375 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
)
1376 if (i
->IsKleinBottle() && i
->GetSecondaryMaterial() && i
->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD
) return true;
1378 for (int c
= 0; c
< GetEquipments(); ++c
) {
1379 item
*Item
= GetEquipment(c
);
1381 if (Item
&& Item
->IsKleinBottle() && Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD
) return true;
1383 return false; //combineequipmentpredicates()(this, &item::IsKleinBottle, 1);
1387 truth
character::HasCurdledBlood () const {
1388 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
)
1389 if (i
->IsKleinBottle() && i
->GetSecondaryMaterial() && i
->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD
) return true;
1391 for (int c
= 0; c
< GetEquipments(); ++c
) {
1392 item
*Item
= GetEquipment(c
);
1394 if (Item
&& Item
->IsKleinBottle() && Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD
) return true;
1396 return false; //combineequipmentpredicates()(this, &item::IsKleinBottle, 1);
1400 truth
character::CurdleOmmelBlood () const {
1401 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1402 if (i
->IsKleinBottle() && i
->GetSecondaryMaterial() && i
->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD
) {
1403 i
->ChangeSecondaryMaterial(MAKE_MATERIAL(CURDLED_OMMEL_BLOOD
));
1408 for (int c
= 0; c
< GetEquipments(); ++c
) {
1409 item
*Item
= GetEquipment(c
);
1411 if (Item
&& Item
->IsKleinBottle() && Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD
) {
1412 Item
->ChangeSecondaryMaterial(MAKE_MATERIAL(CURDLED_OMMEL_BLOOD
));
1416 return false; //combineequipmentpredicates()(this, &item::IsKleinBottle, 1);
1420 truth
character::RemoveCurdledOmmelBlood () {
1421 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1422 if (i
->IsKleinBottle() && i
->GetSecondaryMaterial() && i
->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD
) {
1423 (*i
)->RemoveFromSlot();
1429 for (int c
= 0; c
< GetEquipments(); ++c
) {
1430 item
*Item
= GetEquipment(c
);
1432 if (Item
&& Item
->IsKleinBottle() && Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD
) {
1433 Item
->RemoveFromSlot();
1442 int character::GeneralRemoveItem (ItemCheckerCB chk
, truth allItems
) {
1448 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
1450 if (Item
&& chk(Item
)) {
1451 Item
->RemoveFromSlot();
1454 if (!allItems
) return cnt
;
1463 for (int c
= 0; c
< GetEquipments(); ++c
) {
1464 item
*Item
= GetEquipment(c
);
1465 if (Item
&& chk(Item
)) {
1466 Item
->RemoveFromSlot();
1469 if (!allItems
) return cnt
;
1479 static truth
isEncryptedScroll (item
*i
) { return i
->IsEncryptedScroll(); }
1480 truth
character::RemoveEncryptedScroll () { return GeneralRemoveItem(::isEncryptedScroll
) != 0; }
1483 static truth
isMondedrPass (item
*i
) { return i
->IsMondedrPass(); }
1484 truth
character::RemoveMondedrPass () { return GeneralRemoveItem(::isMondedrPass
) != 0; }
1487 static truth
isRingOfThieves (item
*i
) { return i
->IsRingOfThieves(); }
1488 truth
character::RemoveRingOfThieves () { return GeneralRemoveItem(::isRingOfThieves
) != 0; }
1491 truth
character::ReadItem (item
*ToBeRead
) {
1492 if (!ToBeRead
->CanBeRead(this)) {
1493 if (IsPlayer()) ADD_MESSAGE("You can't read this.");
1496 if (!GetLSquareUnder()->IsDark() || game::GetSeeWholeMapCheatMode()) {
1497 if (StateIsActivated(CONFUSED
) && !(RAND()&7)) {
1498 if (!ToBeRead
->IsDestroyable(this)) {
1499 ADD_MESSAGE("You read some words of %s and understand exactly nothing.", ToBeRead
->CHAR_NAME(DEFINITE
));
1501 ADD_MESSAGE("%s is very confusing. Or perhaps you are just too confused?", ToBeRead
->CHAR_NAME(DEFINITE
));
1502 ActivateRandomState(SRC_CONFUSE_READ
, 1000+RAND()%1500);
1503 ToBeRead
->RemoveFromSlot();
1504 ToBeRead
->SendToHell();
1509 if (ToBeRead
->Read(this)) {
1510 if (!game::WizardModeIsActive()) {
1511 /* This AP is used to take the stuff out of backpack */
1518 if (IsPlayer()) ADD_MESSAGE("It's too dark here to read.");
1523 void character::CalculateBurdenState () {
1524 int OldBurdenState
= BurdenState
;
1525 sLong SumOfMasses
= GetCarriedWeight();
1526 sLong CarryingStrengthUnits
= sLong(GetCarryingStrength())*2500;
1527 if (SumOfMasses
> (CarryingStrengthUnits
<< 1) + CarryingStrengthUnits
) BurdenState
= OVER_LOADED
;
1528 else if (SumOfMasses
> CarryingStrengthUnits
<< 1) BurdenState
= STRESSED
;
1529 else if (SumOfMasses
> CarryingStrengthUnits
) BurdenState
= BURDENED
;
1530 else BurdenState
= UNBURDENED
;
1531 if (!IsInitializing() && BurdenState
!= OldBurdenState
) CalculateBattleInfo();
1535 void character::Save (outputfile
&SaveFile
) const {
1536 SaveFile
<< (uShort
)GetType();
1537 Stack
->Save(SaveFile
);
1539 for (int c
= 0; c
< BASE_ATTRIBUTES
; ++c
) SaveFile
<< BaseExperience
[c
];
1541 SaveFile
<< ExpModifierMap
;
1542 SaveFile
<< NP
<< AP
<< Stamina
<< GenerationDanger
<< ScienceTalks
<< CounterToMindWormHatch
;
1543 SaveFile
<< TemporaryState
<< EquipmentState
<< Money
<< GoingTo
<< RegenerationCounter
<< Route
<< Illegal
;
1544 SaveFile
<< CurrentSweatMaterial
;
1545 SaveFile
.Put(!!IsEnabled());
1546 SaveFile
<< HomeData
<< BlocksSinceLastTurn
<< CommandFlags
;
1547 SaveFile
<< WarnFlags
<< (uShort
)Flags
;
1549 for (int c
= 0; c
< BodyParts
; ++c
) SaveFile
<< BodyPartSlot
[c
] << OriginalBodyPartID
[c
];
1551 SaveLinkedList(SaveFile
, TrapData
);
1554 for (int c
= 0; c
< STATES
; ++c
) SaveFile
<< TemporaryStateCounter
[c
];
1558 SaveFile
<< Team
->GetID(); // feuLong
1560 SaveFile
.Put(false);
1563 if (GetTeam() && GetTeam()->GetLeader() == this) SaveFile
.Put(true); else SaveFile
.Put(false);
1565 SaveFile
<< AssignedName
<< PolymorphBackup
;
1567 for (int c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) SaveFile
<< CWeaponSkill
[c
];
1569 SaveFile
<< (uShort
)GetConfig();
1573 void character::Load (inputfile
&SaveFile
) {
1575 Stack
->Load(SaveFile
);
1577 game::AddCharacterID(this, ID
);
1579 for (int c
= 0; c
< BASE_ATTRIBUTES
; ++c
) SaveFile
>> BaseExperience
[c
];
1581 SaveFile
>> ExpModifierMap
;
1582 SaveFile
>> NP
>> AP
>> Stamina
>> GenerationDanger
>> ScienceTalks
>> CounterToMindWormHatch
;
1583 SaveFile
>> TemporaryState
>> EquipmentState
>> Money
>> GoingTo
>> RegenerationCounter
>> Route
>> Illegal
;
1584 SaveFile
>> CurrentSweatMaterial
;
1586 if (!SaveFile
.Get()) Disable();
1588 SaveFile
>> HomeData
>> BlocksSinceLastTurn
>> CommandFlags
;
1589 SaveFile
>> WarnFlags
;
1590 WarnFlags
&= ~WARNED
;
1591 Flags
|= ReadType(uShort
, SaveFile
) & ~ENTITY_FLAGS
;
1593 for (int c
= 0; c
< BodyParts
; ++c
) {
1594 SaveFile
>> BodyPartSlot
[c
] >> OriginalBodyPartID
[c
];
1595 item
*BodyPart
= *BodyPartSlot
[c
];
1596 if (BodyPart
) BodyPart
->Disable();
1599 LoadLinkedList(SaveFile
, TrapData
);
1602 if (Action
) Action
->SetActor(this);
1604 for (int c
= 0; c
< STATES
; ++c
) SaveFile
>> TemporaryStateCounter
[c
];
1606 if (SaveFile
.Get()) SetTeam(game::GetTeam(ReadType(feuLong
, SaveFile
)));
1608 if (SaveFile
.Get()) GetTeam()->SetLeader(this);
1610 SaveFile
>> AssignedName
>> PolymorphBackup
;
1612 for (int c
= 0; c
< AllowedWeaponSkillCategories
; ++c
) SaveFile
>> CWeaponSkill
[c
];
1614 databasecreator
<character
>::InstallDataBase(this, ReadType(uShort
, SaveFile
));
1616 if (IsEnabled() && !game::IsInWilderness()) {
1617 for (int c
= 1; c
< GetSquaresUnder(); ++c
) GetSquareUnder(c
)->SetCharacter(this);
1621 const fearray<festring> < = GetLevelTags();
1623 fprintf(stderr, "====\n");
1624 for (uInt f = 0; f < lt.Size; ++f) fprintf(stderr, " %u: [%s]\n", f, lt[f].CStr());
1630 truth
character::Engrave (cfestring
&What
) {
1631 GetLSquareUnder()->Engrave(What
);
1635 truth
character::MoveRandomly () {
1636 if (!IsEnabled()) return false;
1637 for (int c
= 0; c
< 10; ++c
) {
1638 v2 ToTry
= game::GetMoveVector(RAND()&7);
1639 if (GetLevel()->IsValidPos(GetPos()+ToTry
)) {
1640 lsquare
*Square
= GetNearLSquare(GetPos()+ToTry
);
1641 if (!Square
->IsDangerous(this) && !Square
->IsScary(this) && TryMove(ToTry
, false, false)) return true;
1648 truth
character::TestForPickup (item
*ToBeTested
) const {
1649 if (MakesBurdened(ToBeTested
->GetWeight()+GetCarriedWeight())) return false;
1654 void character::AddScoreEntry (cfestring
&Description
, double Multiplier
, truth AddEndLevel
) const {
1655 if (!game::WizardModeIsReallyActive()) {
1657 if (!HScore
.CheckVersion()) {
1658 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;
1661 festring Desc
= game::GetPlayerName();
1662 Desc
<< ", " << Description
;
1663 if (AddEndLevel
) Desc
<< " in "+(game::IsInWilderness() ? "the world map" : game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex()));
1664 HScore
.Add(sLong(game::GetScore()*Multiplier
), Desc
);
1670 truth
character::CheckDeath (cfestring
&Msg
, ccharacter
*Murderer
, feuLong DeathFlags
) {
1671 if (!IsEnabled()) return true;
1672 if (game::IsSumoWrestling() && IsDead()) {
1673 game::EndSumoWrestling(!!IsPlayer());
1676 if (DeathFlags
& FORCE_DEATH
|| IsDead()) {
1677 if (Murderer
&& Murderer
->IsPlayer() && GetTeam()->GetKillEvilness()) game::DoEvilDeed(GetTeam()->GetKillEvilness());
1678 festring SpecifierMsg
;
1679 int SpecifierParts
= 0;
1680 if (GetPolymorphBackup()) {
1681 SpecifierMsg
<< " polymorphed into ";
1682 id::AddName(SpecifierMsg
, INDEFINITE
);
1685 if (!(DeathFlags
& IGNORE_TRAPS
) && IsStuck()) {
1686 if (SpecifierParts
++) SpecifierMsg
<< " and";
1687 SpecifierMsg
<< " caught in " << GetTrapDescription();
1689 if (GetAction() && !(DeathFlags
& IGNORE_UNCONSCIOUSNESS
&& GetAction()->IsUnconsciousness())) {
1690 festring ActionMsg
= GetAction()->GetDeathExplanation();
1691 if (!ActionMsg
.IsEmpty()) {
1692 if (SpecifierParts
> 1) {
1693 SpecifierMsg
= ActionMsg
<< ',' << SpecifierMsg
;
1695 if (SpecifierParts
) SpecifierMsg
<< " and";
1696 SpecifierMsg
<< ActionMsg
;
1701 festring NewMsg
= Msg
;
1702 if (Murderer
== this) {
1703 SEARCH_N_REPLACE(NewMsg
, "@bkp", CONST_S("by ") + GetPossessivePronoun(false) + " own");
1704 SEARCH_N_REPLACE(NewMsg
, "@bk", CONST_S("by ") + GetObjectPronoun(false) + "self");
1705 SEARCH_N_REPLACE(NewMsg
, "@k", GetObjectPronoun(false) + "self");
1707 SEARCH_N_REPLACE(NewMsg
, "@bkp", CONST_S("by ") + Murderer
->GetName(INDEFINITE
) + "'s");
1708 SEARCH_N_REPLACE(NewMsg
, "@bk", CONST_S("by ") + Murderer
->GetName(INDEFINITE
));
1709 SEARCH_N_REPLACE(NewMsg
, "@k", CONST_S("by ") + Murderer
->GetName(INDEFINITE
));
1711 if (SpecifierParts
) NewMsg
<< " while" << SpecifierMsg
;
1712 if (IsPlayer() && game::WizardModeIsActive()) ADD_MESSAGE("Death message: %s. Score: %d.", NewMsg
.CStr(), game::GetScore());
1713 Die(Murderer
, NewMsg
, DeathFlags
);
1720 truth
character::CheckStarvationDeath (cfestring
&Msg
) {
1721 if (GetNP() < 1 && UsesNutrition()) return CheckDeath(Msg
, 0, FORCE_DEATH
);
1726 void character::ThrowItem (int Direction
, item
*ToBeThrown
) {
1727 if (Direction
> 7) ABORT("Throw in TOO odd direction...");
1728 ToBeThrown
->Fly(this, Direction
, GetAttribute(ARM_STRENGTH
));
1732 void character::HasBeenHitByItem (character
*Thrower
, item
*Thingy
, int Damage
, double ToHitValue
, int Direction
) {
1733 if (IsPlayer()) ADD_MESSAGE("%s hits you.", Thingy
->CHAR_NAME(DEFINITE
));
1734 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s hits %s.", Thingy
->CHAR_NAME(DEFINITE
), CHAR_NAME(DEFINITE
));
1735 int BodyPart
= ChooseBodyPartToReceiveHit(ToHitValue
, DodgeValue
);
1736 int WeaponSkillHits
= Thrower
? CalculateWeaponSkillHits(Thrower
) : 0;
1737 int DoneDamage
= ReceiveBodyPartDamage(Thrower
, Damage
, PHYSICAL_DAMAGE
, BodyPart
, Direction
);
1738 truth Succeeded
= (GetBodyPart(BodyPart
) && HitEffect(Thrower
, Thingy
, Thingy
->GetPos(), THROW_ATTACK
, BodyPart
, Direction
, !DoneDamage
)) || DoneDamage
;
1739 if (Succeeded
&& Thrower
) Thrower
->WeaponSkillHit(Thingy
, THROW_ATTACK
, WeaponSkillHits
);
1740 festring DeathMsg
= CONST_S("killed by a flying ")+Thingy
->GetName(UNARTICLED
);
1741 if (CheckDeath(DeathMsg
, Thrower
)) return;
1743 if (Thrower
->CanBeSeenByPlayer())
1744 DeActivateVoluntaryAction(CONST_S("The attack of ")+Thrower
->GetName(DEFINITE
)+CONST_S(" interrupts you."));
1746 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
1748 DeActivateVoluntaryAction(CONST_S("The hit interrupts you."));
1753 truth
character::DodgesFlyingItem (item
*Item
, double ToHitValue
) {
1754 return !Item
->EffectIsGood() && RAND() % int(100+ToHitValue
/DodgeValue
*100) < 100;
1758 void character::GetPlayerCommand () {
1760 truth HasActed
= false;
1762 game::DrawEverything();
1763 if (game::GetDangerFound()) {
1764 if (game::GetDangerFound() > 500.) {
1765 if (game::GetCausePanicFlag()) {
1766 game::SetCausePanicFlag(false);
1767 BeginTemporaryState(PANIC
, 500+RAND_N(500));
1769 game::AskForEscPress(CONST_S("You are horrified by your situation!"));
1770 } else if (ivanconfig::GetWarnAboutDanger()) {
1771 if (game::GetDangerFound() > 50.) game::AskForEscPress(CONST_S("You sense great danger!"));
1772 else game::AskForEscPress(CONST_S("You sense danger!"));
1774 game::SetDangerFound(0);
1776 game::SetIsInGetCommand(true);
1777 int Key
= GET_KEY();
1778 game::SetIsInGetCommand(false);
1779 if (Key
!= '+' && Key
!= '-' && Key
!= 'M') msgsystem::ThyMessagesAreNowOld(); // gum
1780 truth ValidKeyPressed
= false;
1782 for (int c
= 0; c
< DIRECTION_COMMAND_KEYS
; ++c
) {
1783 if (Key
== game::GetMoveCommandKey(c
)) {
1784 HasActed
= TryMove(ApplyStateModification(game::GetMoveVector(c
)), true, game::PlayerIsRunning());
1785 ValidKeyPressed
= true;
1789 if (!ValidKeyPressed
) {
1790 for (int c
= 0; (cmd
= commandsystem::GetCommand(c
)); ++c
) {
1792 /* Numpad aliases for most commonly used commands */
1793 if (Key
== KEY_DEL
&& cmd
->GetName() == "Eat") Key
= cmd
->GetKey();
1794 if (Key
== KEY_INS
&& cmd
->GetName() == "PickUp") Key
= cmd
->GetKey();
1795 if (Key
== KEY_PLUS
&& cmd
->GetName() == "EquipmentScreen") Key
= cmd
->GetKey();
1797 if (Key
== cmd
->GetKey()) {
1798 if (game::IsInWilderness() && !commandsystem::GetCommand(c
)->IsUsableInWilderness()) {
1799 ADD_MESSAGE("This function cannot be used while in wilderness.");
1800 } else if (!game::WizardModeIsActive() && commandsystem::GetCommand(c
)->IsWizardModeFunction()) {
1801 ADD_MESSAGE("Activate wizardmode to use this function.");
1803 HasActed
= commandsystem::GetCommand(c
)->GetLinkedFunction()(this);
1805 ValidKeyPressed
= true;
1810 if (!ValidKeyPressed
) ADD_MESSAGE("Unknown key. Press '?' for a list of commands.");
1812 game::IncreaseTurn();
1816 void character::Vomit (v2 Pos
, int Amount
, truth ShowMsg
) {
1817 if (!CanVomit()) return;
1819 if (IsPlayer()) ADD_MESSAGE("You vomit.");
1820 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s vomits.", CHAR_NAME(DEFINITE
));
1822 if (VomittingIsUnhealthy()) {
1823 EditExperience(ARM_STRENGTH
, -75, 1 << 9);
1824 EditExperience(LEG_STRENGTH
, -75, 1 << 9);
1827 EditNP(-2500-RAND()%2501);
1828 CheckStarvationDeath(CONST_S("vomited himself to death"));
1830 if (StateIsActivated(PARASITIZED
) && !(RAND() & 7)) {
1831 if (IsPlayer()) ADD_MESSAGE("You notice a dead broad tapeworm among your former stomach contents.");
1832 DeActivateTemporaryState(PARASITIZED
);
1834 if (!game::IsInWilderness()) {
1835 GetNearLSquare(Pos
)->ReceiveVomit(this, liquid::Spawn(GetVomitMaterial(), sLong(sqrt(GetBodyVolume())*Amount
/1000)));
1840 truth
character::Polymorph (character
*NewForm
, int Counter
) {
1841 if (!IsPolymorphable() || (!IsPlayer() && game::IsInWilderness())) {
1846 if (GetAction()) GetAction()->Terminate(false);
1847 NewForm
->SetAssignedName("");
1849 ADD_MESSAGE("Your body glows in a crimson light. You transform into %s!", NewForm
->CHAR_NAME(INDEFINITE
));
1850 else if (CanBeSeenByPlayer())
1851 ADD_MESSAGE("%s glows in a crimson light and %s transforms into %s!", CHAR_NAME(DEFINITE
), GetPersonalPronoun().CStr(), NewForm
->CHAR_NAME(INDEFINITE
));
1853 Flags
|= C_IN_NO_MSG_MODE
;
1854 NewForm
->Flags
|= C_IN_NO_MSG_MODE
;
1855 NewForm
->ChangeTeam(GetTeam());
1856 NewForm
->GenerationDanger
= GenerationDanger
;
1857 NewForm
->mOnEvents
= this->mOnEvents
;
1859 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(NewForm
);
1863 NewForm
->PutToOrNear(Pos
);
1864 NewForm
->SetAssignedName(GetAssignedName());
1865 NewForm
->ActivateTemporaryState(POLYMORPHED
);
1866 NewForm
->SetTemporaryStateCounter(POLYMORPHED
, Counter
);
1868 if (TemporaryStateIsActivated(POLYMORPHED
)) {
1869 NewForm
->SetPolymorphBackup(GetPolymorphBackup());
1870 SetPolymorphBackup(0);
1873 NewForm
->SetPolymorphBackup(this);
1874 Flags
|= C_POLYMORPHED
;
1878 GetStack()->MoveItemsTo(NewForm
->GetStack());
1879 NewForm
->SetMoney(GetMoney());
1880 DonateEquipmentTo(NewForm
);
1881 Flags
&= ~C_IN_NO_MSG_MODE
;
1882 NewForm
->Flags
&= ~C_IN_NO_MSG_MODE
;
1883 NewForm
->CalculateAll();
1887 game::SetPlayer(NewForm
);
1888 game::SendLOSUpdateRequest();
1892 NewForm
->TestWalkability();
1897 void character::BeKicked (character
*Kicker
, item
*Boot
, bodypart
*Leg
, v2 HitPos
, double KickDamage
,
1898 double ToHitValue
, int Success
, int Direction
, truth Critical
, truth ForceHit
)
1901 game::ClearEventData();
1902 game::mActor
= Kicker
;
1903 if (game::RunOnCharEvent(this, CONST_S("before_be_kicked"))) { game::ClearEventData(); return; }
1904 game::ClearEventData();
1906 switch (TakeHit(Kicker
, Boot
, Leg
, HitPos
, KickDamage
, ToHitValue
, Success
, KICK_ATTACK
, Direction
, Critical
, ForceHit
)) {
1910 if (IsEnabled() && !CheckBalance(KickDamage
)) {
1911 if (IsPlayer()) ADD_MESSAGE("The kick throws you off balance.");
1912 else if (Kicker
->IsPlayer()) ADD_MESSAGE("The kick throws %s off balance.", CHAR_DESCRIPTION(DEFINITE
));
1913 v2 FallToPos
= GetPos()+game::GetMoveVector(Direction
);
1914 FallTo(Kicker
, FallToPos
);
1920 /* Return true if still in balance */
1921 truth
character::CheckBalance (double KickDamage
) {
1922 return !CanMove() || IsStuck() || !KickDamage
|| (!IsFlying() && KickDamage
*5 < RAND()%GetSize());
1926 void character::FallTo (character
*GuiltyGuy
, v2 Where
) {
1928 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
1929 int Squares
= CalculateNewSquaresUnder(MoveToSquare
, Where
);
1931 truth NoRoom
= false;
1932 for (int c
= 0; c
< Squares
; ++c
) {
1933 olterrain
*Terrain
= MoveToSquare
[c
]->GetOLTerrain();
1934 if (Terrain
&& !CanMoveOn(Terrain
)) { NoRoom
= true; break; }
1938 if (IsPlayer()) ADD_MESSAGE("You hit your head on the wall.");
1939 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s hits %s head on the wall.", CHAR_NAME(DEFINITE
), GetPossessivePronoun().CStr());
1941 ReceiveDamage(GuiltyGuy
, 1+RAND()%5, PHYSICAL_DAMAGE
, HEAD
);
1942 CheckDeath(CONST_S("killed by hitting a wall due to being kicked @bk"), GuiltyGuy
);
1944 if (IsFreeForMe(MoveToSquare
[0])) Move(Where
, true);
1945 // Place code that handles characters bouncing to each other here
1951 truth
character::CheckCannibalism (cmaterial
*What
) const {
1952 return GetTorso()->GetMainMaterial()->IsSameAs(What
);
1956 void character::StandIdleAI () {
1957 SeekLeader(GetLeader());
1958 if (CheckForEnemies(true, true, true)) return;
1959 if (CheckForUsefulItemsOnGround()) return;
1960 if (FollowLeader(GetLeader())) return;
1961 if (CheckForDoors()) return;
1962 if (MoveTowardsHomePos()) return;
1963 if (CheckSadism()) return;
1968 truth
character::LoseConsciousness (int Counter
, truth HungerFaint
) {
1969 if (!AllowUnconsciousness()) return false;
1970 action
*Action
= GetAction();
1972 if (HungerFaint
&& !Action
->AllowUnconsciousness()) return false;
1973 if (Action
->IsUnconsciousness()) {
1974 static_cast<unconsciousness
*>(Action
)->RaiseCounterTo(Counter
);
1977 Action
->Terminate(false);
1979 if (IsPlayer()) ADD_MESSAGE("You lose consciousness.");
1980 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s loses consciousness.", CHAR_NAME(DEFINITE
));
1981 unconsciousness
*Unconsciousness
= unconsciousness::Spawn(this);
1982 Unconsciousness
->SetCounter(Counter
);
1983 SetAction(Unconsciousness
);
1988 void character::DeActivateVoluntaryAction (cfestring
&Reason
) {
1989 if (GetAction() && GetAction()->IsVoluntary()) {
1991 if (Reason
.GetSize()) ADD_MESSAGE("%s", Reason
.CStr());
1992 if (game::TruthQuestion(CONST_S("Continue ") + GetAction()->GetDescription()+"? [y/N]")) GetAction()->ActivateInDNDMode();
1993 else GetAction()->Terminate(false);
1996 GetAction()->Terminate(false);
2002 void character::ActionAutoTermination () {
2003 if (!GetAction() || !GetAction()->IsVoluntary() || GetAction()->InDNDMode()) return;
2005 for (int c
= 0; c
< game::GetTeams(); ++c
) {
2006 if (GetTeam()->GetRelation(game::GetTeam(c
)) == HOSTILE
) {
2007 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
2009 if (ch
->IsEnabled() && ch
->CanBeSeenBy(this, false, true) && (ch
->CanMove() || ch
->GetPos().IsAdjacent(Pos
)) && ch
->CanAttack()) {
2011 ADD_MESSAGE("%s seems to be hostile.", ch
->CHAR_NAME(DEFINITE
));
2012 if (game::TruthQuestion(CONST_S("Continue ")+GetAction()->GetDescription()+"? [y/N]")) GetAction()->ActivateInDNDMode();
2013 else GetAction()->Terminate(false);
2015 GetAction()->Terminate(false);
2025 truth
character::CheckForEnemies (truth CheckDoors
, truth CheckGround
, truth MayMoveRandomly
, truth RunTowardsTarget
) {
2026 if (!IsEnabled()) return false;
2027 truth HostileCharsNear
= false;
2028 character
*NearestChar
= 0;
2029 sLong NearestDistance
= 0x7FFFFFFF;
2031 for (int c
= 0; c
< game::GetTeams(); ++c
) {
2032 if (GetTeam()->GetRelation(game::GetTeam(c
)) == HOSTILE
) {
2033 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
2035 if (ch
->IsEnabled() && GetAttribute(WISDOM
) < ch
->GetAttackWisdomLimit()) {
2036 sLong ThisDistance
= Max
<sLong
>(abs(ch
->GetPos().X
- Pos
.X
), abs(ch
->GetPos().Y
- Pos
.Y
));
2037 if (ThisDistance
<= GetLOSRangeSquare()) HostileCharsNear
= true;
2038 if ((ThisDistance
< NearestDistance
|| (ThisDistance
== NearestDistance
&& !(RAND() % 3))) &&
2039 ch
->CanBeSeenBy(this, false, IsGoingSomeWhere()) &&
2040 (!IsGoingSomeWhere() || HasClearRouteTo(ch
->GetPos()))) {
2042 NearestDistance
= ThisDistance
;
2050 if (GetAttribute(INTELLIGENCE
) >= 10 || IsSpy()) game::CallForAttention(GetPos(), 100);
2051 if (SpecialEnemySightedReaction(NearestChar
)) return true;
2052 if (IsExtraCoward() && !StateIsActivated(PANIC
) && NearestChar
->GetRelativeDanger(this) >= 0.5) {
2053 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s sees %s.", CHAR_NAME(DEFINITE
), NearestChar
->CHAR_DESCRIPTION(DEFINITE
));
2054 BeginTemporaryState(PANIC
, 500+RAND()%500);
2056 if (!IsRetreating()) {
2057 if (CheckGround
&& NearestDistance
> 2 && CheckForUsefulItemsOnGround(false)) return true;
2058 SetGoingTo(NearestChar
->GetPos());
2060 SetGoingTo(Pos
-((NearestChar
->GetPos()-Pos
)<<4));
2062 return MoveTowardsTarget(true);
2064 character
*Leader
= GetLeader();
2065 if (Leader
== this) Leader
= 0;
2066 if (!Leader
&& IsGoingSomeWhere()) {
2067 if (!MoveTowardsTarget(RunTowardsTarget
)) {
2071 if (!IsEnabled()) return true;
2072 if (GetPos() == GoingTo
) TerminateGoingTo();
2076 if ((!Leader
|| (Leader
&& !IsGoingSomeWhere())) && HostileCharsNear
) {
2077 if (CheckDoors
&& CheckForDoors()) return true;
2078 if (CheckGround
&& CheckForUsefulItemsOnGround()) return true;
2079 if (MayMoveRandomly
&& MoveRandomly()) return true; // one has heard that an enemy is near but doesn't know where
2087 truth
character::CheckForDoors () {
2088 if (!CanOpen() || !IsEnabled()) return false;
2089 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
2090 lsquare
*Square
= GetNeighbourLSquare(d
);
2091 if (Square
&& Square
->GetOLTerrain() && Square
->GetOLTerrain()->Open(this)) return true;
2097 truth
character::CheckForUsefulItemsOnGround (truth CheckFood
) {
2098 if (StateIsActivated(PANIC
) || !IsEnabled()) return false;
2099 itemvector ItemVector
;
2100 GetStackUnder()->FillItemVector(ItemVector
);
2101 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) {
2102 if (ItemVector
[c
]->CanBeSeenBy(this) && ItemVector
[c
]->IsPickable(this)) {
2103 if (!(CommandFlags
& DONT_CHANGE_EQUIPMENT
) && TryToEquip(ItemVector
[c
])) return true;
2104 if (CheckFood
&& UsesNutrition() && !CheckIfSatiated() && TryToConsume(ItemVector
[c
])) return true;
2105 if (IsRangedAttacker() && (ItemVector
[c
])->GetThrowItemTypes() && TryToAddToInventory(ItemVector
[c
])) return true;
2112 truth
character::TryToAddToInventory (item
*Item
) {
2113 if (!(GetBurdenState() > STRESSED
) || !CanUseEquipment() || Item
->GetSquaresUnder() != 1) return false;
2114 room
*Room
= GetRoom();
2115 if (!Room
|| Room
->PickupItem(this, Item
, 1)) {
2116 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s picks up %s from the ground.", CHAR_NAME(DEFINITE
), Item
->CHAR_NAME(INDEFINITE
));
2117 Item
->MoveTo(GetStack());
2125 truth
character::CheckInventoryForItemToThrow (item
*ToBeChecked
) {
2126 return (ToBeChecked
->GetThrowItemTypes() & GetWhatThrowItemTypesToThrow()) ? true : false; //hehe
2130 truth
character::CheckThrowItemOpportunity () {
2131 if (!IsRangedAttacker() || !CanThrow() || !IsHumanoid() || !IsSmall() || !IsEnabled()) return false; // total gum
2132 //fprintf(stderr, "character::CheckThrowItemOpportunity...\n");
2134 // (1) - Acquire target as nearest enemy
2135 // (2) - Check that this enemy is in range, and is in appropriate direction; no friendly fire!
2136 // (3) - check inventory for throwing weapon, select this weapon
2137 // (4) - throw item in direction where the enemy is
2139 //Check the visible area for hostiles
2140 int ThrowDirection
= 0;
2141 int TargetFound
= 0;
2144 int RangeMax
= GetLOSRange();
2145 int CandidateDirections
[7] = {0, 0, 0, 0, 0, 0, 0};
2146 int HostileFound
= 0;
2147 item
*ToBeThrown
= 0;
2148 level
*Level
= GetLevel();
2150 for (int r
= 1; r
<= RangeMax
; ++r
) {
2151 for (int dir
= 0; dir
< 8; ++dir
) {
2154 case 0: TestPos
= v2(Pos
.X
-r
, Pos
.Y
-r
); break;
2155 case 1: TestPos
= v2(Pos
.X
, Pos
.Y
-r
); break;
2156 case 2: TestPos
= v2(Pos
.X
+r
, Pos
.Y
-r
); break;
2157 case 3: TestPos
= v2(Pos
.X
-r
, Pos
.Y
); break;
2158 case 4: TestPos
= v2(Pos
.X
+r
, Pos
.Y
); break;
2159 case 5: TestPos
= v2(Pos
.X
-r
, Pos
.Y
+r
); break;
2160 case 6: TestPos
= v2(Pos
.X
, Pos
.Y
+r
); break;
2161 case 7: TestPos
= v2(Pos
.X
+r
, Pos
.Y
+r
); break;
2163 if (Level
->IsValidPos(TestPos
)) {
2164 square
*TestSquare
= GetNearSquare(TestPos
);
2165 character
*Dude
= TestSquare
->GetCharacter();
2167 if (Dude
&& Dude
->IsEnabled() && Dude
->CanBeSeenBy(this, false, true)) {
2168 if (GetRelation(Dude
) != HOSTILE
) CandidateDirections
[dir
] = BLOCKED
;
2169 else if (GetRelation(Dude
) == HOSTILE
&& CandidateDirections
[dir
] != BLOCKED
) {
2170 //then load this candidate position direction into the vector of possible throw directions
2171 CandidateDirections
[dir
] = SUCCESS
;
2180 for (int dir
= 0; dir
< 8; ++dir
) {
2181 if (CandidateDirections
[dir
] == SUCCESS
&& !TargetFound
) {
2182 ThrowDirection
= dir
;
2187 if (!TargetFound
) return false;
2191 //fprintf(stderr, "throw: has target.\n");
2192 // check inventory for throwing weapon
2193 itemvector ItemVector
;
2194 GetStack()->FillItemVector(ItemVector
);
2195 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) {
2196 if (ItemVector
[c
]->IsThrowingWeapon()) {
2197 ToBeThrown
= ItemVector
[c
];
2201 if (!ToBeThrown
) return false;
2202 //fprintf(stderr, "throw: has throwing weapon.\n");
2203 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s throws %s.", CHAR_NAME(DEFINITE
), ToBeThrown
->CHAR_NAME(INDEFINITE
));
2204 ThrowItem(ThrowDirection
, ToBeThrown
);
2205 EditExperience(ARM_STRENGTH
, 75, 1<<8);
2206 EditExperience(DEXTERITY
, 75, 1<<8);
2207 EditExperience(PERCEPTION
, 75, 1<<8);
2215 truth
character::CheckAIZapOpportunity () {
2216 if (/*!IsRangedAttacker() || */ !CanZap() || !IsHumanoid() || !IsSmall() || !IsEnabled()) return false; // total gum
2218 // (1) - Acquire target as nearest enemy
2219 // (2) - Check that this enemy is in range, and is in appropriate direction; no friendly fire!
2220 // (3) - check inventory for zappable item
2221 // (4) - zap item in direction where the enemy is
2222 //Check the rest of the visible area for hostiles
2225 int SensibleRange
= 5;
2226 int RangeMax
= GetLOSRange();
2227 if (RangeMax
< SensibleRange
) SensibleRange
= RangeMax
;
2228 int CandidateDirections
[7] = {0, 0, 0, 0, 0, 0, 0};
2229 int HostileFound
= 0;
2230 int ZapDirection
= 0;
2231 int TargetFound
= 0;
2232 item
*ToBeZapped
= 0;
2233 level
*Level
= GetLevel();
2235 for (int r
= 2; r
<= SensibleRange
; ++r
) {
2236 for (int dir
= 0; dir
< 8; ++dir
) {
2238 case 0: TestPos
= v2(Pos
.X
-r
, Pos
.Y
-r
); break;
2239 case 1: TestPos
= v2(Pos
.X
, Pos
.Y
-r
); break;
2240 case 2: TestPos
= v2(Pos
.X
+r
, Pos
.Y
-r
); break;
2241 case 3: TestPos
= v2(Pos
.X
-r
, Pos
.Y
); break;
2242 case 4: TestPos
= v2(Pos
.X
+r
, Pos
.Y
); break;
2243 case 5: TestPos
= v2(Pos
.X
-r
, Pos
.Y
+r
); break;
2244 case 6: TestPos
= v2(Pos
.X
, Pos
.Y
+r
); break;
2245 case 7: TestPos
= v2(Pos
.X
+r
, Pos
.Y
+r
); break;
2247 if (Level
->IsValidPos(TestPos
)) {
2248 square
*TestSquare
= GetNearSquare(TestPos
);
2249 character
*Dude
= TestSquare
->GetCharacter();
2251 if (Dude
&& Dude
->IsEnabled() && Dude
->CanBeSeenBy(this, false, true)) {
2252 if (GetRelation(Dude
) != HOSTILE
) CandidateDirections
[dir
] = BLOCKED
;
2253 else if (GetRelation(Dude
) == HOSTILE
&& CandidateDirections
[dir
] != BLOCKED
) {
2254 //then load this candidate position direction into the vector of possible zap directions
2255 CandidateDirections
[dir
] = SUCCESS
;
2264 for (int dir
= 0; dir
< 8; ++dir
) {
2265 if (CandidateDirections
[dir
] == SUCCESS
&& !TargetFound
) {
2271 if (!TargetFound
) return false;
2275 // check inventory for zappable item
2276 itemvector ItemVector
;
2277 GetStack()->FillItemVector(ItemVector
);
2278 for (unsigned int c
= 0; c
< ItemVector
.size(); ++c
) {
2279 if (ItemVector
[c
]->GetMinCharges() > 0 && ItemVector
[c
]->GetPrice()) {
2280 // bald-faced gum solution for choosing zappables that have shots left.
2281 // MinCharges needs to be replaced. Empty wands have zero price!
2282 ToBeZapped
= ItemVector
[c
];
2286 if (!ToBeZapped
) return false;
2287 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s zaps %s.", CHAR_NAME(DEFINITE
), ToBeZapped
->CHAR_NAME(INDEFINITE
));
2288 if (ToBeZapped
->Zap(this, GetPos(), ZapDirection
)) {
2289 EditAP(-100000/APBonus(GetAttribute(PERCEPTION
)));
2299 truth
character::FollowLeader (character
*Leader
) {
2300 if (!Leader
|| Leader
== this || !IsEnabled()) return false;
2301 if (CommandFlags
& FOLLOW_LEADER
&& Leader
->CanBeSeenBy(this) && Leader
->SquareUnderCanBeSeenBy(this, true)) {
2302 v2 Distance
= GetPos()-GoingTo
;
2303 if (abs(Distance
.X
) <= 2 && abs(Distance
.Y
) <= 2) return false;
2304 return MoveTowardsTarget(false);
2306 if (IsGoingSomeWhere()) {
2307 if (!MoveTowardsTarget(true)) {
2317 void character::SeekLeader (ccharacter
*Leader
) {
2318 if (Leader
&& Leader
!= this) {
2319 if (Leader
->CanBeSeenBy(this) && (Leader
->SquareUnderCanBeSeenBy(this, true) || !IsGoingSomeWhere())) {
2320 if (CommandFlags
& FOLLOW_LEADER
) SetGoingTo(Leader
->GetPos());
2321 } else if (!IsGoingSomeWhere()) {
2322 team
*Team
= GetTeam();
2323 for (std::list
<character
*>::const_iterator i
= Team
->GetMember().begin(); i
!= Team
->GetMember().end(); ++i
) {
2325 if (ch
->IsEnabled() && ch
->GetID() != GetID() &&
2326 (CommandFlags
& FOLLOW_LEADER
) == (ch
->CommandFlags
& FOLLOW_LEADER
) && ch
->CanBeSeenBy(this)) {
2327 v2 Pos
= ch
->GetPos();
2328 v2 Distance
= GetPos()-Pos
;
2329 if (abs(Distance
.X
) > 2 && abs(Distance
.Y
) > 2) {
2340 int character::GetMoveEase () const {
2341 switch (BurdenState
) {
2343 case STRESSED
: return 50;
2344 case BURDENED
: return 75;
2345 case UNBURDENED
: return 100;
2351 int character::GetLOSRange () const {
2352 if (!game::IsInWilderness()) return GetAttribute(PERCEPTION
)*GetLevel()->GetLOSModifier()/48;
2357 truth
character::Displace (character
*Who
, truth Forced
) {
2358 if (GetBurdenState() == OVER_LOADED
) {
2360 cchar
*CrawlVerb
= StateIsActivated(LEVITATION
) ? "float" : "crawl";
2361 ADD_MESSAGE("You try very hard to %s forward. But your load is too heavy.", CrawlVerb
);
2368 double Danger
= GetRelativeDanger(Who
);
2369 int PriorityDifference
= Limit(GetDisplacePriority()-Who
->GetDisplacePriority(), -31, 31);
2371 if (IsPlayer()) ++PriorityDifference
;
2372 else if (Who
->IsPlayer()) --PriorityDifference
;
2374 if (PriorityDifference
>= 0) Danger
*= 1 << PriorityDifference
;
2375 else Danger
/= 1 << -PriorityDifference
;
2377 if (IsSmall() && Who
->IsSmall() &&
2378 (Forced
|| Danger
> 1.0 || !(Who
->IsPlayer() || Who
->IsBadPath(GetPos()))) &&
2379 !IsStuck() && !Who
->IsStuck() && (!Who
->GetAction() || Who
->GetAction()->TryDisplace()) &&
2380 CanMove() && Who
->CanMove() && Who
->CanMoveOn(GetLSquareUnder())) {
2381 if (IsPlayer()) ADD_MESSAGE("You displace %s!", Who
->CHAR_DESCRIPTION(DEFINITE
));
2382 else if (Who
->IsPlayer()) ADD_MESSAGE("%s displaces you!", CHAR_DESCRIPTION(DEFINITE
));
2383 else if (CanBeSeenByPlayer() || Who
->CanBeSeenByPlayer()) ADD_MESSAGE("%s displaces %s!", CHAR_DESCRIPTION(DEFINITE
), Who
->CHAR_DESCRIPTION(DEFINITE
));
2384 lsquare
*OldSquareUnder1
[MAX_SQUARES_UNDER
];
2385 lsquare
*OldSquareUnder2
[MAX_SQUARES_UNDER
];
2386 for (int c
= 0; c
< GetSquaresUnder(); ++c
) OldSquareUnder1
[c
] = GetLSquareUnder(c
);
2387 for (int c
= 0; c
< Who
->GetSquaresUnder(); ++c
) OldSquareUnder2
[c
] = Who
->GetLSquareUnder(c
);
2389 v2 WhoPos
= Who
->GetPos();
2394 EditAP(-GetMoveAPRequirement(GetSquareUnder()->GetEntryDifficulty()) - 500);
2395 EditNP(-12*GetSquareUnder()->GetEntryDifficulty());
2396 EditExperience(AGILITY
, 75, GetSquareUnder()->GetEntryDifficulty() << 7);
2397 if (IsPlayer()) ShowNewPosInfo();
2398 if (Who
->IsPlayer()) Who
->ShowNewPosInfo();
2399 SignalStepFrom(OldSquareUnder1
);
2400 Who
->SignalStepFrom(OldSquareUnder2
);
2404 ADD_MESSAGE("%s resists!", Who
->CHAR_DESCRIPTION(DEFINITE
));
2413 void character::SetNP (sLong What
) {
2414 int OldState
= GetHungerState();
2417 int NewState
= GetHungerState();
2418 if (NewState
== STARVING
&& OldState
> STARVING
) DeActivateVoluntaryAction(CONST_S("You are getting really hungry."));
2419 else if (NewState
== VERY_HUNGRY
&& OldState
> VERY_HUNGRY
) DeActivateVoluntaryAction(CONST_S("You are getting very hungry."));
2420 else if (NewState
== HUNGRY
&& OldState
> HUNGRY
) DeActivateVoluntaryAction(CONST_S("You are getting hungry."));
2425 void character::ShowNewPosInfo () const {
2426 msgsystem::EnterBigMessageMode();
2429 if (ivanconfig::GetAutoCenterMap()) {
2430 game::UpdateCameraX();
2431 game::UpdateCameraY();
2433 if (Pos
.X
< game::GetCamera().X
+3 || Pos
.X
>= game::GetCamera().X
+game::GetScreenXSize()-3) game::UpdateCameraX();
2434 if (Pos
.Y
< game::GetCamera().Y
+3 || Pos
.Y
>= game::GetCamera().Y
+game::GetScreenYSize()-3) game::UpdateCameraY();
2437 game::SendLOSUpdateRequest();
2438 game::DrawEverythingNoBlit();
2441 if (!game::IsInWilderness()) {
2442 if (GetLSquareUnder()->IsDark() && !game::GetSeeWholeMapCheatMode()) ADD_MESSAGE("It's dark in here!");
2444 GetLSquareUnder()->ShowSmokeMessage();
2445 itemvectorvector PileVector
;
2446 GetStackUnder()->Pile(PileVector
, this, CENTER
);
2448 if (PileVector
.size()) {
2449 truth Feel
= !GetLSquareUnder()->IsTransparent() || GetLSquareUnder()->IsDark();
2451 if (PileVector
.size() == 1) {
2453 ADD_MESSAGE("You feel %s lying here.", PileVector
[0][0]->GetName(INDEFINITE
, PileVector
[0].size()).CStr());
2455 if (ivanconfig::GetShowFullItemDesc() && PileVector
[0][0]->AllowDetailedDescription()) {
2458 PileVector
[0][0]->AddInventoryEntry(PLAYER
, text
, PileVector
[0].size(), true);
2459 //fprintf(stderr, "invdsc : [%s]\n", text.CStr());
2460 ADD_MESSAGE("%s %s lying here.", text
.CStr(), PileVector
[0].size() == 1 ? "is" : "are");
2462 ADD_MESSAGE("%s %s lying here.", PileVector
[0][0]->GetName(INDEFINITE
, PileVector
[0].size()).CStr(), PileVector
[0].size() == 1 ? "is" : "are");
2465 fprintf(stderr, "description: [%s]\n", PileVector[0][0]->GetDescription(INDEFINITE).CStr());
2466 fprintf(stderr, "strength : [%s]\n", PileVector[0][0]->GetStrengthValueDescription());
2467 fprintf(stderr, "basetohit : [%s]\n", PileVector[0][0]->GetBaseToHitValueDescription());
2468 fprintf(stderr, "baseblock : [%s]\n", PileVector[0][0]->GetBaseBlockValueDescription());
2469 fprintf(stderr, "extdsc : [%s]\n", PileVector[0][0]->GetExtendedDescription().CStr());
2474 for (uInt c
= 0; c
< PileVector
.size(); ++c
) {
2475 if ((Items
+= PileVector
[c
].size()) > 3) break;
2478 if (Feel
) ADD_MESSAGE("You feel several items lying here.");
2479 else ADD_MESSAGE("Several items are lying here.");
2481 if (Feel
) ADD_MESSAGE("You feel a few items lying here.");
2482 else ADD_MESSAGE("A few items are lying here.");
2488 GetLSquareUnder()->GetSideItemDescription(SideItems
);
2490 if (!SideItems
.IsEmpty()) ADD_MESSAGE("There is %s.", SideItems
.CStr());
2492 if (GetLSquareUnder()->HasEngravings()) {
2493 if (CanRead()) ADD_MESSAGE("Something has been engraved here: \"%s\"", GetLSquareUnder()->GetEngraved());
2494 else ADD_MESSAGE("Something has been engraved here.");
2498 msgsystem::LeaveBigMessageMode();
2502 void character::Hostility (character
*Enemy
) {
2503 if (Enemy
== this || !Enemy
|| !Team
|| !Enemy
->Team
) return;
2504 if (Enemy
->IsMasochist() && GetRelation(Enemy
) == FRIEND
) return;
2505 if (!IsAlly(Enemy
)) {
2506 GetTeam()->Hostility(Enemy
->GetTeam());
2507 } else if (IsPlayer() && !Enemy
->IsPlayer()) {
2508 // I believe both may be players due to polymorph feature...
2509 if (Enemy
->CanBeSeenByPlayer()) ADD_MESSAGE("%s becomes enraged.", Enemy
->CHAR_NAME(DEFINITE
));
2510 Enemy
->ChangeTeam(game::GetTeam(BETRAYED_TEAM
));
2515 stack
*character::GetGiftStack () const {
2516 if (GetLSquareUnder()->GetRoomIndex() && !GetLSquareUnder()->GetRoom()->AllowDropGifts()) return GetStack();
2517 return GetStackUnder();
2521 truth
character::MoveRandomlyInRoom () {
2522 for (int c
= 0; c
< 10; ++c
) {
2523 v2 ToTry
= game::GetMoveVector(RAND()&7);
2524 if (GetLevel()->IsValidPos(GetPos()+ToTry
)) {
2525 lsquare
*Square
= GetNearLSquare(GetPos()+ToTry
);
2526 if (!Square
->IsDangerous(this) && !Square
->IsScary(this) &&
2527 (!Square
->GetOLTerrain() || !Square
->GetOLTerrain()->IsDoor()) &&
2528 TryMove(ToTry
, false, false)) return true;
2535 //#define dirlogf(...) do { fprintf(stderr, __VA_ARGS__); } while (0)
2536 #define dirlogf(...) ((void)0)
2563 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
};
2564 static const bool orthoDir
[8] = { false, true, false, true, true, false, true, false };
2567 // only for ortho moveDir
2568 static inline truth
IsDirExcluded (int moveDir
, int dir
) {
2569 if (moveDir
== dir
) return true;
2571 case MDIR_UP
: return (dir
== MDIR_UP_LEFT
|| dir
== MDIR_UP_RIGHT
);
2572 case MDIR_LEFT
: return (dir
== MDIR_UP_LEFT
|| dir
== MDIR_DOWN_LEFT
);
2573 case MDIR_RIGHT
: return (dir
== MDIR_UP_RIGHT
|| dir
== MDIR_DOWN_RIGHT
);
2574 case MDIR_DOWN
: return (dir
== MDIR_DOWN_LEFT
|| dir
== MDIR_DOWN_RIGHT
);
2580 truth
character::IsPassableSquare (int x
, int y
) const {
2581 if (x
>= 0 && y
>= 0) {
2582 area
*ca
= GetSquareUnder()->GetArea();
2585 if (x
>= ca
->GetXSize() || y
>= ca
->GetYSize()) return false;
2586 sq
= static_cast<lsquare
*>(ca
->GetSquare(x
, y
));
2587 return sq
&& CanMoveOn(sq
);
2593 void character::CountPossibleMoveDirs (cv2 pos
, int *odirs
, int *ndirs
, int exclideDir
) const {
2594 if (odirs
) *odirs
= 0;
2595 if (ndirs
) *ndirs
= 0;
2596 for (int f
= 0; f
< 8; ++f
) {
2597 if (!IsDirExcluded(exclideDir
, f
)) {
2598 if (IsPassableSquare(pos
+game::GetMoveVector(f
))) {
2600 if (odirs
) ++(*odirs
);
2602 if (ndirs
) ++(*ndirs
);
2611 * in corridor (for orto-dirs):
2612 * count dirs excluding ortho-dir we going:
2613 * if there is one or less ortho-dirs and one or less non-ortho-dirs, we are in corridor
2615 // only for ortho-dirs
2616 truth
character::IsInCorridor (int x
, int y
, int moveDir
) const {
2619 dirlogf("IsInCorridor(%d,%d,%d)\n", x
, y
, moveDir
);
2621 moveDir
= (moveDir
>= 0 && moveDir
< MDIR_STAND
? revDir
[moveDir
] : -1);
2622 dirlogf(" reversedDir: %d\n", moveDir
);
2623 CountPossibleMoveDirs(v2(x
, y
), &od
, &nd
, moveDir
);
2624 dirlogf(" possibleDirs: (%d:%d)\n", od
, nd
);
2625 dirlogf(" IsInCorridor: %s\n", ((od
<= 1 && nd
<= 1) ? "yes" : "no"));
2626 return (od
<= 1 && nd
<= 1);
2630 cv2
character::GetDiagonalForDirs (int moveDir
, int newDir
) const {
2634 case MDIR_LEFT
: return game::GetMoveVector(MDIR_UP_LEFT
);
2635 case MDIR_RIGHT
: return game::GetMoveVector(MDIR_UP_RIGHT
);
2640 case MDIR_LEFT
: return game::GetMoveVector(MDIR_DOWN_LEFT
);
2641 case MDIR_RIGHT
: return game::GetMoveVector(MDIR_DOWN_RIGHT
);
2646 case MDIR_UP
: return game::GetMoveVector(MDIR_UP_LEFT
);
2647 case MDIR_DOWN
: return game::GetMoveVector(MDIR_DOWN_LEFT
);
2652 case MDIR_UP
: return game::GetMoveVector(MDIR_UP_RIGHT
);
2653 case MDIR_DOWN
: return game::GetMoveVector(MDIR_DOWN_RIGHT
);
2657 ABORT("wtf in character::GetDiagonalForDirs()");
2661 truth
character::IsInTunnelDeadEnd () const {
2664 CountPossibleMoveDirs(GetPos(), &od
, &nd
, -1);
2665 return (od
<= 1 && nd
== 0);
2670 * try to walk in the given dir
2671 * can do two steps without a turn and still in corridor?
2675 * go in non-ortho dir, set prevdir to last ortho-dir from corridor tracing
2677 // only for ortho-dirs; assume that the char is in corridor
2678 int character::CheckCorridorMove (v2
&moveVector
, cv2 pos
, int moveDir
, truth
*markAsTurn
) const {
2679 v2
ps1(pos
+(moveVector
= game::GetMoveVector(moveDir
)));
2681 if (markAsTurn
) *markAsTurn
= true;
2683 if (IsPassableSquare(ps1
)) {
2684 // we can do first step in the given dir
2685 // check if we will be in corridor after it
2686 dirlogf("CheckCorridorMove: can do first step\n");
2687 if (IsInCorridor(ps1
, moveDir
)) {
2688 // check second step
2689 v2
ps2(ps1
+moveVector
);
2691 dirlogf("CheckCorridorMove: still in corridor after the first step\n");
2692 if (IsPassableSquare(ps2
)) {
2693 // can do second step
2694 dirlogf("CheckCorridorMove: can do second step\n");
2697 // can't do second step; but we still in corridor, so we should make a turn
2698 int newDir
= -1; // direction to turn
2700 for (int f
= 0; f
< MDIR_STAND
; ++f
) {
2701 if (f
!= moveDir
&& orthoDir
[f
] && f
!= revDir
[moveDir
] && IsPassableSquare(ps1
+game::GetMoveVector(f
))) {
2706 dirlogf("CheckCorridorMove: can't do second step; moveDir=%d; newDir=%d\n", moveDir
, newDir
);
2708 // dead end, will stop
2709 //ABORT("wtd in character::CheckCorridorMove()");
2712 // we should do diagonal move
2713 moveVector
= GetDiagonalForDirs(moveDir
, newDir
);
2714 // if this is 'one-tile-turn', we should not change the direction to newDir
2715 if (IsPassableSquare(ps1
+game::GetMoveVector(newDir
)+game::GetMoveVector(moveDir
))) {
2716 // yes, this is 'one-tile-turn'
2717 dirlogf("CheckCorridorMove: one-tile-turn, don't change dir\n");
2725 * 'g'o right: should stop at '*', but it just goes right
2727 if (markAsTurn
) *markAsTurn
= IsInCorridor(ps1
+game::GetMoveVector(newDir
), newDir
);
2734 dirlogf("CheckCorridorMove: can do one or two steps; move forward\n");
2735 // can do one or two steps; just move forward
2738 dirlogf("CheckCorridorMove: dead end\n");
2739 // can't go, assume invalid direction
2744 void character::GoOn (go
*Go
, truth FirstStep
) {
2745 dirlogf("=== character::GoOn; dir=%d; pos=(%d,%d) ===\n", Go
->GetDirection(), GetPos().X
, GetPos().Y
);
2747 dirlogf("FirstStep\n");
2748 mPrevMoveDir
= Go
->GetDirection();
2749 Go
->SetIsWalkingInOpen(!IsInCorridor(Go
->GetDirection()));
2752 v2 MoveVector
= ApplyStateModification(game::GetMoveVector(Go
->GetDirection()));
2753 lsquare
*MoveToSquare
[MAX_SQUARES_UNDER
];
2754 int Squares
= CalculateNewSquaresUnder(MoveToSquare
, GetPos()+MoveVector
);
2755 int moveDir
= game::MoveVectorToDirection(MoveVector
);
2757 if (!Squares
|| !CanMoveOn(MoveToSquare
[0])) {
2758 dirlogf("just can't move\n");
2759 Go
->Terminate(false);
2764 if (!Go
->GetPrevWasTurn() && Go
->IsWalkingInOpen() != !IsInCorridor(GetPos(), moveDir
)) {
2765 dirlogf("moved to/from open place\n");
2766 Go
->Terminate(false);
2770 uInt OldRoomIndex
= GetLSquareUnder()->GetRoomIndex();
2771 uInt CurrentRoomIndex
= MoveToSquare
[0]->GetRoomIndex();
2773 if (OldRoomIndex
&& (CurrentRoomIndex
!= OldRoomIndex
)) {
2774 // room about to be changed, stop here
2775 dirlogf("room about to be changed\n");
2776 Go
->Terminate(false);
2780 // stop near the dangerous square
2781 for (int c
= 0; c
< Squares
; ++c
) {
2782 if ((MoveToSquare
[c
]->GetCharacter() && GetTeam() != MoveToSquare
[c
]->GetCharacter()->GetTeam()) ||
2783 MoveToSquare
[c
]->IsDangerous(this)) {
2784 dirlogf("sense the danger\n");
2785 Go
->Terminate(false);
2791 if (moveDir
!= Go
->GetDirection()) {
2792 // state modified the direction, move and stop
2793 dirlogf("move affected by state\n");
2794 if (TryMove(MoveVector
, true, game::PlayerIsRunning())) {
2795 game::DrawEverything();
2796 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
2798 Go
->Terminate(false);
2802 truth doStop
= false, markAsTurn
= false;
2805 // continuous walking
2806 if (Go
->IsWalkingInOpen() || !orthoDir
[moveDir
]) {
2807 // walking in open space or diagonal walking
2808 v2
newPos(GetPos()+MoveVector
);
2809 int ood
, ond
, nod
, nnd
;
2811 * open: stop if # of possible dirs in next step != # of possible dirs in current step
2812 * (or next step is in corridor)
2814 dirlogf("open walking\n");
2815 if (IsInCorridor(newPos
, moveDir
)) {
2816 // trying to enter the corridor, stop right here
2817 dirlogf("entering the corridor\n");
2818 Go
->Terminate(false);
2821 CountPossibleMoveDirs(GetPos(), &ood
, &ond
);
2822 CountPossibleMoveDirs(newPos
, &nod
, &nnd
);
2823 if (ood
!= nod
|| ond
!= nnd
) {
2824 // # of directions to walk to changed, stop right here
2825 dirlogf("# of directions changed from (%d:%d) to (%d:%d)\n", ood
, ond
, nod
, nnd
);
2826 //Go->Terminate(false);
2830 // ok, we can do this move
2832 // ortho-walking thru the corridor
2833 int newDir
= CheckCorridorMove(MoveVector
, GetPos(), moveDir
, &markAsTurn
);
2836 // ah, something weird; stop right here
2837 Go
->Terminate(false);
2840 Go
->SetDirection(newDir
); // perform possible turn
2843 // first step, just do it
2845 // now try to perform the move
2847 dirlogf("trying to make the move\n");
2848 // stop near the dangerous square (fuckin' copypasta)
2849 Squares
= CalculateNewSquaresUnder(MoveToSquare
, GetPos()+MoveVector
);
2851 for (int c
= 0; c
< Squares
; ++c
) {
2852 if ((MoveToSquare
[c
]->GetCharacter() && GetTeam() != MoveToSquare
[c
]->GetCharacter()->GetTeam()) ||
2853 MoveToSquare
[c
]->IsDangerous(this)) {
2854 dirlogf(" danger!\n");
2855 Go
->Terminate(false);
2861 square
*BeginSquare
= GetSquareUnder();
2862 uInt OldRoomIndex
= GetLSquareUnder()->GetRoomIndex();
2863 uInt CurrentRoomIndex
= MoveToSquare
[0]->GetRoomIndex();
2865 // stop on the square with something interesting
2868 area
*ca
= GetSquareUnder()->GetArea();
2869 v2 npos
= GetPos()+MoveVector
;
2871 for (int f
= 0; f
< MDIR_STAND
; ++f
) {
2872 v2 np
= npos
+game::GetMoveVector(f
);
2874 if (np
.X
>= 0 && np
.Y
>= 0 && np
.X
< ca
->GetXSize() && np
.Y
< ca
->GetYSize()) {
2875 lsquare
*sq
= static_cast<lsquare
*>(ca
->GetSquare(np
.X
, np
.Y
));
2876 olterrain
*terra
= sq
->GetOLTerrain();
2879 dirlogf("** OK terra at %d; door: %s; seen: %s\n", f
, (terra
->IsDoor() ? "yes" : "no"), (sq
->IsGoSeen() ? "yes" : "no"));
2880 if (terra
->IsDoor()) {
2881 if (ivanconfig::GetStopOnSeenDoors() || !sq
->IsGoSeen()) {
2882 dirlogf(" *** stop near the door\n");
2892 for (int c
= 0; c
< Squares
; ++c
) {
2893 if (MoveToSquare
[c
]->GetStack()->HasSomethingFunny(this, ivanconfig::GetStopOnCorpses(), ivanconfig::GetStopOnSeenItems())) {
2894 dirlogf(" stepped near something interesting\n");
2902 Go
->SetPrevWasTurn(markAsTurn
&& MoveVector
.X
&& MoveVector
.Y
); // diagonal move?
2904 truth moveOk
= TryMove(MoveVector
, true, game::PlayerIsRunning());
2906 if (!moveOk
|| BeginSquare
== GetSquareUnder() || (CurrentRoomIndex
&& (OldRoomIndex
!= CurrentRoomIndex
))) {
2907 dirlogf(" stopped\n");
2909 game::DrawEverything();
2910 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
2912 Go
->Terminate(false);
2917 mPrevMoveDir
= Go
->GetDirection();
2918 Go
->SetIsWalkingInOpen(!IsInCorridor(moveDir
));
2921 game::DrawEverything();
2922 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
2923 if (doStop
) Go
->Terminate(false);
2927 void character::SetTeam (team
*What
) {
2928 /*k8 if(Team) int esko = esko = 2; */
2930 SetTeamIterator(What
->Add(this));
2934 void character::ChangeTeam (team
*What
) {
2935 if (Team
) Team
->Remove(GetTeamIterator());
2937 SendNewDrawRequest();
2938 if (Team
) SetTeamIterator(Team
->Add(this));
2942 truth
character::ChangeRandomAttribute (int HowMuch
) {
2943 for (int c
= 0; c
< 50; ++c
) {
2944 int AttribID
= RAND()%ATTRIBUTES
;
2945 if (EditAttribute(AttribID
, HowMuch
)) return true;
2951 int character::RandomizeReply (sLong
&Said
, int Replies
) {
2952 truth NotSaid
= false;
2953 for (int c
= 0; c
< Replies
; ++c
) {
2954 if (!(Said
& (1 << c
))) {
2959 if (!NotSaid
) Said
= 0;
2961 while (Said
& 1 << (ToSay
= RAND() % Replies
));
2967 void character::DisplayInfo (festring
&Msg
) {
2969 Msg
<< " You are " << GetStandVerb() << " here.";
2971 Msg
<< ' ' << GetName(INDEFINITE
).CapitalizeCopy() << " is " << GetStandVerb() << " here. " << GetPersonalPronoun().CapitalizeCopy();
2972 cchar
*Separator1
= GetAction() ? "," : " and";
2973 cchar
*Separator2
= " and";
2974 if (GetTeam() == PLAYER
->GetTeam()) {
2977 int Relation
= GetRelation(PLAYER
);
2978 if (Relation
== HOSTILE
) Msg
<< " is hostile";
2979 else if (Relation
== UNCARING
) {
2980 Msg
<< " does not care about you";
2981 Separator1
= Separator2
= " and is";
2983 Msg
<< " is friendly";
2986 if (StateIsActivated(PANIC
)) {
2987 Msg
<< Separator1
<< " panicked";
2988 Separator2
= " and";
2990 if (GetAction()) Msg
<< Separator2
<< ' ' << GetAction()->GetDescription();
2996 void character::TestWalkability () {
2997 if (!IsEnabled()) return;
2998 square
*SquareUnder
= !game::IsInWilderness() ? GetSquareUnder() : PLAYER
->GetSquareUnder();
2999 if (SquareUnder
->IsFatalToStay() && !CanMoveOn(SquareUnder
)) {
3000 truth Alive
= false;
3001 if (!game::IsInWilderness() || IsPlayer()) {
3002 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
3003 square
*Square
= GetNeighbourSquare(d
);
3004 if (Square
&& CanMoveOn(Square
) && IsFreeForMe(Square
)) {
3005 if (IsPlayer()) ADD_MESSAGE("%s.", SquareUnder
->SurviveMessage(this));
3006 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE
), SquareUnder
->MonsterSurviveMessage(this));
3007 Move(Square
->GetPos(), true); // actually, this shouldn't be a teleport move
3008 SquareUnder
->SurviveEffect(this);
3018 festring DeathMsg
= festring(SquareUnder
->DeathMessage(this));
3019 game::AskForEscPress(DeathMsg
+".");
3020 festring Msg
= SquareUnder
->ScoreEntry(this);
3021 PLAYER
->AddScoreEntry(Msg
);
3024 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE
), SquareUnder
->MonsterDeathVerb(this));
3025 Die(0, SquareUnder
->ScoreEntry(this), DISALLOW_MSG
);
3032 int character::GetSize () const {
3033 return GetTorso()->GetSize();
3037 void character::SetMainMaterial (material
*NewMaterial
, int SpecialFlags
) {
3038 NewMaterial
->SetVolume(GetBodyPart(0)->GetMainMaterial()->GetVolume());
3039 GetBodyPart(0)->SetMainMaterial(NewMaterial
, SpecialFlags
);
3040 for (int c
= 1; c
< BodyParts
; ++c
) {
3041 NewMaterial
= NewMaterial
->SpawnMore(GetBodyPart(c
)->GetMainMaterial()->GetVolume());
3042 GetBodyPart(c
)->SetMainMaterial(NewMaterial
, SpecialFlags
);
3047 void character::ChangeMainMaterial (material
*NewMaterial
, int SpecialFlags
) {
3048 NewMaterial
->SetVolume(GetBodyPart(0)->GetMainMaterial()->GetVolume());
3049 GetBodyPart(0)->ChangeMainMaterial(NewMaterial
, SpecialFlags
);
3050 for (int c
= 1; c
< BodyParts
; ++c
) {
3051 NewMaterial
= NewMaterial
->SpawnMore(GetBodyPart(c
)->GetMainMaterial()->GetVolume());
3052 GetBodyPart(c
)->ChangeMainMaterial(NewMaterial
, SpecialFlags
);
3057 void character::SetSecondaryMaterial (material
*, int) {
3058 ABORT("Illegal character::SetSecondaryMaterial call!");
3062 void character::ChangeSecondaryMaterial (material
*, int) {
3063 ABORT("Illegal character::ChangeSecondaryMaterial call!");
3067 void character::TeleportRandomly (truth Intentional
) {
3068 v2 TelePos
= ERROR_V2
;
3069 if (StateIsActivated(TELEPORT_CONTROL
)) {
3071 v2 Input
= game::PositionQuestion(CONST_S("Where do you wish to teleport? [direction keys move cursor, space accepts]"), GetPos(), &game::TeleportHandler
, 0, false);
3072 if (Input
== ERROR_V2
) Input
= GetPos(); // esc pressed
3073 lsquare
*Square
= GetNearLSquare(Input
);
3074 if (CanMoveOn(Square
) || game::GoThroughWallsCheatIsActive()) {
3075 if (Square
->GetPos() == GetPos()) {
3076 ADD_MESSAGE("You disappear and reappear.");
3079 if (IsFreeForMe(Square
)) {
3080 if ((Input
-GetPos()).GetLengthSquare() <= GetTeleportRangeSquare()) {
3081 EditExperience(INTELLIGENCE
, 100, 1 << 10);
3084 ADD_MESSAGE("You cannot concentrate yourself enough to control a teleport that far.");
3087 character
*C
= Square
->GetCharacter();
3088 if (C
) ADD_MESSAGE("For a moment you feel very much like %s.", C
->CHAR_NAME(INDEFINITE
));
3089 else ADD_MESSAGE("You feel that something weird has happened, but can't really tell what exactly.");
3092 ADD_MESSAGE("You feel like having been hit by something really hard from the inside.");
3094 } else if (!Intentional
) {
3095 if (IsGoingSomeWhere() && GetLevel()->IsValidPos(GoingTo
)) {
3096 v2 Where
= GetLevel()->GetNearestFreeSquare(this, GoingTo
);
3097 if (Where
!= ERROR_V2
&& (Where
-GetPos()).GetLengthSquare() <= GetTeleportRangeSquare()) {
3098 EditExperience(INTELLIGENCE
, 100, 1 << 10);
3106 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.");
3109 //if (TelePos != ERROR_V2) Move(TelePos, true);
3110 //else Move(GetLevel()->GetRandomSquare(this), true);
3111 //if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE));
3112 //if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
3114 if (TelePos
== ERROR_V2
) TelePos
= GetLevel()->GetRandomSquare(this);
3116 room
*PossibleRoom
= game::GetCurrentLevel()->GetLSquare(TelePos
)->GetRoom();
3118 if (!PossibleRoom
) {
3119 //if it's outside of a room
3120 if (TelePos
!= ERROR_V2
) Move(TelePos
, true);
3121 else Move(GetLevel()->GetRandomSquare(this), true);
3122 if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE
));
3123 if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
3124 } else if (PossibleRoom
&& PossibleRoom
->IsOKToTeleportInto()) {
3125 // If it's inside of a room, check whether a ward is active that might impede the player
3126 if (TelePos
!= ERROR_V2
) Move(TelePos
, true);
3127 else Move(GetLevel()->GetRandomSquare(this), true);
3128 if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE
));
3129 if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
3132 ADD_MESSAGE("A mighty force blasts you back to where you were standing. A ward prevents you from teleporting.");
3134 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);
3139 CONST_S("killed by an explosion triggered when attempting to teleport into room protected by a ward"),
3143 lsquare* Square = GetNearLSquare(GetPos());
3144 Square->DrawParticles(RED);
3145 Square->FireBall(Beam);*/
3150 void character::DoDetecting () {
3151 material
*TempMaterial
;
3154 festring Temp
= game::DefaultQuestion(CONST_S("What material do you want to detect?"), game::GetDefaultDetectMaterial());
3155 TempMaterial
= protosystem::CreateMaterial(Temp
);
3156 if (TempMaterial
) break;
3157 game::DrawEverythingNoBlit();
3160 level
*Level
= GetLevel();
3161 int Squares
= Level
->DetectMaterial(TempMaterial
);
3163 if (Squares
> GetAttribute(INTELLIGENCE
) * (25+RAND()%51)) {
3164 ADD_MESSAGE("An enormous burst of geographical information overwhelms your consciousness. Your mind cannot cope with it and your memories blur.");
3165 Level
->BlurMemory();
3166 BeginTemporaryState(CONFUSED
, 1000 + RAND() % 1000);
3167 EditExperience(INTELLIGENCE
, -100, 1 << 12);
3168 } else if (!Squares
) {
3169 ADD_MESSAGE("You feel a sudden urge to imagine the dark void of a starless night sky.");
3170 EditExperience(INTELLIGENCE
, 200, 1 << 12);
3172 ADD_MESSAGE("You feel attracted to all things made of %s.", TempMaterial
->GetName(false, false).CStr());
3173 game::PositionQuestion(CONST_S("Detecting material [direction keys move cursor, space exits]"), GetPos(), 0, 0, false);
3174 EditExperience(INTELLIGENCE
, 300, 1 << 12);
3177 delete TempMaterial
;
3178 Level
->CalculateLuminances();
3179 game::SendLOSUpdateRequest();
3183 void character::RestoreHP () {
3184 doforbodyparts()(this, &bodypart::FastRestoreHP
);
3189 void character::RestoreLivingHP () {
3191 for (int c
= 0; c
< BodyParts
; ++c
) {
3192 bodypart
*BodyPart
= GetBodyPart(c
);
3193 if (BodyPart
&& BodyPart
->CanRegenerate()) {
3194 BodyPart
->FastRestoreHP();
3195 HP
+= BodyPart
->GetHP();
3201 truth
character::AllowDamageTypeBloodSpill (int Type
) {
3202 switch (Type
&0xFFF) {
3203 case PHYSICAL_DAMAGE
:
3212 case MUSTARD_GAS_DAMAGE
:
3216 ABORT("Unknown blood effect destroyed the dungeon!");
3221 /* Returns truly done damage */
3222 int character::ReceiveBodyPartDamage (character
*Damager
, int Damage
, int Type
, int BodyPartIndex
,
3223 int Direction
, truth PenetrateResistance
, truth Critical
, truth ShowNoDamageMsg
, truth CaptureBodyPart
)
3225 bodypart
*BodyPart
= GetBodyPart(BodyPartIndex
);
3226 if (!Damager
|| Damager
->AttackMayDamageArmor()) BodyPart
->DamageArmor(Damager
, Damage
, Type
);
3227 if (!PenetrateResistance
) {
3228 Damage
-= (BodyPart
->GetTotalResistance(Type
)>>1)+RAND()%((BodyPart
->GetTotalResistance(Type
)>>1)+1);
3230 if (int(Damage
) < 1) {
3234 if (ShowNoDamageMsg
) {
3235 if (IsPlayer()) ADD_MESSAGE("You are not hurt.");
3236 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s is not hurt.", GetPersonalPronoun().CStr());
3242 if (Critical
&& AllowDamageTypeBloodSpill(Type
) && !game::IsInWilderness()) {
3243 BodyPart
->SpillBlood(2+(RAND()&1));
3244 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
3245 lsquare
*Square
= GetNeighbourLSquare(d
);
3246 if (Square
&& Square
->IsFlyable()) BodyPart
->SpillBlood(1, Square
->GetPos());
3250 if (BodyPart
->ReceiveDamage(Damager
, Damage
, Type
, Direction
) && BodyPartCanBeSevered(BodyPartIndex
)) {
3251 if (DamageTypeDestroysBodyPart(Type
)) {
3252 if (IsPlayer()) ADD_MESSAGE("Your %s is destroyed!", BodyPart
->GetBodyPartName().CStr());
3253 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s is destroyed!", GetPossessivePronoun().CStr(), BodyPart
->GetBodyPartName().CStr());
3254 GetBodyPart(BodyPartIndex
)->DropEquipment();
3255 item
*Severed
= SevereBodyPart(BodyPartIndex
);
3256 if (Severed
) Severed
->DestroyBodyPart(!game::IsInWilderness() ? GetStackUnder() : GetStack());
3257 SendNewDrawRequest();
3258 if (IsPlayer()) game::AskForEscPress(CONST_S("Bodypart destroyed!"));
3260 if (IsPlayer()) ADD_MESSAGE("Your %s is severed off!", BodyPart
->GetBodyPartName().CStr());
3261 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s is severed off!", GetPossessivePronoun().CStr(), BodyPart
->GetBodyPartName().CStr());
3262 item
*Severed
= SevereBodyPart(BodyPartIndex
);
3263 SendNewDrawRequest();
3265 if (CaptureBodyPart
) {
3266 Damager
->GetLSquareUnder()->AddItem(Severed
);
3267 } else if (!game::IsInWilderness()) {
3268 /** No multi-tile humanoid support! */
3269 GetStackUnder()->AddItem(Severed
);
3270 if (Direction
!= YOURSELF
) Severed
->Fly(0, Direction
, Damage
);
3272 GetStack()->AddItem(Severed
);
3274 Severed
->DropEquipment();
3275 } else if (IsPlayer() || CanBeSeenByPlayer()) {
3276 ADD_MESSAGE("It vanishes.");
3278 if (IsPlayer()) game::AskForEscPress(CONST_S("Bodypart severed!"));
3280 if (CanPanicFromSeveredBodyPart() && RAND()%100 < GetPanicLevel() && !StateIsActivated(PANIC
) && !IsDead()) {
3281 BeginTemporaryState(PANIC
, 1000+RAND()%1001);
3283 SpecialBodyPartSeverReaction();
3286 if (!IsDead()) CheckPanic(500);
3292 /* Returns 0 if bodypart disappears */
3293 item
*character::SevereBodyPart (int BodyPartIndex
, truth ForceDisappearance
, stack
*EquipmentDropStack
) {
3294 bodypart
*BodyPart
= GetBodyPart(BodyPartIndex
);
3295 if (StateIsActivated(LEPROSY
)) BodyPart
->GetMainMaterial()->SetIsInfectedByLeprosy(true);
3296 if (ForceDisappearance
|| BodyPartsDisappearWhenSevered() || StateIsActivated(POLYMORPHED
) || game::AllBodyPartsVanish()) {
3297 BodyPart
->DropEquipment(EquipmentDropStack
);
3298 BodyPart
->RemoveFromSlot();
3299 CalculateAttributeBonuses();
3300 CalculateBattleInfo();
3301 BodyPart
->SendToHell();
3302 SignalPossibleTransparencyChange();
3303 RemoveTraps(BodyPartIndex
);
3306 BodyPart
->SetOwnerDescription("of " + GetName(INDEFINITE
));
3307 BodyPart
->SetIsUnique(LeftOversAreUnique());
3308 UpdateBodyPartPicture(BodyPartIndex
, true);
3309 BodyPart
->RemoveFromSlot();
3310 BodyPart
->RandomizePosition();
3311 CalculateAttributeBonuses();
3312 CalculateBattleInfo();
3314 SignalPossibleTransparencyChange();
3315 RemoveTraps(BodyPartIndex
);
3320 /* The second int is actually TargetFlags, which is not used here, but seems to be used in humanoid::ReceiveDamage.
3321 * Returns true if the character really receives damage */
3322 truth
character::ReceiveDamage (character
*Damager
, int Damage
, int Type
, int, int Direction
,
3323 truth
, truth PenetrateArmor
, truth Critical
, truth ShowMsg
)
3325 truth Affected
= ReceiveBodyPartDamage(Damager
, Damage
, Type
, 0, Direction
, PenetrateArmor
, Critical
, ShowMsg
);
3326 if (DamageTypeAffectsInventory(Type
)) {
3327 for (int c
= 0; c
< GetEquipments(); ++c
) {
3328 item
*Equipment
= GetEquipment(c
);
3329 if (Equipment
) Equipment
->ReceiveDamage(Damager
, Damage
, Type
);
3331 GetStack()->ReceiveDamage(Damager
, Damage
, Type
);
3337 festring
character::GetDescription (int Case
) const {
3338 if (IsPlayer()) return CONST_S("you");
3339 if (CanBeSeenByPlayer()) return GetName(Case
);
3340 return CONST_S("something");
3344 festring
character::GetPersonalPronoun (truth PlayersView
) const {
3345 if (IsPlayer() && PlayersView
) return CONST_S("you");
3346 if (GetSex() == UNDEFINED
|| (PlayersView
&& !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("it");
3347 if (GetSex() == MALE
) return CONST_S("he");
3348 return CONST_S("she");
3352 festring
character::GetPossessivePronoun (truth PlayersView
) const {
3353 if (IsPlayer() && PlayersView
) return CONST_S("your");
3354 if (GetSex() == UNDEFINED
|| (PlayersView
&& !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("its");
3355 if (GetSex() == MALE
) return CONST_S("his");
3356 return CONST_S("her");
3360 festring
character::GetObjectPronoun (truth PlayersView
) const {
3361 if (IsPlayer() && PlayersView
) return CONST_S("you");
3362 if (GetSex() == UNDEFINED
|| (PlayersView
&& !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("it");
3363 if (GetSex() == MALE
) return CONST_S("him");
3364 return CONST_S("her");
3368 void character::AddName (festring
&String
, int Case
) const {
3369 if (AssignedName
.IsEmpty()) {
3370 id::AddName(String
, Case
);
3371 } else if (!(Case
& PLURAL
)) {
3372 if (!ShowClassDescription()) {
3373 String
<< AssignedName
;
3375 String
<< AssignedName
<< ' ';
3376 id::AddName(String
, (Case
|ARTICLE_BIT
)&~INDEFINE_BIT
);
3379 id::AddName(String
, Case
);
3380 String
<< " named " << AssignedName
;
3385 int character::GetHungerState () const {
3386 if (!UsesNutrition()) return NOT_HUNGRY
;
3387 if (GetNP() > OVER_FED_LEVEL
) return OVER_FED
;
3388 if (GetNP() > BLOATED_LEVEL
) return BLOATED
;
3389 if (GetNP() > SATIATED_LEVEL
) return SATIATED
;
3390 if (GetNP() > NOT_HUNGER_LEVEL
) return NOT_HUNGRY
;
3391 if (GetNP() > HUNGER_LEVEL
) return HUNGRY
;
3392 if (GetNP() > VERY_HUNGER_LEVEL
) return VERY_HUNGRY
;
3397 truth
character::CanConsume (material
*Material
) const {
3398 return GetConsumeFlags() & Material
->GetConsumeType();
3402 void character::SetTemporaryStateCounter (sLong State
, int What
) {
3403 for (int c
= 0; c
< STATES
; ++c
) {
3404 if ((1 << c
) & State
) TemporaryStateCounter
[c
] = What
;
3409 void character::EditTemporaryStateCounter (sLong State
, int What
) {
3410 for (int c
= 0; c
< STATES
; ++c
) {
3411 if ((1 << c
) & State
) TemporaryStateCounter
[c
] += What
;
3416 int character::GetTemporaryStateCounter (sLong State
) const {
3417 for (int c
= 0; c
< STATES
; ++c
) {
3418 if ((1 << c
) & State
) return TemporaryStateCounter
[c
];
3420 ABORT("Illegal GetTemporaryStateCounter request!");
3425 truth
character::CheckKick () const {
3427 if (IsPlayer()) ADD_MESSAGE("This race can't kick.");
3434 int character::GetResistance (int Type
) const {
3435 switch (Type
&0xFFF) {
3436 case PHYSICAL_DAMAGE
:
3439 case MUSTARD_GAS_DAMAGE
:
3442 case ENERGY
: return GetEnergyResistance();
3443 case FIRE
: return GetFireResistance();
3444 case POISON
: return GetPoisonResistance();
3445 case ELECTRICITY
: return GetElectricityResistance();
3446 case ACID
: return GetAcidResistance();
3448 ABORT("Resistance lack detected!");
3453 void character::Regenerate () {
3454 if (HP
== MaxHP
) return;
3455 sLong RegenerationBonus
= 0;
3456 truth NoHealableBodyParts
= true;
3457 for (int c
= 0; c
< BodyParts
; ++c
) {
3458 bodypart
*BodyPart
= GetBodyPart(c
);
3459 if (BodyPart
&& BodyPart
->CanRegenerate()) {
3460 RegenerationBonus
+= BodyPart
->GetMaxHP();
3461 if (NoHealableBodyParts
&& BodyPart
->GetHP() < BodyPart
->GetMaxHP()) NoHealableBodyParts
= false;
3464 if (!RegenerationBonus
|| NoHealableBodyParts
) return;
3465 RegenerationBonus
*= (50+GetAttribute(ENDURANCE
));
3467 if (Action
&& Action
->IsRest()) {
3468 if (SquaresUnder
== 1) RegenerationBonus
*= GetSquareUnder()->GetRestModifier() << 1;
3470 int Lowest
= GetSquareUnder(0)->GetRestModifier();
3471 for (int c
= 1; c
< GetSquaresUnder(); ++c
) {
3472 int Mod
= GetSquareUnder(c
)->GetRestModifier();
3473 if (Mod
< Lowest
) Lowest
= Mod
;
3475 RegenerationBonus
*= Lowest
<< 1;
3479 RegenerationCounter
+= RegenerationBonus
;
3481 while (RegenerationCounter
> 1250000) {
3482 bodypart
*BodyPart
= HealHitPoint();
3483 if (!BodyPart
) break;
3484 EditNP(-Max(7500/MaxHP
, 1));
3485 RegenerationCounter
-= 1250000;
3486 int HP
= BodyPart
->GetHP();
3487 EditExperience(ENDURANCE
, Min(1000*BodyPart
->GetMaxHP()/(HP
*HP
), 300), 1000);
3492 void character::PrintInfo () const {
3493 felist
Info(CONST_S("Information about ")+GetName(DEFINITE
));
3494 for (int c
= 0; c
< GetEquipments(); ++c
) {
3495 item
*Equipment
= GetEquipment(c
);
3496 if ((EquipmentEasilyRecognized(c
) || game::WizardModeIsActive()) && Equipment
) {
3497 int ImageKey
= game::AddToItemDrawVector(itemvector(1, Equipment
));
3498 Info
.AddEntry(festring(GetEquipmentName(c
))+": "+Equipment
->GetName(INDEFINITE
), LIGHT_GRAY
, 0, ImageKey
, true);
3501 if (Info
.IsEmpty()) {
3502 ADD_MESSAGE("There's nothing special to tell about %s.", CHAR_NAME(DEFINITE
));
3504 game::SetStandardListAttributes(Info
);
3505 Info
.SetEntryDrawer(game::ItemEntryDrawer
);
3508 game::ClearItemDrawVector();
3512 truth
character::TryToRiseFromTheDead () {
3513 for (int c
= 0; c
< BodyParts
; ++c
) {
3514 bodypart
*BodyPart
= GetBodyPart(c
);
3516 BodyPart
->ResetSpoiling();
3517 if (BodyPart
->CanRegenerate() || BodyPart
->GetHP() < 1) BodyPart
->SetHP(1);
3525 truth
character::RaiseTheDead (character
*) {
3526 truth Useful
= false;
3527 for (int c
= 0; c
< BodyParts
; ++c
) {
3528 bodypart
*BodyPart
= GetBodyPart(c
);
3529 if (!BodyPart
&& CanCreateBodyPart(c
)) {
3530 CreateBodyPart(c
)->SetHP(1);
3531 if (IsPlayer()) ADD_MESSAGE("Suddenly you grow a new %s.", GetBodyPartName(c
).CStr());
3532 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE
), GetBodyPartName(c
).CStr());
3534 } else if (BodyPart
&& BodyPart
->CanRegenerate() && BodyPart
->GetHP() < 1) {
3539 if (IsPlayer()) ADD_MESSAGE("You shudder.");
3540 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s shudders.", CHAR_NAME(DEFINITE
));
3546 void character::SetSize (int Size
) {
3547 for (int c
= 0; c
< BodyParts
; ++c
) {
3548 bodypart
*BodyPart
= GetBodyPart(c
);
3549 if (BodyPart
) BodyPart
->SetSize(GetBodyPartSize(c
, Size
));
3554 sLong
character::GetBodyPartSize (int I
, int TotalSize
) const {
3555 if (I
== TORSO_INDEX
) return TotalSize
;
3556 ABORT("Weird bodypart size request for a character!");
3561 sLong
character::GetBodyPartVolume (int I
) const {
3562 if (I
== TORSO_INDEX
) return GetTotalVolume();
3563 ABORT("Weird bodypart volume request for a character!");
3568 void character::CreateBodyParts (int SpecialFlags
) {
3569 for (int c
= 0; c
< BodyParts
; ++c
) if (CanCreateBodyPart(c
)) CreateBodyPart(c
, SpecialFlags
);
3573 void character::RestoreBodyParts () {
3574 for (int c
= 0; c
< BodyParts
; ++c
) if (!GetBodyPart(c
) && CanCreateBodyPart(c
)) CreateBodyPart(c
);
3578 void character::UpdatePictures () {
3579 if (!PictureUpdatesAreForbidden()) for (int c
= 0; c
< BodyParts
; ++c
) UpdateBodyPartPicture(c
, false);
3583 bodypart
*character::MakeBodyPart (int I
) const {
3584 if (I
== TORSO_INDEX
) return normaltorso::Spawn(0, NO_MATERIALS
);
3585 ABORT("Weird bodypart to make for a character!");
3590 bodypart
*character::CreateBodyPart (int I
, int SpecialFlags
) {
3591 bodypart
*BodyPart
= MakeBodyPart(I
);
3592 material
*Material
= CreateBodyPartMaterial(I
, GetBodyPartVolume(I
));
3593 BodyPart
->InitMaterials(Material
, false);
3594 BodyPart
->SetSize(GetBodyPartSize(I
, GetTotalSize()));
3595 BodyPart
->SetBloodMaterial(GetBloodMaterial());
3596 BodyPart
->SetNormalMaterial(Material
->GetConfig());
3598 SetBodyPart(I
, BodyPart
);
3599 BodyPart
->InitSpecialAttributes();
3600 if (!(SpecialFlags
& NO_PIC_UPDATE
)) UpdateBodyPartPicture(I
, false);
3601 if (!IsInitializing()) {
3602 CalculateBattleInfo();
3603 SendNewDrawRequest();
3604 SignalPossibleTransparencyChange();
3610 v2
character::GetBodyPartBitmapPos (int I
, truth
) const {
3611 if (I
== TORSO_INDEX
) return GetTorsoBitmapPos();
3612 ABORT("Weird bodypart BitmapPos request for a character!");
3617 void character::UpdateBodyPartPicture (int I
, truth Severed
) {
3618 bodypart
*BP
= GetBodyPart(I
);
3620 BP
->SetBitmapPos(GetBodyPartBitmapPos(I
, Severed
));
3621 BP
->GetMainMaterial()->SetSkinColor(GetBodyPartColorA(I
, Severed
));
3622 BP
->GetMainMaterial()->SetSkinColorIsSparkling(GetBodyPartSparkleFlags(I
) & SPARKLING_A
);
3623 BP
->SetMaterialColorB(GetBodyPartColorB(I
, Severed
));
3624 BP
->SetMaterialColorC(GetBodyPartColorC(I
, Severed
));
3625 BP
->SetMaterialColorD(GetBodyPartColorD(I
, Severed
));
3626 BP
->SetSparkleFlags(GetBodyPartSparkleFlags(I
));
3627 BP
->SetSpecialFlags(GetSpecialBodyPartFlags(I
));
3628 BP
->SetWobbleData(GetBodyPartWobbleData(I
));
3629 BP
->UpdatePictures();
3634 void character::LoadDataBaseStats () {
3635 for (int c
= 0; c
< BASE_ATTRIBUTES
; ++c
) {
3636 BaseExperience
[c
] = DataBase
->NaturalExperience
[c
];
3637 if (BaseExperience
[c
]) LimitRef(BaseExperience
[c
], MIN_EXP
, MAX_EXP
);
3639 SetMoney(GetDefaultMoney());
3640 SetInitialSweatMaterial(GetSweatMaterial());
3641 const fearray
<sLong
> &Skills
= GetKnownCWeaponSkills();
3643 const fearray
<sLong
> &Hits
= GetCWeaponSkillHits();
3644 if (Hits
.Size
== 1) {
3645 for (uInt c
= 0; c
< Skills
.Size
; ++c
) {
3646 if (Skills
[c
] < AllowedWeaponSkillCategories
) CWeaponSkill
[Skills
[c
]].AddHit(Hits
[0]*100);
3648 } else if (Hits
.Size
== Skills
.Size
) {
3649 for (uInt c
= 0; c
< Skills
.Size
; ++c
) {
3650 if (Skills
[c
] < AllowedWeaponSkillCategories
) CWeaponSkill
[Skills
[c
]].AddHit(Hits
[c
]*100);
3653 ABORT("Illegal weapon skill hit array size detected!");
3659 character
*characterprototype::SpawnAndLoad (inputfile
&SaveFile
) const {
3660 character
*Char
= Spawner(0, LOAD
);
3661 Char
->Load(SaveFile
);
3662 Char
->CalculateAll();
3667 void character::Initialize (int NewConfig
, int SpecialFlags
) {
3668 Flags
|= C_INITIALIZING
|C_IN_NO_MSG_MODE
;
3669 CalculateBodyParts();
3670 CalculateAllowedWeaponSkillCategories();
3671 CalculateSquaresUnder();
3672 BodyPartSlot
= new bodypartslot
[BodyParts
];
3673 OriginalBodyPartID
= new std::list
<feuLong
>[BodyParts
];
3674 CWeaponSkill
= new cweaponskill
[AllowedWeaponSkillCategories
];
3675 SquareUnder
= new square
*[SquaresUnder
];
3677 if (SquaresUnder
== 1) *SquareUnder
= 0; else memset(SquareUnder
, 0, SquaresUnder
*sizeof(square
*));
3679 for (int c
= 0; c
< BodyParts
; ++c
) BodyPartSlot
[c
].SetMaster(this);
3681 if (!(SpecialFlags
& LOAD
)) {
3682 ID
= game::CreateNewCharacterID(this);
3683 databasecreator
<character
>::InstallDataBase(this, NewConfig
);
3684 LoadDataBaseStats();
3685 TemporaryState
|= GetClassStates();
3686 if (TemporaryState
) {
3687 for (int c
= 0; c
< STATES
; ++c
) if (TemporaryState
& (1 << c
)) TemporaryStateCounter
[c
] = PERMANENT
;
3690 CreateBodyParts(SpecialFlags
| NO_PIC_UPDATE
);
3691 InitSpecialAttributes();
3692 CommandFlags
= GetDefaultCommandFlags();
3694 if (GetAttribute(INTELLIGENCE
, false) < 8) CommandFlags
&= ~DONT_CONSUME_ANYTHING_VALUABLE
; // gum
3695 if (!GetDefaultName().IsEmpty()) SetAssignedName(GetDefaultName());
3698 if (!(SpecialFlags
& LOAD
)) PostConstruct();
3700 if (!(SpecialFlags
& LOAD
)) {
3701 if (!(SpecialFlags
& NO_EQUIPMENT
)) CreateInitialEquipment((SpecialFlags
& NO_EQUIPMENT_PIC_UPDATE
) >> 1);
3702 if (!(SpecialFlags
& NO_PIC_UPDATE
)) UpdatePictures();
3708 Flags
&= ~(C_INITIALIZING
|C_IN_NO_MSG_MODE
);
3712 truth
character::TeleportNear (character
*Caller
) {
3713 v2 Where
= GetLevel()->GetNearestFreeSquare(this, Caller
->GetPos());
3714 if (Where
== ERROR_V2
) return false;
3720 void character::ReceiveHeal (sLong Amount
) {
3722 for (c
= 0; c
< Amount
/ 10; ++c
) if (!HealHitPoint()) break;
3724 if (RAND()%10 < Amount
) HealHitPoint();
3725 if (Amount
>= 250 || RAND()%250 < Amount
) {
3726 bodypart
*NewBodyPart
= GenerateRandomBodyPart();
3727 if (!NewBodyPart
) return;
3728 NewBodyPart
->SetHP(1);
3729 if (IsPlayer()) ADD_MESSAGE("You grow a new %s.", NewBodyPart
->GetBodyPartName().CStr());
3730 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE
), NewBodyPart
->GetBodyPartName().CStr());
3735 void character::AddHealingLiquidConsumeEndMessage () const {
3736 if (IsPlayer()) ADD_MESSAGE("You feel better.");
3737 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks healthier.", CHAR_NAME(DEFINITE
));
3741 void character::ReceiveSchoolFood (sLong SizeOfEffect
) {
3742 SizeOfEffect
+= RAND()%SizeOfEffect
;
3743 if (SizeOfEffect
>= 250) VomitAtRandomDirection(SizeOfEffect
);
3744 if (!(RAND() % 3) && SizeOfEffect
>= 500 && EditAttribute(ENDURANCE
, SizeOfEffect
/500)) {
3745 if (IsPlayer()) ADD_MESSAGE("You gain a little bit of toughness for surviving this stuff.");
3746 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks tougher.", CHAR_NAME(DEFINITE
));
3748 BeginTemporaryState(POISONED
, (SizeOfEffect
>>1));
3752 void character::AddSchoolFoodConsumeEndMessage () const {
3753 if (IsPlayer()) ADD_MESSAGE("Yuck! This stuff tasted like vomit and old mousepads.");
3757 void character::AddSchoolFoodHitMessage () const {
3758 if (IsPlayer()) ADD_MESSAGE("Yuck! This stuff feels like vomit and old mousepads.");
3762 void character::ReceiveNutrition (sLong SizeOfEffect
) {
3763 EditNP(SizeOfEffect
);
3767 void character::ReceiveOmmelUrine (sLong Amount
) {
3768 EditExperience(ARM_STRENGTH
, 500, Amount
<<4);
3769 EditExperience(LEG_STRENGTH
, 500, Amount
<<4);
3770 if (IsPlayer()) game::DoEvilDeed(Amount
/25);
3774 void character::ReceiveOmmelCerumen (sLong Amount
) {
3775 EditExperience(INTELLIGENCE
, 500, Amount
<< 5);
3776 EditExperience(WISDOM
, 500, Amount
<< 5);
3777 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3781 void character::ReceiveOmmelSweat (sLong Amount
) {
3782 EditExperience(AGILITY
, 500, Amount
<< 4);
3783 EditExperience(DEXTERITY
, 500, Amount
<< 4);
3785 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3789 void character::ReceiveOmmelTears (sLong Amount
) {
3790 EditExperience(PERCEPTION
, 500, Amount
<< 4);
3791 EditExperience(CHARISMA
, 500, Amount
<< 4);
3792 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3796 void character::ReceiveOmmelSnot (sLong Amount
) {
3797 EditExperience(ENDURANCE
, 500, Amount
<< 5);
3799 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3803 void character::ReceiveOmmelBone (sLong Amount
) {
3804 EditExperience(ARM_STRENGTH
, 500, Amount
<< 6);
3805 EditExperience(LEG_STRENGTH
, 500, Amount
<< 6);
3806 EditExperience(DEXTERITY
, 500, Amount
<< 6);
3807 EditExperience(AGILITY
, 500, Amount
<< 6);
3808 EditExperience(ENDURANCE
, 500, Amount
<< 6);
3809 EditExperience(PERCEPTION
, 500, Amount
<< 6);
3810 EditExperience(INTELLIGENCE
, 500, Amount
<< 6);
3811 EditExperience(WISDOM
, 500, Amount
<< 6);
3812 EditExperience(CHARISMA
, 500, Amount
<< 6);
3815 if (IsPlayer()) game::DoEvilDeed(Amount
/ 25);
3819 void character::AddOmmelConsumeEndMessage () const {
3820 if (IsPlayer()) ADD_MESSAGE("You feel a primitive force coursing through your veins.");
3821 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks more powerful.", CHAR_NAME(DEFINITE
));
3825 void character::ReceivePepsi (sLong Amount
) {
3826 ReceiveDamage(0, Amount
/ 100, POISON
, TORSO
);
3827 EditExperience(PERCEPTION
, Amount
, 1 << 14);
3828 if (CheckDeath(CONST_S("was poisoned by pepsi"), 0)) return;
3829 if (IsPlayer()) game::DoEvilDeed(Amount
/ 10);
3833 void character::AddPepsiConsumeEndMessage () const {
3834 if (IsPlayer()) ADD_MESSAGE("Urgh. You feel your guruism fading away.");
3835 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks very lame.", CHAR_NAME(DEFINITE
));
3839 void character::ReceiveDarkness (sLong Amount
) {
3840 EditExperience(INTELLIGENCE
, -Amount
/ 5, 1 << 13);
3841 EditExperience(WISDOM
, -Amount
/ 5, 1 << 13);
3842 EditExperience(CHARISMA
, -Amount
/ 5, 1 << 13);
3843 if (IsPlayer()) game::DoEvilDeed(int(Amount
/ 50));
3847 void character::AddFrogFleshConsumeEndMessage () const {
3848 if (IsPlayer()) ADD_MESSAGE("Arg. You feel the fate of a navastater placed upon you...");
3849 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks like a navastater.", CHAR_NAME(DEFINITE
));
3853 void character::ReceiveKoboldFlesh (sLong
) {
3854 /* As it is commonly known, the possibility of fainting per 500 cubic
3855 centimeters of kobold flesh is exactly 5%. */
3856 if (!(RAND() % 20)) {
3857 if (IsPlayer()) ADD_MESSAGE("You lose control of your legs and fall down.");
3858 LoseConsciousness(250 + RAND_N(250));
3863 void character::AddKoboldFleshConsumeEndMessage () const {
3864 if (IsPlayer()) ADD_MESSAGE("This stuff tasted really funny.");
3868 void character::AddKoboldFleshHitMessage () const {
3869 if (IsPlayer()) ADD_MESSAGE("You feel very funny.");
3873 void character::AddBoneConsumeEndMessage () const {
3874 if (IsPlayer()) ADD_MESSAGE("You feel like a hippie.");
3875 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s barks happily.", CHAR_NAME(DEFINITE
)); // this suspects that nobody except dogs can eat bones
3878 truth
character::RawEditAttribute (double &Experience
, int Amount
) const {
3879 /* Check if the attribute is disabled for creature */
3880 if (!Experience
) return false;
3881 if ((Amount
< 0 && Experience
< 2 * EXP_MULTIPLIER
) || (Amount
> 0 && Experience
> 999 * EXP_MULTIPLIER
)) return false;
3882 Experience
+= Amount
* EXP_MULTIPLIER
;
3883 LimitRef
<double>(Experience
, MIN_EXP
, MAX_EXP
);
3888 void character::DrawPanel (truth AnimationDraw
) const {
3889 if (AnimationDraw
) { DrawStats(true); return; }
3890 igraph::BlitBackGround(v2(19 + (game::GetScreenXSize() << 4), 0), v2(RES
.X
- 19 - (game::GetScreenXSize() << 4), RES
.Y
));
3891 igraph::BlitBackGround(v2(16, 45 + (game::GetScreenYSize() << 4)), v2(game::GetScreenXSize() << 4, 9));
3892 FONT
->Printf(DOUBLE_BUFFER
, v2(16, 45 + (game::GetScreenYSize() << 4)), WHITE
, "%s", GetPanelName().CStr());
3893 game::UpdateAttributeMemory();
3894 int PanelPosX
= RES
.X
- 96;
3895 int PanelPosY
= DrawStats(false);
3896 PrintAttribute("End", ENDURANCE
, PanelPosX
, PanelPosY
++);
3897 PrintAttribute("Per", PERCEPTION
, PanelPosX
, PanelPosY
++);
3898 PrintAttribute("Int", INTELLIGENCE
, PanelPosX
, PanelPosY
++);
3899 PrintAttribute("Wis", WISDOM
, PanelPosX
, PanelPosY
++);
3900 PrintAttribute("Wil", WILL_POWER
, PanelPosX
, PanelPosY
++);
3901 PrintAttribute("Cha", CHARISMA
, PanelPosX
, PanelPosY
++);
3902 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Siz %d", GetSize());
3903 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), IsInBadCondition() ? RED
: WHITE
, "HP %d/%d", GetHP(), GetMaxHP());
3905 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Gold: %d", GetMoney());
3908 if (game::IsInWilderness())
3909 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Worldmap");
3911 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", game::GetCurrentDungeon()->GetShortLevelDescription(game::GetCurrentLevelIndex()).CapitalizeCopy().CStr());
3914 game::GetTime(Time
);
3915 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Day %d", Time
.Day
);
3916 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Time %d:%s%d", Time
.Hour
, Time
.Min
< 10 ? "0" : "", Time
.Min
);
3917 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Turn %d", game::GetTurn());
3922 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", festring(GetAction()->GetDescription()).CapitalizeCopy().CStr());
3924 for (int c
= 0; c
< STATES
; ++c
)
3925 if (!(StateData
[c
].Flags
& SECRET
) && StateIsActivated(1 << c
) && (1 << c
!= HASTE
|| !StateIsActivated(SLOW
)) && (1 << c
!= SLOW
|| !StateIsActivated(HASTE
)))
3926 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), (1 << c
) & EquipmentState
|| TemporaryStateCounter
[c
] == PERMANENT
? BLUE
: WHITE
, "%s", StateData
[c
].Description
);
3928 /* Make this more elegant!!! */
3929 if (GetHungerState() == STARVING
)
3930 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Starving");
3931 else if (GetHungerState() == VERY_HUNGRY
)
3932 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), BLUE
, "Very hungry");
3933 else if (GetHungerState() == HUNGRY
)
3934 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), BLUE
, "Hungry");
3935 else if (GetHungerState() == SATIATED
)
3936 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Satiated");
3937 else if (GetHungerState() == BLOATED
)
3938 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Bloated");
3939 else if (GetHungerState() == OVER_FED
)
3940 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Overfed!");
3942 switch (GetBurdenState()) {
3944 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Overload!");
3947 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), BLUE
, "Stressed");
3950 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), BLUE
, "Burdened");
3954 switch (GetTirednessState()) {
3956 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), RED
, "Fainting");
3959 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "Exhausted");
3963 if (game::PlayerIsRunning()) {
3964 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", GetRunDescriptionLine(0));
3965 cchar
*SecondLine
= GetRunDescriptionLine(1);
3966 if (strlen(SecondLine
)) FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
++ * 10), WHITE
, "%s", SecondLine
);
3971 void character::CalculateDodgeValue () {
3972 DodgeValue
= 0.05 * GetMoveEase() * GetAttribute(AGILITY
) / sqrt(GetSize());
3973 if (IsFlying()) DodgeValue
*= 2;
3974 if (DodgeValue
< 1) DodgeValue
= 1;
3978 truth
character::DamageTypeAffectsInventory (int Type
) {
3979 switch (Type
&0xFFF) {
3986 case PHYSICAL_DAMAGE
:
3989 case MUSTARD_GAS_DAMAGE
:
3993 ABORT("Unknown reaping effect destroyed dungeon!");
3998 int character::CheckForBlockWithArm (character
*Enemy
, item
*Weapon
, arm
*Arm
,
3999 double WeaponToHitValue
, int Damage
, int Success
, int Type
)
4001 int BlockStrength
= Arm
->GetBlockCapability();
4002 double BlockValue
= Arm
->GetBlockValue();
4003 if (BlockStrength
&& BlockValue
) {
4004 item
*Blocker
= Arm
->GetWielded();
4005 if (RAND() % int(100+WeaponToHitValue
/BlockValue
/(1<<BlocksSinceLastTurn
)*(100+Success
)) < 100) {
4006 int NewDamage
= BlockStrength
< Damage
? Damage
-BlockStrength
: 0;
4008 case UNARMED_ATTACK
: AddBlockMessage(Enemy
, Blocker
, Enemy
->UnarmedHitNoun(), NewDamage
); break;
4009 case WEAPON_ATTACK
: AddBlockMessage(Enemy
, Blocker
, "attack", NewDamage
); break;
4010 case KICK_ATTACK
: AddBlockMessage(Enemy
, Blocker
, Enemy
->KickNoun(), NewDamage
); break;
4011 case BITE_ATTACK
: AddBlockMessage(Enemy
, Blocker
, Enemy
->BiteNoun(), NewDamage
); break;
4013 sLong Weight
= Blocker
->GetWeight();
4014 sLong StrExp
= Limit(15 * Weight
/ 200, 75, 300);
4015 sLong DexExp
= Weight
? Limit(75000 / Weight
, 75, 300) : 300;
4016 Arm
->EditExperience(ARM_STRENGTH
, StrExp
, 1 << 8);
4017 Arm
->EditExperience(DEXTERITY
, DexExp
, 1 << 8);
4018 EditStamina(-10000 / GetAttribute(ARM_STRENGTH
), false);
4019 if (Arm
->TwoHandWieldIsActive()) {
4020 arm
*PairArm
= Arm
->GetPairArm();
4021 PairArm
->EditExperience(ARM_STRENGTH
, StrExp
, 1 << 8);
4022 PairArm
->EditExperience(DEXTERITY
, DexExp
, 1 << 8);
4024 Blocker
->WeaponSkillHit(Enemy
->CalculateWeaponSkillHits(this));
4025 Blocker
->ReceiveDamage(this, Damage
, PHYSICAL_DAMAGE
);
4026 Blocker
->BlockEffect(this, Enemy
, Weapon
, Type
);
4027 if (Weapon
) Weapon
->ReceiveDamage(Enemy
, Damage
- NewDamage
, PHYSICAL_DAMAGE
);
4028 if (BlocksSinceLastTurn
< 16) ++BlocksSinceLastTurn
;
4036 sLong
character::GetStateAPGain (sLong BaseAPGain
) const {
4037 if (!StateIsActivated(HASTE
) == !StateIsActivated(SLOW
)) return BaseAPGain
;
4038 if (StateIsActivated(HASTE
)) return (BaseAPGain
* 5) >> 2;
4039 return (BaseAPGain
<< 2) / 5;
4043 void character::SignalEquipmentAdd (int EquipmentIndex
) {
4044 item
*Equipment
= GetEquipment(EquipmentIndex
);
4045 if (Equipment
->IsInCorrectSlot(EquipmentIndex
)) {
4046 sLong AddedStates
= Equipment
->GetGearStates();
4048 for (int c
= 0; c
< STATES
; ++c
) {
4049 if (AddedStates
& (1 << c
)) {
4050 if (!StateIsActivated(1 << c
)) {
4051 if (!IsInNoMsgMode()) (this->*StateData
[c
].PrintBeginMessage
)();
4052 EquipmentState
|= 1 << c
;
4053 if (StateData
[c
].BeginHandler
) (this->*StateData
[c
].BeginHandler
)();
4055 EquipmentState
|= 1 << c
;
4061 if (!IsInitializing() && Equipment
->IsInCorrectSlot(EquipmentIndex
)) ApplyEquipmentAttributeBonuses(Equipment
);
4065 void character::SignalEquipmentRemoval (int, citem
*Item
) {
4066 CalculateEquipmentState();
4067 if (CalculateAttributeBonuses()) CheckDeath(festring("lost ")+GetPossessivePronoun(false)+" vital "+Item
->GetName(INDEFINITE
));
4071 void character::CalculateEquipmentState () {
4072 sLong Back
= EquipmentState
;
4074 for (int c
= 0; c
< GetEquipments(); ++c
) {
4075 item
*Equipment
= GetEquipment(c
);
4076 if (Equipment
&& Equipment
->IsInCorrectSlot(c
)) EquipmentState
|= Equipment
->GetGearStates();
4078 for (int c
= 0; c
< STATES
; ++c
) {
4079 if (Back
& (1 << c
) && !StateIsActivated(1 << c
)) {
4080 if (StateData
[c
].EndHandler
) {
4081 (this->*StateData
[c
].EndHandler
)();
4082 if (!IsEnabled()) return;
4084 if (!IsInNoMsgMode()) (this->*StateData
[c
].PrintEndMessage
)();
4090 /* Counter = duration in ticks */
4091 void character::BeginTemporaryState (sLong State
, int Counter
) {
4092 if (!Counter
) return;
4094 if (State
== POLYMORPHED
) ABORT("No Polymorphing with BeginTemporaryState!");
4095 for (Index
= 0; Index
< STATES
; ++Index
) if (1 << Index
== State
) break;
4096 if (Index
== STATES
) ABORT("BeginTemporaryState works only when State == 2^n!");
4097 if (TemporaryStateIsActivated(State
)) {
4098 int OldCounter
= GetTemporaryStateCounter(State
);
4099 if (OldCounter
!= PERMANENT
) EditTemporaryStateCounter(State
, Max(Counter
, 50-OldCounter
));
4100 } else if (StateData
[Index
].IsAllowed
== 0 || (this->*StateData
[Index
].IsAllowed
)()) {
4101 SetTemporaryStateCounter(State
, Max(Counter
, 50));
4102 if (!EquipmentStateIsActivated(State
)) {
4103 if (!IsInNoMsgMode()) (this->*StateData
[Index
].PrintBeginMessage
)();
4104 ActivateTemporaryState(State
);
4105 if (StateData
[Index
].BeginHandler
) (this->*StateData
[Index
].BeginHandler
)();
4107 ActivateTemporaryState(State
);
4113 void character::HandleStates () {
4114 if (!TemporaryState
&& !EquipmentState
) return;
4115 for (int c
= 0; c
< STATES
; ++c
) {
4116 if (TemporaryState
& (1 << c
) && TemporaryStateCounter
[c
] != PERMANENT
) {
4117 if (!--TemporaryStateCounter
[c
]) {
4118 TemporaryState
&= ~(1 << c
);
4119 if (!(EquipmentState
& (1 << c
))) {
4120 if (StateData
[c
].EndHandler
) {
4121 (this->*StateData
[c
].EndHandler
)();
4122 if (!IsEnabled()) return;
4124 if (!TemporaryStateCounter
[c
]) (this->*StateData
[c
].PrintEndMessage
)();
4128 if (StateIsActivated(1 << c
)) {
4129 if (StateData
[c
].Handler
) (this->*StateData
[c
].Handler
)();
4131 if (!IsEnabled()) return;
4136 void character::PrintBeginPolymorphControlMessage () const {
4137 if (IsPlayer()) ADD_MESSAGE("You feel your mind has total control over your body.");
4141 void character::PrintEndPolymorphControlMessage () const {
4142 if (IsPlayer()) ADD_MESSAGE("You are somehow uncertain of your willpower.");
4146 void character::PrintBeginLifeSaveMessage () const {
4147 if (IsPlayer()) ADD_MESSAGE("You hear Hell's gates being locked just now.");
4151 void character::PrintEndLifeSaveMessage () const {
4152 if (IsPlayer()) ADD_MESSAGE("You feel the Afterlife is welcoming you once again.");
4156 void character::PrintBeginLycanthropyMessage () const {
4157 if (IsPlayer()) ADD_MESSAGE("You suddenly notice you've always loved full moons.");
4161 void character::PrintEndLycanthropyMessage () const {
4162 if (IsPlayer()) ADD_MESSAGE("You feel the wolf inside you has had enough of your bad habits.");
4166 void character::PrintBeginVampirismMessage () const {
4167 if (IsPlayer()) ADD_MESSAGE("You suddenly decide you have always hated garlic.");
4171 void character::PrintEndVampirismMessage () const {
4172 if (IsPlayer()) ADD_MESSAGE("You recall your delight of the morning sunshine back in New Attnam. You are a vampire no longer.");
4176 void character::PrintBeginInvisibilityMessage () const {
4177 if ((PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm()) || (PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5)) {
4178 if (IsPlayer()) ADD_MESSAGE("You seem somehow transparent.");
4179 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s seems somehow transparent.", CHAR_NAME(DEFINITE
));
4181 if (IsPlayer()) ADD_MESSAGE("You fade away.");
4182 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s disappears!", CHAR_NAME(DEFINITE
));
4187 void character::PrintEndInvisibilityMessage () const {
4188 if ((PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm()) || (PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5)) {
4189 if (IsPlayer()) ADD_MESSAGE("Your notice your transparency has ended.");
4190 else if (CanBeSeenByPlayer()) ADD_MESSAGE("The appearance of %s seems far more solid now.", CHAR_NAME(INDEFINITE
));
4192 if (IsPlayer()) ADD_MESSAGE("You reappear.");
4193 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s appears from nowhere!", CHAR_NAME(INDEFINITE
));
4198 void character::PrintBeginInfraVisionMessage () const {
4200 if (StateIsActivated(INVISIBLE
) && IsWarm() && !(StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5))
4201 ADD_MESSAGE("You reappear.");
4203 ADD_MESSAGE("You feel your perception being magically altered.");
4208 void character::PrintEndInfraVisionMessage () const {
4210 if (StateIsActivated(INVISIBLE
) && IsWarm() && !(StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5))
4211 ADD_MESSAGE("You disappear.");
4213 ADD_MESSAGE("You feel your perception returning to normal.");
4218 void character::PrintBeginESPMessage () const {
4219 if (IsPlayer()) ADD_MESSAGE("You suddenly feel like being only a tiny part of a great network of intelligent minds.");
4223 void character::PrintEndESPMessage () const {
4224 if (IsPlayer()) ADD_MESSAGE("You are filled with desire to be just yourself from now on.");
4228 void character::PrintBeginHasteMessage () const {
4229 if (IsPlayer()) ADD_MESSAGE("Time slows down to a crawl.");
4230 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks faster!", CHAR_NAME(DEFINITE
));
4234 void character::PrintEndHasteMessage () const {
4235 if (IsPlayer()) ADD_MESSAGE("Everything seems to move much faster now.");
4236 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks slower!", CHAR_NAME(DEFINITE
));
4240 void character::PrintBeginSlowMessage () const {
4241 if (IsPlayer()) ADD_MESSAGE("Everything seems to move much faster now.");
4242 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks slower!", CHAR_NAME(DEFINITE
));
4246 void character::PrintEndSlowMessage () const {
4247 if (IsPlayer()) ADD_MESSAGE("Time slows down to a crawl.");
4248 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks faster!", CHAR_NAME(DEFINITE
));
4252 void character::EndPolymorph () {
4253 ForceEndPolymorph();
4257 character
*character::ForceEndPolymorph () {
4259 ADD_MESSAGE("You return to your true form.");
4260 } else if (game::IsInWilderness()) {
4261 ActivateTemporaryState(POLYMORPHED
);
4262 SetTemporaryStateCounter(POLYMORPHED
, 10);
4263 return this; // fast gum solution, state ends when the player enters a dungeon
4265 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s returns to %s true form.", CHAR_NAME(DEFINITE
), GetPossessivePronoun().CStr());
4267 if (GetAction()) GetAction()->Terminate(false);
4271 character
*Char
= GetPolymorphBackup();
4272 Flags
|= C_IN_NO_MSG_MODE
;
4273 Char
->Flags
|= C_IN_NO_MSG_MODE
;
4274 Char
->ChangeTeam(GetTeam());
4275 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(Char
);
4276 SetPolymorphBackup(0);
4277 Char
->PutToOrNear(Pos
);
4279 Char
->Flags
&= ~C_POLYMORPHED
;
4280 GetStack()->MoveItemsTo(Char
->GetStack());
4281 DonateEquipmentTo(Char
);
4282 Char
->SetMoney(GetMoney());
4283 Flags
&= ~C_IN_NO_MSG_MODE
;
4284 Char
->Flags
&= ~C_IN_NO_MSG_MODE
;
4285 Char
->CalculateAll();
4286 Char
->SetAssignedName(GetAssignedName());
4289 game::SetPlayer(Char
);
4290 game::SendLOSUpdateRequest();
4293 Char
->TestWalkability();
4298 void character::LycanthropyHandler () {
4299 if (!(RAND() % 2000)) {
4300 if (StateIsActivated(POLYMORPH_CONTROL
) && !game::TruthQuestion(CONST_S("Do you wish to change into a werewolf? [y/N]"))) return;
4301 Polymorph(werewolfwolf::Spawn(), 1000 + RAND() % 2000);
4306 void character::SaveLife () {
4307 if (TemporaryStateIsActivated(LIFE_SAVED
)) {
4309 ADD_MESSAGE("But wait! You glow briefly red and seem to be in a better shape!");
4310 else if (CanBeSeenByPlayer())
4311 ADD_MESSAGE("But wait, suddenly %s glows briefly red and seems to be in a better shape!", GetPersonalPronoun().CStr());
4312 DeActivateTemporaryState(LIFE_SAVED
);
4314 item
*LifeSaver
= 0;
4315 for (int c
= 0; c
< GetEquipments(); ++c
) {
4316 item
*Equipment
= GetEquipment(c
);
4317 if (Equipment
&& Equipment
->IsInCorrectSlot(c
) && Equipment
->GetGearStates() & LIFE_SAVED
) LifeSaver
= Equipment
;
4319 if (!LifeSaver
) ABORT("The Universe can only kill you once!");
4321 ADD_MESSAGE("But wait! Your %s glows briefly red and disappears and you seem to be in a better shape!", LifeSaver
->CHAR_NAME(UNARTICLED
));
4322 else if (CanBeSeenByPlayer())
4323 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());
4324 LifeSaver
->RemoveFromSlot();
4325 LifeSaver
->SendToHell();
4328 if (IsPlayer()) game::AskForEscPress(CONST_S("Life saved!"));
4336 if (GetNP() < SATIATED_LEVEL
) SetNP(SATIATED_LEVEL
);
4338 SendNewDrawRequest();
4340 if (GetAction()) GetAction()->Terminate(false);
4344 character
*character::PolymorphRandomly (int MinDanger
, int MaxDanger
, int Time
) {
4345 character
*NewForm
= 0;
4346 if (StateIsActivated(POLYMORPH_CONTROL
)) {
4348 if (!GetNewFormForPolymorphWithControl(NewForm
)) return NewForm
;
4350 NewForm
= protosystem::CreateMonster(MinDanger
*10, MaxDanger
*10, NO_EQUIPMENT
);
4353 NewForm
= protosystem::CreateMonster(MinDanger
, MaxDanger
, NO_EQUIPMENT
);
4355 Polymorph(NewForm
, Time
);
4360 /* In reality, the reading takes Time / (Intelligence * 10) turns */
4361 void character::StartReading (item
*Item
, sLong Time
) {
4362 study
*Read
= study::Spawn(this);
4363 Read
->SetLiteratureID(Item
->GetID());
4364 if (game::WizardModeIsActive()) Time
= 1;
4365 Read
->SetCounter(Time
);
4367 if (IsPlayer()) ADD_MESSAGE("You start reading %s.", Item
->CHAR_NAME(DEFINITE
));
4368 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s starts reading %s.", CHAR_NAME(DEFINITE
), Item
->CHAR_NAME(DEFINITE
));
4372 /* Call when one makes something with his/her/its hands.
4373 * Difficulty of 5 takes about one turn, so it's the most common to use. */
4374 void character::DexterityAction (int Difficulty
) {
4375 EditAP(-20000 * Difficulty
/ APBonus(GetAttribute(DEXTERITY
)));
4376 EditExperience(DEXTERITY
, Difficulty
* 15, 1 << 7);
4380 /* If Theoretically != false, range is not a factor. */
4381 truth
character::CanBeSeenByPlayer (truth Theoretically
, truth IgnoreESP
) const {
4382 if (IsEnabled() && !game::IsGenerating() && (Theoretically
|| GetSquareUnder())) {
4383 truth MayBeESPSeen
= PLAYER
->IsEnabled() && !IgnoreESP
&& PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5;
4384 truth MayBeInfraSeen
= PLAYER
->IsEnabled() && PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm();
4385 truth Visible
= !StateIsActivated(INVISIBLE
) || MayBeESPSeen
|| MayBeInfraSeen
;
4386 if (game::IsInWilderness()) return Visible
;
4387 if (MayBeESPSeen
&& (Theoretically
|| GetDistanceSquareFrom(PLAYER
) <= PLAYER
->GetESPRangeSquare())) return true;
4388 if (!Visible
) return false;
4389 return Theoretically
|| SquareUnderCanBeSeenByPlayer(MayBeInfraSeen
);
4395 truth
character::CanBeSeenBy (ccharacter
*Who
, truth Theoretically
, truth IgnoreESP
) const {
4396 if (Who
->IsPlayer()) return CanBeSeenByPlayer(Theoretically
, IgnoreESP
);
4397 if (IsEnabled() && !game::IsGenerating() && (Theoretically
|| GetSquareUnder())) {
4398 truth MayBeESPSeen
= Who
->IsEnabled() && !IgnoreESP
&& Who
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5;
4399 truth MayBeInfraSeen
= Who
->IsEnabled() && Who
->StateIsActivated(INFRA_VISION
) && IsWarm();
4400 truth Visible
= !StateIsActivated(INVISIBLE
) || MayBeESPSeen
|| MayBeInfraSeen
;
4401 if (game::IsInWilderness()) return Visible
;
4402 if (MayBeESPSeen
&& (Theoretically
|| GetDistanceSquareFrom(Who
) <= Who
->GetESPRangeSquare())) return true;
4403 if (!Visible
) return false;
4404 return Theoretically
|| SquareUnderCanBeSeenBy(Who
, MayBeInfraSeen
);
4410 truth
character::SquareUnderCanBeSeenByPlayer (truth IgnoreDarkness
) const {
4411 if (!GetSquareUnder()) return false;
4412 int S1
= SquaresUnder
, S2
= PLAYER
->SquaresUnder
;
4413 if (S1
== 1 && S2
== 1) {
4414 if (GetSquareUnder()->CanBeSeenByPlayer(IgnoreDarkness
)) return true;
4415 if (IgnoreDarkness
) {
4416 int LOSRangeSquare
= PLAYER
->GetLOSRangeSquare();
4417 if ((GetPos() - PLAYER
->GetPos()).GetLengthSquare() <= LOSRangeSquare
) {
4418 eyecontroller::Map
= GetLevel()->GetMap();
4419 return mapmath
<eyecontroller
>::DoLine(PLAYER
->GetPos().X
, PLAYER
->GetPos().Y
, GetPos().X
, GetPos().Y
, SKIP_FIRST
);
4424 for (int c1
= 0; c1
< S1
; ++c1
) {
4425 lsquare
*Square
= GetLSquareUnder(c1
);
4426 if (Square
->CanBeSeenByPlayer(IgnoreDarkness
)) return true;
4427 else if (IgnoreDarkness
) {
4428 v2 Pos
= Square
->GetPos();
4429 int LOSRangeSquare
= PLAYER
->GetLOSRangeSquare();
4430 for (int c2
= 0; c2
< S2
; ++c2
) {
4431 v2 PlayerPos
= PLAYER
->GetPos(c2
);
4432 if ((Pos
-PlayerPos
).GetLengthSquare() <= LOSRangeSquare
) {
4433 eyecontroller::Map
= GetLevel()->GetMap();
4434 if (mapmath
<eyecontroller
>::DoLine(PlayerPos
.X
, PlayerPos
.Y
, Pos
.X
, Pos
.Y
, SKIP_FIRST
)) return true;
4444 truth
character::SquareUnderCanBeSeenBy (ccharacter
*Who
, truth IgnoreDarkness
) const {
4445 int S1
= SquaresUnder
, S2
= Who
->SquaresUnder
;
4446 int LOSRangeSquare
= Who
->GetLOSRangeSquare();
4447 if (S1
== 1 && S2
== 1) return GetSquareUnder()->CanBeSeenFrom(Who
->GetPos(), LOSRangeSquare
, IgnoreDarkness
);
4448 for (int c1
= 0; c1
< S1
; ++c1
) {
4449 lsquare
*Square
= GetLSquareUnder(c1
);
4450 for (int c2
= 0; c2
< S2
; ++c2
) if (Square
->CanBeSeenFrom(Who
->GetPos(c2
), LOSRangeSquare
, IgnoreDarkness
)) return true;
4456 int character::GetDistanceSquareFrom (ccharacter
*Who
) const {
4457 int S1
= SquaresUnder
, S2
= Who
->SquaresUnder
;
4458 if (S1
== 1 && S2
== 1) return (GetPos() - Who
->GetPos()).GetLengthSquare();
4459 v2
MinDist(0x7FFF, 0x7FFF);
4460 int MinLength
= 0xFFFF;
4461 for (int c1
= 0; c1
< S1
; ++c1
) {
4462 for (int c2
= 0; c2
< S2
; ++c2
) {
4463 v2 Dist
= GetPos(c1
)-Who
->GetPos(c2
);
4464 if (Dist
.X
< 0) Dist
.X
= -Dist
.X
;
4465 if (Dist
.Y
< 0) Dist
.Y
= -Dist
.Y
;
4466 if (Dist
.X
<= MinDist
.X
&& Dist
.Y
<= MinDist
.Y
) {
4468 MinLength
= Dist
.GetLengthSquare();
4469 } else if (Dist
.X
< MinDist
.X
|| Dist
.Y
< MinDist
.Y
) {
4470 int Length
= Dist
.GetLengthSquare();
4471 if (Length
< MinLength
) {
4482 void character::AttachBodyPart (bodypart
*BodyPart
) {
4483 SetBodyPart(BodyPart
->GetBodyPartIndex(), BodyPart
);
4484 if (!AllowSpoil()) BodyPart
->ResetSpoiling();
4485 BodyPart
->ResetPosition();
4486 BodyPart
->UpdatePictures();
4487 CalculateAttributeBonuses();
4488 CalculateBattleInfo();
4489 SendNewDrawRequest();
4490 SignalPossibleTransparencyChange();
4494 /* Returns true if the character has all bodyparts, false if not. */
4495 truth
character::HasAllBodyParts () const {
4496 for (int c
= 0; c
< BodyParts
; ++c
) if (!GetBodyPart(c
) && CanCreateBodyPart(c
)) return false;
4501 bodypart
*character::GenerateRandomBodyPart () {
4502 int NeededBodyPart
[MAX_BODYPARTS
];
4504 for (int c
= 0; c
< BodyParts
; ++c
) if (!GetBodyPart(c
) && CanCreateBodyPart(c
)) NeededBodyPart
[Index
++] = c
;
4505 return Index
? CreateBodyPart(NeededBodyPart
[RAND() % Index
]) : 0;
4509 /* Searches the character's Stack and if it find some bodyparts there that are the character's
4510 * old bodyparts returns a stackiterator to one of them (choosen in random).
4511 * If no fitting bodyparts are found the function returns 0 */
4512 bodypart
*character::FindRandomOwnBodyPart (truth AllowNonLiving
) const {
4513 itemvector LostAndFound
;
4514 for (int c
= 0; c
< BodyParts
; ++c
) {
4515 if (!GetBodyPart(c
)) {
4516 for (std::list
<feuLong
>::iterator i
= OriginalBodyPartID
[c
].begin(); i
!= OriginalBodyPartID
[c
].end(); ++i
) {
4517 bodypart
*Found
= static_cast<bodypart
*>(SearchForItem(*i
));
4518 if (Found
&& (AllowNonLiving
|| Found
->CanRegenerate())) LostAndFound
.push_back(Found
);
4522 if (LostAndFound
.empty()) return 0;
4523 return static_cast<bodypart
*>(LostAndFound
[RAND() % LostAndFound
.size()]);
4527 void character::PrintBeginPoisonedMessage () const {
4528 if (IsPlayer()) ADD_MESSAGE("You seem to be very ill.");
4529 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks very ill.", CHAR_NAME(DEFINITE
));
4533 void character::PrintEndPoisonedMessage () const {
4534 if (IsPlayer()) ADD_MESSAGE("You feel better again.");
4535 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks better.", CHAR_NAME(DEFINITE
));
4539 void character::PoisonedHandler () {
4540 if (!(RAND() % 100)) VomitAtRandomDirection(500 + RAND_N(250));
4542 for (int Used
= 0; Used
< GetTemporaryStateCounter(POISONED
); Used
+= 100) if (!(RAND() % 100)) ++Damage
;
4544 ReceiveDamage(0, Damage
, POISON
, ALL
, 8, false, false, false, false);
4545 CheckDeath(CONST_S("died of acute poisoning"), 0);
4550 truth
character::IsWarm () const {
4551 return combinebodypartpredicates()(this, &bodypart::IsWarm
, 1);
4555 void character::BeginInvisibility () {
4557 SendNewDrawRequest();
4558 SignalPossibleTransparencyChange();
4562 void character::BeginInfraVision () {
4563 if (IsPlayer()) GetArea()->SendNewDrawRequest();
4567 void character::BeginESP () {
4568 if (IsPlayer()) GetArea()->SendNewDrawRequest();
4572 void character::EndInvisibility () {
4574 SendNewDrawRequest();
4575 SignalPossibleTransparencyChange();
4579 void character::EndInfraVision () {
4580 if (IsPlayer() && IsEnabled()) GetArea()->SendNewDrawRequest();
4584 void character::EndESP () {
4585 if (IsPlayer() && IsEnabled()) GetArea()->SendNewDrawRequest();
4589 void character::Draw (blitdata
&BlitData
) const {
4590 col24 L
= BlitData
.Luminance
;
4591 if (PLAYER
->IsEnabled() &&
4592 ((PLAYER
->StateIsActivated(ESP
) && GetAttribute(INTELLIGENCE
) >= 5 &&
4593 (PLAYER
->GetPos() - GetPos()).GetLengthSquare() <= PLAYER
->GetESPRangeSquare()) ||
4594 (PLAYER
->StateIsActivated(INFRA_VISION
) && IsWarm())))
4595 BlitData
.Luminance
= ivanconfig::GetContrastLuminance();
4597 DrawBodyParts(BlitData
);
4598 BlitData
.Luminance
= ivanconfig::GetContrastLuminance();
4599 BlitData
.Src
.Y
= 16;
4600 cint SquareIndex
= BlitData
.CustomData
& SQUARE_INDEX_MASK
;
4602 if (GetTeam() == PLAYER
->GetTeam() && !IsPlayer() && SquareIndex
== GetTameSymbolSquareIndex()) {
4603 BlitData
.Src
.X
= 32;
4604 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4607 if (IsFlying() && SquareIndex
== GetFlySymbolSquareIndex()) {
4608 BlitData
.Src
.X
= 128;
4609 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4612 if (IsSwimming() && SquareIndex
== GetSwimmingSymbolSquareIndex()) {
4613 BlitData
.Src
.X
= 240;
4614 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4617 if (GetAction() && GetAction()->IsUnconsciousness() && SquareIndex
== GetUnconsciousSymbolSquareIndex()) {
4618 BlitData
.Src
.X
= 224;
4619 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData
);
4622 BlitData
.Src
.X
= BlitData
.Src
.Y
= 0;
4623 BlitData
.Luminance
= L
;
4627 void character::DrawBodyParts (blitdata
&BlitData
) const {
4628 GetTorso()->Draw(BlitData
);
4632 void character::PrintBeginTeleportMessage () const {
4633 if (IsPlayer()) ADD_MESSAGE("You feel jumpy.");
4637 void character::PrintEndTeleportMessage () const {
4638 if (IsPlayer()) ADD_MESSAGE("You suddenly realize you've always preferred walking to jumping.");
4642 void character::PrintBeginDetectMessage () const {
4643 if (IsPlayer()) ADD_MESSAGE("You feel curious about your surroundings.");
4647 void character::PrintEndDetectMessage () const {
4648 if (IsPlayer()) ADD_MESSAGE("You decide to rely on your intuition from now on.");
4652 void character::TeleportHandler () {
4653 if (!(RAND() % 1500) && !game::IsInWilderness()) {
4654 if (IsPlayer()) ADD_MESSAGE("You feel an urgent spatial relocation is now appropriate.");
4655 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s disappears.", CHAR_NAME(DEFINITE
));
4661 void character::DetectHandler () {
4663 //the AI can't be asked position questions! So only the player can hav this state really :/ a bit daft of me
4664 if (!(RAND()%3000) && !game::IsInWilderness()) {
4665 ADD_MESSAGE("Your mind wanders in search of something.");
4666 DoDetecting(); //in fact, who knows what would happen if a dark frog had the detecting state?
4672 void character::PrintBeginPolymorphMessage () const {
4673 if (IsPlayer()) ADD_MESSAGE("An unconfortable uncertainty of who you really are overwhelms you.");
4677 void character::PrintEndPolymorphMessage () const {
4678 if (IsPlayer()) ADD_MESSAGE("You feel you are you and no one else.");
4682 void character::PolymorphHandler () {
4683 if (!(RAND() % 1500)) PolymorphRandomly(1, 999999, 200 + RAND() % 800);
4686 void character::PrintBeginTeleportControlMessage () const {
4687 if (IsPlayer()) ADD_MESSAGE("You feel very controlled.");
4691 void character::PrintEndTeleportControlMessage () const {
4692 if (IsPlayer()) ADD_MESSAGE("You feel your control slipping.");
4696 void character::DisplayStethoscopeInfo (character
*) const {
4697 felist
Info(CONST_S("Information about ") + GetDescription(DEFINITE
));
4698 AddSpecialStethoscopeInfo(Info
);
4699 Info
.AddEntry(CONST_S("Endurance: ") + GetAttribute(ENDURANCE
), LIGHT_GRAY
);
4700 Info
.AddEntry(CONST_S("Perception: ") + GetAttribute(PERCEPTION
), LIGHT_GRAY
);
4701 Info
.AddEntry(CONST_S("Intelligence: ") + GetAttribute(INTELLIGENCE
), LIGHT_GRAY
);
4702 Info
.AddEntry(CONST_S("Wisdom: ") + GetAttribute(WISDOM
), LIGHT_GRAY
);
4703 //Info.AddEntry(CONST_S("Willpower: ") + GetAttribute(WILL_POWER), LIGHT_GRAY);
4704 Info
.AddEntry(CONST_S("Charisma: ") + GetAttribute(CHARISMA
), LIGHT_GRAY
);
4705 Info
.AddEntry(CONST_S("HP: ") + GetHP() + "/" + GetMaxHP(), IsInBadCondition() ? RED
: LIGHT_GRAY
);
4706 if (GetAction()) Info
.AddEntry(festring(GetAction()->GetDescription()).CapitalizeCopy(), LIGHT_GRAY
);
4707 for (int c
= 0; c
< STATES
; ++c
) {
4708 if (StateIsActivated(1 << c
) && (1 << c
!= HASTE
|| !StateIsActivated(SLOW
)) && (1 << c
!= SLOW
|| !StateIsActivated(HASTE
)))
4709 Info
.AddEntry(StateData
[c
].Description
, LIGHT_GRAY
);
4711 switch (GetTirednessState()) {
4712 case FAINTING
: Info
.AddEntry("Fainting", RED
); break;
4713 case EXHAUSTED
: Info
.AddEntry("Exhausted", LIGHT_GRAY
); break;
4715 game::SetStandardListAttributes(Info
);
4720 truth
character::CanUseStethoscope (truth PrintReason
) const {
4721 if (PrintReason
) ADD_MESSAGE("This type of monster can't use a stethoscope.");
4726 /* Effect used by at least Sophos.
4727 * NOTICE: Doesn't check for death! */
4728 void character::TeleportSomePartsAway (int NumberToTeleport
) {
4729 for (int c
= 0; c
< NumberToTeleport
; ++c
) {
4730 int RandomBodyPart
= GetRandomNonVitalBodyPart();
4731 if (RandomBodyPart
== NONE_INDEX
) {
4732 for (; c
< NumberToTeleport
; ++c
) {
4733 GetTorso()->SetHP((GetTorso()->GetHP() << 2) / 5);
4734 sLong TorsosVolume
= GetTorso()->GetMainMaterial()->GetVolume() / 10;
4735 if (!TorsosVolume
) break;
4736 sLong Amount
= (RAND() % TorsosVolume
)+1;
4737 item
*Lump
= GetTorso()->GetMainMaterial()->CreateNaturalForm(Amount
);
4738 GetTorso()->GetMainMaterial()->EditVolume(-Amount
);
4739 Lump
->MoveTo(GetNearLSquare(GetLevel()->GetRandomSquare())->GetStack());
4740 if (IsPlayer()) ADD_MESSAGE("Parts of you teleport away.");
4741 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Parts of %s teleport away.", CHAR_NAME(DEFINITE
));
4744 item
*SeveredBodyPart
= SevereBodyPart(RandomBodyPart
);
4745 if (SeveredBodyPart
) {
4746 GetNearLSquare(GetLevel()->GetRandomSquare())->AddItem(SeveredBodyPart
);
4747 SeveredBodyPart
->DropEquipment();
4748 if (IsPlayer()) ADD_MESSAGE("Your %s teleports away.", GetBodyPartName(RandomBodyPart
).CStr());
4749 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s teleports away.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart
).CStr());
4751 if (IsPlayer()) ADD_MESSAGE("Your %s disappears.", GetBodyPartName(RandomBodyPart
).CStr());
4752 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s disappears.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart
).CStr());
4759 /* Returns an index of a random bodypart that is not vital. If no non-vital bodypart is found returns NONE_INDEX */
4760 int character::GetRandomNonVitalBodyPart () const {
4761 int OKBodyPart
[MAX_BODYPARTS
];
4762 int OKBodyParts
= 0;
4763 for (int c
= 0; c
< BodyParts
; ++c
) if (GetBodyPart(c
) && !BodyPartIsVital(c
)) OKBodyPart
[OKBodyParts
++] = c
;
4764 return OKBodyParts
? OKBodyPart
[RAND() % OKBodyParts
] : NONE_INDEX
;
4768 void character::CalculateVolumeAndWeight () {
4769 Volume
= Stack
->GetVolume();
4770 Weight
= Stack
->GetWeight();
4772 CarriedWeight
= Weight
;
4773 for (int c
= 0; c
< BodyParts
; ++c
) {
4774 bodypart
*BodyPart
= GetBodyPart(c
);
4776 BodyVolume
+= BodyPart
->GetBodyPartVolume();
4777 Volume
+= BodyPart
->GetVolume();
4778 CarriedWeight
+= BodyPart
->GetCarriedWeight();
4779 Weight
+= BodyPart
->GetWeight();
4785 void character::SignalVolumeAndWeightChange () {
4786 if (!IsInitializing()) {
4787 CalculateVolumeAndWeight();
4788 if (IsEnabled()) CalculateBurdenState();
4789 if (MotherEntity
) MotherEntity
->SignalVolumeAndWeightChange();
4794 void character::SignalEmitationIncrease (col24 EmitationUpdate
) {
4795 if (game::CompareLights(EmitationUpdate
, Emitation
) > 0) {
4796 game::CombineLights(Emitation
, EmitationUpdate
);
4797 if (MotherEntity
) MotherEntity
->SignalEmitationIncrease(EmitationUpdate
);
4798 else if (SquareUnder
[0] && !game::IsInWilderness()) {
4799 for(int c
= 0; c
< GetSquaresUnder(); ++c
) GetLSquareUnder()->SignalEmitationIncrease(EmitationUpdate
);
4805 void character::SignalEmitationDecrease (col24 EmitationUpdate
) {
4806 if (game::CompareLights(EmitationUpdate
, Emitation
) >= 0 && Emitation
) {
4807 col24 Backup
= Emitation
;
4808 CalculateEmitation();
4809 if (Backup
!= Emitation
) {
4810 if (MotherEntity
) MotherEntity
->SignalEmitationDecrease(EmitationUpdate
);
4811 else if (SquareUnder
[0] && !game::IsInWilderness()) {
4812 for (int c
= 0; c
< GetSquaresUnder(); ++c
) GetLSquareUnder(c
)->SignalEmitationDecrease(EmitationUpdate
);
4819 void character::CalculateEmitation () {
4820 Emitation
= GetBaseEmitation();
4821 for (int c
= 0; c
< BodyParts
; ++c
) {
4822 bodypart
*BodyPart
= GetBodyPart(c
);
4823 if (BodyPart
) game::CombineLights(Emitation
, BodyPart
->GetEmitation());
4825 game::CombineLights(Emitation
, Stack
->GetEmitation());
4829 void character::CalculateAll () {
4830 Flags
|= C_INITIALIZING
;
4831 CalculateAttributeBonuses();
4832 CalculateVolumeAndWeight();
4833 CalculateEmitation();
4834 CalculateBodyPartMaxHPs(0);
4835 CalculateMaxStamina();
4836 CalculateBurdenState();
4837 CalculateBattleInfo();
4838 Flags
&= ~C_INITIALIZING
;
4842 void character::CalculateHP () {
4843 HP
= sumbodypartproperties()(this, &bodypart::GetHP
);
4847 void character::CalculateMaxHP () {
4848 MaxHP
= sumbodypartproperties()(this, &bodypart::GetMaxHP
);
4852 void character::CalculateBodyPartMaxHPs (feuLong Flags
) {
4853 doforbodypartswithparam
<feuLong
>()(this, &bodypart::CalculateMaxHP
, Flags
);
4859 truth
character::EditAttribute (int Identifier
, int Value
) {
4860 if (Identifier
== ENDURANCE
&& UseMaterialAttributes()) return false;
4861 if (RawEditAttribute(BaseExperience
[Identifier
], Value
)) {
4862 if (!IsInitializing()) {
4863 if (Identifier
== LEG_STRENGTH
) CalculateBurdenState();
4864 else if (Identifier
== ENDURANCE
) CalculateBodyPartMaxHPs();
4865 else if (IsPlayer() && Identifier
== PERCEPTION
) game::SendLOSUpdateRequest();
4866 else if (IsPlayerKind() && (Identifier
== INTELLIGENCE
|| Identifier
== WISDOM
|| Identifier
== CHARISMA
)) UpdatePictures();
4867 CalculateBattleInfo();
4875 truth
character::ActivateRandomState (int Flags
, int Time
, sLong Seed
) {
4877 if (Seed
) femath::SetSeed(Seed
);
4878 sLong ToBeActivated
= GetRandomState(Flags
|DUR_TEMPORARY
);
4880 if (!ToBeActivated
) return false;
4881 BeginTemporaryState(ToBeActivated
, Time
);
4886 truth
character::GainRandomIntrinsic (int Flags
) {
4887 sLong ToBeActivated
= GetRandomState(Flags
|DUR_PERMANENT
);
4888 if (!ToBeActivated
) return false;
4889 GainIntrinsic(ToBeActivated
);
4894 /* Returns 0 if state not found */
4895 sLong
character::GetRandomState (int Flags
) const {
4896 sLong OKStates
[STATES
];
4897 int NumberOfOKStates
= 0;
4898 for (int c
= 0; c
< STATES
; ++c
) {
4899 if (StateData
[c
].Flags
& Flags
& DUR_FLAGS
&& StateData
[c
].Flags
& Flags
& SRC_FLAGS
) OKStates
[NumberOfOKStates
++] = 1 << c
;
4901 return NumberOfOKStates
? OKStates
[RAND() % NumberOfOKStates
] : 0;
4905 int characterprototype::CreateSpecialConfigurations (characterdatabase
**TempConfig
, int Configs
, int Level
) {
4906 if (Level
== 0 && TempConfig
[0]->CreateDivineConfigurations
) {
4907 Configs
= databasecreator
<character
>::CreateDivineConfigurations(this, TempConfig
, Configs
);
4909 if (Level
== 1 && TempConfig
[0]->CreateUndeadConfigurations
) {
4910 for (int c
= 1; c
< protocontainer
<character
>::GetSize(); ++c
) {
4911 const character::prototype
*Proto
= protocontainer
<character
>::GetProto(c
);
4912 const character::database
*const *CharacterConfigData
= Proto
->GetConfigData();
4913 if (!CharacterConfigData
) ABORT("No database entry for character <%s>!", Proto
->GetClassID());
4914 const character::database
*const* End
= CharacterConfigData
+ Proto
->GetConfigSize();
4915 for (++CharacterConfigData
; CharacterConfigData
!= End
; ++CharacterConfigData
) {
4916 const character::database
*CharacterDataBase
= *CharacterConfigData
;
4917 if (CharacterDataBase
->UndeadVersions
) {
4918 character::database
* ConfigDataBase
= new character::database(**TempConfig
);
4919 ConfigDataBase
->InitDefaults(this, (c
<< 8) | CharacterDataBase
->Config
);
4920 ConfigDataBase
->PostFix
<< "of ";
4921 if (CharacterDataBase
->Adjective
.GetSize()) {
4922 if (CharacterDataBase
->UsesLongAdjectiveArticle
) ConfigDataBase
->PostFix
<< "an ";
4923 else ConfigDataBase
->PostFix
<< "a ";
4924 ConfigDataBase
->PostFix
<< CharacterDataBase
->Adjective
<< ' ';
4926 if (CharacterDataBase
->UsesLongArticle
) ConfigDataBase
->PostFix
<< "an ";
4927 else ConfigDataBase
->PostFix
<< "a ";
4929 ConfigDataBase
->PostFix
<< CharacterDataBase
->NameSingular
;
4930 if (CharacterDataBase
->PostFix
.GetSize()) ConfigDataBase
->PostFix
<< ' ' << CharacterDataBase
->PostFix
;
4931 int P1
= TempConfig
[0]->UndeadAttributeModifier
;
4932 int P2
= TempConfig
[0]->UndeadVolumeModifier
;
4934 for (c2
= 0; c2
< ATTRIBUTES
; ++c2
) ConfigDataBase
->*ExpPtr
[c2
] = CharacterDataBase
->*ExpPtr
[c2
] * P1
/ 100;
4935 for (c2
= 0; c2
< EQUIPMENT_DATAS
; ++c2
) ConfigDataBase
->*EquipmentDataPtr
[c2
] = contentscript
<item
>();
4936 ConfigDataBase
->DefaultIntelligence
= 5;
4937 ConfigDataBase
->DefaultWisdom
= 5;
4938 ConfigDataBase
->DefaultCharisma
= 5;
4939 ConfigDataBase
->TotalSize
= CharacterDataBase
->TotalSize
;
4940 ConfigDataBase
->Sex
= CharacterDataBase
->Sex
;
4941 ConfigDataBase
->AttributeBonus
= CharacterDataBase
->AttributeBonus
;
4942 ConfigDataBase
->TotalVolume
= CharacterDataBase
->TotalVolume
* P2
/ 100;
4943 if (TempConfig
[0]->UndeadCopyMaterials
) {
4944 ConfigDataBase
->HeadBitmapPos
= CharacterDataBase
->HeadBitmapPos
;
4945 ConfigDataBase
->HairColor
= CharacterDataBase
->HairColor
;
4946 ConfigDataBase
->EyeColor
= CharacterDataBase
->EyeColor
;
4947 ConfigDataBase
->CapColor
= CharacterDataBase
->CapColor
;
4948 ConfigDataBase
->FleshMaterial
= CharacterDataBase
->FleshMaterial
;
4949 ConfigDataBase
->BloodMaterial
= CharacterDataBase
->BloodMaterial
;
4950 ConfigDataBase
->VomitMaterial
= CharacterDataBase
->VomitMaterial
;
4951 ConfigDataBase
->SweatMaterial
= CharacterDataBase
->SweatMaterial
;
4953 ConfigDataBase
->KnownCWeaponSkills
= CharacterDataBase
->KnownCWeaponSkills
;
4954 ConfigDataBase
->CWeaponSkillHits
= CharacterDataBase
->CWeaponSkillHits
;
4955 ConfigDataBase
->PostProcess();
4956 TempConfig
[Configs
++] = ConfigDataBase
;
4961 if (Level
== 0 && TempConfig
[0]->CreateGolemMaterialConfigurations
) {
4962 for (int c
= 1; c
< protocontainer
<material
>::GetSize(); ++c
) {
4963 const material::prototype
* Proto
= protocontainer
<material
>::GetProto(c
);
4964 const material::database
*const* MaterialConfigData
= Proto
->GetConfigData();
4965 const material::database
*const* End
= MaterialConfigData
+ Proto
->GetConfigSize();
4966 for (++MaterialConfigData
; MaterialConfigData
!= End
; ++MaterialConfigData
) {
4967 const material::database
* MaterialDataBase
= *MaterialConfigData
;
4968 if (MaterialDataBase
->CategoryFlags
& IS_GOLEM_MATERIAL
) {
4969 character::database
* ConfigDataBase
= new character::database(**TempConfig
);
4970 ConfigDataBase
->InitDefaults(this, MaterialDataBase
->Config
);
4971 ConfigDataBase
->Adjective
= MaterialDataBase
->NameStem
;
4972 ConfigDataBase
->UsesLongAdjectiveArticle
= MaterialDataBase
->NameFlags
& USE_AN
;
4973 ConfigDataBase
->AttachedGod
= MaterialDataBase
->AttachedGod
;
4974 TempConfig
[Configs
++] = ConfigDataBase
;
4983 double character::GetTimeToDie (ccharacter
*Enemy
, int Damage
, double ToHitValue
, truth AttackIsBlockable
, truth UseMaxHP
) const {
4984 double DodgeValue
= GetDodgeValue();
4985 if (!Enemy
->CanBeSeenBy(this, true)) ToHitValue
*= 2;
4986 if (!CanBeSeenBy(Enemy
, true)) DodgeValue
*= 2;
4987 double MinHits
= 1000;
4989 for (int c
= 0; c
< BodyParts
; ++c
) {
4990 if (BodyPartIsVital(c
) && GetBodyPart(c
)) {
4991 double Hits
= GetBodyPart(c
)->GetTimeToDie(Damage
, ToHitValue
, DodgeValue
, AttackIsBlockable
, UseMaxHP
);
4992 if (First
) { MinHits
= Hits
; First
= false; } else MinHits
= 1 / (1 / MinHits
+ 1 / Hits
);
4999 double character::GetRelativeDanger (ccharacter
*Enemy
, truth UseMaxHP
) const {
5000 double Danger
= Enemy
->GetTimeToKill(this, UseMaxHP
) / GetTimeToKill(Enemy
, UseMaxHP
);
5001 int EnemyAP
= Enemy
->GetMoveAPRequirement(1);
5002 int ThisAP
= GetMoveAPRequirement(1);
5003 if (EnemyAP
> ThisAP
) Danger
*= 1.25;
5004 else if (ThisAP
> EnemyAP
) Danger
*= 0.80;
5005 if (!Enemy
->CanBeSeenBy(this, true)) Danger
*= Enemy
->IsPlayer() ? 0.2 : 0.5;
5006 if (!CanBeSeenBy(Enemy
, true)) Danger
*= IsPlayer() ? 5. : 2.;
5007 if (GetAttribute(INTELLIGENCE
) < 10 && !IsPlayer()) Danger
*= 0.80;
5008 if (Enemy
->GetAttribute(INTELLIGENCE
) < 10 && !Enemy
->IsPlayer()) Danger
*= 1.25;
5009 return Limit(Danger
, 0.001, 1000.0);
5013 festring
character::GetBodyPartName (int I
, truth Articled
) const {
5014 if (I
== TORSO_INDEX
) return Articled
? CONST_S("a torso") : CONST_S("torso");
5015 ABORT("Illegal character bodypart name request!");
5020 item
*character::SearchForItem(feuLong ID
) const {
5021 item
*Equipment
= findequipment
<feuLong
>()(this, &item::HasID
, ID
);
5022 if (Equipment
) return Equipment
;
5023 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (i
->GetID() == ID
) return *i
;
5028 truth
character::ContentsCanBeSeenBy (ccharacter
*Viewer
) const {
5029 return Viewer
== this;
5033 truth
character::HitEffect (character
*Enemy
, item
* Weapon
, v2 HitPos
, int Type
, int BodyPartIndex
,
5034 int Direction
, truth BlockedByArmour
)
5036 if (Weapon
) return Weapon
->HitEffect(this, Enemy
, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
5038 case UNARMED_ATTACK
: return Enemy
->SpecialUnarmedEffect(this, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
5039 case KICK_ATTACK
: return Enemy
->SpecialKickEffect(this, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
5040 case BITE_ATTACK
: return Enemy
->SpecialBiteEffect(this, HitPos
, BodyPartIndex
, Direction
, BlockedByArmour
);
5046 void character::WeaponSkillHit (item
*Weapon
, int Type
, int Hits
) {
5049 case UNARMED_ATTACK
: Category
= UNARMED
; break;
5050 case WEAPON_ATTACK
: Weapon
->WeaponSkillHit(Hits
); return;
5051 case KICK_ATTACK
: Category
= KICK
; break;
5052 case BITE_ATTACK
: Category
= BITE
; break;
5054 if (!IsHumanoid()) return;
5055 Category
= Weapon
->GetWeaponCategory();
5058 ABORT("Illegal Type %d passed to character::WeaponSkillHit()!", Type
);
5061 if (GetCWeaponSkill(Category
)->AddHit(Hits
)) {
5062 CalculateBattleInfo();
5063 if (IsPlayer()) GetCWeaponSkill(Category
)->AddLevelUpMessage(Category
);
5068 /* Returns 0 if character cannot be duplicated */
5069 character
*character::Duplicate (feuLong Flags
) {
5070 if (!(Flags
& IGNORE_PROHIBITIONS
) && !CanBeCloned()) return 0;
5071 character
*Char
= GetProtoType()->Clone(this);
5072 if (Flags
& MIRROR_IMAGE
) {
5073 DuplicateEquipment(Char
, Flags
& ~IGNORE_PROHIBITIONS
);
5074 Char
->SetLifeExpectancy(Flags
>> LE_BASE_SHIFT
& LE_BASE_RANGE
, Flags
>> LE_RAND_SHIFT
& LE_RAND_RANGE
);
5076 Char
->CalculateAll();
5077 Char
->CalculateEmitation();
5078 Char
->UpdatePictures();
5079 Char
->Flags
&= ~(C_INITIALIZING
|C_IN_NO_MSG_MODE
);
5084 truth
character::TryToEquip (item
*Item
) {
5085 if (!Item
->AllowEquip() || !CanUseEquipment() || GetAttribute(WISDOM
) >= Item
->GetWearWisdomLimit() || Item
->GetSquaresUnder() != 1)
5087 for (int e
= 0; e
< GetEquipments(); ++e
) {
5088 if (GetBodyPartOfEquipment(e
) && EquipmentIsAllowed(e
)) {
5089 sorter Sorter
= EquipmentSorter(e
);
5090 if ((Sorter
== 0 || (Item
->*Sorter
)(this)) &&
5091 ((e
!= RIGHT_WIELDED_INDEX
&& e
!= LEFT_WIELDED_INDEX
) ||
5092 Item
->IsWeapon(this) || Item
->IsShield(this)) && AllowEquipment(Item
, e
)) {
5093 item
*OldEquipment
= GetEquipment(e
);
5094 if (BoundToUse(OldEquipment
, e
)) continue;
5095 lsquare
*LSquareUnder
= GetLSquareUnder();
5096 stack
*StackUnder
= LSquareUnder
->GetStack();
5097 msgsystem::DisableMessages();
5098 Flags
|= C_PICTURE_UPDATES_FORBIDDEN
;
5099 LSquareUnder
->Freeze();
5100 StackUnder
->Freeze();
5101 double Danger
= GetRelativeDanger(PLAYER
);
5102 if (OldEquipment
) OldEquipment
->RemoveFromSlot();
5103 Item
->RemoveFromSlot();
5104 SetEquipment(e
, Item
);
5105 double NewDanger
= GetRelativeDanger(PLAYER
);
5106 Item
->RemoveFromSlot();
5107 StackUnder
->AddItem(Item
);
5108 if (OldEquipment
) SetEquipment(e
, OldEquipment
);
5109 msgsystem::EnableMessages();
5110 Flags
&= ~C_PICTURE_UPDATES_FORBIDDEN
;
5111 LSquareUnder
->UnFreeze();
5112 StackUnder
->UnFreeze();
5114 if (NewDanger
> Danger
|| BoundToUse(Item
, e
)) {
5115 room
*Room
= GetRoom();
5116 if (!Room
|| Room
->PickupItem(this, Item
, 1)) {
5117 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
));
5118 if (Room
) Room
->DropItem(this, OldEquipment
, 1);
5119 OldEquipment
->MoveTo(StackUnder
);
5120 Item
->RemoveFromSlot();
5121 SetEquipment(e
, Item
);
5127 if (NewDanger
> Danger
|| (NewDanger
== Danger
&& e
!= RIGHT_WIELDED_INDEX
&& e
!= LEFT_WIELDED_INDEX
) || BoundToUse(Item
, e
)) {
5128 room
*Room
= GetRoom();
5129 if (!Room
|| Room
->PickupItem(this, Item
, 1)) {
5130 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s picks up and equips %s.", CHAR_NAME(DEFINITE
), Item
->CHAR_NAME(INDEFINITE
));
5131 Item
->RemoveFromSlot();
5132 SetEquipment(e
, Item
);
5145 truth
character::TryToConsume (item
*Item
) {
5146 return Item
->CanBeEatenByAI(this) && ConsumeItem(Item
, Item
->GetConsumeMaterial(this)->GetConsumeVerb());
5150 void character::UpdateESPLOS () const {
5151 if (StateIsActivated(ESP
) && !game::IsInWilderness()) {
5152 for (int c
= 0; c
< game::GetTeams(); ++c
) {
5153 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
5154 const character
*ch
= *i
;
5155 if (ch
->IsEnabled()) ch
->SendNewDrawRequest();
5162 int character::GetCWeaponSkillLevel (citem
*Item
) const {
5163 if (Item
->GetWeaponCategory() < GetAllowedWeaponSkillCategories()) return GetCWeaponSkill(Item
->GetWeaponCategory())->GetLevel();
5168 void character::PrintBeginPanicMessage () const {
5169 if (IsPlayer()) ADD_MESSAGE("You panic!");
5170 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s panics.", CHAR_NAME(DEFINITE
));
5174 void character::PrintEndPanicMessage () const {
5175 if (IsPlayer()) ADD_MESSAGE("You finally calm down.");
5176 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s calms down.", CHAR_NAME(DEFINITE
));
5180 void character::CheckPanic (int Ticks
) {
5181 if (GetPanicLevel() > 1 && !StateIsActivated(PANIC
) && GetHP() * 100 < RAND() % (GetPanicLevel() * GetMaxHP() << 1))
5182 BeginTemporaryState(PANIC
, ((Ticks
* 3) >> 2) + RAND() % ((Ticks
>> 1) + 1)); // 25% randomness to ticks...
5186 /* returns 0 if fails else the newly created character */
5187 character
*character::DuplicateToNearestSquare (character
*Cloner
, feuLong Flags
) {
5188 character
*NewlyCreated
= Duplicate(Flags
);
5189 if (!NewlyCreated
) return 0;
5190 if (Flags
& CHANGE_TEAM
&& Cloner
) NewlyCreated
->ChangeTeam(Cloner
->GetTeam());
5191 NewlyCreated
->PutNear(GetPos());
5192 return NewlyCreated
;
5196 void character::SignalSpoil (material
*m
) {
5197 if (GetMotherEntity()) GetMotherEntity()->SignalSpoil(m
);
5198 else Disappear(0, "spoil", &item::IsVeryCloseToSpoiling
);
5202 truth
character::CanHeal () const {
5203 for (int c
= 0; c
< BodyParts
; ++c
) {
5204 bodypart
*BodyPart
= GetBodyPart(c
);
5205 if (BodyPart
&& BodyPart
->CanRegenerate() && BodyPart
->GetHP() < BodyPart
->GetMaxHP()) return true;
5211 int character::GetRelation (ccharacter
*Who
) const {
5212 return GetTeam()->GetRelation(Who
->GetTeam());
5216 truth (item::*AffectTest
[BASE_ATTRIBUTES
])() const = {
5217 &item::AffectsEndurance
,
5218 &item::AffectsPerception
,
5219 &item::AffectsIntelligence
,
5220 &item::AffectsWisdom
,
5221 &item::AffectsWillPower
,
5222 &item::AffectsCharisma
,
5227 /* Returns nonzero if endurance has decreased and death may occur */
5228 truth
character::CalculateAttributeBonuses () {
5229 doforbodyparts()(this, &bodypart::CalculateAttributeBonuses
);
5230 int BackupBonus
[BASE_ATTRIBUTES
];
5231 int BackupCarryingBonus
= CarryingBonus
;
5234 for (c1
= 0; c1
< BASE_ATTRIBUTES
; ++c1
) {
5235 BackupBonus
[c1
] = AttributeBonus
[c1
];
5236 AttributeBonus
[c1
] = 0;
5238 for (c1
= 0; c1
< GetEquipments(); ++c1
) {
5239 item
*Equipment
= GetEquipment(c1
);
5240 if (!Equipment
|| !Equipment
->IsInCorrectSlot(c1
)) continue;
5241 for (int c2
= 0; c2
< BASE_ATTRIBUTES
; ++c2
) {
5242 if ((Equipment
->*AffectTest
[c2
])()) AttributeBonus
[c2
] += Equipment
->GetEnchantment();
5244 if (Equipment
->AffectsCarryingCapacity()) CarryingBonus
+= Equipment
->GetCarryingBonus();
5247 ApplySpecialAttributeBonuses();
5249 if (IsPlayer() && !IsInitializing() && AttributeBonus
[PERCEPTION
] != BackupBonus
[PERCEPTION
]) game::SendLOSUpdateRequest();
5250 if (IsPlayer() && !IsInitializing() && AttributeBonus
[INTELLIGENCE
] != BackupBonus
[INTELLIGENCE
]) UpdateESPLOS();
5252 if (!IsInitializing() && CarryingBonus
!= BackupCarryingBonus
) CalculateBurdenState();
5254 if (!IsInitializing() && AttributeBonus
[ENDURANCE
] != BackupBonus
[ENDURANCE
]) {
5255 CalculateBodyPartMaxHPs();
5256 CalculateMaxStamina();
5257 return AttributeBonus
[ENDURANCE
] < BackupBonus
[ENDURANCE
];
5264 void character::ApplyEquipmentAttributeBonuses (item
*Equipment
) {
5265 if (Equipment
->AffectsEndurance()) {
5266 AttributeBonus
[ENDURANCE
] += Equipment
->GetEnchantment();
5267 CalculateBodyPartMaxHPs();
5268 CalculateMaxStamina();
5270 if (Equipment
->AffectsPerception()) {
5271 AttributeBonus
[PERCEPTION
] += Equipment
->GetEnchantment();
5272 if (IsPlayer()) game::SendLOSUpdateRequest();
5274 if (Equipment
->AffectsIntelligence()) {
5275 AttributeBonus
[INTELLIGENCE
] += Equipment
->GetEnchantment();
5276 if (IsPlayer()) UpdateESPLOS();
5278 if (Equipment
->AffectsWisdom()) AttributeBonus
[WISDOM
] += Equipment
->GetEnchantment();
5279 if (Equipment
->AffectsWillPower()) AttributeBonus
[WILL_POWER
] += Equipment
->GetEnchantment();
5280 if (Equipment
->AffectsCharisma()) AttributeBonus
[CHARISMA
] += Equipment
->GetEnchantment();
5281 if (Equipment
->AffectsMana()) AttributeBonus
[MANA
] += Equipment
->GetEnchantment();
5282 if (Equipment
->AffectsCarryingCapacity()) {
5283 CarryingBonus
+= Equipment
->GetCarryingBonus();
5284 CalculateBurdenState();
5289 void character::ReceiveAntidote (sLong Amount
) {
5290 if (StateIsActivated(POISONED
)) {
5291 if (GetTemporaryStateCounter(POISONED
) > Amount
) {
5292 EditTemporaryStateCounter(POISONED
, -Amount
);
5295 if (IsPlayer()) ADD_MESSAGE("Aaaah... You feel much better.");
5296 Amount
-= GetTemporaryStateCounter(POISONED
);
5297 DeActivateTemporaryState(POISONED
);
5300 if ((Amount
>= 100 || RAND_N(100) < Amount
) && StateIsActivated(PARASITIZED
)) {
5301 if (IsPlayer()) ADD_MESSAGE("Something in your belly didn't seem to like this stuff.");
5302 DeActivateTemporaryState(PARASITIZED
);
5303 Amount
-= Min(100, Amount
);
5305 if ((Amount
>= 100 || RAND_N(100) < Amount
) && StateIsActivated(LEPROSY
)) {
5306 if (IsPlayer()) ADD_MESSAGE("You are not falling to pieces anymore.");
5307 DeActivateTemporaryState(LEPROSY
);
5308 Amount
-= Min(100, Amount
);
5313 void character::AddAntidoteConsumeEndMessage () const {
5314 if (StateIsActivated(POISONED
)) {
5315 // true only if the antidote didn't cure the poison completely
5316 if (IsPlayer()) ADD_MESSAGE("Your body processes the poison in your veins with rapid speed.");
5321 truth
character::IsDead () const {
5322 for (int c
= 0; c
< BodyParts
; ++c
) {
5323 bodypart
*BodyPart
= GetBodyPart(c
);
5324 if (BodyPartIsVital(c
) && (!BodyPart
|| BodyPart
->GetHP() < 1)) return true;
5330 void character::SignalSpoilLevelChange (material
*m
) {
5331 if (GetMotherEntity()) GetMotherEntity()->SignalSpoilLevelChange(m
); else UpdatePictures();
5335 void character::AddOriginalBodyPartID (int I
, feuLong What
) {
5336 if (std::find(OriginalBodyPartID
[I
].begin(), OriginalBodyPartID
[I
].end(), What
) == OriginalBodyPartID
[I
].end()) {
5337 OriginalBodyPartID
[I
].push_back(What
);
5338 if (OriginalBodyPartID
[I
].size() > 100) OriginalBodyPartID
[I
].erase(OriginalBodyPartID
[I
].begin());
5343 void character::AddToInventory (const fearray
<contentscript
<item
> > &ItemArray
, int SpecialFlags
) {
5344 for (uInt c1
= 0; c1
< ItemArray
.Size
; ++c1
) {
5345 if (ItemArray
[c1
].IsValid()) {
5346 const interval
*TimesPtr
= ItemArray
[c1
].GetTimes();
5347 int Times
= TimesPtr
? TimesPtr
->Randomize() : 1;
5348 for (int c2
= 0; c2
< Times
; ++c2
) {
5349 item
*Item
= ItemArray
[c1
].Instantiate(SpecialFlags
);
5351 Stack
->AddItem(Item
);
5352 Item
->SpecialGenerationHandler();
5360 truth
character::HasHadBodyPart (citem
*Item
) const {
5361 for (int c
= 0; c
< BodyParts
; ++c
)
5362 if (std::find(OriginalBodyPartID
[c
].begin(), OriginalBodyPartID
[c
].end(), Item
->GetID()) != OriginalBodyPartID
[c
].end())
5364 return GetPolymorphBackup() && GetPolymorphBackup()->HasHadBodyPart(Item
);
5368 festring
&character::ProcessMessage (festring
&Msg
) const {
5369 SEARCH_N_REPLACE(Msg
, "@nu", GetName(UNARTICLED
));
5370 SEARCH_N_REPLACE(Msg
, "@ni", GetName(INDEFINITE
));
5371 SEARCH_N_REPLACE(Msg
, "@nd", GetName(DEFINITE
));
5372 SEARCH_N_REPLACE(Msg
, "@du", GetDescription(UNARTICLED
));
5373 SEARCH_N_REPLACE(Msg
, "@di", GetDescription(INDEFINITE
));
5374 SEARCH_N_REPLACE(Msg
, "@dd", GetDescription(DEFINITE
));
5375 SEARCH_N_REPLACE(Msg
, "@pp", GetPersonalPronoun());
5376 SEARCH_N_REPLACE(Msg
, "@sp", GetPossessivePronoun());
5377 SEARCH_N_REPLACE(Msg
, "@op", GetObjectPronoun());
5378 SEARCH_N_REPLACE(Msg
, "@Nu", GetName(UNARTICLED
).CapitalizeCopy());
5379 SEARCH_N_REPLACE(Msg
, "@Ni", GetName(INDEFINITE
).CapitalizeCopy());
5380 SEARCH_N_REPLACE(Msg
, "@Nd", GetName(DEFINITE
).CapitalizeCopy());
5381 SEARCH_N_REPLACE(Msg
, "@Du", GetDescription(UNARTICLED
).CapitalizeCopy());
5382 SEARCH_N_REPLACE(Msg
, "@Di", GetDescription(INDEFINITE
).CapitalizeCopy());
5383 SEARCH_N_REPLACE(Msg
, "@Dd", GetDescription(DEFINITE
).CapitalizeCopy());
5384 SEARCH_N_REPLACE(Msg
, "@Pp", GetPersonalPronoun().CapitalizeCopy());
5385 SEARCH_N_REPLACE(Msg
, "@Sp", GetPossessivePronoun().CapitalizeCopy());
5386 SEARCH_N_REPLACE(Msg
, "@Op", GetObjectPronoun().CapitalizeCopy());
5387 SEARCH_N_REPLACE(Msg
, "@Gd", GetMasterGod()->GetName());
5392 void character::ProcessAndAddMessage (festring Msg
) const {
5393 ADD_MESSAGE("%s", ProcessMessage(Msg
).CStr());
5397 void character::BeTalkedTo () {
5399 if (GetRelation(PLAYER
) == HOSTILE
)
5400 ProcessAndAddMessage(GetHostileReplies()[RandomizeReply(Said
, GetHostileReplies().Size
)]);
5402 ProcessAndAddMessage(GetFriendlyReplies()[RandomizeReply(Said
, GetFriendlyReplies().Size
)]);
5406 truth
character::CheckZap () {
5408 ADD_MESSAGE("This monster type can't zap.");
5415 void character::DamageAllItems (character
*Damager
, int Damage
, int Type
) {
5416 GetStack()->ReceiveDamage(Damager
, Damage
, Type
);
5417 for (int c
= 0; c
< GetEquipments(); ++c
) {
5418 item
*Equipment
= GetEquipment(c
);
5419 if (Equipment
) Equipment
->ReceiveDamage(Damager
, Damage
, Type
);
5424 truth
character::Equips (citem
*Item
) const {
5425 return combineequipmentpredicateswithparam
<feuLong
>()(this, &item::HasID
, Item
->GetID(), 1);
5429 void character::PrintBeginConfuseMessage () const {
5430 if (IsPlayer()) ADD_MESSAGE("You feel quite happy.");
5434 void character::PrintEndConfuseMessage () const {
5435 if (IsPlayer()) ADD_MESSAGE("The world is boring again.");
5439 v2
character::ApplyStateModification (v2 TryDirection
) const {
5440 if (!StateIsActivated(CONFUSED
) || RAND() & 15 || game::IsInWilderness()) return TryDirection
;
5441 v2 To
= GetLevel()->GetFreeAdjacentSquare(this, GetPos(), true);
5442 if (To
== ERROR_V2
) return TryDirection
;
5444 if (To
!= TryDirection
&& IsPlayer()) ADD_MESSAGE("Whoa! You somehow don't manage to walk straight.");
5449 void character::AddConfuseHitMessage () const {
5450 if (IsPlayer()) ADD_MESSAGE("This stuff is confusing.");
5454 item
*character::SelectFromPossessions (cfestring
&Topic
, sorter Sorter
) {
5455 itemvector ReturnVector
;
5456 SelectFromPossessions(ReturnVector
, Topic
, NO_MULTI_SELECT
, Sorter
);
5457 return !ReturnVector
.empty() ? ReturnVector
[0] : 0;
5461 truth
character::SelectFromPossessions (itemvector
&ReturnVector
, cfestring
&Topic
, int Flags
, sorter Sorter
) {
5463 truth InventoryPossible
= GetStack()->SortedItems(this, Sorter
);
5464 if (InventoryPossible
) List
.AddEntry(CONST_S("choose from inventory"), LIGHT_GRAY
, 20, game::AddToItemDrawVector(itemvector()));
5469 for (c
= 0; c
< BodyParts
; ++c
) {
5470 bodypart
*BodyPart
= GetBodyPart(c
);
5471 if (BodyPart
&& (Sorter
== 0 || (BodyPart
->*Sorter
)(this))) {
5472 Item
.push_back(BodyPart
);
5474 BodyPart
->AddName(Entry
, UNARTICLED
);
5475 int ImageKey
= game::AddToItemDrawVector(itemvector(1, BodyPart
));
5476 List
.AddEntry(Entry
, LIGHT_GRAY
, 20, ImageKey
, true);
5480 for (c
= 0; c
< GetEquipments(); ++c
) {
5481 item
*Equipment
= GetEquipment(c
);
5482 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) {
5483 Item
.push_back(Equipment
);
5484 Entry
= GetEquipmentName(c
);
5487 Equipment
->AddInventoryEntry(this, Entry
, 1, true);
5488 AddSpecialEquipmentInfo(Entry
, c
);
5489 int ImageKey
= game::AddToItemDrawVector(itemvector(1, Equipment
));
5490 List
.AddEntry(Entry
, LIGHT_GRAY
, 20, ImageKey
, true);
5495 game::SetStandardListAttributes(List
);
5496 List
.SetFlags(SELECTABLE
|DRAW_BACKGROUND_AFTERWARDS
);
5497 List
.SetEntryDrawer(game::ItemEntryDrawer
);
5498 game::DrawEverythingNoBlit();
5499 int Chosen
= List
.Draw();
5500 game::ClearItemDrawVector();
5501 if (Chosen
!= ESCAPED
) {
5502 if ((InventoryPossible
&& !Chosen
) || Chosen
& FELIST_ERROR_BIT
) {
5503 GetStack()->DrawContents(ReturnVector
, this, Topic
, Flags
, Sorter
);
5505 ReturnVector
.push_back(Item
[InventoryPossible
? Chosen
- 1 : Chosen
]);
5506 if (Flags
& SELECT_PAIR
&& ReturnVector
[0]->HandleInPairs()) {
5507 item
*PairEquipment
= GetPairEquipment(ReturnVector
[0]->GetEquipmentIndex());
5508 if (PairEquipment
&& PairEquipment
->CanBePiledWith(ReturnVector
[0], this)) ReturnVector
.push_back(PairEquipment
);
5513 if (!GetStack()->SortedItems(this, Sorter
)) return false;
5514 game::ClearItemDrawVector();
5515 GetStack()->DrawContents(ReturnVector
, this, Topic
, Flags
, Sorter
);
5521 truth
character::EquipsSomething (sorter Sorter
) {
5522 for (int c
= 0; c
< GetEquipments(); ++c
) {
5523 item
*Equipment
= GetEquipment(c
);
5524 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) return true;
5530 material
*character::CreateBodyPartMaterial (int, sLong Volume
) const {
5531 return MAKE_MATERIAL(GetFleshMaterial(), Volume
);
5535 truth
character::CheckTalk () {
5537 ADD_MESSAGE("This monster does not know the art of talking.");
5544 truth
character::MoveTowardsHomePos () {
5545 if (HomeDataIsValid() && IsEnabled()) {
5546 SetGoingTo(HomeData
->Pos
);
5547 return MoveTowardsTarget(false) || (!GetPos().IsAdjacent(HomeData
->Pos
) && MoveRandomly());
5553 truth
character::TryToChangeEquipment (stack
*MainStack
, stack
*SecStack
, int Chosen
) {
5554 if (!GetBodyPartOfEquipment(Chosen
)) {
5555 ADD_MESSAGE("Bodypart missing!");
5558 item
*OldEquipment
= GetEquipment(Chosen
);
5559 if (!IsPlayer() && BoundToUse(OldEquipment
, Chosen
)) {
5560 ADD_MESSAGE("%s refuses to unequip %s.", CHAR_DESCRIPTION(DEFINITE
), OldEquipment
->CHAR_NAME(DEFINITE
));
5563 if (OldEquipment
) OldEquipment
->MoveTo(MainStack
);
5564 sorter Sorter
= EquipmentSorter(Chosen
);
5565 if (!MainStack
->SortedItems(this, Sorter
) && (!SecStack
|| !SecStack
->SortedItems(this, Sorter
))) {
5566 ADD_MESSAGE("You haven't got any item that could be used for this purpose.");
5569 game::DrawEverythingNoBlit();
5570 itemvector ItemVector
;
5571 int Return
= MainStack
->DrawContents(ItemVector
, SecStack
, this,
5572 CONST_S("Choose ") + GetEquipmentName(Chosen
) + ':',
5573 SecStack
? CONST_S("Items in your inventory") : CONST_S(""),
5574 SecStack
? festring(CONST_S("Items in ") + GetPossessivePronoun() + " inventory") : CONST_S(""),
5575 SecStack
? festring(GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState()) : CONST_S(""),
5576 GetVerbalBurdenStateColor(),
5577 NONE_AS_CHOICE
|NO_MULTI_SELECT
,
5579 if (Return
== ESCAPED
) {
5581 OldEquipment
->RemoveFromSlot();
5582 SetEquipment(Chosen
, OldEquipment
);
5586 item
*Item
= ItemVector
.empty() ? 0 : ItemVector
[0];
5588 if (!IsPlayer() && !AllowEquipment(Item
, Chosen
)) {
5589 ADD_MESSAGE("%s refuses to equip %s.", CHAR_DESCRIPTION(DEFINITE
), Item
->CHAR_NAME(DEFINITE
));
5592 Item
->RemoveFromSlot();
5593 SetEquipment(Chosen
, Item
);
5594 if (CheckIfEquipmentIsNotUsable(Chosen
)) Item
->MoveTo(MainStack
); // small bug?
5596 return Item
!= OldEquipment
;
5600 void character::PrintBeginParasitizedMessage () const {
5601 if (IsPlayer()) ADD_MESSAGE("You feel you are no longer alone.");
5605 void character::PrintEndParasitizedMessage () const {
5606 if (IsPlayer()) ADD_MESSAGE("A feeling of sLong welcome emptiness overwhelms you.");
5610 void character::ParasitizedHandler () {
5612 if (!(RAND() % 250)) {
5613 if (IsPlayer()) ADD_MESSAGE("Ugh. You feel something violently carving its way through your intestines.");
5614 ReceiveDamage(0, 1, POISON
, TORSO
, 8, false, false, false, false);
5615 CheckDeath(CONST_S("killed by a vile parasite"), 0);
5620 truth
character::CanFollow () const {
5621 return CanMove() && !StateIsActivated(PANIC
) && !IsStuck();
5625 festring
character::GetKillName () const {
5626 if (!GetPolymorphBackup()) return GetName(INDEFINITE
);
5628 GetPolymorphBackup()->AddName(KillName
, INDEFINITE
);
5629 KillName
<< " polymorphed into ";
5630 id::AddName(KillName
, INDEFINITE
);
5635 festring
character::GetPanelName () const {
5637 Name
<< AssignedName
<< " the " << game::GetVerbalPlayerAlignment() << ' ';
5638 id::AddName(Name
, UNARTICLED
);
5643 sLong
character::GetMoveAPRequirement (int Difficulty
) const {
5644 return (!StateIsActivated(PANIC
) ? 10000000 : 8000000) * Difficulty
/ (APBonus(GetAttribute(AGILITY
)) * GetMoveEase());
5648 bodypart
*character::HealHitPoint() {
5649 int NeedHeal
= 0, NeedHealIndex
[MAX_BODYPARTS
];
5650 for (int c
= 0; c
< BodyParts
; ++c
) {
5651 bodypart
*BodyPart
= GetBodyPart(c
);
5652 if (BodyPart
&& BodyPart
->CanRegenerate() && BodyPart
->GetHP() < BodyPart
->GetMaxHP()) NeedHealIndex
[NeedHeal
++] = c
;
5655 bodypart
*BodyPart
= GetBodyPart(NeedHealIndex
[RAND() % NeedHeal
]);
5656 BodyPart
->IncreaseHP();
5664 void character::CreateHomeData () {
5665 HomeData
= new homedata
;
5666 lsquare
*Square
= GetLSquareUnder();
5667 HomeData
->Pos
= Square
->GetPos();
5668 HomeData
->Dungeon
= Square
->GetDungeonIndex();
5669 HomeData
->Level
= Square
->GetLevelIndex();
5670 HomeData
->Room
= Square
->GetRoomIndex();
5674 room
*character::GetHomeRoom() const {
5675 if (HomeDataIsValid() && HomeData
->Room
) return GetLevel()->GetRoom(HomeData
->Room
);
5680 void character::RemoveHomeData () {
5686 void character::AddESPConsumeMessage () const {
5687 if (IsPlayer()) ADD_MESSAGE("You feel a strange mental activity.");
5691 void character::SetBodyPart (int I
, bodypart
*What
) {
5692 BodyPartSlot
[I
].PutInItem(What
);
5694 What
->SignalPossibleUsabilityChange();
5696 AddOriginalBodyPartID(I
, What
->GetID());
5697 if (What
->GetMainMaterial()->IsInfectedByLeprosy()) GainIntrinsic(LEPROSY
);
5698 else if (StateIsActivated(LEPROSY
)) What
->GetMainMaterial()->SetIsInfectedByLeprosy(true);
5703 truth
character::ConsumeItem (item
*Item
, cfestring
&ConsumeVerb
) {
5704 if (IsPlayer() && HasHadBodyPart(Item
) && !game::TruthQuestion(CONST_S("Are you sure? You may be able to put it back... [y/N]")))
5706 if (Item
->IsOnGround() && GetRoom() && !GetRoom()->ConsumeItem(this, Item
, 1))
5708 if (IsPlayer()) ADD_MESSAGE("You begin %s %s.", ConsumeVerb
.CStr(), Item
->CHAR_NAME(DEFINITE
));
5709 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s begins %s %s.", CHAR_NAME(DEFINITE
), ConsumeVerb
.CStr(), Item
->CHAR_NAME(DEFINITE
));
5710 consume
*Consume
= consume::Spawn(this);
5711 Consume
->SetDescription(ConsumeVerb
);
5712 Consume
->SetConsumingID(Item
->GetID());
5719 truth
character::CheckThrow () const {
5721 ADD_MESSAGE("This monster type cannot throw.");
5728 void character::GetHitByExplosion (const explosion
*Explosion
, int Damage
) {
5729 int DamageDirection
= GetPos() == Explosion
->Pos
? RANDOM_DIR
: game::CalculateRoughDirection(GetPos() - Explosion
->Pos
);
5730 if (!IsPet() && Explosion
->Terrorist
&& Explosion
->Terrorist
->IsPet()) Explosion
->Terrorist
->Hostility(this);
5731 GetTorso()->SpillBlood((8 - Explosion
->Size
+ RAND() % (8 - Explosion
->Size
)) >> 1);
5732 v2 SpillPos
= GetPos() + game::GetMoveVector(DamageDirection
);
5733 if (GetArea()->IsValidPos(SpillPos
)) GetTorso()->SpillBlood((8-Explosion
->Size
+RAND()%(8-Explosion
->Size
))>>1, SpillPos
);
5734 if (IsPlayer()) ADD_MESSAGE("You are hit by the explosion!");
5735 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s is hit by the explosion.", CHAR_NAME(DEFINITE
));
5736 truth WasUnconscious
= GetAction() && GetAction()->IsUnconsciousness();
5737 ReceiveDamage(Explosion
->Terrorist
, Damage
>> 1, FIRE
, ALL
, DamageDirection
, true, false, false, false);
5739 ReceiveDamage(Explosion
->Terrorist
, Damage
>> 1, PHYSICAL_DAMAGE
, ALL
, DamageDirection
, true, false, false, false);
5740 CheckDeath(Explosion
->DeathMsg
, Explosion
->Terrorist
, !WasUnconscious
? IGNORE_UNCONSCIOUSNESS
: 0);
5745 void character::SortAllItems (const sortdata
&SortData
) {
5746 GetStack()->SortAllItems(SortData
);
5747 doforequipmentswithparam
<const sortdata
&>()(this, &item::SortAllItems
, SortData
);
5751 void character::PrintBeginSearchingMessage () const {
5752 if (IsPlayer()) ADD_MESSAGE("You feel you can now notice even the very smallest details around you.");
5756 void character::PrintEndSearchingMessage () const {
5757 if (IsPlayer()) ADD_MESSAGE("You feel less perceptive.");
5761 void character::SearchingHandler () {
5762 if (!game::IsInWilderness()) Search(15);
5766 void character::Search (int Perception
) {
5767 for (int d
= 0; d
< GetExtendedNeighbourSquares(); ++d
) {
5768 lsquare
*LSquare
= GetNeighbourLSquare(d
);
5769 if (LSquare
) LSquare
->GetStack()->Search(this, Min(Perception
, 200));
5774 // surprisingly returns 0 if fails
5775 character
*character::GetRandomNeighbour (int RelationFlags
) const {
5776 character
*Chars
[MAX_NEIGHBOUR_SQUARES
];
5778 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
5779 lsquare
*LSquare
= GetNeighbourLSquare(d
);
5781 character
*Char
= LSquare
->GetCharacter();
5782 if (Char
&& (GetRelation(Char
) & RelationFlags
)) Chars
[Index
++] = Char
;
5785 return Index
? Chars
[RAND() % Index
] : 0;
5789 void character::ResetStates () {
5790 for (int c
= 0; c
< STATES
; ++c
) {
5791 if (1 << c
!= POLYMORPHED
&& TemporaryStateIsActivated(1 << c
) && TemporaryStateCounter
[c
] != PERMANENT
) {
5792 TemporaryState
&= ~(1 << c
);
5793 if (StateData
[c
].EndHandler
) {
5794 (this->*StateData
[c
].EndHandler
)();
5795 if (!IsEnabled())return;
5802 void characterdatabase::InitDefaults (const characterprototype
*NewProtoType
, int NewConfig
) {
5804 ProtoType
= NewProtoType
;
5810 void character::PrintBeginGasImmunityMessage () const {
5811 if (IsPlayer()) ADD_MESSAGE("All smells fade away.");
5815 void character::PrintEndGasImmunityMessage () const {
5816 if (IsPlayer()) ADD_MESSAGE("Yuck! The world smells bad again.");
5820 void character::ShowAdventureInfo () const {
5821 static const char *lists
[4][4] = {
5822 { "Show massacre history",
5824 "Show message history",
5827 "Show message history",
5830 { "Show message history",
5834 { "Show massacre history",
5835 "Show message history",
5839 // massacre, inventory, messages
5840 static const int nums
[4][3] = {
5847 if (GetStack()->GetItems()) {
5848 idx
= game::MassacreListsEmpty() ? 1 : 0;
5850 idx
= game::MassacreListsEmpty() ? 2 : 3;
5854 sel
= game::ListSelectorArray(sel
, CONST_S("Do you want to see some funny history?"), lists
[idx
]);
5856 if (sel
== nums
[idx
][0] && !game::MassacreListsEmpty()) {
5857 game::DisplayMassacreLists();
5859 if (sel
== nums
[idx
][1] && GetStack()->GetItems()) {
5860 GetStack()->DrawContents(this, CONST_S("Your inventory"), NO_SELECT
);
5861 for(stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) i
->DrawContents(this);
5862 doforequipmentswithparam
<ccharacter
*>()(this, &item::DrawContents
, this);
5864 if (sel
== nums
[idx
][2]) {
5865 msgsystem::DrawMessageHistory();
5871 truth
character::EditAllAttributes (int Amount
) {
5872 if (!Amount
) return true;
5874 truth MayEditMore
= false;
5875 for (c
= 0; c
< BodyParts
; ++c
) {
5876 bodypart
*BodyPart
= GetBodyPart(c
);
5877 if (BodyPart
&& BodyPart
->EditAllAttributes(Amount
)) MayEditMore
= true;
5879 for (c
= 0; c
< BASE_ATTRIBUTES
; ++c
) {
5880 if (BaseExperience
[c
]) {
5881 BaseExperience
[c
] += Amount
* EXP_MULTIPLIER
;
5882 LimitRef(BaseExperience
[c
], MIN_EXP
, MAX_EXP
);
5883 if ((Amount
< 0 && BaseExperience
[c
] != MIN_EXP
) || (Amount
> 0 && BaseExperience
[c
] != MAX_EXP
)) MayEditMore
= true;
5890 game::SendLOSUpdateRequest();
5893 if (IsPlayerKind()) UpdatePictures();
5899 void character::AddAttributeInfo (festring
&Entry
) const {
5901 Entry
<< GetAttribute(ENDURANCE
);
5903 Entry
<< GetAttribute(PERCEPTION
);
5905 Entry
<< GetAttribute(INTELLIGENCE
);
5907 Entry
<< GetAttribute(WISDOM
);
5909 Entry
<< GetAttribute(CHARISMA
);
5911 Entry
<< GetAttribute(MANA
);
5915 void character::AddDefenceInfo (felist
&List
) const {
5917 for (int c
= 0; c
< BodyParts
; ++c
) {
5918 bodypart
*BodyPart
= GetBodyPart(c
);
5920 Entry
= CONST_S(" ");
5921 BodyPart
->AddName(Entry
, UNARTICLED
);
5923 Entry
<< BodyPart
->GetMaxHP();
5925 Entry
<< BodyPart
->GetTotalResistance(PHYSICAL_DAMAGE
);
5926 List
.AddEntry(Entry
, LIGHT_GRAY
);
5932 void character::DetachBodyPart () {
5933 ADD_MESSAGE("You haven't got any extra bodyparts.");
5938 void character::ReceiveHolyBanana (sLong Amount
) {
5940 EditExperience(ARM_STRENGTH
, Amount
, 1 << 13);
5941 EditExperience(LEG_STRENGTH
, Amount
, 1 << 13);
5942 EditExperience(DEXTERITY
, Amount
, 1 << 13);
5943 EditExperience(AGILITY
, Amount
, 1 << 13);
5944 EditExperience(ENDURANCE
, Amount
, 1 << 13);
5945 EditExperience(PERCEPTION
, Amount
, 1 << 13);
5946 EditExperience(INTELLIGENCE
, Amount
, 1 << 13);
5947 EditExperience(WISDOM
, Amount
, 1 << 13);
5948 EditExperience(CHARISMA
, Amount
, 1 << 13);
5953 void character::AddHolyBananaConsumeEndMessage () const {
5954 if (IsPlayer()) ADD_MESSAGE("You feel a mysterious strengthening fire coursing through your body.");
5955 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s is surrounded by a swirling fire aura.", CHAR_NAME(DEFINITE
));
5959 void character::ReceiveHolyMango (sLong Amount
) {
5961 EditExperience(ARM_STRENGTH
, Amount
, 1 << 13);
5962 EditExperience(LEG_STRENGTH
, Amount
, 1 << 13);
5963 EditExperience(DEXTERITY
, Amount
, 1 << 13);
5964 EditExperience(AGILITY
, Amount
, 1 << 13);
5965 EditExperience(ENDURANCE
, Amount
, 1 << 13);
5966 EditExperience(PERCEPTION
, Amount
, 1 << 13);
5967 EditExperience(INTELLIGENCE
, Amount
, 1 << 13);
5968 EditExperience(WISDOM
, Amount
, 1 << 13);
5969 EditExperience(CHARISMA
, Amount
, 1 << 13);
5974 void character::AddHolyMangoConsumeEndMessage () const {
5975 if (IsPlayer()) ADD_MESSAGE("You feel a mysterious strengthening fire coursing through your body.");
5976 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s is surrounded by a swirling fire aura.", CHAR_NAME(DEFINITE
));
5980 truth
character::PreProcessForBone () {
5981 if (IsPet() && IsEnabled()) {
5982 Die(0, CONST_S(""), FORBID_REINCARNATION
);
5985 if (GetAction()) GetAction()->Terminate(false);
5986 if (TemporaryStateIsActivated(POLYMORPHED
)) {
5987 character
*PolymorphBackup
= GetPolymorphBackup();
5989 PolymorphBackup
->PreProcessForBone();
5992 if (MustBeRemovedFromBone()) return false;
5993 if (IsUnique() && !CanBeGenerated()) game::SignalQuestMonsterFound();
5997 GetStack()->PreProcessForBone();
5998 doforequipments()(this, &item::PreProcessForBone
);
5999 doforbodyparts()(this, &bodypart::PreProcessForBone
);
6000 game::RemoveCharacterID(ID
);
6002 game::AddCharacterID(this, ID
);
6007 truth
character::PostProcessForBone (double &DangerSum
, int& Enemies
) {
6008 if (PostProcessForBone()) {
6009 if (GetRelation(PLAYER
) == HOSTILE
) {
6010 double Danger
= GetRelativeDanger(PLAYER
, true);
6011 if (Danger
> 99.0) game::SetTooGreatDangerFound(true);
6012 else if (!IsUnique() && !IgnoreDanger()) {
6013 DangerSum
+= Danger
;
6023 truth
character::PostProcessForBone () {
6024 feuLong NewID
= game::CreateNewCharacterID(this);
6025 game::GetBoneCharacterIDMap().insert(std::make_pair(-ID
, NewID
));
6026 game::RemoveCharacterID(ID
);
6028 if (IsUnique() && CanBeGenerated()) {
6029 if (DataBase
->Flags
& HAS_BEEN_GENERATED
) return false;
6032 GetStack()->PostProcessForBone();
6033 doforequipments()(this, &item::PostProcessForBone
);
6034 doforbodyparts()(this, &bodypart::PostProcessForBone
);
6039 void character::FinalProcessForBone () {
6041 GetStack()->FinalProcessForBone();
6042 doforequipments()(this, &item::FinalProcessForBone
);
6044 for (c
= 0; c
< BodyParts
; ++c
) {
6045 for (std::list
<feuLong
>::iterator i
= OriginalBodyPartID
[c
].begin(); i
!= OriginalBodyPartID
[c
].end();) {
6046 boneidmap::iterator BI
= game::GetBoneItemIDMap().find(*i
);
6047 if (BI
== game::GetBoneItemIDMap().end()) {
6048 std::list
<feuLong
>::iterator Dirt
= i
++;
6049 OriginalBodyPartID
[c
].erase(Dirt
);
6059 void character::SetSoulID (feuLong What
) {
6060 if (GetPolymorphBackup()) GetPolymorphBackup()->SetSoulID(What
);
6064 truth
character::SearchForItem (citem
*Item
) const {
6065 if (combineequipmentpredicateswithparam
<feuLong
>()(this, &item::HasID
, Item
->GetID(), 1)) return true;
6066 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (*i
== Item
) return true;
6071 item
*character::SearchForItem (const sweaponskill
*SWeaponSkill
) const {
6072 for (int c
= 0; c
< GetEquipments(); ++c
) {
6073 item
*Equipment
= GetEquipment(c
);
6074 if (Equipment
&& SWeaponSkill
->IsSkillOf(Equipment
)) return Equipment
;
6076 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (SWeaponSkill
->IsSkillOf(*i
)) return *i
;
6081 void character::PutNear (v2 Pos
) {
6082 v2 NewPos
= game::GetCurrentLevel()->GetNearestFreeSquare(this, Pos
, false);
6083 if (NewPos
== ERROR_V2
) {
6084 do { NewPos
= game::GetCurrentLevel()->GetRandomSquare(this); } while(NewPos
== Pos
);
6090 void character::PutToOrNear (v2 Pos
) {
6091 if (game::IsInWilderness() || (CanMoveOn(game::GetCurrentLevel()->GetLSquare(Pos
)) && IsFreeForMe(game::GetCurrentLevel()->GetLSquare(Pos
))))
6098 void character::PutTo (v2 Pos
) {
6099 SquareUnder
[0] = game::GetCurrentArea()->GetSquare(Pos
);
6100 SquareUnder
[0]->AddCharacter(this);
6104 void character::Remove () {
6105 SquareUnder
[0]->RemoveCharacter();
6110 void character::SendNewDrawRequest () const {
6111 for (int c
= 0; c
< SquaresUnder
; ++c
) {
6112 square
*Square
= GetSquareUnder(c
);
6113 if (Square
) Square
->SendNewDrawRequest();
6118 truth
character::IsOver (v2 Pos
) const {
6119 for (int c
= 0; c
< SquaresUnder
; ++c
) {
6120 square
*Square
= GetSquareUnder(c
);
6121 if (Square
&& Square
->GetPos() == Pos
) return true;
6127 truth
character::CanTheoreticallyMoveOn (const lsquare
*LSquare
) const { return GetMoveType() & LSquare
->GetTheoreticalWalkability(); }
6128 truth
character::CanMoveOn (const lsquare
*LSquare
) const { return GetMoveType() & LSquare
->GetWalkability(); }
6129 truth
character::CanMoveOn (const square
*Square
) const { return GetMoveType() & Square
->GetSquareWalkability(); }
6130 truth
character::CanMoveOn (const olterrain
*OLTerrain
) const { return GetMoveType() & OLTerrain
->GetWalkability(); }
6131 truth
character::CanMoveOn (const oterrain
*OTerrain
) const { return GetMoveType() & OTerrain
->GetWalkability(); }
6132 truth
character::IsFreeForMe(square
*Square
) const { return !Square
->GetCharacter() || Square
->GetCharacter() == this; }
6133 void character::LoadSquaresUnder () { SquareUnder
[0] = game::GetSquareInLoad(); }
6135 truth
character::AttackAdjacentEnemyAI () {
6136 if (!IsEnabled()) return false;
6137 character
*Char
[MAX_NEIGHBOUR_SQUARES
];
6138 v2 Pos
[MAX_NEIGHBOUR_SQUARES
];
6139 int Dir
[MAX_NEIGHBOUR_SQUARES
];
6141 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
6142 square
*Square
= GetNeighbourSquare(d
);
6144 character
*Enemy
= Square
->GetCharacter();
6145 if (Enemy
&& (GetRelation(Enemy
) == HOSTILE
|| StateIsActivated(CONFUSED
))) {
6147 Pos
[Index
] = Square
->GetPos();
6148 Char
[Index
++] = Enemy
;
6153 int ChosenIndex
= RAND() % Index
;
6154 Hit(Char
[ChosenIndex
], Pos
[ChosenIndex
], Dir
[ChosenIndex
]);
6161 void character::SignalStepFrom (lsquare
**OldSquareUnder
) {
6163 lsquare
*NewSquareUnder
[MAX_SQUARES_UNDER
];
6164 for (c
= 0; c
< GetSquaresUnder(); ++c
) NewSquareUnder
[c
] = GetLSquareUnder(c
);
6165 for (c
= 0; c
< GetSquaresUnder(); ++c
) {
6166 if (IsEnabled() && GetLSquareUnder(c
) == NewSquareUnder
[c
]) NewSquareUnder
[c
]->StepOn(this, OldSquareUnder
);
6171 int character::GetSumOfAttributes () const {
6172 return GetAttribute(ENDURANCE
) + GetAttribute(PERCEPTION
) + GetAttribute(INTELLIGENCE
) + GetAttribute(WISDOM
) + GetAttribute(CHARISMA
) + GetAttribute(ARM_STRENGTH
) + GetAttribute(AGILITY
);
6176 void character::IntelligenceAction (int Difficulty
) {
6177 EditAP(-20000 * Difficulty
/ APBonus(GetAttribute(INTELLIGENCE
)));
6178 EditExperience(INTELLIGENCE
, Difficulty
* 15, 1 << 7);
6182 struct walkabilitycontroller
{
6183 static truth
Handler (int x
, int y
) {
6184 return x
>= 0 && y
>= 0 && x
< LevelXSize
&& y
< LevelYSize
&& Map
[x
][y
]->GetTheoreticalWalkability() & MoveType
;
6186 static lsquare
***Map
;
6187 static int LevelXSize
, LevelYSize
;
6188 static int MoveType
;
6192 lsquare
***walkabilitycontroller::Map
;
6193 int walkabilitycontroller::LevelXSize
, walkabilitycontroller::LevelYSize
;
6194 int walkabilitycontroller::MoveType
;
6197 truth
character::CreateRoute () {
6199 if (GetAttribute(INTELLIGENCE
) >= 10 && !StateIsActivated(CONFUSED
)) {
6201 walkabilitycontroller::Map
= GetLevel()->GetMap();
6202 walkabilitycontroller::LevelXSize
= GetLevel()->GetXSize();
6203 walkabilitycontroller::LevelYSize
= GetLevel()->GetYSize();
6204 walkabilitycontroller::MoveType
= GetMoveType();
6206 for (int c
= 0; c
< game::GetTeams(); ++c
)
6207 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
6208 character
*Char
= *i
;
6209 if (Char
->IsEnabled() && !Char
->Route
.empty() && (Char
->GetMoveType()&GetMoveType()) == Char
->GetMoveType()) {
6210 v2 CharGoingTo
= Char
->Route
[0];
6211 v2 iPos
= Char
->Route
.back();
6212 if ((GoingTo
-CharGoingTo
).GetLengthSquare() <= 100 && (Pos
- iPos
).GetLengthSquare() <= 100 &&
6213 mapmath
<walkabilitycontroller
>::DoLine(CharGoingTo
.X
, CharGoingTo
.Y
, GoingTo
.X
, GoingTo
.Y
, SKIP_FIRST
) &&
6214 mapmath
<walkabilitycontroller
>::DoLine(Pos
.X
, Pos
.Y
, iPos
.X
, iPos
.Y
, SKIP_FIRST
)) {
6215 if (!Illegal
.empty() && Illegal
.find(Char
->Route
.back()) != Illegal
.end()) continue;
6216 Node
= GetLevel()->FindRoute(CharGoingTo
, GoingTo
, Illegal
, GetMoveType());
6217 if (Node
) { while(Node
->Last
) { Route
.push_back(Node
->Pos
); Node
= Node
->Last
; } }
6218 else { Route
.clear(); continue; }
6219 Route
.insert(Route
.end(), Char
->Route
.begin(), Char
->Route
.end());
6220 Node
= GetLevel()->FindRoute(Pos
, iPos
, Illegal
, GetMoveType());
6221 if (Node
) { while (Node
->Last
) { Route
.push_back(Node
->Pos
); Node
= Node
->Last
; } }
6222 else { Route
.clear(); continue; }
6223 IntelligenceAction(1);
6228 Node
= GetLevel()->FindRoute(Pos
, GoingTo
, Illegal
, GetMoveType());
6229 if (Node
) { while(Node
->Last
) { Route
.push_back(Node
->Pos
); Node
= Node
->Last
; } }
6230 else TerminateGoingTo();
6231 IntelligenceAction(5);
6238 void character::SetGoingTo (v2 What
) {
6239 if (GoingTo
!= What
) {
6247 void character::TerminateGoingTo () {
6254 truth
character::CheckForFood (int Radius
) {
6255 if (StateIsActivated(PANIC
) || !UsesNutrition() || !IsEnabled()) return false;
6258 for (int r
= 1; r
<= Radius
; ++r
) {
6261 for (y
= Pos
.Y
-r
; y
<= Pos
.Y
+r
; ++y
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
6264 if (x
< GetLevel()->GetXSize()) {
6265 for (y
= Pos
.Y
-r
; y
<= Pos
.Y
+r
; ++y
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
6269 for (x
= Pos
.X
-r
; x
<= Pos
.X
+r
; ++x
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
6272 if (y
< GetLevel()->GetYSize()) {
6273 for (x
= Pos
.X
-r
; x
<= Pos
.X
+r
; ++x
) if (CheckForFoodInSquare(v2(x
, y
))) return true;
6280 truth
character::CheckForFoodInSquare (v2 Pos
) {
6281 level
*Level
= GetLevel();
6282 if (Level
->IsValidPos(Pos
)) {
6283 lsquare
*Square
= Level
->GetLSquare(Pos
);
6284 stack
*Stack
= Square
->GetStack();
6285 if (Stack
->GetItems()) {
6286 for (stackiterator i
= Stack
->GetBottom(); i
.HasItem(); ++i
) {
6287 if (i
->IsPickable(this) && i
->CanBeSeenBy(this) && i
->CanBeEatenByAI(this) && (!Square
->GetRoomIndex() || Square
->GetRoom()->AllowFoodSearch())) {
6289 return MoveTowardsTarget(false);
6298 void character::SetConfig (int NewConfig
, int SpecialFlags
) {
6299 databasecreator
<character
>::InstallDataBase(this, NewConfig
);
6302 if (!(SpecialFlags
& NO_PIC_UPDATE
)) UpdatePictures();
6306 truth
character::IsOver (citem
*Item
) const {
6307 for (int c1
= 0; c1
< Item
->GetSquaresUnder(); ++c1
)
6308 for (int c2
= 0; c2
< SquaresUnder
; ++c2
)
6309 if (Item
->GetPos(c1
) == GetPos(c2
)) return true;
6314 truth
character::CheckConsume (cfestring
&Verb
) const {
6315 if (!UsesNutrition()) {
6316 if (IsPlayer()) ADD_MESSAGE("In this form you can't and don't need to %s.", Verb
.CStr());
6323 void character::PutTo (lsquare
*To
) {
6324 PutTo(To
->GetPos());
6328 double character::RandomizeBabyExperience (double SumE
) {
6329 if (!SumE
) return 0;
6330 double E
= (SumE
/ 4) - (SumE
/ 32) + (double(RAND()) / MAX_RAND
) * (SumE
/ 16 + 1);
6331 return Limit(E
, MIN_EXP
, MAX_EXP
);
6335 liquid
*character::CreateBlood (sLong Volume
) const {
6336 return liquid::Spawn(GetBloodMaterial(), Volume
);
6340 void character::SpillFluid (character
*Spiller
, liquid
*Liquid
, int SquareIndex
) {
6341 sLong ReserveVolume
= Liquid
->GetVolume() >> 1;
6342 Liquid
->EditVolume(-ReserveVolume
);
6343 GetStack()->SpillFluid(Spiller
, Liquid
, sLong(Liquid
->GetVolume() * sqrt(double(GetStack()->GetVolume()) / GetVolume())));
6344 Liquid
->EditVolume(ReserveVolume
);
6346 sLong Modifier
[MAX_BODYPARTS
], ModifierSum
= 0;
6347 for (c
= 0; c
< BodyParts
; ++c
) {
6348 if (GetBodyPart(c
)) {
6349 Modifier
[c
] = sLong(sqrt(GetBodyPart(c
)->GetVolume()));
6350 if (Modifier
[c
]) Modifier
[c
] *= 1 + (RAND() & 3);
6351 ModifierSum
+= Modifier
[c
];
6356 for (c
= 1; c
< GetBodyParts(); ++c
) {
6357 if (GetBodyPart(c
) && IsEnabled())
6358 GetBodyPart(c
)->SpillFluid(Spiller
, Liquid
->SpawnMoreLiquid(Liquid
->GetVolume() * Modifier
[c
] / ModifierSum
), SquareIndex
);
6361 Liquid
->SetVolume(Liquid
->GetVolume() * Modifier
[TORSO_INDEX
] / ModifierSum
);
6362 GetTorso()->SpillFluid(Spiller
, Liquid
, SquareIndex
);
6367 void character::StayOn (liquid
*Liquid
) {
6368 Liquid
->TouchEffect(this, TORSO_INDEX
);
6372 truth
character::IsAlly (ccharacter
*Char
) const {
6373 return Char
->GetTeam()->GetID() == GetTeam()->GetID();
6377 void character::ResetSpoiling () {
6378 doforbodyparts()(this, &bodypart::ResetSpoiling
);
6382 item
*character::SearchForItem (ccharacter
*Char
, sorter Sorter
) const {
6383 item
*Equipment
= findequipment
<ccharacter
*>()(this, Sorter
, Char
);
6384 if (Equipment
) return Equipment
;
6385 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) if (((*i
)->*Sorter
)(Char
)) return *i
;
6390 truth
character::DetectMaterial (cmaterial
*Material
) const {
6391 return GetStack()->DetectMaterial(Material
) ||
6392 combinebodypartpredicateswithparam
<cmaterial
*>()(this, &bodypart::DetectMaterial
, Material
, 1) ||
6393 combineequipmentpredicateswithparam
<cmaterial
*>()(this, &item::DetectMaterial
, Material
, 1);
6397 truth
character::DamageTypeDestroysBodyPart (int Type
) {
6398 return (Type
&0xFFF) != PHYSICAL_DAMAGE
;
6402 truth
character::CheckIfTooScaredToHit (ccharacter
*Enemy
) const {
6403 if (IsPlayer() && StateIsActivated(PANIC
)) {
6404 for (int d
= 0; d
< GetNeighbourSquares(); ++d
) {
6405 square
*Square
= GetNeighbourSquare(d
);
6407 if(CanMoveOn(Square
) && (!Square
->GetCharacter() || Square
->GetCharacter()->IsPet())) {
6408 ADD_MESSAGE("You are too scared to attack %s.", Enemy
->CHAR_DESCRIPTION(DEFINITE
));
6418 void character::PrintBeginLevitationMessage () const {
6420 if (IsPlayer()) ADD_MESSAGE("You rise into the air like a small hot-air balloon.");
6421 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s begins to float.", CHAR_NAME(DEFINITE
));
6426 void character::PrintEndLevitationMessage () const {
6428 if (IsPlayer()) ADD_MESSAGE("You descend gently onto the ground.");
6429 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s drops onto the ground.", CHAR_NAME(DEFINITE
));
6434 truth
character::IsLimbIndex (int I
) {
6436 case RIGHT_ARM_INDEX
:
6437 case LEFT_ARM_INDEX
:
6438 case RIGHT_LEG_INDEX
:
6439 case LEFT_LEG_INDEX
:
6446 void character::EditExperience (int Identifier
, double Value
, double Speed
) {
6447 if (!AllowExperience() || (Identifier
== ENDURANCE
&& UseMaterialAttributes())) return;
6448 int Change
= RawEditExperience(BaseExperience
[Identifier
], GetNaturalExperience(Identifier
), Value
, Speed
);
6449 if (!Change
) return;
6450 cchar
*PlayerMsg
= 0, *NPCMsg
= 0;
6451 switch (Identifier
) {
6454 PlayerMsg
= "You feel tougher than anything!";
6455 if (IsPet()) NPCMsg
= "Suddenly %s looks tougher.";
6457 PlayerMsg
= "You feel less healthy.";
6458 if (IsPet()) NPCMsg
= "Suddenly %s looks less healthy.";
6460 CalculateBodyPartMaxHPs();
6461 CalculateMaxStamina();
6466 PlayerMsg
= "You now see the world in much better detail than before.";
6468 PlayerMsg
= "You feel very guru.";
6469 game::GetGod(VALPURUS
)->AdjustRelation(100);
6471 game::SendLOSUpdateRequest();
6476 if (Change
> 0) PlayerMsg
= "Suddenly the inner structure of the Multiverse around you looks quite simple.";
6477 else PlayerMsg
= "It surely is hard to think today.";
6480 if (IsPlayerKind()) UpdatePictures();
6484 if (Change
> 0) PlayerMsg
= "You feel your life experience increasing all the time.";
6485 else PlayerMsg
= "You feel like having done something unwise.";
6487 if (IsPlayerKind()) UpdatePictures();
6491 PlayerMsg
= "You feel very confident of your social skills.";
6493 if (GetAttribute(CHARISMA
) <= 15) NPCMsg
= "%s looks less ugly.";
6494 else NPCMsg
= "%s looks more attractive.";
6497 PlayerMsg
= "You feel somehow disliked.";
6499 if (GetAttribute(CHARISMA
) < 15) NPCMsg
= "%s looks more ugly.";
6500 else NPCMsg
= "%s looks less attractive.";
6503 if (IsPlayerKind()) UpdatePictures();
6507 PlayerMsg
= "You feel magical forces coursing through your body!";
6508 NPCMsg
= "You notice an odd glow around %s.";
6510 PlayerMsg
= "You feel your magical abilities withering slowly.";
6511 NPCMsg
= "You notice strange vibrations in the air around %s. But they disappear rapidly.";
6516 if (IsPlayer()) ADD_MESSAGE("%s", PlayerMsg
);
6517 else if (NPCMsg
&& CanBeSeenByPlayer()) ADD_MESSAGE(NPCMsg
, CHAR_NAME(DEFINITE
));
6519 CalculateBattleInfo();
6523 int character::RawEditExperience (double &Exp
, double NaturalExp
, double Value
, double Speed
) const {
6524 double OldExp
= Exp
;
6529 if(!OldExp
|| !Value
|| (Value
> 0 && OldExp
>= NaturalExp
* (100 + Value
) / 100) ||
6530 (Value
< 0 && OldExp
<= NaturalExp
* (100 + Value
) / 100)) return 0;
6531 if (!IsPlayer()) Speed
*= 1.5;
6532 Exp
+= (NaturalExp
* (100 + Value
) - 100 * OldExp
) * Speed
* EXP_DIVISOR
;
6533 LimitRef(Exp
, MIN_EXP
, MAX_EXP
);
6534 int NewA
= int(Exp
* EXP_DIVISOR
);
6535 int OldA
= int(OldExp
* EXP_DIVISOR
);
6536 int Delta
= NewA
- OldA
;
6537 if (Delta
> 0) Exp
= Max(Exp
, (NewA
+ 0.05) * EXP_MULTIPLIER
);
6538 else if (Delta
< 0) Exp
= Min(Exp
, (NewA
+ 0.95) * EXP_MULTIPLIER
);
6539 LimitRef(Exp
, MIN_EXP
, MAX_EXP
);
6544 int character::GetAttribute (int Identifier
, truth AllowBonus
) const {
6545 int A
= int(BaseExperience
[Identifier
] * EXP_DIVISOR
);
6546 if (AllowBonus
&& Identifier
== INTELLIGENCE
&& BrainsHurt()) return Max((A
+ AttributeBonus
[INTELLIGENCE
]) / 3, 1);
6547 return A
&& AllowBonus
? Max(A
+ AttributeBonus
[Identifier
], 1) : A
;
6551 void characterdatabase::PostProcess () {
6552 double AM
= (100 + AttributeBonus
) * EXP_MULTIPLIER
/ 100;
6553 for (int c
= 0; c
< ATTRIBUTES
; ++c
) NaturalExperience
[c
] = this->*ExpPtr
[c
] * AM
;
6557 void character::EditDealExperience (sLong Price
) {
6558 EditExperience(CHARISMA
, sqrt(Price
) / 5, 1 << 9);
6562 void character::PrintBeginLeprosyMessage () const {
6563 if (IsPlayer()) ADD_MESSAGE("You feel you're falling in pieces.");
6567 void character::PrintEndLeprosyMessage () const {
6568 if (IsPlayer()) ADD_MESSAGE("You feel your limbs are stuck in place tightly."); // CHANGE OR DIE
6572 void character::TryToInfectWithLeprosy (ccharacter
*Infector
) {
6573 if (!IsImmuneToLeprosy() &&
6574 ((GetRelation(Infector
) == HOSTILE
&& !RAND_N(50 * GetAttribute(ENDURANCE
))) ||
6575 !RAND_N(500 * GetAttribute(ENDURANCE
)))) GainIntrinsic(LEPROSY
);
6579 void character::SignalGeneration () {
6580 const_cast<database
*>(DataBase
)->Flags
|= HAS_BEEN_GENERATED
;
6584 void character::CheckIfSeen () {
6585 if (IsPlayer() || CanBeSeenByPlayer()) SignalSeen();
6589 void character::SignalSeen () {
6590 if (!(WarnFlags
& WARNED
) && GetRelation(PLAYER
) == HOSTILE
) {
6591 double Danger
= GetRelativeDanger(PLAYER
);
6593 game::SetDangerFound(Max(game::GetDangerFound(), Danger
));
6594 if (Danger
> 500.0 && !(WarnFlags
& HAS_CAUSED_PANIC
)) {
6595 WarnFlags
|= HAS_CAUSED_PANIC
;
6596 game::SetCausePanicFlag(true);
6598 WarnFlags
|= WARNED
;
6601 const_cast<database
*>(DataBase
)->Flags
|= HAS_BEEN_SEEN
;
6605 int character::GetPolymorphIntelligenceRequirement () const {
6606 if (DataBase
->PolymorphIntelligenceRequirement
== DEPENDS_ON_ATTRIBUTES
) return Max(GetAttributeAverage() - 5, 0);
6607 return DataBase
->PolymorphIntelligenceRequirement
;
6611 void character::RemoveAllItems () {
6612 GetStack()->Clean();
6613 for (int c
= 0; c
< GetEquipments(); ++c
) {
6614 item
*Equipment
= GetEquipment(c
);
6616 Equipment
->RemoveFromSlot();
6617 Equipment
->SendToHell();
6623 int character::CalculateWeaponSkillHits (ccharacter
*Enemy
) const {
6624 if (Enemy
->IsPlayer()) {
6625 configid
ConfigID(GetType(), GetConfig());
6626 const dangerid
& DangerID
= game::GetDangerMap().find(ConfigID
)->second
;
6627 return Min(int(DangerID
.EquippedDanger
* 2000), 1000);
6629 return Min(int(GetRelativeDanger(Enemy
, true) * 2000), 1000);
6633 truth
character::CanUseEquipment (int I
) const {
6634 return CanUseEquipment() && I
< GetEquipments() && GetBodyPartOfEquipment(I
) && EquipmentIsAllowed(I
);
6638 /* Target mustn't have any equipment */
6639 void character::DonateEquipmentTo (character
*Character
) {
6641 feuLong
*EquipmentMemory
= game::GetEquipmentMemory();
6642 for (int c
= 0; c
< MAX_EQUIPMENT_SLOTS
; ++c
) {
6643 item
*Item
= GetEquipment(c
);
6645 if (Character
->CanUseEquipment(c
)) {
6646 Item
->RemoveFromSlot();
6647 Character
->SetEquipment(c
, Item
);
6649 EquipmentMemory
[c
] = Item
->GetID();
6650 Item
->MoveTo(Character
->GetStack());
6652 } else if (CanUseEquipment(c
)) {
6653 EquipmentMemory
[c
] = 0;
6654 } else if (EquipmentMemory
[c
] && Character
->CanUseEquipment(c
)) {
6655 for (stackiterator i
= Character
->GetStack()->GetBottom(); i
.HasItem(); ++i
) {
6656 if (i
->GetID() == EquipmentMemory
[c
]) {
6658 Item
->RemoveFromSlot();
6659 Character
->SetEquipment(c
, Item
);
6663 EquipmentMemory
[c
] = 0;
6667 for (int c
= 0; c
< GetEquipments(); ++c
) {
6668 item
*Item
= GetEquipment(c
);
6670 if (Character
->CanUseEquipment(c
)) {
6671 Item
->RemoveFromSlot();
6672 Character
->SetEquipment(c
, Item
);
6674 Item
->MoveTo(Character
->GetStackUnder());
6682 void character::ReceivePeaSoup (sLong
) {
6683 if (!game::IsInWilderness()) {
6684 lsquare
*Square
= GetLSquareUnder();
6685 if (Square
->IsFlyable()) Square
->AddSmoke(gas::Spawn(FART
, 250));
6690 void character::AddPeaSoupConsumeEndMessage () const {
6692 if (CanHear()) ADD_MESSAGE("Mmmh! The soup is very tasty. You hear a small puff.");
6693 else ADD_MESSAGE("Mmmh! The soup is very tasty.");
6694 } else if (CanBeSeenByPlayer() && PLAYER
->CanHear()) {
6696 ADD_MESSAGE("You hear a small puff.");
6701 void character::CalculateMaxStamina () {
6702 MaxStamina
= TorsoIsAlive() ? GetAttribute(ENDURANCE
) * 10000 : 0;
6706 void character::EditStamina (int Amount
, truth CanCauseUnconsciousness
) {
6707 if (!TorsoIsAlive()) return;
6708 int UnconsciousnessStamina
= MaxStamina
>> 3;
6709 if (!CanCauseUnconsciousness
&& Amount
< 0) {
6710 if (Stamina
> UnconsciousnessStamina
) {
6712 if (Stamina
< UnconsciousnessStamina
) Stamina
= UnconsciousnessStamina
;
6716 int OldStamina
= Stamina
;
6718 if (Stamina
> MaxStamina
) {
6719 Stamina
= MaxStamina
;
6720 } else if (Stamina
< 0) {
6722 LoseConsciousness(250 + RAND_N(250));
6723 } else if (IsPlayer()) {
6724 if (OldStamina
>= MaxStamina
>> 2 && Stamina
< MaxStamina
>> 2) {
6725 ADD_MESSAGE("You are getting a little tired.");
6726 } else if(OldStamina
>= UnconsciousnessStamina
&& Stamina
< UnconsciousnessStamina
) {
6727 ADD_MESSAGE("You are seriously out of breath!");
6728 game::SetPlayerIsRunning(false);
6731 if (IsPlayer() && StateIsActivated(PANIC
) && GetTirednessState() != FAINTING
) game::SetPlayerIsRunning(true);
6735 void character::RegenerateStamina () {
6736 if (GetTirednessState() != UNTIRED
) {
6737 EditExperience(ENDURANCE
, 50, 1);
6738 if (Sweats() && TorsoIsAlive() && !RAND_N(30) && !game::IsInWilderness()) {
6739 // Sweat amount proportional to endurance also
6740 //sLong Volume = sLong(0.05 * sqrt(GetBodyVolume()));
6741 sLong Volume
= long(0.05*sqrt(GetBodyVolume()*GetAttribute(ENDURANCE
)/10));
6742 if (GetTirednessState() == FAINTING
) Volume
<<= 1;
6743 for (int c
= 0; c
< SquaresUnder
; ++c
) GetLSquareUnder(c
)->SpillFluid(0, CreateSweat(Volume
), false, false);
6748 if (Action
->IsRest()) {
6749 if (SquaresUnder
== 1) {
6750 Bonus
= GetSquareUnder()->GetRestModifier() << 1;
6752 int Lowest
= GetSquareUnder(0)->GetRestModifier();
6753 for (int c
= 1; c
< GetSquaresUnder(); ++c
) {
6754 int Mod
= GetSquareUnder(c
)->GetRestModifier();
6755 if (Mod
< Lowest
) Lowest
= Mod
;
6757 Bonus
= Lowest
<< 1;
6759 } else if (Action
->IsUnconsciousness()) Bonus
= 2;
6762 switch (GetBurdenState()) {
6763 case OVER_LOADED
: Plus1
= 25; break;
6764 case STRESSED
: Plus1
= 50; break;
6765 case BURDENED
: Plus1
= 75; break;
6769 switch (GetHungerState()) {
6770 case STARVING
: Plus2
= 25; break;
6771 case VERY_HUNGRY
: Plus2
= 50; break;
6772 case HUNGRY
: Plus2
= 75; break;
6775 Stamina
+= Plus1
* Plus2
* Bonus
/ 1000;
6776 if (Stamina
> MaxStamina
) Stamina
= MaxStamina
;
6777 if (IsPlayer() && StateIsActivated(PANIC
) && GetTirednessState() != FAINTING
) game::SetPlayerIsRunning(true);
6781 void character::BeginPanic () {
6782 if (IsPlayer() && GetTirednessState() != FAINTING
) game::SetPlayerIsRunning(true);
6783 DeActivateVoluntaryAction();
6787 void character::EndPanic () {
6788 if (IsPlayer()) game::SetPlayerIsRunning(false);
6792 int character::GetTirednessState () const {
6793 if (Stamina
>= MaxStamina
>> 2) return UNTIRED
;
6794 if (Stamina
>= MaxStamina
>> 3) return EXHAUSTED
;
6799 void character::ReceiveBlackUnicorn (sLong Amount
) {
6800 if (!(RAND() % 160)) game::DoEvilDeed(Amount
/ 50);
6801 BeginTemporaryState(TELEPORT
, Amount
/ 100);
6802 for (int c
= 0; c
< STATES
; ++c
) {
6803 if (StateData
[c
].Flags
& DUR_TEMPORARY
) {
6804 BeginTemporaryState(1 << c
, Amount
/ 100);
6805 if (!IsEnabled()) return;
6806 } else if (StateData
[c
].Flags
& DUR_PERMANENT
) {
6807 GainIntrinsic(1 << c
);
6808 if (!IsEnabled()) return;
6814 void character::ReceiveGrayUnicorn (sLong Amount
) {
6815 if (!(RAND() % 80)) game::DoEvilDeed(Amount
/ 50);
6816 BeginTemporaryState(TELEPORT
, Amount
/ 100);
6817 for (int c
= 0; c
< STATES
; ++c
) {
6818 if (1 << c
!= TELEPORT
) {
6819 DecreaseStateCounter(1 << c
, -Amount
/ 100);
6820 if (!IsEnabled()) return;
6826 void character::ReceiveWhiteUnicorn (sLong Amount
) {
6827 if (!(RAND() % 40)) game::DoEvilDeed(Amount
/ 50);
6828 BeginTemporaryState(TELEPORT
, Amount
/ 100);
6829 DecreaseStateCounter(LYCANTHROPY
, -Amount
/ 100);
6830 DecreaseStateCounter(POISONED
, -Amount
/ 100);
6831 DecreaseStateCounter(PARASITIZED
, -Amount
/ 100);
6832 DecreaseStateCounter(LEPROSY
, -Amount
/ 100);
6833 DecreaseStateCounter(VAMPIRISM
, -Amount
/ 100);
6837 /* Counter should be negative. Removes intrinsics. */
6838 void character::DecreaseStateCounter (sLong State
, int Counter
) {
6840 for (Index
= 0; Index
< STATES
; ++Index
) if (1 << Index
== State
) break;
6841 if (Index
== STATES
) ABORT("DecreaseTemporaryStateCounter works only when State == 2^n!");
6842 if (TemporaryState
& State
) {
6843 if (TemporaryStateCounter
[Index
] == PERMANENT
|| (TemporaryStateCounter
[Index
] += Counter
) <= 0) {
6844 TemporaryState
&= ~State
;
6845 if (!(EquipmentState
& State
)) {
6846 if (StateData
[Index
].EndHandler
) {
6847 (this->*StateData
[Index
].EndHandler
)();
6848 if (!IsEnabled()) return;
6850 (this->*StateData
[Index
].PrintEndMessage
)();
6857 truth
character::IsImmuneToLeprosy () const {
6858 return DataBase
->IsImmuneToLeprosy
|| UseMaterialAttributes();
6862 void character::LeprosyHandler () {
6863 EditExperience(ARM_STRENGTH
, -25, 1 << 1);
6864 EditExperience(LEG_STRENGTH
, -25, 1 << 1);
6865 EditExperience(DEXTERITY
, -25, 1 << 1);
6866 EditExperience(AGILITY
, -25, 1 << 1);
6867 EditExperience(ENDURANCE
, -25, 1 << 1);
6868 EditExperience(CHARISMA
, -25, 1 << 1);
6869 CheckDeath(CONST_S("killed by leprosy"));
6873 bodypart
*character::SearchForOriginalBodyPart (int I
) const {
6874 for (stackiterator i1
= GetStackUnder()->GetBottom(); i1
.HasItem(); ++i1
) {
6875 for (std::list
<feuLong
>::iterator i2
= OriginalBodyPartID
[I
].begin(); i2
!= OriginalBodyPartID
[I
].end(); ++i2
)
6876 if (i1
->GetID() == *i2
) return static_cast<bodypart
*>(*i1
);
6882 void character::SetLifeExpectancy (int Base
, int RandPlus
) {
6884 for (c
= 0; c
< BodyParts
; ++c
) {
6885 bodypart
*BodyPart
= GetBodyPart(c
);
6886 if (BodyPart
) BodyPart
->SetLifeExpectancy(Base
, RandPlus
);
6888 for (c
= 0; c
< GetEquipments(); ++c
) {
6889 item
*Equipment
= GetEquipment(c
);
6890 if (Equipment
) Equipment
->SetLifeExpectancy(Base
, RandPlus
);
6895 /* Receiver should be a fresh duplicate of this */
6896 void character::DuplicateEquipment (character
*Receiver
, feuLong Flags
) {
6897 for (int c
= 0; c
< GetEquipments(); ++c
) {
6898 item
*Equipment
= GetEquipment(c
);
6900 item
*Duplicate
= Equipment
->Duplicate(Flags
);
6901 Receiver
->SetEquipment(c
, Duplicate
);
6907 void character::Disappear (corpse
*Corpse
, cchar
*Verb
, truth (item::*ClosePredicate
)() const) {
6908 truth TorsoDisappeared
= false;
6909 truth CanBeSeen
= Corpse
? Corpse
->CanBeSeenByPlayer() : IsPlayer() || CanBeSeenByPlayer();
6911 if ((GetTorso()->*ClosePredicate
)()) {
6913 if (Corpse
) ADD_MESSAGE("%s %ss.", Corpse
->CHAR_NAME(DEFINITE
), Verb
);
6914 else if (IsPlayer()) ADD_MESSAGE("You %s.", Verb
);
6915 else ADD_MESSAGE("%s %ss.", CHAR_NAME(DEFINITE
), Verb
);
6917 TorsoDisappeared
= true;
6918 for (c
= 0; c
< GetEquipments(); ++c
) {
6919 item
*Equipment
= GetEquipment(c
);
6920 if (Equipment
&& (Equipment
->*ClosePredicate
)()) {
6921 Equipment
->RemoveFromSlot();
6922 Equipment
->SendToHell();
6925 itemvector ItemVector
;
6926 GetStack()->FillItemVector(ItemVector
);
6927 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) {
6928 if (ItemVector
[c
] && (ItemVector
[c
]->*ClosePredicate
)()) {
6929 ItemVector
[c
]->RemoveFromSlot();
6930 ItemVector
[c
]->SendToHell();
6934 for (c
= 1; c
< GetBodyParts(); ++c
) {
6935 bodypart
*BodyPart
= GetBodyPart(c
);
6937 if ((BodyPart
->*ClosePredicate
)()) {
6938 if (!TorsoDisappeared
&& CanBeSeen
) {
6939 if(IsPlayer()) ADD_MESSAGE("Your %s %ss.", GetBodyPartName(c
).CStr(), Verb
);
6940 else ADD_MESSAGE("The %s of %s %ss.", GetBodyPartName(c
).CStr(), CHAR_NAME(DEFINITE
), Verb
);
6942 BodyPart
->DropEquipment();
6943 item
*BodyPart
= SevereBodyPart(c
);
6944 if (BodyPart
) BodyPart
->SendToHell();
6945 } else if (TorsoDisappeared
) {
6946 BodyPart
->DropEquipment();
6947 item
*BodyPart
= SevereBodyPart(c
);
6949 if (Corpse
) Corpse
->GetSlot()->AddFriendItem(BodyPart
);
6950 else if (!game::IsInWilderness()) GetStackUnder()->AddItem(BodyPart
);
6951 else BodyPart
->SendToHell();
6956 if (TorsoDisappeared
) {
6958 Corpse
->RemoveFromSlot();
6959 Corpse
->SendToHell();
6961 CheckDeath(festring(Verb
) + "ed", 0, FORCE_DEATH
|DISALLOW_CORPSE
|DISALLOW_MSG
);
6964 CheckDeath(festring(Verb
) + "ed", 0, DISALLOW_MSG
);
6969 void character::SignalDisappearance () {
6970 if (GetMotherEntity()) GetMotherEntity()->SignalDisappearance();
6971 else Disappear(0, "disappear", &item::IsVeryCloseToDisappearance
);
6975 truth
character::HornOfFearWorks () const {
6976 return CanHear() && GetPanicLevel() > RAND() % 33;
6980 void character::BeginLeprosy () {
6981 doforbodypartswithparam
<truth
>()(this, &bodypart::SetIsInfectedByLeprosy
, true);
6985 void character::EndLeprosy () {
6986 doforbodypartswithparam
<truth
>()(this, &bodypart::SetIsInfectedByLeprosy
, false);
6990 truth
character::IsSameAs (ccharacter
*What
) const {
6991 return What
->GetType() == GetType() && What
->GetConfig() == GetConfig();
6995 feuLong
character::GetCommandFlags () const {
6996 return !StateIsActivated(PANIC
) ? CommandFlags
: CommandFlags
|FLEE_FROM_ENEMIES
;
7000 feuLong
character::GetConstantCommandFlags () const {
7001 return !StateIsActivated(PANIC
) ? DataBase
->ConstantCommandFlags
: DataBase
->ConstantCommandFlags
|FLEE_FROM_ENEMIES
;
7005 feuLong
character::GetPossibleCommandFlags () const {
7006 int Int
= GetAttribute(INTELLIGENCE
);
7007 feuLong Flags
= ALL_COMMAND_FLAGS
;
7008 if (!CanMove() || Int
< 4) Flags
&= ~FOLLOW_LEADER
;
7009 if (!CanMove() || Int
< 6) Flags
&= ~FLEE_FROM_ENEMIES
;
7010 if (!CanUseEquipment() || Int
< 8) Flags
&= ~DONT_CHANGE_EQUIPMENT
;
7011 if (!UsesNutrition() || Int
< 8) Flags
&= ~DONT_CONSUME_ANYTHING_VALUABLE
;
7016 truth
character::IsRetreating () const {
7017 return StateIsActivated(PANIC
) || (CommandFlags
& FLEE_FROM_ENEMIES
&& IsPet());
7021 truth
character::ChatMenu () {
7022 if (GetAction() && !GetAction()->CanBeTalkedTo()) {
7023 ADD_MESSAGE("%s is silent.", CHAR_DESCRIPTION(DEFINITE
));
7024 PLAYER
->EditAP(-200);
7027 feuLong ManagementFlags
= GetManagementFlags();
7028 if (ManagementFlags
== CHAT_IDLY
|| !IsPet()) return ChatIdly();
7029 static cchar
*const ChatMenuEntry
[CHAT_MENU_ENTRIES
] = {
7036 static const petmanagementfunction PMF
[CHAT_MENU_ENTRIES
] = {
7037 &character::ChangePetEquipment
,
7038 &character::TakePetItems
,
7039 &character::GivePetItems
,
7040 &character::IssuePetCommands
,
7041 &character::ChatIdly
7043 felist
List(CONST_S("Choose action:"));
7044 game::SetStandardListAttributes(List
);
7045 List
.AddFlags(SELECTABLE
);
7047 for (c
= 0; c
< CHAT_MENU_ENTRIES
; ++c
) if (1 << c
& ManagementFlags
) List
.AddEntry(ChatMenuEntry
[c
], LIGHT_GRAY
);
7048 int Chosen
= List
.Draw();
7049 if (Chosen
& FELIST_ERROR_BIT
) return false;
7050 for (c
= 0, i
= 0; c
< CHAT_MENU_ENTRIES
; ++c
) {
7051 if (1 << c
& ManagementFlags
&& i
++ == Chosen
) return (this->*PMF
[c
])();
7053 return false; // dummy
7057 truth
character::ChangePetEquipment () {
7058 if (EquipmentScreen(PLAYER
->GetStack(), GetStack())) {
7066 truth
character::TakePetItems () {
7067 truth Success
= false;
7068 stack::SetSelected(0);
7071 game::DrawEverythingNoBlit();
7072 GetStack()->DrawContents(
7076 CONST_S("What do you want to take from ") + CHAR_DESCRIPTION(DEFINITE
) + '?',
7079 GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState(),
7080 GetVerbalBurdenStateColor(),
7082 if (ToTake
.empty()) break;
7083 for (uInt c
= 0; c
< ToTake
.size(); ++c
) ToTake
[c
]->MoveTo(PLAYER
->GetStack());
7084 ADD_MESSAGE("You take %s.", ToTake
[0]->GetName(DEFINITE
, ToTake
.size()).CStr());
7089 PLAYER
->DexterityAction(2);
7095 truth
character::GivePetItems () {
7096 truth Success
= false;
7097 stack::SetSelected(0);
7100 game::DrawEverythingNoBlit();
7101 PLAYER
->GetStack()->DrawContents(
7105 CONST_S("What do you want to give to ") + CHAR_DESCRIPTION(DEFINITE
) + '?',
7108 GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState(),
7109 GetVerbalBurdenStateColor(),
7111 if (ToGive
.empty()) break;
7112 for (uInt c
= 0; c
< ToGive
.size(); ++c
) ToGive
[c
]->MoveTo(GetStack());
7113 ADD_MESSAGE("You give %s to %s.", ToGive
[0]->GetName(DEFINITE
, ToGive
.size()).CStr(), CHAR_DESCRIPTION(DEFINITE
));
7118 PLAYER
->DexterityAction(2);
7124 truth
character::IssuePetCommands () {
7125 if (!IsConscious()) {
7126 ADD_MESSAGE("%s is unconscious.", CHAR_DESCRIPTION(DEFINITE
));
7129 feuLong PossibleC
= GetPossibleCommandFlags();
7131 ADD_MESSAGE("%s cannot be commanded.", CHAR_DESCRIPTION(DEFINITE
));
7134 feuLong OldC
= GetCommandFlags();
7135 feuLong NewC
= OldC
, VaryFlags
= 0;
7136 game::CommandScreen(CONST_S("Issue commands to ")+GetDescription(DEFINITE
), PossibleC
, GetConstantCommandFlags(), VaryFlags
, NewC
);
7137 if (NewC
== OldC
) return false;
7138 SetCommandFlags(NewC
);
7139 PLAYER
->EditAP(-500);
7140 PLAYER
->EditExperience(CHARISMA
, 25, 1 << 7);
7145 truth
character::ChatIdly () {
7146 if (!TryToTalkAboutScience()) {
7148 PLAYER
->EditExperience(CHARISMA
, 75, 1 << 7);
7150 PLAYER
->EditAP(-1000);
7155 truth
character::EquipmentScreen (stack
*MainStack
, stack
*SecStack
) {
7156 if (!CanUseEquipment()) {
7157 ADD_MESSAGE("%s cannot use equipment.", CHAR_DESCRIPTION(DEFINITE
));
7161 truth EquipmentChanged
= false;
7162 felist
List(CONST_S("Equipment menu [ESC exits]"));
7166 List
.EmptyDescription();
7168 List
.AddDescription(CONST_S(""));
7169 List
.AddDescription(festring(GetDescription(DEFINITE
) + " is " + GetVerbalBurdenState()).CapitalizeCopy(), GetVerbalBurdenStateColor());
7171 for (int c
= 0; c
< GetEquipments(); ++c
) {
7172 Entry
= GetEquipmentName(c
);
7175 item
*Equipment
= GetEquipment(c
);
7177 Equipment
->AddInventoryEntry(this, Entry
, 1, true);
7178 AddSpecialEquipmentInfo(Entry
, c
);
7179 int ImageKey
= game::AddToItemDrawVector(itemvector(1, Equipment
));
7180 List
.AddEntry(Entry
, LIGHT_GRAY
, 20, ImageKey
, true);
7182 Entry
<< (GetBodyPartOfEquipment(c
) ? "-" : "can't use");
7183 List
.AddEntry(Entry
, LIGHT_GRAY
, 20, game::AddToItemDrawVector(itemvector()));
7186 game::DrawEverythingNoBlit();
7187 game::SetStandardListAttributes(List
);
7188 List
.SetFlags(SELECTABLE
|DRAW_BACKGROUND_AFTERWARDS
);
7189 List
.SetEntryDrawer(game::ItemEntryDrawer
);
7190 Chosen
= List
.Draw();
7191 game::ClearItemDrawVector();
7192 if (Chosen
>= GetEquipments()) break;
7193 EquipmentChanged
= TryToChangeEquipment(MainStack
, SecStack
, Chosen
);
7195 if (EquipmentChanged
) DexterityAction(5);
7196 return EquipmentChanged
;
7200 feuLong
character::GetManagementFlags () const {
7201 feuLong Flags
= ALL_MANAGEMENT_FLAGS
;
7202 if (!CanUseEquipment() || !AllowPlayerToChangeEquipment()) Flags
&= ~CHANGE_EQUIPMENT
;
7203 if (!GetStack()->GetItems()) Flags
&= ~TAKE_ITEMS
;
7204 if (!WillCarryItems()) Flags
&= ~GIVE_ITEMS
;
7205 if (!GetPossibleCommandFlags()) Flags
&= ~ISSUE_COMMANDS
;
7210 cchar
*VerbalBurdenState
[] = { "overloaded", "stressed", "burdened", "unburdened" };
7211 col16 VerbalBurdenStateColor
[] = { RED
, BLUE
, BLUE
, WHITE
};
7213 cchar
*character::GetVerbalBurdenState () const { return VerbalBurdenState
[BurdenState
]; }
7214 col16
character::GetVerbalBurdenStateColor () const { return VerbalBurdenStateColor
[BurdenState
]; }
7215 int character::GetAttributeAverage () const { return GetSumOfAttributes()/7; }
7217 cfestring
&character::GetStandVerb() const {
7218 if (ForceCustomStandVerb()) return DataBase
->StandVerb
;
7219 static festring Hovering
= "hovering";
7220 static festring Swimming
= "swimming";
7221 if (StateIsActivated(LEVITATION
)) return Hovering
;
7222 if (IsSwimming()) return Swimming
;
7223 return DataBase
->StandVerb
;
7227 truth
character::CheckApply () const {
7229 ADD_MESSAGE("This monster type cannot apply.");
7236 void character::EndLevitation () {
7237 if (!IsFlying() && GetSquareUnder()) {
7238 if (!game::IsInWilderness()) SignalStepFrom(0);
7239 if (game::IsInWilderness() || !GetLSquareUnder()->IsFreezed()) TestWalkability();
7244 truth
character::CanMove () const {
7245 return !IsRooted() || StateIsActivated(LEVITATION
);
7249 void character::CalculateEnchantments () {
7250 doforequipments()(this, &item::CalculateEnchantment
);
7251 GetStack()->CalculateEnchantments();
7255 truth
character::GetNewFormForPolymorphWithControl (character
*&NewForm
) {
7256 festring Topic
, Temp
;
7259 festring Temp
= game::DefaultQuestion(CONST_S("What do you want to become? [press '?' for a list]"), game::GetDefaultPolymorphTo(), &game::PolymorphControlKeyHandler
);
7260 NewForm
= protosystem::CreateMonster(Temp
);
7262 if (NewForm
->IsSameAs(this)) {
7264 ADD_MESSAGE("You choose not to polymorph.");
7268 if (PolymorphBackup
&& NewForm
->IsSameAs(PolymorphBackup
)) {
7270 NewForm
= ForceEndPolymorph();
7273 if (NewForm
->GetPolymorphIntelligenceRequirement() > GetAttribute(INTELLIGENCE
) && !game::WizardModeIsActive()) {
7274 ADD_MESSAGE("You feel your mind isn't yet powerful enough to call forth the form of %s.", NewForm
->CHAR_NAME(INDEFINITE
));
7278 NewForm
->RemoveAllItems();
7286 liquid
*character::CreateSweat(sLong Volume
) const {
7287 //return liquid::Spawn(GetSweatMaterial(), Volume);
7288 return liquid::Spawn(GetCurrentSweatMaterial(), Volume
);
7292 truth
character::TeleportRandomItem (truth TryToHinderVisibility
) {
7293 if (IsImmuneToItemTeleport()) return false;
7294 itemvector ItemVector
;
7295 std::vector
<sLong
> PossibilityVector
;
7296 int TotalPossibility
= 0;
7297 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
7298 ItemVector
.push_back(*i
);
7299 int Possibility
= i
->GetTeleportPriority();
7300 if (TryToHinderVisibility
) Possibility
+= i
->GetHinderVisibilityBonus(this);
7301 PossibilityVector
.push_back(Possibility
);
7302 TotalPossibility
+= Possibility
;
7304 for (int c
= 0; c
< GetEquipments(); ++c
) {
7305 item
*Equipment
= GetEquipment(c
);
7307 ItemVector
.push_back(Equipment
);
7308 int Possibility
= Equipment
->GetTeleportPriority();
7309 if (TryToHinderVisibility
) Possibility
+= Equipment
->GetHinderVisibilityBonus(this);
7310 PossibilityVector
.push_back(Possibility
<<= 1);
7311 TotalPossibility
+= Possibility
;
7314 if (!TotalPossibility
) return false;
7315 int Chosen
= femath::WeightedRand(PossibilityVector
, TotalPossibility
);
7316 item
*Item
= ItemVector
[Chosen
];
7317 truth Equipped
= PLAYER
->Equips(Item
);
7318 truth Seen
= Item
->CanBeSeenByPlayer();
7319 Item
->RemoveFromSlot();
7320 if (Seen
) ADD_MESSAGE("%s disappears.", Item
->CHAR_NAME(DEFINITE
));
7321 if (Equipped
) game::AskForEscPress(CONST_S("Equipment lost!"));
7323 int Range
= Item
->GetEmitation() && TryToHinderVisibility
? 25 : 5;
7324 rect
Border(Pos
+ v2(-Range
, -Range
), Pos
+ v2(Range
, Range
));
7325 Pos
= GetLevel()->GetRandomSquare(this, 0, &Border
);
7326 if (Pos
== ERROR_V2
) Pos
= GetLevel()->GetRandomSquare();
7327 GetNearLSquare(Pos
)->GetStack()->AddItem(Item
);
7328 if (Item
->CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", Item
->CHAR_NAME(INDEFINITE
));
7333 truth
character::HasClearRouteTo (v2 Pos
) const {
7334 pathcontroller::Map
= GetLevel()->GetMap();
7335 pathcontroller::Character
= this;
7336 v2 ThisPos
= GetPos();
7337 return mapmath
<pathcontroller
>::DoLine(ThisPos
.X
, ThisPos
.Y
, Pos
.X
, Pos
.Y
, SKIP_FIRST
);
7341 truth
character::IsTransparent () const {
7342 return !IsEnormous() || GetTorso()->GetMainMaterial()->IsTransparent() || StateIsActivated(INVISIBLE
);
7346 void character::SignalPossibleTransparencyChange () {
7347 if (!game::IsInWilderness()) {
7348 for (int c
= 0; c
< SquaresUnder
; ++c
) {
7349 lsquare
*Square
= GetLSquareUnder(c
);
7350 if (Square
) Square
->SignalPossibleTransparencyChange();
7356 int character::GetCursorData () const {
7358 int Color
= game::PlayerIsRunning() ? BLUE_CURSOR
: DARK_CURSOR
;
7359 for (int c
= 0; c
< BodyParts
; ++c
) {
7360 bodypart
*BodyPart
= GetBodyPart(c
);
7361 if (BodyPart
&& BodyPart
->IsUsable()) {
7362 int ConditionColorIndex
= BodyPart
->GetConditionColorIndex();
7363 if ((BodyPartIsVital(c
) && !ConditionColorIndex
) || (ConditionColorIndex
<= 1 && ++Bad
== 2)) return Color
|CURSOR_FLASH
;
7364 } else if (++Bad
== 2) return Color
|CURSOR_FLASH
;
7366 Color
= game::PlayerIsRunning() ? YELLOW_CURSOR
: RED_CURSOR
;
7367 return Bad
? Color
|CURSOR_FLASH
: Color
;
7371 void character::TryToName () {
7372 if (!IsPet()) ADD_MESSAGE("%s refuses to let YOU decide what %s's called.", CHAR_NAME(DEFINITE
), CHAR_PERSONAL_PRONOUN
);
7373 else if (IsPlayer()) ADD_MESSAGE("You can't rename yourself.");
7374 else if (!IsNameable()) ADD_MESSAGE("%s refuses to be called anything else but %s.", CHAR_NAME(DEFINITE
), CHAR_NAME(DEFINITE
));
7376 festring Topic
= CONST_S("What name will you give to ")+GetName(DEFINITE
)+'?';
7377 festring Name
= game::StringQuestion(Topic
, WHITE
, 0, 80, true);
7378 if (Name
.GetSize()) SetAssignedName(Name
);
7383 double character::GetSituationDanger (ccharacter
*Enemy
, v2 ThisPos
, v2 EnemyPos
, truth SeesEnemy
) const {
7385 if (IgnoreDanger() && !IsPlayer()) {
7386 if (Enemy
->IgnoreDanger() && !Enemy
->IsPlayer()) {
7387 Danger
= double(GetHP())*GetHPRequirementForGeneration()/(Enemy
->GetHP()*Enemy
->GetHPRequirementForGeneration());
7390 Danger
= 0.25*GetHPRequirementForGeneration()/Enemy
->GetHP();
7392 } else if (Enemy
->IgnoreDanger() && !Enemy
->IsPlayer()) {
7393 Danger
= 4.0*GetHP()/Enemy
->GetHPRequirementForGeneration();
7395 Danger
= GetRelativeDanger(Enemy
);
7397 Danger
*= 3.0/((EnemyPos
-ThisPos
).GetManhattanLength()+2);
7398 if (!SeesEnemy
) Danger
*= 0.2;
7399 if (StateIsActivated(PANIC
)) Danger
*= 0.2;
7400 Danger
*= double(GetHP())*Enemy
->GetMaxHP()/(Enemy
->GetHP()*GetMaxHP());
7405 void character::ModifySituationDanger (double &Danger
) const {
7406 switch (GetTirednessState()) {
7407 case FAINTING
: Danger
*= 1.5;
7408 case EXHAUSTED
: Danger
*= 1.25;
7410 for (int c
= 0; c
< STATES
; ++c
) {
7411 if (StateIsActivated(1 << c
) && StateData
[c
].SituationDangerModifier
!= 0) (this->*StateData
[c
].SituationDangerModifier
)(Danger
);
7416 void character::LycanthropySituationDangerModifier (double &Danger
) const {
7417 character
*Wolf
= werewolfwolf::Spawn();
7418 double DangerToWolf
= GetRelativeDanger(Wolf
);
7419 Danger
*= pow(DangerToWolf
, 0.1);
7424 void character::PoisonedSituationDangerModifier (double &Danger
) const {
7425 int C
= GetTemporaryStateCounter(POISONED
);
7426 Danger
*= (1+(C
*C
)/(GetHP()*10000.0*(GetGlobalResistance(POISON
)+1)));
7430 void character::PolymorphingSituationDangerModifier (double &Danger
) const {
7431 if (!StateIsActivated(POLYMORPH_CONTROL
)) Danger
*= 1.5;
7435 void character::PanicSituationDangerModifier (double &Danger
) const {
7440 void character::ConfusedSituationDangerModifier (double &Danger
) const {
7445 void character::ParasitizedSituationDangerModifier (double &Danger
) const {
7450 void character::LeprosySituationDangerModifier (double &Danger
) const {
7455 void character::AddRandomScienceName (festring
&String
) const {
7456 festring Science
= GetScienceTalkName().GetRandomElement().CStr();
7457 if (Science
[0] == '!') {
7458 String
<< Science
.CStr()+1;
7461 festring Attribute
= GetScienceTalkAdjectiveAttribute().GetRandomElement();
7463 truth NoAttrib
= Attribute
.IsEmpty(), NoSecondAdjective
= false;
7464 if (!Attribute
.IsEmpty() && Attribute
[0] == '!') {
7465 NoSecondAdjective
= true;
7466 Attribute
.Erase(0, 1);
7468 if (!Science
.Find("the ")) {
7469 Science
.Erase(0, 4);
7470 if (!Attribute
.Find("the ", 0, 4)) Attribute
<< " the"; else Attribute
.Insert(0, "the ", 4);
7472 if (islower(Science
[0]) && Science
.Find(' ') == festring::NPos
&& Science
.Find('-') == festring::NPos
&&
7473 Science
.Find("phobia") == festring::NPos
) {
7474 Prefix
= GetScienceTalkPrefix().GetRandomElement();
7475 if (!Prefix
.IsEmpty() && Science
.Find(Prefix
) != festring::NPos
) Prefix
.Empty();
7477 int L
= Prefix
.GetSize();
7478 if (L
&& Prefix
[L
-1] == Science
[0]) Science
.Erase(0, 1);
7479 if (!NoAttrib
&& !NoSecondAdjective
== !RAND_GOOD(3)) {
7480 int S1
= NoSecondAdjective
? 0 : GetScienceTalkAdjectiveAttribute().Size
;
7481 int S2
= GetScienceTalkSubstantiveAttribute().Size
;
7482 festring OtherAttribute
;
7483 int Chosen
= RAND_GOOD(S1
+S2
);
7484 if (Chosen
< S1
) OtherAttribute
= GetScienceTalkAdjectiveAttribute()[Chosen
];
7485 else OtherAttribute
= GetScienceTalkSubstantiveAttribute()[Chosen
- S1
];
7486 if (!OtherAttribute
.IsEmpty() && OtherAttribute
.Find("the ", 0, 4) && Attribute
.Find(OtherAttribute
) == festring::NPos
) {
7487 String
<< Attribute
<< ' ' << OtherAttribute
<< ' ' << Prefix
<< Science
;
7491 String
<< Attribute
;
7492 if (!NoAttrib
) String
<< ' ';
7493 String
<< Prefix
<< Science
;
7497 truth
character::TryToTalkAboutScience () {
7498 if (GetRelation(PLAYER
) == HOSTILE
||
7499 GetScienceTalkPossibility() <= RAND_GOOD(100) ||
7500 PLAYER
->GetAttribute(INTELLIGENCE
) < GetScienceTalkIntelligenceRequirement() ||
7501 PLAYER
->GetAttribute(WISDOM
) < GetScienceTalkWisdomRequirement() ||
7502 PLAYER
->GetAttribute(CHARISMA
) < GetScienceTalkCharismaRequirement())
7506 AddRandomScienceName(Science
);
7509 AddRandomScienceName(S1
);
7510 AddRandomScienceName(S2
);
7511 if (S1
.Find(S2
) == festring::NPos
&& S2
.Find(S1
) == festring::NPos
) {
7512 switch (RAND_GOOD(3)) {
7513 case 0: Science
= "the relation of "; break;
7514 case 1: Science
= "the differences of "; break;
7515 case 2: Science
= "the similarities of "; break;
7517 Science
<< S1
<< " and " << S2
;
7520 AddRandomScienceName(Science
);
7523 switch ((RAND() + GET_TICK()) % 10) {
7525 ADD_MESSAGE("You have a rather pleasant chat about %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7528 ADD_MESSAGE("%s explains a few of %s opinions regarding %s to you.", CHAR_DESCRIPTION(DEFINITE
), CHAR_POSSESSIVE_PRONOUN
, Science
.CStr());
7531 ADD_MESSAGE("%s reveals a number of %s insightful views of %s to you.", CHAR_DESCRIPTION(DEFINITE
), CHAR_POSSESSIVE_PRONOUN
, Science
.CStr());
7534 ADD_MESSAGE("You exhange some information pertaining to %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7537 ADD_MESSAGE("You engage in a pretty intriguing conversation about %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7540 ADD_MESSAGE("You discuss at length about %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7543 ADD_MESSAGE("You have a somewhat boring talk concerning %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7546 ADD_MESSAGE("You are drawn into a heated argument regarding %s with %s.", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7549 ADD_MESSAGE("%s delivers a sLong monologue concerning eg. %s.", CHAR_DESCRIPTION(DEFINITE
), Science
.CStr());
7552 ADD_MESSAGE("You dive into a brief but thought-provoking debate over %s with %s", Science
.CStr(), CHAR_DESCRIPTION(DEFINITE
));
7555 PLAYER
->EditExperience(INTELLIGENCE
, 1000, 50. * GetScienceTalkIntelligenceModifier() / ++ScienceTalks
);
7556 PLAYER
->EditExperience(WISDOM
, 1000, 50. * GetScienceTalkWisdomModifier() / ++ScienceTalks
);
7557 PLAYER
->EditExperience(CHARISMA
, 1000, 50. * GetScienceTalkCharismaModifier() / ++ScienceTalks
);
7562 truth
character::IsUsingWeaponOfCategory (int Category
) const {
7564 ((GetMainWielded() && GetMainWielded()->GetWeaponCategory() == Category
) ||
7565 (GetSecondaryWielded() && GetSecondaryWielded()->GetWeaponCategory() == Category
));
7569 truth
character::TryToUnStickTraps (v2 Dir
) {
7570 if (!TrapData
) return true;
7571 std::vector
<trapdata
> TrapVector
;
7572 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) TrapVector
.push_back(*TrapData
);
7573 for (uInt c
= 0; c
< TrapVector
.size(); ++c
) {
7575 entity
*Trap
= game::SearchTrap(TrapVector
[c
].TrapID
);
7576 /*k8:??? if(!Trap->Exists()) int esko = esko = 2; */
7577 if (!Trap
->Exists()) continue; /*k8: ??? added by me; what this means? */
7578 if (Trap
->GetVictimID() == GetID() && Trap
->TryToUnStick(this, Dir
)) break;
7581 return !TrapData
&& IsEnabled();
7585 struct trapidcomparer
{
7586 trapidcomparer (feuLong ID
) : ID(ID
) {}
7587 truth
operator () (const trapdata
*T
) const { return T
->TrapID
== ID
; }
7592 void character::RemoveTrap (feuLong ID
) {
7593 trapdata
*&T
= ListFind(TrapData
, trapidcomparer(ID
));
7595 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange
);
7599 void character::AddTrap (feuLong ID
, feuLong BodyParts
) {
7600 trapdata
*&T
= ListFind(TrapData
, trapidcomparer(ID
));
7601 if (T
) T
->BodyParts
|= BodyParts
;
7602 else T
= new trapdata(ID
, GetID(), BodyParts
);
7603 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange
);
7607 truth
character::IsStuckToTrap (feuLong ID
) const {
7608 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) if (T
->TrapID
== ID
) return true;
7613 void character::RemoveTraps () {
7614 for (trapdata
*T
= TrapData
; T
;) {
7615 entity
*Trap
= game::SearchTrap(T
->TrapID
);
7616 if (Trap
) Trap
->UnStick();
7617 trapdata
*ToDel
= T
;
7622 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange
);
7626 void character::RemoveTraps (int BodyPartIndex
) {
7627 feuLong Flag
= 1 << BodyPartIndex
;
7628 for (trapdata
**T
= &TrapData
; *T
;) {
7629 if ((*T
)->BodyParts
& Flag
) {
7630 entity
*Trap
= game::SearchTrap((*T
)->TrapID
);
7631 if (!((*T
)->BodyParts
&= ~Flag
)) {
7632 if (Trap
) Trap
->UnStick();
7633 trapdata
*ToDel
= *T
;
7637 if (Trap
) Trap
->UnStick(BodyPartIndex
);
7645 if (GetBodyPart(BodyPartIndex
)) GetBodyPart(BodyPartIndex
)->SignalPossibleUsabilityChange();
7649 festring
character::GetTrapDescription () const {
7651 std::pair
<entity
*, int> TrapStack
[3];
7653 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) {
7655 entity
*Trap
= game::SearchTrap(T
->TrapID
);
7658 for (c
= 0; c
< Index
; ++c
) if (TrapStack
[c
].first
->GetTrapType() == Trap
->GetTrapType()) ++TrapStack
[c
].second
;
7659 if (c
== Index
) TrapStack
[Index
++] = std::make_pair(Trap
, 1);
7667 TrapStack
[0].first
->AddTrapName(Desc
, TrapStack
[0].second
);
7670 TrapStack
[1].first
->AddTrapName(Desc
, TrapStack
[1].second
);
7671 } else if (Index
== 3) {
7673 TrapStack
[1].first
->AddTrapName(Desc
, TrapStack
[1].second
);
7675 TrapStack
[2].first
->AddTrapName(Desc
, TrapStack
[2].second
);
7678 Desc
<< "lots of traps";
7684 int character::RandomizeHurtBodyPart (feuLong BodyParts
) const {
7685 int BodyPartIndex
[MAX_BODYPARTS
];
7687 for (int c
= 0; c
< GetBodyParts(); ++c
) {
7688 if (1 << c
& BodyParts
) {
7689 /*k8: ??? if(!GetBodyPart(c)) int esko = esko = 2; */
7690 if (!GetBodyPart(c
)) continue;
7691 BodyPartIndex
[Index
++] = c
;
7693 /*k8: ??? if(!Index) int esko = esko = 2;*/
7696 fprintf(stderr
, "FATAL: RandomizeHurtBodyPart -- Index==0\n");
7699 return BodyPartIndex
[RAND_N(Index
)];
7703 truth
character::BodyPartIsStuck (int I
) const {
7704 for (const trapdata
*T
= TrapData
; T
; T
= T
->Next
) if (1 << I
& T
->BodyParts
) return true;
7709 void character::PrintAttribute (cchar
*Desc
, int I
, int PanelPosX
, int PanelPosY
) const {
7710 int Attribute
= GetAttribute(I
);
7711 int NoBonusAttribute
= GetAttribute(I
, false);
7712 col16 C
= game::GetAttributeColor(I
);
7713 festring String
= Desc
;
7715 String
<< Attribute
;
7717 FONT
->Printf(DOUBLE_BUFFER
, v2(PanelPosX
, PanelPosY
* 10), C
, "%s", String
.CStr());
7718 if (Attribute
!= NoBonusAttribute
) {
7719 int Where
= PanelPosX
+ ((String
.GetSize() + 1) << 3);
7720 FONT
->Printf(DOUBLE_BUFFER
, v2(Where
, PanelPosY
* 10), LIGHT_GRAY
, "%d", NoBonusAttribute
);
7725 truth
character::AllowUnconsciousness () const {
7726 return DataBase
->AllowUnconsciousness
&& TorsoIsAlive();
7730 truth
character::CanPanic () const {
7731 return !Action
|| !Action
->IsUnconsciousness();
7735 int character::GetRandomBodyPart (feuLong Possible
) const {
7736 int OKBodyPart
[MAX_BODYPARTS
];
7737 int OKBodyParts
= 0;
7738 for (int c
= 0; c
< BodyParts
; ++c
) if (1 << c
& Possible
&& GetBodyPart(c
)) OKBodyPart
[OKBodyParts
++] = c
;
7739 return OKBodyParts
? OKBodyPart
[RAND_N(OKBodyParts
)] : NONE_INDEX
;
7743 void character::EditNP (sLong What
) {
7744 int OldState
= GetHungerState();
7746 int NewState
= GetHungerState();
7747 if (OldState
> VERY_HUNGRY
&& NewState
== VERY_HUNGRY
) DeActivateVoluntaryAction(CONST_S("You are getting really hungry."));
7748 if (OldState
> STARVING
&& NewState
== STARVING
) DeActivateVoluntaryAction(CONST_S("You are getting extremely hungry."));
7752 truth
character::IsSwimming () const {
7753 return !IsFlying() && GetSquareUnder() && GetSquareUnder()->GetSquareWalkability() & SWIM
;
7757 void character::AddBlackUnicornConsumeEndMessage () const {
7758 if (IsPlayer()) ADD_MESSAGE("You feel dirty and loathsome.");
7762 void character::AddGrayUnicornConsumeEndMessage () const {
7763 if (IsPlayer()) ADD_MESSAGE("You feel neutralized.");
7767 void character::AddWhiteUnicornConsumeEndMessage () const {
7768 if (IsPlayer()) ADD_MESSAGE("You feel purified.");
7772 void character::AddOmmelBoneConsumeEndMessage () const {
7773 if (IsPlayer()) ADD_MESSAGE("You feel the power of all your canine ancestors combining in your body.");
7774 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s looks extremely ferocious. You shudder.", CHAR_NAME(DEFINITE
));
7778 void character::AddLiquidHorrorConsumeEndMessage () const {
7779 if (IsPlayer()) ADD_MESSAGE("Untold horrors flash before your eyes. The melancholy of the world is on your shoulders!");
7780 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks as if the melancholy of the world is on %s shoulders!.", CHAR_NAME(DEFINITE
), GetPossessivePronoun().CStr());
7784 int character::GetBodyPartSparkleFlags (int) const {
7786 ((GetNaturalSparkleFlags() & SKIN_COLOR
? SPARKLING_A
: 0) |
7787 (GetNaturalSparkleFlags() & TORSO_MAIN_COLOR
? SPARKLING_B
: 0) |
7788 (GetNaturalSparkleFlags() & TORSO_SPECIAL_COLOR
? SPARKLING_D
: 0));
7792 truth
character::IsAnimated () const {
7793 return combinebodypartpredicates()(this, &bodypart::IsAnimated
, 1);
7797 double character::GetNaturalExperience (int Identifier
) const {
7798 return DataBase
->NaturalExperience
[Identifier
];
7802 truth
character::HasBodyPart (sorter Sorter
) const {
7803 if (Sorter
== 0) return true;
7804 return combinebodypartpredicateswithparam
<ccharacter
*>()(this, Sorter
, this, 1);
7808 truth
character::PossessesItem (sorter Sorter
) const {
7809 if (Sorter
== 0) return true;
7811 (GetStack()->SortedItems(this, Sorter
) ||
7812 combinebodypartpredicateswithparam
<ccharacter
*>()(this, Sorter
, this, 1) ||
7813 combineequipmentpredicateswithparam
<ccharacter
*>()(this, Sorter
, this, 1));
7817 truth
character::MoreThanOnePossessesItem (sorter Sorter
) const {
7821 for (int c
= 0; c
< BodyParts
; ++c
) {
7822 bodypart
*BodyPart
= GetBodyPart(c
);
7824 if (BodyPart
&& (Sorter
== 0 || (BodyPart
->*Sorter
)(this))) {
7825 if (++count
> 1) return true;
7828 for (int c
= 0; c
< GetEquipments(); ++c
) {
7829 item
*Equipment
= GetEquipment(c
);
7831 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) {
7832 if (++count
> 1) return true;
7835 for (int c
= 0; c
< GetStack()->GetItems(); ++c
) {
7836 item
*Stk
= GetStack()->GetItem(c
);
7838 if (Stk
&& (Sorter
== 0 || (Stk
->*Sorter
)(this))) {
7839 if (++count
> 1) return true;
7848 item
*character::FirstPossessesItem (sorter Sorter
) const {
7850 for (int c
= 0; c
< BodyParts
; ++c
) {
7851 bodypart
*BodyPart
= GetBodyPart(c
);
7853 if (BodyPart
&& (Sorter
== 0 || (BodyPart
->*Sorter
)(this))) return BodyPart
;
7855 for (int c
= 0; c
< GetEquipments(); ++c
) {
7856 item
*Equipment
= GetEquipment(c
);
7858 if (Equipment
&& (Sorter
== 0 || (Equipment
->*Sorter
)(this))) return Equipment
;
7860 for (int c
= 0; c
< GetStack()->GetItems(); ++c
) {
7861 item
*Stk
= GetStack()->GetItem(c
);
7863 if (Stk
&& (Sorter
== 0 || (Stk
->*Sorter
)(this))) return Stk
;
7871 cchar
*character::GetRunDescriptionLine (int I
) const {
7872 if (!GetRunDescriptionLineOne().IsEmpty()) return !I
? GetRunDescriptionLineOne().CStr() : GetRunDescriptionLineTwo().CStr();
7873 if (IsFlying()) return !I
? "Flying" : "very fast";
7874 if (IsSwimming()) return !I
? "Swimming" : "very fast";
7875 return !I
? "Running" : "";
7879 void character::VomitAtRandomDirection (int Amount
) {
7880 if (game::IsInWilderness()) return;
7881 /* Lacks support of multitile monsters */
7884 for (int d
= 0; d
< 9; ++d
) {
7885 lsquare
*Square
= GetLSquareUnder()->GetNeighbourLSquare(d
);
7886 if (Square
&& !Square
->VomitingIsDangerous(this)) Possible
[Index
++] = Square
->GetPos();
7888 if (Index
) Vomit(Possible
[RAND_N(Index
)], Amount
);
7889 else Vomit(GetPos(), Amount
);
7893 void character::RemoveLifeSavers () {
7894 for (int c
= 0; c
< GetEquipments(); ++c
) {
7895 item
*Equipment
= GetEquipment(c
);
7896 if (Equipment
&& Equipment
->IsInCorrectSlot(c
) && Equipment
->GetGearStates() & LIFE_SAVED
) {
7897 Equipment
->SendToHell();
7898 Equipment
->RemoveFromSlot();
7904 ccharacter
*character::FindCarrier () const {
7905 return this; //check
7909 void character::PrintBeginHiccupsMessage () const {
7910 if (IsPlayer()) ADD_MESSAGE("Your diaphragm is spasming vehemently.");
7914 void character::PrintEndHiccupsMessage () const {
7915 if (IsPlayer()) ADD_MESSAGE("You feel your annoying hiccoughs have finally subsided.");
7919 void character::HiccupsHandler () {
7921 if (!(RAND() % 2000)) {
7922 if (IsPlayer()) ADD_MESSAGE("");
7923 else if (CanBeSeenByPlayer()) ADD_MESSAGE("");
7924 else if ((PLAYER->GetPos()-GetPos()).GetLengthSquare() <= 400) ADD_MESSAGE("");
7925 game::CallForAttention(GetPos(), 400);
7931 void character::VampirismHandler () {
7932 //EditExperience(ARM_STRENGTH, -25, 1 << 1);
7933 //EditExperience(LEG_STRENGTH, -25, 1 << 1);
7934 //EditExperience(DEXTERITY, -25, 1 << 1);
7935 //EditExperience(AGILITY, -25, 1 << 1);
7936 //EditExperience(ENDURANCE, -25, 1 << 1);
7937 EditExperience(CHARISMA
, -25, 1 << 1);
7938 EditExperience(WISDOM
, -25, 1 << 1);
7939 EditExperience(INTELLIGENCE
, -25, 1 << 1);
7940 CheckDeath(CONST_S("killed by vampirism"));
7944 void character::HiccupsSituationDangerModifier (double &Danger
) const {
7949 void character::VampirismSituationDangerModifier (double &Danger
) const {
7950 character
*Vampire
= vampire::Spawn();
7951 double DangerToVampire
= GetRelativeDanger(Vampire
);
7952 Danger
*= pow(DangerToVampire
, 0.1);
7957 bool character::IsConscious () const {
7958 return !Action
|| !Action
->IsUnconsciousness();
7962 wsquare
*character::GetNearWSquare (v2 Pos
) const {
7963 return static_cast<wsquare
*>(GetSquareUnder()->GetArea()->GetSquare(Pos
));
7967 wsquare
*character::GetNearWSquare (int x
, int y
) const {
7968 return static_cast<wsquare
*>(GetSquareUnder()->GetArea()->GetSquare(x
, y
));
7972 void character::ForcePutNear (v2 Pos
) {
7973 /* GUM SOLUTION!!! */
7974 v2 NewPos
= game::GetCurrentLevel()->GetNearestFreeSquare(PLAYER
, Pos
, false);
7975 if (NewPos
== ERROR_V2
) do { NewPos
= game::GetCurrentLevel()->GetRandomSquare(this); } while(NewPos
== Pos
);
7980 void character::ReceiveMustardGas (int BodyPart
, sLong Volume
) {
7981 if (Volume
) GetBodyPart(BodyPart
)->AddFluid(liquid::Spawn(MUSTARD_GAS_LIQUID
, Volume
), CONST_S("skin"), 0, true);
7985 void character::ReceiveMustardGasLiquid (int BodyPartIndex
, sLong Modifier
) {
7986 bodypart
*BodyPart
= GetBodyPart(BodyPartIndex
);
7987 if (BodyPart
->GetMainMaterial()->GetInteractionFlags() & IS_AFFECTED_BY_MUSTARD_GAS
) {
7988 sLong Tries
= Modifier
;
7989 Modifier
-= Tries
; //opt%?
7991 for (sLong c
= 0; c
< Tries
; ++c
) if (!(RAND() % 100)) ++Damage
;
7992 if (Modifier
&& !(RAND() % 1000 / Modifier
)) ++Damage
;
7994 feuLong Minute
= game::GetTotalMinutes();
7995 if (GetLastAcidMsgMin() != Minute
&& (CanBeSeenByPlayer() || IsPlayer())) {
7996 SetLastAcidMsgMin(Minute
);
7997 if (IsPlayer()) ADD_MESSAGE("Mustard gas dissolves the skin of your %s.", BodyPart
->GetBodyPartName().CStr());
7998 else ADD_MESSAGE("Mustard gas dissolves %s.", CHAR_NAME(DEFINITE
));
8000 ReceiveBodyPartDamage(0, Damage
, MUSTARD_GAS_DAMAGE
, BodyPartIndex
, YOURSELF
, false, false, false);
8001 CheckDeath(CONST_S("killed by a fatal exposure to mustard gas"));
8007 truth
character::IsBadPath (v2 Pos
) const {
8008 if (!IsGoingSomeWhere()) return false;
8009 v2 TPos
= !Route
.empty() ? Route
.back() : GoingTo
;
8010 return ((TPos
- Pos
).GetManhattanLength() > (TPos
- GetPos()).GetManhattanLength());
8014 double &character::GetExpModifierRef (expid E
) {
8015 return ExpModifierMap
.insert(std::make_pair(E
, 1.)).first
->second
;
8019 /* Should probably do more. Now only makes Player forget gods */
8020 truth
character::ForgetRandomThing () {
8022 /* hopefully this code isn't some where else */
8023 std::vector
<god
*> Known
;
8024 for (int c
= 1; c
<= GODS
; ++c
) if (game::GetGod(c
)->IsKnown()) Known
.push_back(game::GetGod(c
));
8025 if (Known
.empty()) return false;
8026 int RandomGod
= RAND_N(Known
.size());
8027 Known
.at(RAND_N(Known
.size()))->SetIsKnown(false);
8028 ADD_MESSAGE("You forget how to pray to %s.", Known
.at(RandomGod
)->GetName());
8035 int character::CheckForBlock (character
*Enemy
, item
*Weapon
, double ToHitValue
, int Damage
, int Success
, int Type
) {
8040 void character::ApplyAllGodsKnownBonus () {
8041 stack
*AddPlace
= GetStackUnder();
8042 if (game::IsInWilderness()) AddPlace
= GetStack(); else AddPlace
= GetStackUnder();
8043 pantheonbook
*NewBook
= pantheonbook::Spawn();
8044 AddPlace
->AddItem(NewBook
);
8045 ADD_MESSAGE("\"MORTAL! BEHOLD THE HOLY SAGA\"");
8046 ADD_MESSAGE("%s materializes near your feet.", NewBook
->CHAR_NAME(INDEFINITE
));
8050 void character::ReceiveSirenSong (character
*Siren
) {
8051 if (Siren
->GetTeam() == GetTeam()) return;
8053 if (IsPlayer()) ADD_MESSAGE("The beautiful melody of %s makes you feel sleepy.", Siren
->CHAR_NAME(DEFINITE
));
8054 else if (CanBeSeenByPlayer()) ADD_MESSAGE("The beautiful melody of %s makes %s look sleepy.", Siren
->CHAR_NAME(DEFINITE
), CHAR_NAME(DEFINITE
)); /*k8*/
8055 Stamina
-= (1 + RAND_N(4)) * 10000;
8058 if (!IsPlayer() && IsCharmable() && !RAND_N(5)) {
8059 ChangeTeam(Siren
->GetTeam());
8060 ADD_MESSAGE("%s seems to be totally brainwashed by %s melodies.", CHAR_NAME(DEFINITE
), Siren
->CHAR_NAME(DEFINITE
));
8064 item
*What
= GiveMostExpensiveItem(Siren
);
8067 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
);
8069 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
);
8072 if (IsPlayer()) ADD_MESSAGE("You would like to give something to %s.", Siren
->CHAR_NAME(DEFINITE
));
8079 // return 0, if no item found
8080 item
*character::FindMostExpensiveItem () const {
8082 item
*MostExpensive
= 0;
8083 for (stackiterator i
= GetStack()->GetBottom(); i
.HasItem(); ++i
) {
8084 if ((*i
)->GetPrice() > MaxPrice
) {
8085 MaxPrice
= (*i
)->GetPrice();
8086 MostExpensive
= (*i
);
8089 for (int c
= 0; c
< GetEquipments(); ++c
) {
8090 item
*Equipment
= GetEquipment(c
);
8091 if (Equipment
&& Equipment
->GetPrice() > MaxPrice
) {
8092 MaxPrice
= Equipment
->GetPrice();
8093 MostExpensive
= Equipment
;
8096 return MostExpensive
;
8100 // returns 0 if no items available
8101 item
*character::GiveMostExpensiveItem(character
*ToWhom
) {
8102 item
*ToGive
= FindMostExpensiveItem();
8103 if (!ToGive
) return 0;
8104 truth Equipped
= PLAYER
->Equips(ToGive
);
8105 ToGive
->RemoveFromSlot();
8106 if (Equipped
) game::AskForEscPress(CONST_S("Equipment lost!"));
8107 ToWhom
->ReceiveItemAsPresent(ToGive
);
8113 void character::ReceiveItemAsPresent (item
*Present
) {
8114 if (TestForPickup(Present
)) GetStack()->AddItem(Present
); else GetStackUnder()->AddItem(Present
);
8118 /* returns 0 if no enemies in sight */
8119 character
*character::GetNearestEnemy () const {
8120 character
*NearestEnemy
= 0;
8121 sLong NearestEnemyDistance
= 0x7FFFFFFF;
8123 for (int c
= 0; c
< game::GetTeams(); ++c
) {
8124 if (GetTeam()->GetRelation(game::GetTeam(c
)) == HOSTILE
) {
8125 for (std::list
<character
*>::const_iterator i
= game::GetTeam(c
)->GetMember().begin(); i
!= game::GetTeam(c
)->GetMember().end(); ++i
) {
8126 if ((*i
)->IsEnabled()) {
8127 sLong ThisDistance
= Max
<sLong
>(abs((*i
)->GetPos().X
- Pos
.X
), abs((*i
)->GetPos().Y
- Pos
.Y
));
8128 if ((ThisDistance
< NearestEnemyDistance
|| (ThisDistance
== NearestEnemyDistance
&& !(RAND() % 3))) && (*i
)->CanBeSeenBy(this)) {
8130 NearestEnemyDistance
= ThisDistance
;
8136 return NearestEnemy
;
8140 truth
character::MindWormCanPenetrateSkull (mindworm
*) const {
8145 truth
character::CanTameWithDulcis (const character
*Tamer
) const {
8146 int TamingDifficulty
= GetTamingDifficulty();
8147 if (TamingDifficulty
== NO_TAMING
) return false;
8148 if (GetAttachedGod() == DULCIS
) return true;
8149 int Modifier
= Tamer
->GetAttribute(WISDOM
) + Tamer
->GetAttribute(CHARISMA
);
8150 if (Tamer
->IsPlayer()) Modifier
+= game::GetGod(DULCIS
)->GetRelation() / 20;
8151 else if (Tamer
->GetAttachedGod() == DULCIS
) Modifier
+= 50;
8152 if (TamingDifficulty
== 0) {
8153 if (!IgnoreDanger()) TamingDifficulty
= int(10 * GetRelativeDanger(Tamer
));
8154 else TamingDifficulty
= 10 * GetHPRequirementForGeneration()/Max(Tamer
->GetHP(), 1);
8156 return Modifier
>= TamingDifficulty
* 3;
8160 truth
character::CanTameWithLyre (const character
*Tamer
) const {
8161 int TamingDifficulty
= GetTamingDifficulty();
8162 if (TamingDifficulty
== NO_TAMING
) return false;
8163 if (TamingDifficulty
== 0) {
8164 if (!IgnoreDanger()) TamingDifficulty
= int(10 * GetRelativeDanger(Tamer
));
8165 else TamingDifficulty
= 10*GetHPRequirementForGeneration()/Max(Tamer
->GetHP(), 1);
8167 return Tamer
->GetAttribute(CHARISMA
) >= TamingDifficulty
;
8171 truth
character::CanTameWithScroll (const character
*Tamer
) const {
8172 int TamingDifficulty
= GetTamingDifficulty();
8174 (TamingDifficulty
!= NO_TAMING
&&
8175 (TamingDifficulty
== 0 ||
8176 Tamer
->GetAttribute(INTELLIGENCE
) * 4 + Tamer
->GetAttribute(CHARISMA
) >= TamingDifficulty
* 5));
8180 truth
character::CheckSadism () {
8181 if (!IsSadist() || !HasSadistAttackMode() || !IsSmall()) return false; // gum
8183 for (int d
= 0; d
< 8; ++d
) {
8184 square
*Square
= GetNeighbourSquare(d
);
8186 character
*Char
= Square
->GetCharacter();
8187 if (Char
&& Char
->IsMasochist() && GetRelation(Char
) == FRIEND
&&
8188 Char
->GetHP() * 3 >= Char
->GetMaxHP() * 2 && Hit(Char
, Square
->GetPos(), d
, SADIST_HIT
)) {
8199 truth
character::CheckForBeverage () {
8200 if (StateIsActivated(PANIC
) || !IsEnabled() || !UsesNutrition() || CheckIfSatiated()) return false;
8201 itemvector ItemVector
;
8202 GetStack()->FillItemVector(ItemVector
);
8203 for (uInt c
= 0; c
< ItemVector
.size(); ++c
) if (ItemVector
[c
]->IsBeverage(this) && TryToConsume(ItemVector
[c
])) return true;
8208 void character::Haste () {
8209 doforbodyparts()(this, &bodypart::Haste
);
8210 doforequipments()(this, &item::Haste
);
8211 BeginTemporaryState(HASTE
, 500 + RAND() % 1000);
8215 void character::Slow () {
8216 doforbodyparts()(this, &bodypart::Slow
);
8217 doforequipments()(this, &item::Slow
);
8218 //BeginTemporaryState(HASTE, 500 + RAND() % 1000); // this seems to be a bug
8219 BeginTemporaryState(SLOW
, 500 + RAND() % 1000);
8223 void character::SurgicallyDetachBodyPart () {
8224 ADD_MESSAGE("You haven't got any extra bodyparts.");
8228 truth
character::CanHear() const
8230 return DataBase
->CanHear
&& HasHead();
8234 truth
character::IsAllowedInDungeon (int dunIndex
) {
8235 const fearray
<int> &dlist
= GetAllowedDungeons();
8237 for (uInt f
= 0; f
< dlist
.Size
; ++f
) {
8238 if (dlist
[f
] == ALL_DUNGEONS
|| dlist
[f
] == dunIndex
) {
8239 fprintf(stderr
, "OK!\n");
8243 fprintf(stderr
, "NO!\n");
8248 truth
character::IsESPBlockedByEquipment () const {
8249 for (int c
= 0; c
< GetEquipments(); ++c
) {
8250 item
*Item
= GetEquipment(c
);
8252 if (Item
&& Item
->IsHelmet(this) &&
8253 ((Item
->GetMainMaterial() && Item
->GetMainMaterial()->BlockESP()) ||
8254 (Item
->GetSecondaryMaterial() && Item
->GetSecondaryMaterial()->BlockESP()))) return true;
8260 truth
character::TemporaryStateIsActivated (sLong What
) const {
8261 if ((What
&ESP
) && (TemporaryState
&ESP
) && IsESPBlockedByEquipment()) {
8262 return ((TemporaryState
&What
)&(~ESP
));
8264 return (TemporaryState
& What
);
8268 truth
character::StateIsActivated (sLong What
) const {
8269 if ((What
&ESP
) && ((TemporaryState
|EquipmentState
)&ESP
) && IsESPBlockedByEquipment()) {
8270 return ((TemporaryState
&What
)&(~ESP
)) || ((EquipmentState
&What
)&(~ESP
));
8272 return (TemporaryState
& What
) || (EquipmentState
& What
);