1 /*
2  *
3  *  Iter Vehemens ad Necem (IVAN)
4  *  Copyright (C) Timo Kiviluoto
5  *  Released under the GNU General
6  *  Public License
7  *
8  *  See LICENSING which should be included
9  *  along with this file for more details
10  *
11  */
12 
13 /* Compiled through charset.cpp */
14 
15 /* These statedata structs contain functions and values used for handling
16  * states. Remember to update them. All normal states must have
17  * PrintBeginMessage and PrintEndMessage functions and a Description string.
18  * BeginHandler, EndHandler, Handler (called each tick) and IsAllowed are
19  * optional, enter zero if the state doesn't need one. If the SECRET flag
20  * is set, Description is not shown in the panel without magical means.
21  * You can also set some source (SRC_*) and duration (DUR_*) flags, which
22  * control whether the state can be randomly activated in certain situations.
23  * These flags can be found in ivandef.h. RANDOMIZABLE sets all source
24  * & duration flags at once. */
25 
26 #include "hiteffect.h" //TODO move to charsset.cpp?
27 #include "lterras.h"
28 #include "gods.h"
29 
30 //#define DBGMSG_V2
31 #include "dbgmsgproj.h"
32 #include <bitset>
33 #include <cmath>
34 
35 struct statedata
36 {
37   cchar* Description;
38   int Flags;
39   void (character::*PrintBeginMessage)() const;
40   void (character::*PrintEndMessage)() const;
41   void (character::*BeginHandler)();
42   void (character::*EndHandler)();
43   void (character::*Handler)();
44   truth (character::*IsAllowed)() const;
45   void (character::*SituationDangerModifier)(double&) const;
46 };
47 
48 statedata StateData[STATES] =
49 {
50   {
51     "Polymorphed",
52     NO_FLAGS,
53     0,
54     0,
55     0,
56     &character::EndPolymorph,
57     0,
58     0,
59     0
60   }, {
61     "Hasted",
62     RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL),
63     &character::PrintBeginHasteMessage,
64     &character::PrintEndHasteMessage,
65     0,
66     0,
67     0,
68     0,
69     0
70   }, {
71     "Slowed",
72     RANDOMIZABLE&~SRC_GOOD,
73     &character::PrintBeginSlowMessage,
74     &character::PrintEndSlowMessage,
75     0,
76     0,
77     0,
78     0,
79     0
80   }, {
81     "PolyControl",
82     RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL|SRC_GOOD),
83     &character::PrintBeginPolymorphControlMessage,
84     &character::PrintEndPolymorphControlMessage,
85     0,
86     0,
87     0,
88     0,
89     0
90   }, {
91     "Life Saved",
92     SECRET,
93     &character::PrintBeginLifeSaveMessage,
94     &character::PrintEndLifeSaveMessage,
95     0,
96     0,
97     0,
98     0,
99     0
100   }, {
101     "Lycanthropy",
102     SECRET|SRC_FOUNTAIN|SRC_CONFUSE_READ|DUR_FLAGS,
103     &character::PrintBeginLycanthropyMessage,
104     &character::PrintEndLycanthropyMessage,
105     0,
106     0,
107     &character::LycanthropyHandler,
108     0,
109     &character::LycanthropySituationDangerModifier
110   }, {
111     "Invisible",
112     RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL),
113     &character::PrintBeginInvisibilityMessage,
114     &character::PrintEndInvisibilityMessage,
115     &character::BeginInvisibility, &character::EndInvisibility,
116     0,
117     0,
118     0
119   }, {
120     "Infravision",
121     RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL),
122     &character::PrintBeginInfraVisionMessage,
123     &character::PrintEndInfraVisionMessage,
124     &character::BeginInfraVision,
125     &character::EndInfraVision,
126     0,
127     0,
128     0
129   }, {
130     "ESP",
131     RANDOMIZABLE&~SRC_EVIL,
132     &character::PrintBeginESPMessage,
133     &character::PrintEndESPMessage,
134     &character::BeginESP,
135     &character::EndESP,
136     0,
137     0,
138     0
139   }, {
140     "Poisoned",
141     DUR_TEMPORARY,
142     &character::PrintBeginPoisonedMessage,
143     &character::PrintEndPoisonedMessage,
144     0,
145     0,
146     &character::PoisonedHandler,
147     &character::CanBePoisoned,
148     &character::PoisonedSituationDangerModifier
149   }, {
150     "Teleporting",
151     SECRET|(RANDOMIZABLE&~(SRC_MUSHROOM|SRC_GOOD)),
152     &character::PrintBeginTeleportMessage,
153     &character::PrintEndTeleportMessage,
154     0,
155     0,
156     &character::TeleportHandler,
157     0,
158     0
159   }, {
160     "Polymorphitis",
161     SECRET|(RANDOMIZABLE&~(SRC_MUSHROOM|SRC_GOOD)),
162     &character::PrintBeginPolymorphMessage,
163     &character::PrintEndPolymorphMessage,
164     0,
165     0,
166     &character::PolymorphHandler,
167     0,
168     &character::PolymorphingSituationDangerModifier
169   }, {
170     "TeleControl",
171     RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL),
172     &character::PrintBeginTeleportControlMessage,
173     &character::PrintEndTeleportControlMessage,
174     0,
175     0,
176     0,
177     0,
178     0
179   }, {
180     "Panicked",
181     NO_FLAGS,
182     &character::PrintBeginPanicMessage,
183     &character::PrintEndPanicMessage,
184     &character::BeginPanic,
185     &character::EndPanic,
186     0,
187     &character::CanPanic,
188     &character::PanicSituationDangerModifier
189   }, {
190     "Confused",
191     SECRET|(RANDOMIZABLE&~(DUR_PERMANENT|SRC_GOOD)),
192     &character::PrintBeginConfuseMessage,
193     &character::PrintEndConfuseMessage,
194     0,
195     0,
196     0,
197     &character::CanBeConfused,
198     &character::ConfusedSituationDangerModifier
199   }, {
200     "Parasite (tapeworm)",
201     SECRET|(RANDOMIZABLE&~DUR_TEMPORARY),
202     &character::PrintBeginParasitizedMessage,
203     &character::PrintEndParasitizedMessage,
204     0,
205     0,
206     &character::ParasitizedHandler,
207     &character::CanBeParasitized,
208     &character::ParasitizedSituationDangerModifier
209   }, {
210     "Searching",
211     NO_FLAGS,
212     &character::PrintBeginSearchingMessage,
213     &character::PrintEndSearchingMessage,
214     0,
215     0,
216     &character::SearchingHandler,
217     0,
218     0
219   }, {
220     "Gas Immunity",
221     SECRET|(RANDOMIZABLE&~(SRC_GOOD|SRC_EVIL)),
222     &character::PrintBeginGasImmunityMessage,
223     &character::PrintEndGasImmunityMessage,
224     0,
225     0,
226     0,
227     0,
228     0
229   }, {
230     "Levitating",
231     RANDOMIZABLE&~SRC_EVIL,
232     &character::PrintBeginLevitationMessage,
233     &character::PrintEndLevitationMessage,
234     0,
235     &character::EndLevitation,
236     0,
237     0,
238     0
239   }, {
240     "Leprosy",
241     SECRET|(RANDOMIZABLE&~DUR_TEMPORARY),
242     &character::PrintBeginLeprosyMessage,
243     &character::PrintEndLeprosyMessage,
244     &character::BeginLeprosy,
245     &character::EndLeprosy,
246     &character::LeprosyHandler,
247     0,
248     &character::LeprosySituationDangerModifier
249   }, {
250     "Hiccups",
251     SRC_FOUNTAIN|SRC_CONFUSE_READ|DUR_FLAGS,
252     &character::PrintBeginHiccupsMessage,
253     &character::PrintEndHiccupsMessage,
254     0,
255     0,
256     &character::HiccupsHandler,
257     0,
258     &character::HiccupsSituationDangerModifier
259   }, {
260     "Ethereal",
261     NO_FLAGS,
262     &character::PrintBeginEtherealityMessage,
263     &character::PrintEndEtherealityMessage,
264     &character::BeginEthereality, &character::EndEthereality,
265     0,
266     0,
267     0
268   }, {
269     "Vampirism",
270     DUR_FLAGS,
271     &character::PrintBeginVampirismMessage,
272     &character::PrintEndVampirismMessage,
273     0,
274     0,
275     &character::VampirismHandler,
276     0,
277     0
278   }, {
279     "Swimming",
280     SECRET|(RANDOMIZABLE&~SRC_EVIL),
281     &character::PrintBeginSwimmingMessage,
282     &character::PrintEndSwimmingMessage,
283     &character::BeginSwimming, &character::EndSwimming,
284     0,
285     0,
286     0
287   }, {
288     "Detecting",
289     SECRET|(RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL)),
290     &character::PrintBeginDetectMessage,
291     &character::PrintEndDetectMessage,
292     0,
293     0,
294     &character::DetectHandler,
295     0,
296     0
297   }, {
298     "Polymorph Locked",
299     SECRET|(RANDOMIZABLE&~(SRC_GOOD|SRC_EVIL|DUR_PERMANENT)),
300     &character::PrintBeginPolymorphLockMessage,
301     &character::PrintEndPolymorphLockMessage,
302     0,
303     0,
304     &character::PolymorphLockHandler,
305     0,
306     0
307   }, {
308     "Regenerating",
309     SECRET|(RANDOMIZABLE&~SRC_EVIL),
310     &character::PrintBeginRegenerationMessage,
311     &character::PrintEndRegenerationMessage,
312     0,
313     0,
314     0,
315     0,
316     0
317   }, {
318     "Disease Immunity",
319     SECRET|(RANDOMIZABLE&~SRC_EVIL),
320     &character::PrintBeginDiseaseImmunityMessage,
321     &character::PrintEndDiseaseImmunityMessage,
322     0,
323     0,
324     0,
325     0,
326     0
327   }, {
328     "Teleport Locked",
329     SECRET|(RANDOMIZABLE&~(SRC_GOOD|SRC_EVIL|DUR_PERMANENT)),
330     &character::PrintBeginTeleportLockMessage,
331     &character::PrintEndTeleportLockMessage,
332     0,
333     0,
334     0,
335     0,
336     0
337   }, {
338     "Fearless",
339     RANDOMIZABLE&~SRC_EVIL,
340     &character::PrintBeginFearlessMessage,
341     &character::PrintEndFearlessMessage,
342     0,
343     0,
344     0,
345     0,
346     0
347   }, {
348     "Fasting",
349     SECRET|(RANDOMIZABLE&~SRC_EVIL),
350     &character::PrintBeginFastingMessage,
351     &character::PrintEndFastingMessage,
352     0,
353     0,
354     0,
355     0,
356     0
357   }, {
358     "Parasite (mindworm)",
359     SECRET|DUR_TEMPORARY|SRC_FOUNTAIN,
360     &character::PrintBeginMindwormedMessage,
361     &character::PrintEndMindwormedMessage,
362     0,
363     0,
364     &character::MindwormedHandler,
365     &character::CanBeParasitized, // We are using TorsoIsAlive right now, but I think it's OK,
366                                   // because with unliving torso, your head will not be much better for mind worms.
367     &character::ParasitizedSituationDangerModifier
368   }
369 };
370 
characterprototype(const characterprototype * Base,characterspawner Spawner,charactercloner Cloner,cchar * ClassID)371 characterprototype::characterprototype(const characterprototype* Base,
372                                        characterspawner Spawner,
373                                        charactercloner Cloner,
374                                        cchar* ClassID)
375 : Base(Base), Spawner(Spawner), Cloner(Cloner), ClassID(ClassID)
376 { Index = protocontainer<character>::Add(this); }
GetTeamIterator()377 std::list<character*>::iterator character::GetTeamIterator()
378 { return TeamIterator; }
SetTeamIterator(std::list<character * >::iterator What)379 void character::SetTeamIterator(std::list<character*>::iterator What)
380 { TeamIterator = What; }
CreateInitialEquipment(int SpecialFlags)381 void character::CreateInitialEquipment(int SpecialFlags)
382 { AddToInventory(DataBase->Inventory, SpecialFlags); }
EditAP(long What)383 void character::EditAP(long What)
384 { AP = Limit<long>(AP + What, -12000, 1200); }
GetRandomStepperBodyPart() const385 int character::GetRandomStepperBodyPart() const { return TORSO_INDEX; }
GainIntrinsic(long What)386 void character::GainIntrinsic(long What)
387 { BeginTemporaryState(What, PERMANENT); }
IsUsingArms() const388 truth character::IsUsingArms() const { return GetAttackStyle() & USE_ARMS; }
IsUsingLegs() const389 truth character::IsUsingLegs() const { return GetAttackStyle() & USE_LEGS; }
IsUsingHead() const390 truth character::IsUsingHead() const { return GetAttackStyle() & USE_HEAD; }
CalculateAllowedWeaponSkillCategories()391 void character::CalculateAllowedWeaponSkillCategories()
392 { AllowedWeaponSkillCategories = MARTIAL_SKILL_CATEGORIES; }
GetBeVerb() const393 festring character::GetBeVerb() const
394 { return IsPlayer() ? CONST_S("are") : CONST_S("is"); }
SetEndurance(int What)395 void character::SetEndurance(int What)
396 { BaseExperience[ENDURANCE] = What * EXP_MULTIPLIER; }
SetPerception(int What)397 void character::SetPerception(int What)
398 { BaseExperience[PERCEPTION] = What * EXP_MULTIPLIER; }
SetIntelligence(int What)399 void character::SetIntelligence(int What)
400 { BaseExperience[INTELLIGENCE] = What * EXP_MULTIPLIER; }
SetWisdom(int What)401 void character::SetWisdom(int What)
402 { BaseExperience[WISDOM] = What * EXP_MULTIPLIER; }
SetWillPower(int What)403 void character::SetWillPower(int What)
404 { BaseExperience[WILL_POWER] = What * EXP_MULTIPLIER; }
SetCharisma(int What)405 void character::SetCharisma(int What)
406 { BaseExperience[CHARISMA] = What * EXP_MULTIPLIER; }
SetMana(int What)407 void character::SetMana(int What)
408 { BaseExperience[MANA] = What * EXP_MULTIPLIER; }
IsOnGround() const409 truth character::IsOnGround() const
410 { return MotherEntity && MotherEntity->IsOnGround(); }
LeftOversAreUnique() const411 truth character::LeftOversAreUnique() const
412 { return GetArticleMode() || AssignedName.GetSize(); }
HomeDataIsValid() const413 truth character::HomeDataIsValid() const
414 { return (HomeData && HomeData->Level == GetLSquareUnder()->GetLevelIndex()
415           && HomeData->Dungeon == GetLSquareUnder()->GetDungeonIndex()); }
SetHomePos(v2 Pos)416 void character::SetHomePos(v2 Pos) { HomeData->Pos = Pos; }
FirstPersonUnarmedHitVerb() const417 cchar* character::FirstPersonUnarmedHitVerb() const { return "hit"; }
FirstPersonCriticalUnarmedHitVerb() const418 cchar* character::FirstPersonCriticalUnarmedHitVerb() const
419 { return "critically hit"; }
ThirdPersonUnarmedHitVerb() const420 cchar* character::ThirdPersonUnarmedHitVerb() const { return "hits"; }
ThirdPersonCriticalUnarmedHitVerb() const421 cchar* character::ThirdPersonCriticalUnarmedHitVerb() const
422 { return "critically hits"; }
FirstPersonKickVerb() const423 cchar* character::FirstPersonKickVerb() const { return "kick"; }
FirstPersonCriticalKickVerb() const424 cchar* character::FirstPersonCriticalKickVerb() const
425 { return "critically kick"; }
ThirdPersonKickVerb() const426 cchar* character::ThirdPersonKickVerb() const { return "kicks"; }
ThirdPersonCriticalKickVerb() const427 cchar* character::ThirdPersonCriticalKickVerb() const
428 { return "critically kicks"; }
FirstPersonBiteVerb() const429 cchar* character::FirstPersonBiteVerb() const { return "bite"; }
FirstPersonCriticalBiteVerb() const430 cchar* character::FirstPersonCriticalBiteVerb() const
431 { return "critically bite"; }
ThirdPersonBiteVerb() const432 cchar* character::ThirdPersonBiteVerb() const { return "bites"; }
ThirdPersonCriticalBiteVerb() const433 cchar* character::ThirdPersonCriticalBiteVerb() const
434 { return "critically bites"; }
UnarmedHitNoun() const435 cchar* character::UnarmedHitNoun() const { return "attack"; }
KickNoun() const436 cchar* character::KickNoun() const { return "kick"; }
BiteNoun() const437 cchar* character::BiteNoun() const { return "attack"; }
GetEquipmentName(int) const438 cchar* character::GetEquipmentName(int) const { return ""; }
GetOriginalBodyPartID(int I) const439 const std::list<ulong>& character::GetOriginalBodyPartID(int I) const
440 { return OriginalBodyPartID[I]; }
GetNeighbourSquare(int I) const441 square* character::GetNeighbourSquare(int I) const
442 { return GetSquareUnder()->GetNeighbourSquare(I); }
GetNeighbourLSquare(int I) const443 lsquare* character::GetNeighbourLSquare(int I) const
444 { return static_cast<lsquare*>(GetSquareUnder())->GetNeighbourLSquare(I); }
GetNeighbourWSquare(int I) const445 wsquare* character::GetNeighbourWSquare(int I) const
446 { return static_cast<wsquare*>(GetSquareUnder())->GetNeighbourWSquare(I); }
GetMasterGod() const447 god* character::GetMasterGod() const { return game::GetGod(GetConfig())!=NULL ? game::GetGod(GetConfig()) : game::GetGod(GetAttachedGod()); } //TODO explain why GetConfig() works in most cases? test-case Terra@UT4 vs Lycanthropy. Is the priest Config ID at char.dat the same found for such ID at ivandef.h? so in short, should this just use GetAttachedGod() coherency from the very beggining?
GetBodyPartColorA(int,truth) const448 col16 character::GetBodyPartColorA(int, truth) const
449 { return GetSkinColor(); }
GetBodyPartColorB(int,truth) const450 col16 character::GetBodyPartColorB(int, truth) const
451 { return GetTorsoMainColor(); }
GetBodyPartColorC(int,truth) const452 col16 character::GetBodyPartColorC(int, truth) const
453 { return GetBeltColor(); } // sorry...
GetBodyPartColorD(int,truth) const454 col16 character::GetBodyPartColorD(int, truth) const
455 { return GetTorsoSpecialColor(); }
GetRandomApplyBodyPart() const456 int character::GetRandomApplyBodyPart() const { return TORSO_INDEX; }
MustBeRemovedFromBone() const457 truth character::MustBeRemovedFromBone() const
458 { return IsUnique() && !CanBeGenerated(); }
IsPet() const459 truth character::IsPet() const { return GetTeam()->GetID() == PLAYER_TEAM; }
GetLeader() const460 character* character::GetLeader() const { return GetTeam()->GetLeader(); }
GetMoveType() const461 int character::GetMoveType() const
462 { return ((!StateIsActivated(LEVITATION)
463           ? DataBase->MoveType
464           : DataBase->MoveType | FLY) |
465           (!StateIsActivated(ETHEREAL_MOVING)
466           ? DataBase->MoveType
467           : DataBase->MoveType | ETHEREAL) |
468           ((!StateIsActivated(SWIMMING) &&
469             !(IsPlayer() && game::IsInWilderness() && game::PlayerHasBoat()))
470           ? DataBase->MoveType
471           : DataBase->MoveType | SWIM) );
472 }
GetZombieDescription() const473 festring character::GetZombieDescription() const
474 { return " of " + GetName(INDEFINITE); }
BodyPartCanBeSevered(int I) const475 truth character::BodyPartCanBeSevered(int I) const { return I; }
HasBeenSeen() const476 truth character::HasBeenSeen() const
477 { return DataBase->Flags & HAS_BEEN_SEEN; }
IsTemporary() const478 truth character::IsTemporary() const
479 { return GetTorso()->GetLifeExpectancy(); }
480 
GetNormalDeathMessage() const481 cchar* character::GetNormalDeathMessage() const
482 {
483   const char* killed_by[] = { "murdered @k", "eliminated @k", "slain @k",
484     "dismembered @k", "sent to the next life @k", "overpowered @k",
485     "killed @k", "inhumed @k", "dispatched @k", "exterminated @k",
486     "done in @k", "defeated @k", "struck down @k", "offed @k", "mowed down @k",
487     "taken down @k", "sent to the grave @k", "destroyed @k", "executed @k",
488     "slaughtered @k", "annihilated @k", "finished @k", "neutralized @k",
489     "obliterated @k", "snuffed @k", "done away with @k", "put to death @k",
490     "released of this mortal coil @k", "taken apart @k", "unmade @k" };
491   return killed_by[RAND() % 30];
492 }
493 
GetGhostDescription() const494 festring character::GetGhostDescription() const
495 { return " of " + GetName(INDEFINITE); }
496 
497 int characterdatabase::* ExpPtr[ATTRIBUTES] =
498 {
499   &characterdatabase::DefaultEndurance,
500   &characterdatabase::DefaultPerception,
501   &characterdatabase::DefaultIntelligence,
502   &characterdatabase::DefaultWisdom,
503   &characterdatabase::DefaultWillPower,
504   &characterdatabase::DefaultCharisma,
505   &characterdatabase::DefaultMana,
506   &characterdatabase::DefaultArmStrength,
507   &characterdatabase::DefaultLegStrength,
508   &characterdatabase::DefaultDexterity,
509   &characterdatabase::DefaultAgility
510 };
511 
512 contentscript<item> characterdatabase::* EquipmentDataPtr[EQUIPMENT_DATAS] =
513 {
514   &characterdatabase::Helmet,
515   &characterdatabase::Amulet,
516   &characterdatabase::Cloak,
517   &characterdatabase::BodyArmor,
518   &characterdatabase::Belt,
519   &characterdatabase::RightWielded,
520   &characterdatabase::LeftWielded,
521   &characterdatabase::RightRing,
522   &characterdatabase::LeftRing,
523   &characterdatabase::RightGauntlet,
524   &characterdatabase::LeftGauntlet,
525   &characterdatabase::RightBoot,
526   &characterdatabase::LeftBoot
527 };
528 
character(ccharacter & Char)529 character::character(ccharacter& Char)
530 : entity(Char), id(Char), NP(Char.NP), AP(Char.AP),
531   TemporaryState(Char.TemporaryState&~POLYMORPHED),
532   Team(Char.Team), GoingTo(ERROR_V2),
533   RandomMoveDir(femath::RandReal(8)),
534   Money(0), AssignedName(Char.AssignedName), Action(0),
535   DataBase(Char.DataBase), MotherEntity(0),
536   PolymorphBackup(0), EquipmentState(0), SquareUnder(0),
537   AllowedWeaponSkillCategories(Char.AllowedWeaponSkillCategories),
538   BodyParts(Char.BodyParts),
539   RegenerationCounter(Char.RegenerationCounter),
540   SquaresUnder(Char.SquaresUnder), LastAcidMsgMin(0),
541   Stamina(Char.Stamina), MaxStamina(Char.MaxStamina),
542   BlocksSinceLastTurn(0), GenerationDanger(Char.GenerationDanger),
543   CommandFlags(Char.CommandFlags), WarnFlags(0),
544   ScienceTalks(Char.ScienceTalks), TrapData(0), CounterToMindWormHatch(0)
545 {
546   Flags &= ~C_PLAYER;
547   Flags |= C_INITIALIZING|C_IN_NO_MSG_MODE;
548   Stack = new stack(0, this, HIDDEN);
549 
550   int c;
551 
552   for(c = 0; c < MAX_EQUIPMENT_SLOTS; c++)
553     MemorizedEquippedItemIDs[c] = Char.MemorizedEquippedItemIDs[c];
554 
555   v2HoldPos=Char.v2HoldPos;
556 
557   for(c = 0; c < STATES; ++c)
558     TemporaryStateCounter[c] = Char.TemporaryStateCounter[c];
559 
560   if(Team)
561     TeamIterator = Team->Add(this);
562 
563   for(c = 0; c < BASE_ATTRIBUTES; ++c)
564     BaseExperience[c] = Char.BaseExperience[c];
565 
566   BodyPartSlot = new bodypartslot[BodyParts];
567   OriginalBodyPartID = new std::list<ulong>[BodyParts];
568   CWeaponSkill = new cweaponskill[AllowedWeaponSkillCategories];
569   SquareUnder = new square*[SquaresUnder];
570 
571   if(SquaresUnder == 1)
572     *SquareUnder = 0;
573   else
574     memset(SquareUnder, 0, SquaresUnder * sizeof(square*));
575 
576   for(c = 0; c < BodyParts; ++c)
577   {
578     BodyPartSlot[c].SetMaster(this);
579     bodypart* CharBodyPart = Char.GetBodyPart(c);
580     OriginalBodyPartID[c] = Char.OriginalBodyPartID[c];
581 
582     if(CharBodyPart)
583     {
584       bodypart* BodyPart = static_cast<bodypart*>(CharBodyPart->Duplicate());
585       SetBodyPart(c, BodyPart);
586       BodyPart->CalculateEmitation();
587     }
588   }
589 
590   for(c = 0; c < AllowedWeaponSkillCategories; ++c)
591     CWeaponSkill[c] = Char.CWeaponSkill[c];
592 
593   HomeData = Char.HomeData ? new homedata(*Char.HomeData) : 0;
594   ID = game::CreateNewCharacterID(this);
595 }
596 
character()597 character::character()
598 : entity(HAS_BE), NP(50000), AP(0), TemporaryState(0), Team(0),
599   GoingTo(ERROR_V2), RandomMoveDir(femath::RandReal(8)),
600   Money(0), Action(0), MotherEntity(0), PolymorphBackup(0), EquipmentState(0),
601   SquareUnder(0), RegenerationCounter(0), HomeData(0), LastAcidMsgMin(0),
602   BlocksSinceLastTurn(0), GenerationDanger(DEFAULT_GENERATION_DANGER),
603   WarnFlags(0), ScienceTalks(0), TrapData(0), CounterToMindWormHatch(0)
604 {
605   Stack = new stack(0, this, HIDDEN);
606 
607   int c;
608 
609   for(c = 0; c < MAX_EQUIPMENT_SLOTS; c++)
610     MemorizedEquippedItemIDs[c]=0;
611 
612   v2HoldPos=v2(0,0);
613 }
614 
~character()615 character::~character()
616 {
617   if(Action)
618     delete Action;
619 
620   if(Team)
621     Team->Remove(GetTeamIterator());
622 
623   delete Stack;
624   int c;
625 
626   for(c = 0; c < BodyParts; ++c)
627     delete GetBodyPart(c);
628 
629   delete [] BodyPartSlot;
630   delete [] OriginalBodyPartID;
631   delete PolymorphBackup;
632   delete [] SquareUnder;
633   delete [] CWeaponSkill;
634   delete HomeData;
635 
636   for(trapdata* T = TrapData; T;)
637   {
638     trapdata* ToDel = T;
639     T = T->Next;
640     delete ToDel;
641   }
642 
643   game::RemoveCharacterID(ID);
644 }
645 
Hunger()646 void character::Hunger()
647 {
648   switch(GetBurdenState())
649   {
650    case OVER_LOADED:
651    case STRESSED:
652     EditNP(-8);
653     EditExperience(LEG_STRENGTH, 150, 1 << 2);
654     EditExperience(AGILITY, -50, 1 << 2);
655     break;
656    case BURDENED:
657     EditNP(-2);
658     EditExperience(LEG_STRENGTH, 75, 1 << 1);
659     EditExperience(AGILITY, -25, 1 << 1);
660     break;
661    case UNBURDENED:
662     EditNP(-1);
663     break;
664   }
665 
666   switch(GetHungerState())
667   {
668    case STARVING:
669     EditExperience(ARM_STRENGTH, -75, 1 << 3);
670     EditExperience(LEG_STRENGTH, -75, 1 << 3);
671     break;
672    case VERY_HUNGRY:
673     EditExperience(ARM_STRENGTH, -50, 1 << 2);
674     EditExperience(LEG_STRENGTH, -50, 1 << 2);
675     break;
676    case HUNGRY:
677     EditExperience(ARM_STRENGTH, -25, 1 << 1);
678     EditExperience(LEG_STRENGTH, -25, 1 << 1);
679     break;
680    case SATIATED:
681     EditExperience(AGILITY, -25, 1 << 1);
682     break;
683    case BLOATED:
684     EditExperience(AGILITY, -50, 1 << 2);
685     break;
686    case OVER_FED:
687     EditExperience(AGILITY, -75, 1 << 3);
688     break;
689   }
690 
691   CheckStarvationDeath(CONST_S("starved to death"));
692 }
693 
TakeHit(character * Enemy,item * Weapon,bodypart * EnemyBodyPart,v2 HitPos,double Damage,double ToHitValue,int Success,int Type,int GivenDir,truth Critical,truth ForceHit)694 int character::TakeHit(character* Enemy, item* Weapon,
695                        bodypart* EnemyBodyPart, v2 HitPos,
696                        double Damage, double ToHitValue,
697                        int Success, int Type, int GivenDir,
698                        truth Critical, truth ForceHit)
699 {
700   hiteffectSetup* pHitEff=NULL;DBGLN;
701   bool bShowHitEffect = false;
702   static bool bHardestMode = false; //TODO make these an user hardcore combat option?
703   if(!bHardestMode){
704     //w/o ESP/infravision and if the square is visible even if both fighting are invisible
705     static bool bPlayerCanHearWhereTheFightIsHappening = true; //TODO this feels like cheating? making things easier? if so, set it to false
706     static bool bPlayerCanSensePetFighting = true; //TODO this feels like cheating? making things easier? if so, set it to false
707 
708     if(bPlayerCanHearWhereTheFightIsHappening) //TODO should then also show at non directly visible squares?
709       if(GetLSquareUnder()->CanBeSeenByPlayer() || Enemy->GetLSquareUnder()->CanBeSeenByPlayer())bShowHitEffect = true;
710 
711     if(bPlayerCanSensePetFighting && IsPet())bShowHitEffect=true; // override for team awareness
712   }
713   if(CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer())bShowHitEffect=true; //throwing hits in the air is valid (seen) if the other one is invisible
714   if(IsPlayer())bShowHitEffect=true; //override
715   if(bShowHitEffect){DBGLN;
716     pHitEff=new hiteffectSetup();
717     pHitEff->Critical=Critical;
718     pHitEff->GivenDir=GivenDir;
719     pHitEff->Type=Type;
720     pHitEff->WhoHits=Enemy; DBGLN;DBG2(Enemy,"WhoHits"); DBGSV2(Enemy->GetPos()); DBG1(Enemy->GetName(DEFINITE).CStr());
721     pHitEff->WhoIsHit=this;
722     pHitEff->lItemEffectReferenceID = Weapon!=NULL ? Weapon->GetID() : EnemyBodyPart->GetID();
723   }
724 
725   int Dir = Type == BITE_ATTACK ? YOURSELF : GivenDir;
726   double DodgeValue = GetDodgeValue();
727 
728   if(!Enemy->IsPlayer() && GetAttackWisdomLimit() != NO_LIMIT)
729     Enemy->EditExperience(WISDOM, 75, 1 << 13);
730 
731   if(!Enemy->CanBeSeenBy(this))
732     ToHitValue *= 2;
733 
734   if(!CanBeSeenBy(Enemy))
735     DodgeValue *= 2;
736 
737   if(Enemy->StateIsActivated(CONFUSED))
738     ToHitValue *= 0.75;
739 
740   switch(Enemy->GetTirednessState())
741   {
742    case FAINTING:
743     ToHitValue *= 0.50;
744    case EXHAUSTED:
745     ToHitValue *= 0.75;
746   }
747 
748   switch(GetTirednessState())
749   {
750    case FAINTING:
751     DodgeValue *= 0.50;
752    case EXHAUSTED:
753     DodgeValue *= 0.75;
754   }
755 
756   if(!ForceHit)
757   {
758     if(!IsRetreating())
759       SetGoingTo(Enemy->GetPos());
760     else
761       SetGoingTo(GetPos() - ((Enemy->GetPos() - GetPos()) << 4));
762 
763     if(!Enemy->IsRetreating())
764       Enemy->SetGoingTo(GetPos());
765     else
766       Enemy->SetGoingTo(Enemy->GetPos()
767                         - ((GetPos() - Enemy->GetPos()) << 4));
768   }
769 
770   /* Effectively, the average chance to hit is 100% / (DV/THV + 1). */
771 
772   if(RAND() % int(100 + ToHitValue / DodgeValue * (100 + Success)) < 100
773      && !Critical && !ForceHit)
774   {
775     Enemy->AddMissMessage(this);
776     EditExperience(AGILITY, 150, 1 << 7);
777     EditExperience(PERCEPTION, 75, 1 << 7);
778 
779     if(Enemy->CanBeSeenByPlayer())
780       DeActivateVoluntaryAction(CONST_S("The attack of ")
781                                 + Enemy->GetName(DEFINITE)
782                                 + CONST_S(" interrupts you."));
783     else
784       DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
785 
786 //    if(hitEff!=NULL)hitEff->End();
787     return HAS_DODGED;
788   }
789 
790   int TrueDamage = int(Damage * (100 + Success) / 100)
791                    + (RAND() % 3 ? 1 : 0);
792 
793   if(Critical)
794   {
795     TrueDamage += TrueDamage >> 1;
796     ++TrueDamage;
797   }
798 
799   int BodyPart = ChooseBodyPartToReceiveHit(ToHitValue, DodgeValue);
800 
801   if(Critical)
802   {
803     switch(Type)
804     {
805      case UNARMED_ATTACK:
806       Enemy->AddPrimitiveHitMessage(this,
807                                     Enemy->FirstPersonCriticalUnarmedHitVerb(),
808                                     Enemy->ThirdPersonCriticalUnarmedHitVerb(),
809                                     BodyPart);
810       break;
811      case WEAPON_ATTACK:
812       Enemy->AddWeaponHitMessage(this, Weapon, BodyPart, true);
813       break;
814      case KICK_ATTACK:
815       Enemy->AddPrimitiveHitMessage(this,
816                                     Enemy->FirstPersonCriticalKickVerb(),
817                                     Enemy->ThirdPersonCriticalKickVerb(),
818                                     BodyPart);
819       break;
820      case BITE_ATTACK:
821       Enemy->AddPrimitiveHitMessage(this,
822                                     Enemy->FirstPersonCriticalBiteVerb(),
823                                     Enemy->ThirdPersonCriticalBiteVerb(),
824                                     BodyPart);
825       break;
826     }
827   }
828   else
829   {
830     switch(Type)
831     {
832      case UNARMED_ATTACK:
833       Enemy->AddPrimitiveHitMessage(this,
834                                     Enemy->FirstPersonUnarmedHitVerb(),
835                                     Enemy->ThirdPersonUnarmedHitVerb(),
836                                     BodyPart);
837       break;
838      case WEAPON_ATTACK:
839       Enemy->AddWeaponHitMessage(this, Weapon, BodyPart, false);
840       break;
841      case KICK_ATTACK:
842       Enemy->AddPrimitiveHitMessage(this,
843                                     Enemy->FirstPersonKickVerb(),
844                                     Enemy->ThirdPersonKickVerb(),
845                                     BodyPart);
846       break;
847      case BITE_ATTACK:
848       Enemy->AddPrimitiveHitMessage(this,
849                                     Enemy->FirstPersonBiteVerb(),
850                                     Enemy->ThirdPersonBiteVerb(),
851                                     BodyPart);
852       break;
853     }
854   }
855 
856   if(!Critical && TrueDamage && Enemy->AttackIsBlockable(Type))
857   {
858     TrueDamage = CheckForBlock(Enemy, Weapon, ToHitValue,
859                                TrueDamage, Success, Type);
860 
861     if(!TrueDamage || (Weapon && !Weapon->Exists()))
862     {
863       if(Enemy->CanBeSeenByPlayer())
864         DeActivateVoluntaryAction(CONST_S("The attack of ")
865                                   + Enemy->GetName(DEFINITE)
866                                   + CONST_S(" interrupts you."));
867       else
868         DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
869 
870 //      if(hitEff!=NULL)hitEff->End();
871       return HAS_BLOCKED;
872     }
873   }
874 
875   int WeaponSkillHits = CalculateWeaponSkillHits(Enemy);
876   int DoneDamage = ReceiveBodyPartDamage(Enemy, TrueDamage,
877                                          PHYSICAL_DAMAGE, BodyPart,
878                                          Dir, false, Critical, true,
879                                          Type == BITE_ATTACK
880                                          && Enemy->BiteCapturesBodyPart());
881   truth Succeeded = (GetBodyPart(BodyPart)
882                      && HitEffect(Enemy, Weapon, HitPos, Type,
883                                   BodyPart, Dir, !DoneDamage, Critical, DoneDamage))
884                     || DoneDamage;
885 
886   if(Succeeded)
887     Enemy->WeaponSkillHit(Weapon, Type, WeaponSkillHits);
888 
889   if(Weapon)
890   {
891     if(Weapon->Exists() && DoneDamage < TrueDamage)
892       Weapon->ReceiveDamage(Enemy, TrueDamage - DoneDamage, PHYSICAL_DAMAGE);
893 
894     if(Weapon->Exists() && DoneDamage
895        && SpillsBlood() && GetBodyPart(BodyPart)
896        && (GetBodyPart(BodyPart)->IsAlive()
897            || GetBodyPart(BodyPart)->GetMainMaterial()->IsLiquid()))
898       Weapon->SpillFluid(0, CreateBlood(15 + RAND() % 15));
899   }
900 
901   if(Enemy->AttackIsBlockable(Type))
902     SpecialBodyDefenceEffect(Enemy, EnemyBodyPart, Type);
903 
904   if(!Succeeded)
905   {
906     if(Enemy->CanBeSeenByPlayer())
907       DeActivateVoluntaryAction(CONST_S("The attack of ")
908                                 + Enemy->GetName(DEFINITE)
909                                 + CONST_S(" interrupts you."));
910     else
911       DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
912 
913 //    if(hitEff!=NULL)hitEff->End();
914     return DID_NO_DAMAGE;
915   }
916 
917   if(pHitEff!=NULL){DBGLN;
918     if(GetLSquareUnder()!=NULL) //may be null if char died TODO right?
919       GetLSquareUnder()->AddHitEffect(*pHitEff); //after all returns of failure and before any other returns
920     delete pHitEff; //already possibly copied
921   }
922 
923   if(CheckDeath(GetNormalDeathMessage(), Enemy,
924                 Enemy->IsPlayer() ? FORCE_MSG : 0))
925     return HAS_DIED;
926 
927   if(Enemy->CanBeSeenByPlayer())
928     DeActivateVoluntaryAction(CONST_S("The attack of ")
929                               + Enemy->GetName(DEFINITE)
930                               + CONST_S(" interrupts you."));
931   else
932     DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
933 
934   return HAS_HIT;
935 }
936 
937 struct svpriorityelement
938 {
svpriorityelementsvpriorityelement939   svpriorityelement(int BodyPart, int StrengthValue)
940   : BodyPart(BodyPart), StrengthValue(StrengthValue) { }
operator <svpriorityelement941   bool operator<(const svpriorityelement& AnotherPair) const
942   { return StrengthValue > AnotherPair.StrengthValue; }
943   int BodyPart;
944   int StrengthValue;
945 };
946 
ChooseBodyPartToReceiveHit(double ToHitValue,double DodgeValue)947 int character::ChooseBodyPartToReceiveHit(double ToHitValue,
948                                           double DodgeValue)
949 {
950   if(BodyParts == 1)
951     return 0;
952 
953   std::priority_queue<svpriorityelement> SVQueue;
954 
955   for(int c = 0; c < BodyParts; ++c)
956   {
957     bodypart* BodyPart = GetBodyPart(c);
958 
959     if(BodyPart && (BodyPart->GetHP() != 1 || BodyPart->CanBeSevered(PHYSICAL_DAMAGE)))
960       SVQueue.push(svpriorityelement(c, ModifyBodyPartHitPreference(c, BodyPart->GetStrengthValue()
961                                                                        + BodyPart->GetHP())));
962   }
963 
964   while(SVQueue.size())
965   {
966     svpriorityelement E = SVQueue.top();
967     int ToHitPercentage = int(GLOBAL_WEAK_BODYPART_HIT_MODIFIER
968                               * ToHitValue
969                               * GetBodyPart(E.BodyPart)->GetBodyPartVolume()
970                               / (DodgeValue * GetBodyVolume()));
971     ToHitPercentage = ModifyBodyPartToHitChance(E.BodyPart, ToHitPercentage);
972 
973     if(ToHitPercentage < 1)
974       ToHitPercentage = 1;
975     else if(ToHitPercentage > 95)
976       ToHitPercentage = 95;
977 
978     if(ToHitPercentage > RAND() % 100)
979       return E.BodyPart;
980 
981     SVQueue.pop();
982   }
983 
984   return 0;
985 }
986 
987 #include "audio.h"
988 
Be()989 void character::Be()
990 {
991   if(game::ForceJumpToPlayerBe())
992   {
993     if(!IsPlayer())
994       return;
995     else
996       game::SetForceJumpToPlayerBe(false);
997   }
998   else
999   {
1000     truth ForceBe = HP != MaxHP || AllowSpoil();
1001 
1002     for(int c = 0; c < BodyParts; ++c)
1003     {
1004       bodypart* BodyPart = GetBodyPart(c);
1005 
1006       if(BodyPart && (ForceBe || BodyPart->NeedsBe()))
1007         BodyPart->Be();
1008     }
1009 
1010     HandleStates();
1011 
1012     if(!IsEnabled())
1013       return;
1014 
1015     if(GetTeam() == PLAYER->GetTeam())
1016     {
1017       for(int c = 0; c < AllowedWeaponSkillCategories; ++c)
1018         if(CWeaponSkill[c].Tick() && IsPlayer())
1019           CWeaponSkill[c].AddLevelDownMessage(c);
1020 
1021       SWeaponSkillTick();
1022     }
1023 
1024     if(IsPlayer())
1025     {
1026       if(GetHungerState() == STARVING && !(RAND() % 50))
1027         LoseConsciousness(250 + RAND_N(250), true);
1028 
1029       if(!Action || Action->AllowFoodConsumption())
1030         Hunger();
1031 
1032       int MinHPPercent = 128;
1033       for(int c = 0; c < BodyParts; ++c)
1034       {
1035         int tempHpPercent;
1036         bodypart* BodyPart = GetBodyPart(c);
1037 
1038         if(BodyPart)
1039         {
1040           tempHpPercent = (BodyPart->GetHP() * audio::MAX_INTENSITY_VOLUME) / BodyPart->GetMaxHP();
1041           if(tempHpPercent < MinHPPercent )
1042           {
1043             MinHPPercent = tempHpPercent;
1044           }
1045         }
1046       }
1047       audio::IntensityLevel( audio::MAX_INTENSITY_VOLUME - MinHPPercent );
1048     }
1049 
1050     if(Stamina != MaxStamina)
1051       RegenerateStamina();
1052 
1053     if(HP != MaxHP || StateIsActivated(REGENERATION))
1054       Regenerate();
1055 
1056     if(Action && AP >= 1000)
1057       ActionAutoTermination();
1058 
1059     if(Action && AP >= 1000)
1060     {
1061       if(IsPlayer() && ivanconfig::IsXBRZScale())
1062         game::UpdateSRegionsXBRZ(false); // to speed up the action processing
1063 
1064       Action->Handle();
1065 
1066       if(!IsEnabled())
1067         return;
1068     }
1069     else
1070       EditAP(GetStateAPGain(100));
1071   }
1072 
1073   if(AP >= 1000)
1074   {
1075     SpecialTurnHandler();
1076     BlocksSinceLastTurn = 0;
1077 
1078     if(IsPlayer())
1079     {
1080       static int Timer = 0;
1081 
1082       if(ivanconfig::GetAutoSaveInterval() && !GetAction()
1083          && ++Timer >= ivanconfig::GetAutoSaveInterval())
1084       {
1085         game::Save(game::GetAutoSaveFileName());
1086         Timer = 0;
1087       }
1088 
1089       game::CalculateNextDanger();
1090 
1091       if(!StateIsActivated(POLYMORPHED))
1092         game::UpdatePlayerAttributeAverage();
1093 
1094       if(!game::IsInWilderness())
1095         Search(GetAttribute(PERCEPTION));
1096 
1097       if(!Action)
1098       {
1099         GetPlayerCommand();
1100       }
1101       else
1102       {
1103         if(Action->ShowEnvironment())
1104         {
1105           static int Counter = 0;
1106 
1107           if(++Counter == 10)
1108           {
1109             game::DrawEverything();
1110             Counter = 0;
1111           }
1112         }
1113 
1114         msgsystem::ThyMessagesAreNowOld();
1115 
1116         if(Action->IsVoluntary() && WAIT_FOR_KEY_DOWN())
1117         {
1118           READ_KEY();
1119           Action->Terminate(false);
1120         }
1121       }
1122     }
1123     else
1124     {
1125       if(!Action && !game::IsInWilderness())
1126         GetAICommand();
1127     }
1128   }
1129 }
1130 
Move(v2 MoveTo,truth TeleportMove,truth Run)1131 void character::Move(v2 MoveTo, truth TeleportMove, truth Run)
1132 {
1133   if(!IsEnabled())
1134     return;
1135 
1136   /* Test whether the player is stuck to something */
1137 
1138   if(!TeleportMove && !TryToUnStickTraps(MoveTo - GetPos()))
1139     return;
1140 
1141   if(Run && !IsPlayer() && TorsoIsAlive()
1142      && (Stamina <= 10000 / Max(GetAttribute(LEG_STRENGTH), 1)
1143          || (!StateIsActivated(PANIC) && Stamina < MaxStamina >> 2)))
1144     Run = false;
1145 
1146   RemoveTraps();
1147 
1148   if(GetBurdenState() != OVER_LOADED || TeleportMove)
1149   {
1150     lsquare* OldSquareUnder[MAX_SQUARES_UNDER];
1151 
1152     if(!game::IsInWilderness())
1153       for(int c = 0; c < GetSquaresUnder(); ++c)
1154         OldSquareUnder[c] = GetLSquareUnder(c);
1155 
1156     Remove();
1157     PutTo(MoveTo);
1158 
1159     if(!TeleportMove)
1160     {
1161       /* Multitiled creatures should behave differently, maybe? */
1162 
1163       if(Run)
1164       {
1165         int ED = GetSquareUnder()->GetEntryDifficulty();
1166         EditAP(-GetMoveAPRequirement(ED) >> 1);
1167         EditNP(-24 * ED);
1168         EditExperience(AGILITY, 125, ED << 7);
1169         int Base = 1000;
1170 
1171         if(IsPlayer())
1172           switch(GetHungerState())
1173           {
1174            case SATIATED:
1175             Base = 1100;
1176             break;
1177            case BLOATED:
1178             Base = 1250;
1179             break;
1180            case OVER_FED:
1181             Base = 1500;
1182             break;
1183           }
1184 
1185         EditStamina(GetAdjustedStaminaCost(-Base, Max(GetAttribute(LEG_STRENGTH), 1)), true);
1186       }
1187       else
1188       {
1189         int ED = GetSquareUnder()->GetEntryDifficulty();
1190         EditAP(-GetMoveAPRequirement(ED));
1191         EditNP(-12 * ED);
1192         EditExperience(AGILITY, 75, ED << 7);
1193       }
1194     }
1195 
1196     if(IsPlayer())
1197       ShowNewPosInfo();
1198 
1199     if(!game::IsInWilderness())
1200       SignalStepFrom(OldSquareUnder);
1201   }
1202   else
1203   {
1204     if(IsPlayer())
1205     {
1206       cchar* CrawlVerb = StateIsActivated(LEVITATION) ? "float" : "crawl";
1207       ADD_MESSAGE("You try very hard to %s forward, but your load is too heavy.", CrawlVerb);
1208     }
1209 
1210     EditAP(-1000);
1211   }
1212 }
1213 
GetAICommand()1214 void character::GetAICommand()
1215 {
1216   if(!IsPlayerAutoPlay()){
1217     SeekLeader(GetLeader());
1218 
1219     if(FollowLeader(GetLeader()))
1220       return;
1221   }
1222 
1223   if(!IsPlayer() && CanBeSeenByPlayer() && !RAND_N(50))
1224   {
1225     // Make NPCs sometimes talk to the player on their own. Hostile enemies will
1226     // make threats, friendly creatures will just chat.
1227     BeTalkedTo();
1228   }
1229 
1230   if(CheckForEnemies(true, true, true))
1231     return;
1232 
1233   if(CheckForUsefulItemsOnGround())
1234     return;
1235 
1236   if(CheckForDoors())
1237     return;
1238 
1239   if(CheckSadism())
1240     return;
1241 
1242   if(MoveRandomly())
1243     return;
1244 
1245   EditAP(-1000);
1246 }
1247 
MoveTowardsTarget(truth Run)1248 truth character::MoveTowardsTarget(truth Run)
1249 {
1250   v2 Pos = GetPos();
1251   v2 TPos;
1252 
1253   if(!Route.empty())
1254   {
1255     TPos = Route.back();
1256     Route.pop_back();
1257   }
1258   else
1259     TPos = GoingTo;
1260 
1261   v2 MoveTo[3];
1262 
1263   if(TPos.X < Pos.X)
1264   {
1265     if(TPos.Y < Pos.Y)
1266     {
1267       MoveTo[0] = v2(-1, -1);
1268       MoveTo[1] = v2(-1,  0);
1269       MoveTo[2] = v2( 0, -1);
1270     }
1271 
1272     if(TPos.Y == Pos.Y)
1273     {
1274       MoveTo[0] = v2(-1,  0);
1275       MoveTo[1] = v2(-1, -1);
1276       MoveTo[2] = v2(-1,  1);
1277     }
1278 
1279     if(TPos.Y > Pos.Y)
1280     {
1281       MoveTo[0] = v2(-1, 1);
1282       MoveTo[1] = v2(-1, 0);
1283       MoveTo[2] = v2( 0, 1);
1284     }
1285   }
1286 
1287   if(TPos.X == Pos.X)
1288   {
1289     if(TPos.Y < Pos.Y)
1290     {
1291       MoveTo[0] = v2( 0, -1);
1292       MoveTo[1] = v2(-1, -1);
1293       MoveTo[2] = v2( 1, -1);
1294     }
1295 
1296     if(TPos.Y == Pos.Y)
1297     {
1298       TerminateGoingTo();
1299       return false;
1300     }
1301 
1302     if(TPos.Y > Pos.Y)
1303     {
1304       MoveTo[0] = v2( 0, 1);
1305       MoveTo[1] = v2(-1, 1);
1306       MoveTo[2] = v2( 1, 1);
1307     }
1308   }
1309 
1310   if(TPos.X > Pos.X)
1311   {
1312     if(TPos.Y < Pos.Y)
1313     {
1314       MoveTo[0] = v2(1, -1);
1315       MoveTo[1] = v2(1,  0);
1316       MoveTo[2] = v2(0, -1);
1317     }
1318 
1319     if(TPos.Y == Pos.Y)
1320     {
1321       MoveTo[0] = v2(1,  0);
1322       MoveTo[1] = v2(1, -1);
1323       MoveTo[2] = v2(1,  1);
1324     }
1325 
1326     if(TPos.Y > Pos.Y)
1327     {
1328       MoveTo[0] = v2(1, 1);
1329       MoveTo[1] = v2(1, 0);
1330       MoveTo[2] = v2(0, 1);
1331     }
1332   }
1333 
1334   v2 ModifiedMoveTo = ApplyStateModification(MoveTo[0]);
1335 
1336   if(TryMove(ModifiedMoveTo, true, Run))
1337   {
1338     RandomMoveDir = femath::RandReal(8);
1339     return true;
1340   }
1341 
1342   int L = (Pos - TPos).GetManhattanLength();
1343 
1344   if(RAND() & 1)
1345     Swap(MoveTo[1], MoveTo[2]);
1346 
1347   if(Pos.IsAdjacent(TPos))
1348   {
1349     TerminateGoingTo();
1350     return false;
1351   }
1352 
1353   if((Pos + MoveTo[1] - TPos).GetManhattanLength() <= L
1354      && TryMove(ApplyStateModification(MoveTo[1]), true, Run))
1355     return true;
1356 
1357   if((Pos + MoveTo[2] - TPos).GetManhattanLength() <= L
1358      && TryMove(ApplyStateModification(MoveTo[2]), true, Run))
1359     return true;
1360 
1361   Illegal.insert(Pos + ModifiedMoveTo);
1362 
1363   if(CreateRoute())
1364     return true;
1365 
1366   return false;
1367 }
1368 
CalculateNewSquaresUnder(lsquare ** NewSquare,v2 Pos) const1369 int character::CalculateNewSquaresUnder(lsquare** NewSquare, v2 Pos) const
1370 {
1371   if(GetLevel()->IsValidPos(Pos))
1372   {
1373     *NewSquare = GetNearLSquare(Pos);
1374     return 1;
1375   }
1376   else
1377     return 0;
1378 }
1379 
TryMove(v2 MoveVector,truth Important,truth Run,truth * pbWaitNeutralMove)1380 truth character::TryMove(v2 MoveVector, truth Important, truth Run, truth* pbWaitNeutralMove)
1381 {
1382   lsquare* MoveToSquare[MAX_SQUARES_UNDER];
1383   character* Pet[MAX_SQUARES_UNDER];
1384   character* Neutral[MAX_SQUARES_UNDER];
1385   character* Hostile[MAX_SQUARES_UNDER];
1386   v2 PetPos[MAX_SQUARES_UNDER];
1387   v2 NeutralPos[MAX_SQUARES_UNDER];
1388   v2 HostilePos[MAX_SQUARES_UNDER];
1389   v2 MoveTo = GetPos() + MoveVector;
1390   int Direction = game::GetDirectionForVector(MoveVector);
1391 
1392   if(Direction == DIR_ERROR)
1393     ABORT("Direction fault.");
1394 
1395   if(!game::IsInWilderness())
1396   {
1397     int Squares = CalculateNewSquaresUnder(MoveToSquare, MoveTo);
1398 
1399     if(Squares)
1400     {
1401       int Pets = 0;
1402       int Neutrals = 0;
1403       int Hostiles = 0;
1404 
1405       for(int c = 0; c < Squares; ++c)
1406       {
1407         character* Char = MoveToSquare[c]->GetCharacter();
1408 
1409         if(Char && Char != this)
1410         {
1411           v2 Pos = MoveToSquare[c]->GetPos();
1412 
1413           if(IsAlly(Char))
1414           {
1415             Pet[Pets] = Char;
1416             PetPos[Pets++] = Pos;
1417           }
1418           else if(Char->GetRelation(this) != HOSTILE)
1419           {
1420             Neutral[Neutrals] = Char;
1421             NeutralPos[Neutrals++] = Pos;
1422           }
1423           else
1424           {
1425             Hostile[Hostiles] = Char;
1426             HostilePos[Hostiles++] = Pos;
1427           }
1428         }
1429       }
1430 
1431       if(Hostiles == 1)
1432         return Hit(Hostile[0], HostilePos[0], Direction);
1433       else if(Hostiles)
1434       {
1435         int Index = RAND() % Hostiles;
1436         return Hit(Hostile[Index], HostilePos[Index], Direction);
1437       }
1438 
1439       if(Neutrals>0 && ivanconfig::IsWaitNeutralsMoveAway() && pbWaitNeutralMove!=NULL){
1440         (*pbWaitNeutralMove)=true;
1441         return false;
1442       }else{
1443         if(Neutrals == 1)
1444         {
1445           if(!IsPlayer() && !Pets && Important && CanMoveOn(MoveToSquare[0]))
1446             return HandleCharacterBlockingTheWay(Neutral[0], NeutralPos[0], Direction);
1447           else
1448             return IsPlayer() && Hit(Neutral[0], NeutralPos[0], Direction);
1449         }
1450         else if(Neutrals)
1451         {
1452           if(IsPlayer())
1453           {
1454             int Index = RAND() % Neutrals;
1455             return Hit(Neutral[Index], NeutralPos[Index], Direction);
1456           }
1457           else
1458             return false;
1459         }
1460       }
1461 
1462       if(!IsPlayer())
1463         for(int c = 0; c < Squares; ++c)
1464           if(MoveToSquare[c]->IsScary(this))
1465             return false;
1466 
1467       if(Pets == 1)
1468       {
1469         if(IsPlayer() && !ivanconfig::GetBeNice()
1470            && Pet[0]->IsMasochist() && HasSadistAttackMode()
1471            && game::TruthQuestion("Do you want to punish " + Pet[0]->GetObjectPronoun() + "? [y/N]"))
1472           return Hit(Pet[0], PetPos[0], Direction, SADIST_HIT);
1473         else
1474           return (Important
1475                   && (CanMoveOn(MoveToSquare[0])
1476                       || (IsPlayer()
1477                           && game::GoThroughWallsCheatIsActive()))
1478                   && Displace(Pet[0]));
1479     }
1480     else if(Pets)
1481       return false;
1482 
1483     if((CanMove() && CanMoveOn(MoveToSquare[0]))
1484        || (game::GoThroughWallsCheatIsActive() && IsPlayer()))
1485     {
1486       Move(MoveTo, false, Run);
1487 
1488       if(IsEnabled() && GetPos() == GoingTo)
1489         TerminateGoingTo();
1490 
1491       return true;
1492     }
1493     else
1494       for(int c = 0; c < Squares; ++c)
1495       {
1496         olterrain* Terrain = MoveToSquare[c]->GetOLTerrain();
1497 
1498         if(Terrain && Terrain->CanBeOpened())
1499         {
1500           if(CanOpen())
1501           {
1502             if(Terrain->IsLocked())
1503             {
1504               if(IsPlayer())
1505               {
1506                 /* not sure if this is better than "the door is locked", but I guess it _might_ be slightly better */
1507                 ADD_MESSAGE("The %s is locked.", Terrain->GetNameSingular().CStr());
1508                 if(!IsPlayerAutoPlay())return false;
1509               }
1510 
1511               if(Important && CheckKick())
1512               {
1513                 room* Room = MoveToSquare[c]->GetRoom();
1514 
1515                 if(!Room || Room->AllowKick(this, MoveToSquare[c]))
1516                 {
1517                   int HP = Terrain->GetHP();
1518 
1519                   if(CanBeSeenByPlayer())
1520                     ADD_MESSAGE("%s kicks %s.", CHAR_NAME(DEFINITE), Terrain->CHAR_NAME(DEFINITE));
1521 
1522                   Kick(MoveToSquare[c], Direction);
1523                   olterrain* NewTerrain = MoveToSquare[c]->GetOLTerrain();
1524 
1525                   if(NewTerrain == Terrain && Terrain->GetHP() == HP) // BUG!
1526                   {
1527                     Illegal.insert(MoveTo);
1528                     CreateRoute();
1529                   }
1530 
1531                   return true;
1532                 }
1533               }
1534             }
1535             else
1536               return MoveToSquare[c]->Open(this);
1537           }
1538           else
1539           {
1540             if(IsPlayer())
1541             {
1542               ADD_MESSAGE("This monster type cannot open doors.");
1543               return false;
1544             }
1545             else if(Important)
1546             {
1547               Illegal.insert(MoveTo);
1548               return CreateRoute();
1549             }
1550           }
1551         }
1552       }
1553 
1554     return false;
1555   }
1556   else
1557   {
1558     if(IsPlayer() && !IsStuck() && GetLevel()->IsOnGround()
1559        && game::TruthQuestion(CONST_S("Do you want to leave ")
1560                               + game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex())
1561                               + "? [y/N]"))
1562     {
1563       if(HasPetrussNut() && !HasGoldenEagleShirt())
1564       {
1565         game::PlayVictoryMusic();
1566         game::TextScreen(CONST_S("An undead and sinister voice greets you as you leave the city behind:\n\n"
1567                                  "\"MoRtAl! ThOu HaSt SlAuGhTeReD pEtRuS aNd PlEaSeD mE!\nfRoM tHiS dAy On, "
1568                                  "ThOu ArT tHe DeArEsT sErVaNt Of AlL eViL!\"\n\nYou are victorious!"));
1569         game::GetCurrentArea()->SendNewDrawRequest();
1570         game::DrawEverything();
1571         ShowAdventureInfo();
1572         festring Msg = CONST_S("killed Petrus and became the Avatar of Chaos");
1573         PLAYER->AddScoreEntry(Msg, 3, false);
1574         game::End(Msg);
1575         return true;
1576       }
1577 
1578       if(game::TryTravel(WORLD_MAP, WORLD_MAP, game::GetCurrentDungeonIndex()))
1579         return true;
1580     }
1581 
1582     return false;
1583   }
1584 }
1585   else // In wilderness:
1586   {
1587     /** No multitile support */
1588 
1589     int Shape = game::GetWorldShape();
1590     area* Area = game::GetCurrentArea();
1591 
1592     if(Shape == 0)
1593     {
1594       MoveTo = MoveTo; // Flat
1595     }
1596     else if(Shape == 1)
1597     {
1598       // Cylinder
1599       if(MoveTo.X > Area->GetXSize() - 1)
1600         MoveTo.X = 0;
1601       if(MoveTo.X < 0)
1602         MoveTo.X = Area->GetXSize() - 1;
1603     }
1604     else if(Shape == 2)
1605     {
1606       // Toroidal
1607     if(MoveTo.X > Area->GetXSize() - 1)
1608       MoveTo.X = 0;
1609     if(MoveTo.X < 0)
1610       MoveTo.X = Area->GetXSize() - 1;
1611     if(MoveTo.Y > Area->GetYSize() - 1)
1612       MoveTo.Y = 0;
1613     if(MoveTo.Y < 0)
1614       MoveTo.Y = Area->GetYSize() - 1;
1615     }
1616     else
1617       MoveTo = MoveTo; // Flat (default)
1618 
1619 
1620     if(CanMove()
1621        && GetArea()->IsValidPos(MoveTo)
1622        && (CanMoveOn(GetNearWSquare(MoveTo))
1623            || game::GoThroughWallsCheatIsActive())
1624       )
1625     {
1626       if(!game::GoThroughWallsCheatIsActive())
1627       {
1628         charactervector& V = game::GetWorldMap()->GetPlayerGroup();
1629 
1630         if(IsPlayer() && game::PlayerHasBoat())
1631         {
1632           if((GetSquareUnder()->GetSquareWalkability() & WALK) && // land
1633              !(GetNearWSquare(MoveTo)->GetWalkability() & WALK)) // ocean
1634           {
1635             if(!game::TruthQuestion("Board your ship? [y/N]"))
1636               return false;
1637 
1638             if(V.empty())
1639               ADD_MESSAGE("You board your ship and prepare to sail.");
1640             else
1641               ADD_MESSAGE("Your team boards your ship and prepares to sail.");
1642 
1643             EditStamina(-30000, false);
1644           }
1645           else if(!(GetSquareUnder()->GetSquareWalkability() & WALK) &&
1646                   (GetNearWSquare(MoveTo)->GetWalkability() & WALK))
1647           {
1648             if(!game::TruthQuestion("Disembark the ship? [y/N]"))
1649               return false;
1650 
1651             if(V.empty())
1652               ADD_MESSAGE("You disembark your ship.");
1653             else
1654               ADD_MESSAGE("You and your team disembark your ship.");
1655 
1656             EditStamina(-30000, false);
1657           }
1658         }
1659         else // Cannot take some pets over ocean without a ship.
1660         {
1661           truth Discard = false;
1662 
1663           for(uint c = 0; c < V.size(); ++c)
1664             if(!V[c]->CanMoveOn(GetNearWSquare(MoveTo)))
1665             {
1666               if(!Discard)
1667               {
1668                 ADD_MESSAGE("One or more of your team members cannot cross this terrain.");
1669 
1670                 if(!game::TruthQuestion("Abandon them? [y/N]"))
1671                   return false;
1672 
1673                 Discard = true;
1674               }
1675 
1676               if(Discard)
1677                 delete V[c];
1678 
1679               V.erase(V.begin() + c--);
1680             }
1681         }
1682       }
1683 
1684       Move(MoveTo, false, Run);
1685       return true;
1686     }
1687     else
1688       return false;
1689   }
1690 }
1691 
CreateCorpse(lsquare * Square)1692 void character::CreateCorpse(lsquare* Square)
1693 {
1694   if(!BodyPartsDisappearWhenSevered() && !game::AllBodyPartsVanish())
1695   {
1696     corpse* Corpse = corpse::Spawn(0, NO_MATERIALS);
1697     Corpse->SetDeceased(this);
1698     Square->AddItem(Corpse);
1699     Disable();
1700   }
1701   else
1702     SendToHell();
1703 }
1704 
1705 bool bSafePrayOnce=false;
AutoPlayAITeleport(bool bDeathCountBased)1706 void character::AutoPlayAITeleport(bool bDeathCountBased)
1707 {
1708   bool bTeleportNow=false;
1709 
1710   if(bDeathCountBased){ // this is good to prevent autoplay AI getting stuck endless dieing
1711     static int iDieMax=10;
1712     static int iDieTeleportCountDown=iDieMax;
1713     if(iDieTeleportCountDown==0){ //this helps on defeating not so strong enemies in spot
1714       if(IsPlayerAutoPlay())
1715         bTeleportNow=true;
1716       iDieTeleportCountDown=iDieMax;
1717       bSafePrayOnce=true;
1718     }else{
1719       static v2 v2DiePos(0,0);
1720       if(v2DiePos==GetPos()){
1721         iDieTeleportCountDown--;
1722       }else{
1723         v2DiePos=GetPos();
1724         iDieTeleportCountDown=iDieMax;
1725       }
1726     }
1727   }
1728 
1729   if(bTeleportNow)
1730     Move(GetLevel()->GetRandomSquare(this), true); //not using teleport function to avoid prompts, but this code is from there TODO and should be in sync! create TeleportRandomDirectly() ?
1731 }
1732 
Die(ccharacter * Killer,cfestring & Msg,ulong DeathFlags)1733 void character::Die(ccharacter* Killer, cfestring& Msg, ulong DeathFlags)
1734 {
1735   /* Note: This function musn't delete any objects, since one of these may be
1736      the one currently processed by pool::Be()! */
1737 
1738   if(!IsEnabled())
1739     return;
1740 
1741   RemoveTraps();
1742 
1743   if(IsPlayer())
1744   {
1745     ADD_MESSAGE("You die.");
1746 
1747     if(game::WizardModeIsActive())
1748     {
1749       game::DrawEverything();
1750 
1751       bool bInstaResurrect=false;
1752       if(!bInstaResurrect && IsPlayerAutoPlay())bInstaResurrect=true;
1753       if(!bInstaResurrect && !game::TruthQuestion(CONST_S("Do you want to do this, cheater? [y/n]"), REQUIRES_ANSWER))bInstaResurrect=true;
1754       if(bInstaResurrect)
1755       {
1756         RestoreBodyParts();
1757         ResetSpoiling();
1758         if(IsBurning())
1759         {
1760           doforbodypartswithparam<truth>()(this, &bodypart::Extinguish, false);
1761           doforbodyparts()(this, &bodypart::ResetThermalEnergies);
1762           doforbodyparts()(this, &bodypart::ResetBurning);
1763         }
1764         RestoreHP();
1765         RestoreStamina();
1766         ResetStates();
1767         SetNP(SATIATED_LEVEL);
1768         SendNewDrawRequest();
1769         if(IsPlayerAutoPlay())AutoPlayAITeleport(true);
1770         return;
1771       }
1772     }
1773   }
1774   else if(CanBeSeenByPlayer() && !(DeathFlags & DISALLOW_MSG))
1775     ProcessAndAddMessage(GetDeathMessage());
1776   else if(DeathFlags & FORCE_MSG)
1777     ADD_MESSAGE("You sense the death of something.");
1778 
1779   if(IsBurning()) // do this anyway, it stops the corpse from emitating and continuing to propagate weirdness
1780   {
1781     doforbodypartswithparam<truth>()(this, &bodypart::Extinguish, false);
1782     doforbodyparts()(this, &bodypart::ResetThermalEnergies);
1783     doforbodyparts()(this, &bodypart::ResetBurning);
1784   }
1785 
1786   if(!(DeathFlags & FORBID_REINCARNATION))
1787   {
1788     if(StateIsActivated(LIFE_SAVED)
1789        && CanMoveOn(!game::IsInWilderness() ? GetSquareUnder() : PLAYER->GetSquareUnder()))
1790     {
1791       SaveLife();
1792       return;
1793     }
1794 
1795     if(SpecialSaveLife())
1796       return;
1797   }
1798   else if(StateIsActivated(LIFE_SAVED))
1799     RemoveLifeSavers();
1800 
1801   Flags |= C_IN_NO_MSG_MODE;
1802   bonesghost* Ghost = 0;
1803 
1804   if(IsPlayer())
1805   {
1806     game::RemoveSaves();
1807 
1808     if(!game::IsInWilderness())
1809     {
1810       Ghost = game::CreateGhost();
1811       Ghost->Disable();
1812     }
1813   }
1814 
1815   square* SquareUnder[MAX_SQUARES_UNDER];
1816   lsquare** LSquareUnder = reinterpret_cast<lsquare**>(SquareUnder);
1817   memset(SquareUnder, 0, sizeof(SquareUnder));
1818   Disable();
1819 
1820   if(IsPlayer() || !game::IsInWilderness())
1821   {
1822     for(int c = 0; c < SquaresUnder; ++c)
1823       SquareUnder[c] = GetSquareUnder(c);
1824 
1825     Remove();
1826   }
1827   else
1828   {
1829     charactervector& V = game::GetWorldMap()->GetPlayerGroup();
1830     V.erase(std::find(V.begin(), V.end(), this));
1831   }
1832 
1833   if(!game::IsInWilderness())
1834   {
1835     if(!StateIsActivated(POLYMORPHED))
1836     {
1837       if(!IsPlayer() && !IsTemporary() && !Msg.IsEmpty())
1838         game::SignalDeath(this, Killer, Msg);
1839 
1840       if(!(DeathFlags & DISALLOW_CORPSE))
1841         CreateCorpse(LSquareUnder[0]);
1842       else
1843         SendToHell();
1844     }
1845     else
1846     {
1847       if(!IsPlayer() && !IsTemporary() && !Msg.IsEmpty())
1848         game::SignalDeath(GetPolymorphBackup(), Killer, Msg);
1849 
1850       GetPolymorphBackup()->CreateCorpse(LSquareUnder[0]);
1851       GetPolymorphBackup()->Flags &= ~C_POLYMORPHED;
1852       SetPolymorphBackup(0);
1853       SendToHell();
1854     }
1855   }
1856   else
1857   {
1858     if(!IsPlayer() && !IsTemporary() && !Msg.IsEmpty())
1859       game::SignalDeath(this, Killer, Msg);
1860 
1861     SendToHell();
1862   }
1863 
1864   if(IsPlayer())
1865   {
1866     if(!game::IsInWilderness())
1867       for(int c = 0; c < GetSquaresUnder(); ++c)
1868         LSquareUnder[c]->SetTemporaryEmitation(GetEmitation());
1869 
1870     game::SRegionAroundDisable();
1871     game::PlayDefeatMusic();
1872     ShowAdventureInfo();
1873 
1874     if(!game::IsInWilderness())
1875       for(int c = 0; c < GetSquaresUnder(); ++c)
1876         LSquareUnder[c]->SetTemporaryEmitation(0);
1877   }
1878 
1879   if(!game::IsInWilderness())
1880   {
1881     if(GetSquaresUnder() == 1)
1882     {
1883       stack* StackUnder = LSquareUnder[0]->GetStack();
1884       GetStack()->MoveItemsTo(StackUnder);
1885       doforbodypartswithparam<stack*>()(this, &bodypart::DropEquipment, StackUnder);
1886     }
1887     else
1888     {
1889       while(GetStack()->GetItems())
1890         GetStack()->GetBottom()->MoveTo(LSquareUnder[RAND_N(GetSquaresUnder())]->GetStack());
1891 
1892       for(int c = 0; c < BodyParts; ++c)
1893       {
1894         bodypart* BodyPart = GetBodyPart(c);
1895 
1896         if(BodyPart)
1897           BodyPart->DropEquipment(LSquareUnder[RAND_N(GetSquaresUnder())]->GetStack());
1898       }
1899     }
1900   }
1901 
1902   if(GetTeam()->GetLeader() == this)
1903     GetTeam()->SetLeader(0);
1904 
1905   Flags &= ~C_IN_NO_MSG_MODE;
1906 
1907   if(IsPlayer())
1908   {
1909     if(game::GetXinrochTombStoryState() == 2)
1910     {
1911       festring MsgBut = CONST_S("delivered the Shadow Veil to the Necromancer and continued to further adventures, but was ");
1912       festring NewMsg = MsgBut << Msg;
1913       AddScoreEntry(NewMsg, 2, true);
1914     }
1915     else if(Max(game::GetAslonaStoryState(), game::GetRebelStoryState()) >= 4) // At least two of the three quests.
1916     {
1917       festring Whom;
1918       if(game::GetAslonaStoryState() > game::GetRebelStoryState())
1919         Whom = "royalists";
1920       else
1921         Whom = "rebels";
1922 
1923       festring MsgBut = "fought in the civil war of Aslona on the side of " + Whom + ", but was ";
1924       festring NewMsg = MsgBut << Msg;
1925       int Bonus = Max(game::GetAslonaStoryState(), game::GetRebelStoryState()) - 2;
1926 
1927       AddScoreEntry(NewMsg, Bonus, true);
1928     }
1929     else
1930       AddScoreEntry(Msg);
1931 
1932     if(!game::IsInWilderness())
1933     {
1934       Ghost->PutTo(LSquareUnder[0]->GetPos());
1935       Ghost->Enable();
1936       game::CreateBone();
1937     }
1938     game::TextScreen(CONST_S("Unfortunately you died."), ZERO_V2, WHITE, true, true, &game::ShowDeathSmiley);
1939     game::End(Msg);
1940   }
1941 }
1942 
AddMissMessage(ccharacter * Enemy) const1943 void character::AddMissMessage(ccharacter* Enemy) const
1944 {
1945   festring Msg;
1946 
1947   if(Enemy->IsPlayer())
1948     Msg = GetDescription(DEFINITE) + " misses you!";
1949   else if(IsPlayer())
1950     Msg = CONST_S("You miss ") + Enemy->GetDescription(DEFINITE) + '!';
1951   else if(CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer())
1952     Msg = GetDescription(DEFINITE) + " misses " + Enemy->GetDescription(DEFINITE) + '!';
1953   else
1954     return;
1955 
1956   ADD_MESSAGE("%s", Msg.CStr());
1957 }
1958 
AddBlockMessage(ccharacter * Enemy,citem * Blocker,cfestring & HitNoun,truth Partial) const1959 void character::AddBlockMessage(ccharacter* Enemy, citem* Blocker, cfestring& HitNoun, truth Partial) const
1960 {
1961   festring Msg;
1962   festring BlockVerb = (Partial ? " to partially block the " : " to block the ") + HitNoun;
1963 
1964   if(IsPlayer())
1965     Msg << "You manage" << BlockVerb << " with your " << Blocker->GetName(UNARTICLED) << '!';
1966   else if(Enemy->IsPlayer() || Enemy->CanBeSeenByPlayer())
1967   {
1968     if(CanBeSeenByPlayer())
1969       Msg << GetName(DEFINITE) << " manages" << BlockVerb << " with "
1970           << GetPossessivePronoun() << ' ' << Blocker->GetName(UNARTICLED) << '!';
1971     else
1972       Msg << "Something manages" << BlockVerb << " with something!";
1973   }
1974   else
1975     return;
1976 
1977   ADD_MESSAGE("%s", Msg.CStr());
1978 }
1979 
AddPrimitiveHitMessage(ccharacter * Enemy,cfestring & FirstPersonHitVerb,cfestring & ThirdPersonHitVerb,int BodyPart) const1980 void character::AddPrimitiveHitMessage(ccharacter* Enemy, cfestring& FirstPersonHitVerb,
1981                                        cfestring& ThirdPersonHitVerb, int BodyPart) const
1982 {
1983   festring Msg;
1984   festring BodyPartDescription;
1985 
1986   if(BodyPart && (Enemy->CanBeSeenByPlayer() || Enemy->IsPlayer()))
1987     BodyPartDescription << " in the " << Enemy->GetBodyPartName(BodyPart);
1988 
1989   if(Enemy->IsPlayer())
1990     Msg << GetDescription(DEFINITE) << ' ' << ThirdPersonHitVerb << " you" << BodyPartDescription << '!';
1991   else if(IsPlayer())
1992     Msg << "You " << FirstPersonHitVerb << ' ' << Enemy->GetDescription(DEFINITE) << BodyPartDescription << '!';
1993   else if(CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer())
1994     Msg << GetDescription(DEFINITE) << ' ' << ThirdPersonHitVerb << ' '
1995         << Enemy->GetDescription(DEFINITE) + BodyPartDescription << '!';
1996   else
1997     return;
1998 
1999   ADD_MESSAGE("%s", Msg.CStr());
2000 }
2001 
2002 cchar*const HitVerb[] = { "strike", "slash", "stab" };
2003 cchar*const HitVerb3rdPersonEnd[] = { "s", "es", "s" };
2004 
AddWeaponHitMessage(ccharacter * Enemy,citem * Weapon,int BodyPart,truth Critical) const2005 void character::AddWeaponHitMessage(ccharacter* Enemy, citem* Weapon, int BodyPart, truth Critical) const
2006 {
2007   festring Msg;
2008   festring BodyPartDescription;
2009 
2010   if(BodyPart && (Enemy->CanBeSeenByPlayer() || Enemy->IsPlayer()))
2011     BodyPartDescription << " in the " << Enemy->GetBodyPartName(BodyPart);
2012 
2013   int FittingTypes = 0;
2014   int DamageFlags = Weapon->GetDamageFlags();
2015   int DamageType = 0;
2016 
2017   for(int c = 0; c < DAMAGE_TYPES; ++c)
2018     if(1 << c & DamageFlags)
2019     {
2020       if(!FittingTypes || !RAND_N(FittingTypes + 1))
2021         DamageType = c;
2022 
2023       ++FittingTypes;
2024     }
2025 
2026   if(!FittingTypes)
2027     ABORT("No damage flags specified for %s!", Weapon->CHAR_NAME(UNARTICLED));
2028 
2029   festring NewHitVerb = Critical ? " critically " : " ";
2030   NewHitVerb << HitVerb[DamageType];
2031   cchar*const E = HitVerb3rdPersonEnd[DamageType];
2032 
2033   if(Enemy->IsPlayer())
2034   {
2035     Msg << GetDescription(DEFINITE) << NewHitVerb << E << " you" << BodyPartDescription;
2036 
2037     if(CanBeSeenByPlayer())
2038       Msg << " with " << GetPossessivePronoun() << ' ' << Weapon->GetName(UNARTICLED);
2039 
2040     Msg << '!';
2041   }
2042   else if(IsPlayer())
2043     Msg << "You" << NewHitVerb << ' ' << Enemy->GetDescription(DEFINITE) << BodyPartDescription << '!';
2044   else if(CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer())
2045   {
2046     Msg << GetDescription(DEFINITE) << NewHitVerb << E << ' '
2047         << Enemy->GetDescription(DEFINITE) << BodyPartDescription;
2048 
2049     if(CanBeSeenByPlayer())
2050       Msg << " with " << GetPossessivePronoun() << ' ' << Weapon->GetName(UNARTICLED);
2051 
2052     Msg << '!';
2053   }
2054   else
2055     return;
2056 
2057   ADD_MESSAGE("%s", Msg.CStr());
2058 }
2059 
HasHeadOfElpuri() const2060 truth character::HasHeadOfElpuri() const
2061 {
2062   for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
2063     if(i->IsHeadOfElpuri())
2064       return true;
2065 
2066   return combineequipmentpredicates()(this, &item::IsHeadOfElpuri, 1);
2067 }
2068 
HasPetrussNut() const2069 truth character::HasPetrussNut() const
2070 {
2071   for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
2072     if(i->IsPetrussNut())
2073       return true;
2074 
2075   return combineequipmentpredicates()(this, &item::IsPetrussNut, 1);
2076 }
2077 
HasGoldenEagleShirt() const2078 truth character::HasGoldenEagleShirt() const
2079 {
2080   for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
2081     if(i->IsGoldenEagleShirt())
2082       return true;
2083 
2084   return combineequipmentpredicates()(this, &item::IsGoldenEagleShirt, 1);
2085 }
2086 
HasEncryptedScroll() const2087 truth character::HasEncryptedScroll() const
2088 {
2089   for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
2090     if(i->IsEncryptedScroll())
2091       return true;
2092 
2093   return combineequipmentpredicates()(this, &item::IsEncryptedScroll, 1);
2094 }
2095 
HasShadowVeil() const2096 truth character::HasShadowVeil() const
2097 {
2098   for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
2099     if(i->IsShadowVeil())
2100       return true;
2101 
2102   return combineequipmentpredicates()(this, &item::IsShadowVeil, 1);
2103 }
2104 
HasLostRubyFlamingSword() const2105 truth character::HasLostRubyFlamingSword() const
2106 {
2107   for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
2108     if(i->IsLostRubyFlamingSword())
2109       return true;
2110 
2111   return combineequipmentpredicates()(this, &item::IsLostRubyFlamingSword, 1);
2112 }
2113 
RemoveEncryptedScroll()2114 truth character::RemoveEncryptedScroll()
2115 {
2116   for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
2117     if(i->IsEncryptedScroll())
2118     {
2119       item* Item = *i;
2120       Item->RemoveFromSlot();
2121       Item->SendToHell();
2122       return true;
2123     }
2124 
2125   for(int c = 0; c < GetEquipments(); ++c)
2126   {
2127     item* Item = GetEquipment(c);
2128 
2129     if(Item && Item->IsEncryptedScroll())
2130     {
2131       Item->RemoveFromSlot();
2132       Item->SendToHell();
2133       return true;
2134     }
2135   }
2136 
2137   return false;
2138 }
2139 
RemoveShadowVeil(character * ToWhom)2140 truth character::RemoveShadowVeil(character* ToWhom)
2141 {
2142   for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
2143     if(i->IsShadowVeil())
2144     {
2145       item* Item = *i;
2146       Item->RemoveFromSlot();
2147       ToWhom->ReceiveItemAsPresent(Item);
2148       return true;
2149     }
2150 
2151   for(int c = 0; c < GetEquipments(); ++c)
2152   {
2153     item* Item = GetEquipment(c);
2154 
2155     if(Item && Item->IsShadowVeil())
2156     {
2157       Item->RemoveFromSlot();
2158       ToWhom->ReceiveItemAsPresent(Item);
2159       return true;
2160     }
2161   }
2162 
2163   return false;
2164 }
2165 
HasNuke() const2166 truth character::HasNuke() const
2167 {
2168   for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
2169     if(i->IsNuke())
2170       return true;
2171 
2172   return combineequipmentpredicates()(this, &item::IsNuke, 1);
2173 }
2174 
RemoveNuke(character * ToWhom)2175 truth character::RemoveNuke(character* ToWhom)
2176 {
2177   for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
2178     if(i->IsNuke())
2179     {
2180       item* Item = *i;
2181       Item->RemoveFromSlot();
2182       ToWhom->ReceiveItemAsPresent(Item);
2183       return true;
2184     }
2185 
2186   for(int c = 0; c < GetEquipments(); ++c)
2187   {
2188     item* Item = GetEquipment(c);
2189 
2190     if(Item && Item->IsNuke())
2191     {
2192       Item->RemoveFromSlot();
2193       ToWhom->ReceiveItemAsPresent(Item);
2194       return true;
2195     }
2196   }
2197 
2198   return false;
2199 }
2200 
HasWeepObsidian() const2201 truth character::HasWeepObsidian() const
2202 {
2203   for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
2204     if(i->IsWeepObsidian())
2205       return true;
2206 
2207   return combineequipmentpredicates()(this, &item::IsWeepObsidian, 1);
2208 }
2209 
RemoveWeepObsidian(character * ToWhom)2210 truth character::RemoveWeepObsidian(character* ToWhom)
2211 {
2212   for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
2213     if(i->IsWeepObsidian())
2214     {
2215       item* Item = *i;
2216       Item->RemoveFromSlot();
2217       ToWhom->ReceiveItemAsPresent(Item);
2218       return true;
2219     }
2220 
2221   for(int c = 0; c < GetEquipments(); ++c)
2222   {
2223     item* Item = GetEquipment(c);
2224 
2225     if(Item && Item->IsWeepObsidian())
2226     {
2227       Item->RemoveFromSlot();
2228       ToWhom->ReceiveItemAsPresent(Item);
2229       return true;
2230     }
2231   }
2232 
2233   return false;
2234 }
2235 
HasMuramasa() const2236 truth character::HasMuramasa() const
2237 {
2238   for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
2239     if(i->IsMuramasa())
2240       return true;
2241 
2242   return combineequipmentpredicates()(this, &item::IsMuramasa, 1);
2243 }
2244 
RemoveMuramasa(character * ToWhom)2245 truth character::RemoveMuramasa(character* ToWhom)
2246 {
2247   for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
2248     if(i->IsMuramasa())
2249     {
2250       item* Item = *i;
2251       Item->RemoveFromSlot();
2252       ToWhom->ReceiveItemAsPresent(Item);
2253       return true;
2254     }
2255 
2256   for(int c = 0; c < GetEquipments(); ++c)
2257   {
2258     item* Item = GetEquipment(c);
2259 
2260     if(Item && Item->IsMuramasa())
2261     {
2262       Item->RemoveFromSlot();
2263       ToWhom->ReceiveItemAsPresent(Item);
2264       return true;
2265     }
2266   }
2267 
2268   return false;
2269 }
2270 
HasMasamune() const2271 truth character::HasMasamune() const
2272 {
2273   for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
2274     if(i->IsMasamune())
2275       return true;
2276 
2277   return combineequipmentpredicates()(this, &item::IsMasamune, 1);
2278 }
2279 
RemoveMasamune(character * ToWhom)2280 truth character::RemoveMasamune(character* ToWhom)
2281 {
2282   for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
2283     if(i->IsMasamune())
2284     {
2285       item* Item = *i;
2286       Item->RemoveFromSlot();
2287       ToWhom->ReceiveItemAsPresent(Item);
2288       return true;
2289     }
2290 
2291   for(int c = 0; c < GetEquipments(); ++c)
2292   {
2293     item* Item = GetEquipment(c);
2294 
2295     if(Item && Item->IsMasamune())
2296     {
2297       Item->RemoveFromSlot();
2298       ToWhom->ReceiveItemAsPresent(Item);
2299       return true;
2300     }
2301   }
2302 
2303   return false;
2304 }
2305 
ReadItem(item * ToBeRead)2306 truth character::ReadItem(item* ToBeRead)
2307 {
2308   if(ToBeRead->CanBeRead(this))
2309   {
2310     int LocalReadDifficulty = ToBeRead->GetBurnLevel() * sqrt(abs(ToBeRead->GetReadDifficulty())) / 2;
2311     if(GetAttribute(INTELLIGENCE) < LocalReadDifficulty)
2312     {
2313       ADD_MESSAGE("%s is completely unreadable.", ToBeRead->CHAR_NAME(DEFINITE));
2314       return false;
2315     }
2316 
2317     if(!GetLSquareUnder()->IsDark() || game::GetSeeWholeMapCheatMode())
2318     {
2319       if(StateIsActivated(CONFUSED) && !(RAND() & 7))
2320       {
2321         if(!ToBeRead->IsDestroyable(this))
2322           ADD_MESSAGE("You read some words of %s and understand exactly nothing.", ToBeRead->CHAR_NAME(DEFINITE));
2323         else
2324         {
2325           ADD_MESSAGE("%s is very confusing. Or perhaps you are just too confused?", ToBeRead->CHAR_NAME(DEFINITE));
2326           ActivateRandomState(SRC_CONFUSE_READ, 1000 + RAND() % 1500);
2327           ToBeRead->RemoveFromSlot();
2328           ToBeRead->SendToHell();
2329         }
2330 
2331         EditAP(-1000);
2332         return true;
2333       }
2334 
2335       if(ToBeRead->Read(this))
2336       {
2337         if(!game::WizardModeIsActive())
2338         {
2339           /* This AP is used to take the stuff out of backpack */
2340           DexterityAction(5);
2341         }
2342 
2343         return true;
2344       }
2345       else
2346         return false;
2347     }
2348     else
2349     {
2350       if(IsPlayer())
2351         ADD_MESSAGE("It's too dark here to read.");
2352 
2353       return false;
2354     }
2355   }
2356   else
2357   {
2358     if(IsPlayer())
2359       ADD_MESSAGE("You can't read this.");
2360 
2361     return false;
2362   }
2363 }
2364 
CalculateBurdenState()2365 void character::CalculateBurdenState()
2366 {
2367   int OldBurdenState = BurdenState;
2368   long SumOfMasses = GetCarriedWeight();
2369   long CarryingStrengthUnits = long(GetCarryingStrength()) * 2500;
2370 
2371   if(SumOfMasses > (CarryingStrengthUnits << 1) + CarryingStrengthUnits)
2372     BurdenState = OVER_LOADED;
2373   else if(SumOfMasses > CarryingStrengthUnits << 1)
2374     BurdenState = STRESSED;
2375   else if(SumOfMasses > CarryingStrengthUnits)
2376     BurdenState = BURDENED;
2377   else
2378     BurdenState = UNBURDENED;
2379 
2380   if(!IsInitializing() && BurdenState != OldBurdenState)
2381     CalculateBattleInfo();
2382 }
2383 
Save(outputfile & SaveFile) const2384 void character::Save(outputfile& SaveFile) const
2385 {
2386   SaveFile << static_cast<ushort>(GetType());
2387   Stack->Save(SaveFile);
2388   SaveFile << ID;
2389   int c;
2390 
2391   for(c = 0; c < BASE_ATTRIBUTES; ++c)
2392     SaveFile << BaseExperience[c];
2393 
2394   SaveFile << ExpModifierMap;
2395   SaveFile << NP << AP << Stamina << GenerationDanger << ScienceTalks
2396            << CounterToMindWormHatch;
2397   SaveFile << TemporaryState << EquipmentState << Money << MyVomitMaterial << GoingTo << RegenerationCounter << Route << Illegal;
2398   SaveFile.Put(!!IsEnabled());
2399   SaveFile << HomeData << BlocksSinceLastTurn << CommandFlags;
2400   SaveFile << WarnFlags << static_cast<ushort>(Flags);
2401 
2402   for(c = 0; c < BodyParts; ++c)
2403     SaveFile << BodyPartSlot[c] << OriginalBodyPartID[c];
2404 
2405   SaveLinkedList(SaveFile, TrapData);
2406   SaveFile << Action; DBG1(Action);
2407 
2408   for(c = 0; c < STATES; ++c)
2409     SaveFile << TemporaryStateCounter[c];
2410 
2411   if(GetTeam())
2412   {
2413     SaveFile.Put(true);
2414     SaveFile << Team->GetID();
2415   }
2416   else
2417     SaveFile.Put(false);
2418 
2419   if(GetTeam() && GetTeam()->GetLeader() == this)
2420     SaveFile.Put(true);
2421   else
2422     SaveFile.Put(false);
2423 
2424   SaveFile << AssignedName << PolymorphBackup;
2425 
2426   for(c = 0; c < AllowedWeaponSkillCategories; ++c)
2427     SaveFile << CWeaponSkill[c];
2428 
2429   SaveFile << static_cast<ushort>(GetConfig());
2430 
2431   for(c = 0; c < MAX_EQUIPMENT_SLOTS; c++)
2432     SaveFile << MemorizedEquippedItemIDs[c];
2433 }
2434 
Load(inputfile & SaveFile)2435 void character::Load(inputfile& SaveFile)
2436 {
2437   LoadSquaresUnder();
2438   int c;
2439   Stack->Load(SaveFile);
2440   SaveFile >> ID;
2441   game::AddCharacterID(this, ID);
2442 
2443   for(c = 0; c < BASE_ATTRIBUTES; ++c)
2444     SaveFile >> BaseExperience[c];
2445 
2446   SaveFile >> ExpModifierMap;
2447   SaveFile >> NP >> AP >> Stamina >> GenerationDanger >> ScienceTalks
2448            >> CounterToMindWormHatch;
2449   SaveFile >> TemporaryState >> EquipmentState >> Money >> MyVomitMaterial >> GoingTo >> RegenerationCounter >> Route >> Illegal;
2450 
2451   if(!SaveFile.Get())
2452     Disable();
2453 
2454   SaveFile >> HomeData >> BlocksSinceLastTurn >> CommandFlags;
2455   SaveFile >> WarnFlags;
2456   WarnFlags &= ~WARNED;
2457   Flags |= ReadType<ushort>(SaveFile) & ~ENTITY_FLAGS;
2458 
2459   for(c = 0; c < BodyParts; ++c)
2460   {
2461     SaveFile >> BodyPartSlot[c] >> OriginalBodyPartID[c];
2462 
2463     item* BodyPart = *BodyPartSlot[c];
2464 
2465     if(BodyPart)
2466       BodyPart->Disable();
2467   }
2468 
2469   LoadLinkedList(SaveFile, TrapData);
2470   SaveFile >> Action;
2471 
2472   if(Action)
2473     Action->SetActor(this);
2474 
2475   for(c = 0; c < STATES; ++c)
2476     SaveFile >> TemporaryStateCounter[c];
2477 
2478   if(SaveFile.Get())
2479     SetTeam(game::GetTeam(ReadType<ulong>(SaveFile)));
2480 
2481   if(SaveFile.Get())
2482     GetTeam()->SetLeader(this);
2483 
2484   SaveFile >> AssignedName >> PolymorphBackup;
2485 
2486   for(c = 0; c < AllowedWeaponSkillCategories; ++c)
2487     SaveFile >> CWeaponSkill[c];
2488 
2489   databasecreator<character>::InstallDataBase(this, ReadType<ushort>(SaveFile));
2490 
2491   if(game::GetCurrentSavefileVersion()>=132){
2492     for(c = 0; c < MAX_EQUIPMENT_SLOTS; c++)
2493       SaveFile >> MemorizedEquippedItemIDs[c];
2494   }
2495 
2496   /////////////// loading ended /////////////////////////
2497 
2498   if(IsEnabled() && !game::IsInWilderness())
2499     for(c = 1; c < GetSquaresUnder(); ++c)
2500       GetSquareUnder(c)->SetCharacter(this);
2501 }
2502 
Engrave(cfestring & What)2503 truth character::Engrave(cfestring& What)
2504 {
2505   GetLSquareUnder()->Engrave(What);
2506   return true;
2507 }
2508 
MoveRandomly()2509 truth character::MoveRandomly()
2510 {
2511   if(!IsEnabled())
2512     return false;
2513 
2514   double DirChange = femath::NormalDistributedRand(0.03);
2515   RandomMoveDir += DirChange;
2516   WrapFRef(RandomMoveDir, 0., 8.);
2517 
2518   int Unit = Sign(DirChange);
2519   if(!Unit) Unit = RAND_2 ? 1 : -1;
2520 
2521   int Aim;
2522   if(femath::RandReal(1) < RandomMoveDir - floor(RandomMoveDir))
2523     Aim = floor(RandomMoveDir);
2524   else
2525     Aim = Wrap(static_cast<int>(ceil(RandomMoveDir)), 0, 8);
2526 
2527   for(int Delta = 0; abs(Delta) <= 4;)
2528   {
2529     int Dir = Wrap(Aim + Delta, 0, 8);
2530     v2 ToTry = game::GetClockwiseMoveVector(Dir);
2531 
2532     if(GetLevel()->IsValidPos(GetPos() + ToTry))
2533     {
2534       lsquare* Square = GetNearLSquare(GetPos() + ToTry);
2535 
2536       if(!Square->IsDangerous(this)
2537          && !Square->IsScary(this)
2538          && TryMove(ToTry, false, false))
2539       {
2540         if(Delta)
2541           RandomMoveDir = Dir;
2542 
2543         return true;
2544       }
2545     }
2546 
2547     Delta = -Delta;
2548 
2549     if(!Sign(Delta) || Sign(Delta) == Unit)
2550       Delta += Unit;
2551   }
2552 
2553   RandomMoveDir = femath::RandReal(8);
2554   return false;
2555 }
2556 
TestForPickup(item * ToBeTested) const2557 truth character::TestForPickup(item* ToBeTested) const
2558 {
2559   if(MakesBurdened(ToBeTested->GetWeight() + GetCarriedWeight()))
2560     return false;
2561 
2562   return true;
2563 }
2564 
AddScoreEntry(cfestring & Description,double Multiplier,truth AddEndLevel) const2565 void character::AddScoreEntry(cfestring& Description, double Multiplier, truth AddEndLevel) const
2566 {
2567   if(!game::WizardModeIsReallyActive())
2568   {
2569     highscore HScore(GetUserDataDir() + HIGH_SCORE_FILENAME);
2570 
2571     if(!HScore.CheckVersion())
2572     {
2573       if(game::Menu(std::vector<bitmap*>(), v2(RES.X >> 1, RES.Y >> 1),
2574                     CONST_S("The highscore version doesn't match.\rDo you want to erase "
2575                             "previous records and start a new file?\rNote, if you answer "
2576                             "no, the score of your current game will be lost!\r"),
2577                     CONST_S("Yes\rNo\r"), LIGHT_GRAY))
2578         return;
2579 
2580       HScore.Clear();
2581     }
2582 
2583     festring Desc = game::GetPlayerName();
2584     Desc << ", " << Description;
2585 
2586     if(AddEndLevel)
2587     {
2588       if(game::IsInWilderness())
2589         Desc << " in the wilderness";
2590       else
2591         Desc << " in " << game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex());
2592     }
2593 
2594     HScore.Add(long(game::GetScore() * Multiplier), Desc);
2595     HScore.Save();
2596   }
2597 }
2598 
CheckDeath(cfestring & Msg,ccharacter * Murderer,ulong DeathFlags)2599 truth character::CheckDeath(cfestring& Msg, ccharacter* Murderer, ulong DeathFlags)
2600 {
2601   if(!IsEnabled())
2602     return true;
2603 
2604   if(game::IsSumoWrestling() && IsDead())
2605   {
2606     game::EndSumoWrestling(!!IsPlayer());
2607     return true;
2608   }
2609 
2610   if(DeathFlags & FORCE_DEATH || IsDead())
2611   {
2612     if(Murderer && Murderer->IsPlayer() && GetTeam()->GetKillEvilness())
2613       game::DoEvilDeed(GetTeam()->GetKillEvilness());
2614 
2615     festring SpecifierMsg;
2616     int SpecifierParts = 0;
2617 
2618     if(GetPolymorphBackup())
2619     {
2620       SpecifierMsg << " polymorphed into ";
2621       id::AddName(SpecifierMsg, INDEFINITE);
2622       ++SpecifierParts;
2623     }
2624 
2625     if(!(DeathFlags & IGNORE_TRAPS) && IsStuck())
2626     {
2627       if(SpecifierParts++)
2628         SpecifierMsg << " and";
2629 
2630       SpecifierMsg << " caught in " << GetTrapDescription();
2631     }
2632 
2633     if(GetAction()
2634        && !(DeathFlags & IGNORE_UNCONSCIOUSNESS
2635             && GetAction()->IsUnconsciousness()))
2636     {
2637       festring ActionMsg = GetAction()->GetDeathExplanation();
2638 
2639       if(!ActionMsg.IsEmpty())
2640       {
2641         if(SpecifierParts > 1)
2642           SpecifierMsg = ActionMsg << ',' << SpecifierMsg;
2643         else
2644         {
2645           if(SpecifierParts)
2646             SpecifierMsg << " and";
2647 
2648           SpecifierMsg << ActionMsg;
2649         }
2650 
2651         ++SpecifierParts;
2652       }
2653     }
2654 
2655     festring NewMsg = Msg;
2656 
2657     if(Murderer == this)
2658     {
2659       SEARCH_N_REPLACE(NewMsg, "@bkp", CONST_S("by ") + GetPossessivePronoun(false) + " own");
2660       SEARCH_N_REPLACE(NewMsg, "@bk", CONST_S("by ") + GetObjectPronoun(false) + "self");
2661       SEARCH_N_REPLACE(NewMsg, "@k", GetObjectPronoun(false) + "self");
2662     }
2663     else
2664     {
2665       SEARCH_N_REPLACE(NewMsg, "@bkp", CONST_S("by ") + Murderer->GetName(INDEFINITE) + "'s");
2666       SEARCH_N_REPLACE(NewMsg, "@bk", CONST_S("by ") + Murderer->GetName(INDEFINITE));
2667       SEARCH_N_REPLACE(NewMsg, "@k", CONST_S("by ") + Murderer->GetName(INDEFINITE));
2668     }
2669 
2670     if(SpecifierParts)
2671       NewMsg << " while" << SpecifierMsg;
2672 
2673     if(IsPlayer() && game::WizardModeIsActive())
2674       ADD_MESSAGE("Death message: %s. Score: %ld.", NewMsg.CStr(), game::GetScore());
2675 
2676     Die(Murderer, NewMsg, DeathFlags);
2677     return true;
2678   }
2679   else
2680     return false;
2681 }
2682 
CheckStarvationDeath(cfestring & Msg)2683 truth character::CheckStarvationDeath(cfestring& Msg)
2684 {
2685   if(GetNP() < 1 && UsesNutrition() && !(StateIsActivated(FASTING)))
2686     return CheckDeath(Msg, 0, FORCE_DEATH);
2687   else
2688     return false;
2689 }
2690 
ThrowItem(int Direction,item * ToBeThrown)2691 void character::ThrowItem(int Direction, item* ToBeThrown)
2692 {
2693   if(Direction > 7)
2694     ABORT("Throw in TOO odd direction...");
2695 
2696   ToBeThrown->Fly(this, Direction, GetAttribute(ARM_STRENGTH),
2697     ToBeThrown->IsWeapon(this) && !ToBeThrown->IsBroken());
2698 }
2699 
HasBeenHitByItem(character * Thrower,item * Thingy,int Damage,double ToHitValue,int Direction)2700 void character::HasBeenHitByItem(character* Thrower, item* Thingy, int Damage, double ToHitValue, int Direction)
2701 {
2702   if(IsPlayer())
2703     ADD_MESSAGE("%s hits you.", Thingy->CHAR_NAME(DEFINITE));
2704   else if(CanBeSeenByPlayer())
2705     ADD_MESSAGE("%s hits %s.", Thingy->CHAR_NAME(DEFINITE), CHAR_NAME(DEFINITE));
2706 
2707   int BodyPart = ChooseBodyPartToReceiveHit(ToHitValue, DodgeValue);
2708   int WeaponSkillHits = Thrower ? CalculateWeaponSkillHits(Thrower) : 0;
2709   int DoneDamage = ReceiveBodyPartDamage(Thrower, Damage, PHYSICAL_DAMAGE, BodyPart, Direction);
2710   truth Succeeded = (GetBodyPart(BodyPart) && HitEffect(Thrower, Thingy, Thingy->GetPos(), THROW_ATTACK,
2711                                                         BodyPart, Direction, !DoneDamage, false, DoneDamage)) || DoneDamage;
2712 
2713   if(Succeeded && Thrower)
2714     Thrower->WeaponSkillHit(Thingy, THROW_ATTACK, WeaponSkillHits);
2715 
2716   festring DeathMsg = CONST_S("killed by a flying ") + Thingy->GetName(UNARTICLED);
2717 
2718   if(CheckDeath(DeathMsg, Thrower))
2719     return;
2720 
2721   if(Thrower)
2722   {
2723     if(Thrower->CanBeSeenByPlayer())
2724       DeActivateVoluntaryAction(CONST_S("The attack of ") + Thrower->GetName(DEFINITE) + CONST_S(" interrupts you."));
2725     else
2726       DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
2727   }
2728   else
2729     DeActivateVoluntaryAction(CONST_S("The hit interrupts you."));
2730 }
2731 
DodgesFlyingItem(item * Item,double ToHitValue)2732 truth character::DodgesFlyingItem(item* Item, double ToHitValue)
2733 {
2734   return !Item->EffectIsGood() && RAND() % int(100 + ToHitValue / DodgeValue * 100) < 100;
2735 }
2736 
2737 character* AutoPlayLastChar=NULL;
2738 const int iMaxWanderTurns=20;
2739 const int iMinWanderTurns=3;
2740 
2741 /**
2742  * 5 seems good, broken cheap weapons, stones, very cheap weapons non broken etc
2743  * btw, lantern price is currently 10.
2744  */
2745 static int iMaxValueless = 5;
2746 
2747 v2 v2KeepGoingTo=v2(0,0);
2748 v2 v2TravelingToAnotherDungeon=v2(0,0);
2749 int iWanderTurns=iMinWanderTurns;
2750 bool bAutoPlayUseRandomNavTargetOnce=false;
2751 std::vector<v2> vv2DebugDrawSqrPrevious;
2752 v2 v2LastDropPlayerWasAt=v2(0,0);
2753 std::vector<v2> vv2FailTravelToTargets;
2754 std::vector<v2> vv2WrongGoingTo;
2755 
AutoPlayAIReset(bool bFailedToo)2756 void character::AutoPlayAIReset(bool bFailedToo)
2757 { DBG7(bFailedToo,iWanderTurns,DBGAV2(v2KeepGoingTo),DBGAV2(v2TravelingToAnotherDungeon),DBGAV2(v2LastDropPlayerWasAt),vv2FailTravelToTargets.size(),vv2DebugDrawSqrPrevious.size());
2758   v2KeepGoingTo=v2(0,0); //will retry
2759   v2TravelingToAnotherDungeon=v2(0,0);
2760   iWanderTurns=0; // warning: this other code was messing the logic ---> if(iWanderTurns<iMinWanderTurns)iWanderTurns=iMinWanderTurns; //to wander just a bit looking for random spot from where Route may work
2761   bAutoPlayUseRandomNavTargetOnce=false;
2762   v2LastDropPlayerWasAt=v2(0,0);
2763   vv2DebugDrawSqrPrevious.clear();
2764 
2765   PLAYER->TerminateGoingTo();
2766 
2767   if(bFailedToo){
2768     vv2FailTravelToTargets.clear();
2769     vv2WrongGoingTo.clear();
2770   }
2771 }
AutoPlayAISetAndValidateKeepGoingTo(v2 v2KGTo)2772 truth character::AutoPlayAISetAndValidateKeepGoingTo(v2 v2KGTo)
2773 {
2774   v2KeepGoingTo=v2KGTo;
2775 
2776   bool bOk=true;
2777 
2778   if(bOk){
2779     lsquare* lsqr = game::GetCurrentLevel()->GetLSquare(v2KeepGoingTo);
2780     if(!CanTheoreticallyMoveOn(lsqr))
2781       bOk=false;
2782 //    olterrain* olt = game::GetCurrentLevel()->GetLSquare(v2KeepGoingTo)->GetOLTerrain();
2783 //    if(olt){
2784 //      if(bOk && !CanMoveOn(olt)){
2785 //        DBG4(DBGAV2(v2KeepGoingTo),"olterrain? fixing it...",olt->GetNameSingular().CStr(),PLAYER->GetPanelName().CStr());
2786 //        bOk=false;
2787 //      }
2788 //
2789 //      /****
2790 //       * keep these commented for awhile, may be useful later
2791 //       *
2792 //      if(bOk && olt->IsWall()){ //TODO this may be unnecessary cuz  of above test
2793 //        //TODO is this a bug in the CanMoveOn() code? navigation AI is disabled when player is ghost TODO confirm about ethereal state, ammy of phasing
2794 //        DBG4(DBGAV2(v2KeepGoingTo),"walls? fixing it...",olt->GetNameSingular().CStr(),PLAYER->GetPanelName().CStr());
2795 //        bOk=false;
2796 //      }
2797 //
2798 //      if(bOk && (olt->GetWalkability() & ETHEREAL)){ //TODO this may be too much unnecessary test
2799 //        bOk=false;
2800 //      }
2801 //      */
2802 //    }
2803   }
2804 
2805   if(bOk){
2806     SetGoingTo(v2KeepGoingTo); DBG3(DBGAV2(GetPos()),DBGAV2(GoingTo),DBGAV2(v2KeepGoingTo));
2807     CreateRoute();
2808     if(Route.empty()){
2809       TerminateGoingTo(); //redundant?
2810       bOk=false;
2811     }
2812   }
2813 
2814   if(!bOk){
2815     DBG1("RouteCreationFailed");
2816     vv2FailTravelToTargets.push_back(v2KeepGoingTo); DBG3("BlockGoToDestination",DBGAV2(v2KeepGoingTo),vv2FailTravelToTargets.size());
2817     bAutoPlayUseRandomNavTargetOnce=true;
2818 
2819     AutoPlayAIReset(false); //v2KeepGoingTo is reset here too
2820   }
2821 
2822   return bOk;
2823 }
2824 
AutoPlayAIDebugDrawSquareRect(v2 v2SqrPos,col16 color,int iPrintIndex,bool bWide,bool bKeepColor)2825 void character::AutoPlayAIDebugDrawSquareRect(v2 v2SqrPos, col16 color, int iPrintIndex, bool bWide, bool bKeepColor)
2826 {
2827   static v2 v2ScrPos=v2(0,0); //static to avoid instancing
2828   static int iAddPos;iAddPos=bWide?2:1;
2829   static int iSubBorder;iSubBorder=bWide?3:2;
2830   if(game::OnScreen(v2SqrPos)){
2831     v2ScrPos=game::CalculateScreenCoordinates(v2SqrPos);
2832 
2833     DOUBLE_BUFFER->DrawRectangle(
2834         v2ScrPos.X+iAddPos, v2ScrPos.Y+iAddPos,
2835         v2ScrPos.X+TILE_SIZE-iSubBorder, v2ScrPos.Y+TILE_SIZE-iSubBorder,
2836         color, bWide);
2837 
2838     if(iPrintIndex>-1)
2839       FONT->Printf(DOUBLE_BUFFER, v2(v2ScrPos.X+1,v2ScrPos.Y+5), DARK_GRAY, "%d", iPrintIndex);
2840 
2841     if(!bKeepColor)
2842       vv2DebugDrawSqrPrevious.push_back(v2SqrPos);
2843   }
2844 }
2845 
2846 const int iVisitAgainMax=10;
2847 int iVisitAgainCount=iVisitAgainMax;
2848 std::vector<lsquare*> vv2AllDungeonSquares;
AutoPlayAICheckAreaLevelChangedAndReset()2849 bool character::AutoPlayAICheckAreaLevelChangedAndReset()
2850 {
2851   static area* areaPrevious=NULL;
2852   area* Area = game::GetCurrentArea();
2853   if(Area != areaPrevious){
2854     areaPrevious=Area;
2855 
2856     iVisitAgainCount=iVisitAgainMax;
2857 
2858     vv2DebugDrawSqrPrevious.clear();
2859 
2860     vv2AllDungeonSquares.clear();
2861     if(!game::IsInWilderness())
2862       for(int iY=0;iY<game::GetCurrentLevel()->GetYSize();iY++){ for(int iX=0;iX<game::GetCurrentLevel()->GetXSize();iX++){
2863         vv2AllDungeonSquares.push_back(game::GetCurrentLevel()->GetLSquare(iX, iY));
2864       }}
2865 
2866     return true;
2867   }
2868 
2869   return false;
2870 }
2871 
AutoPlayAIDebugDrawOverlay()2872 void character::AutoPlayAIDebugDrawOverlay()
2873 {
2874   if(!game::WizardModeIsActive())return;
2875 
2876   AutoPlayAICheckAreaLevelChangedAndReset();
2877 
2878   // redraw previous to clean them
2879   area* Area = game::GetCurrentArea(); //got the Area to draw in the wilderness too and TODO navigate there one day
2880   std::vector<v2> vv2DebugDrawSqrPreviousCopy(vv2DebugDrawSqrPrevious);
2881   for(int i=0;i<vv2DebugDrawSqrPreviousCopy.size();i++){
2882 //    Area->GetSquare(vv2DebugDrawSqrPrevious[i])->SendNewDrawRequest();
2883 //    square* sqr = Area->GetSquare(vv2DebugDrawSqrPrevious[i]);
2884 //    if(sqr)sqr->SendStrongNewDrawRequest(); //TODO sqr NULL?
2885     AutoPlayAIDebugDrawSquareRect(vv2DebugDrawSqrPreviousCopy[i],DARK_GRAY);
2886   }
2887 
2888   // draw new ones
2889   vv2DebugDrawSqrPrevious.clear(); //empty before fillup below
2890 
2891   for(int i=0;i<vv2FailTravelToTargets.size();i++)
2892     AutoPlayAIDebugDrawSquareRect(vv2FailTravelToTargets[i],RED,i==(vv2FailTravelToTargets.size()-1),i,true);
2893 
2894   if(!PLAYER->Route.empty())
2895     for(int i=0;i<PLAYER->Route.size();i++)
2896       AutoPlayAIDebugDrawSquareRect(PLAYER->Route[i],GREEN);
2897 
2898   if(!v2KeepGoingTo.Is0())
2899     AutoPlayAIDebugDrawSquareRect(v2KeepGoingTo,BLUE,PLAYER->Route.size(),true);
2900   else if(iWanderTurns>0)
2901     AutoPlayAIDebugDrawSquareRect(PLAYER->GetPos(),YELLOW,iWanderTurns);
2902 
2903   for(int i=0;i<vv2WrongGoingTo.size();i++)
2904     AutoPlayAIDebugDrawSquareRect(vv2WrongGoingTo[i],BLUE,i,false,true);
2905 }
2906 
AutoPlayAIDropThings()2907 truth character::AutoPlayAIDropThings()
2908 {
2909 //  level* lvl = game::GetCurrentLevel(); DBG1(lvl);
2910 //  area* Area = game::GetCurrentArea();
2911 
2912   /**
2913    *  unburden
2914    */
2915   bool bDropSomething = false;
2916   static item* eqDropChk=NULL;
2917   item* eqBroken=NULL;
2918   for(int i=0;i<GetEquipments();i++){
2919     eqDropChk=GetEquipment(i);
2920     if(eqDropChk!=NULL && eqDropChk->IsBroken()){ DBG2("chkDropBroken",eqDropChk);
2921       eqBroken=eqDropChk;
2922       bDropSomething=true;
2923       break;
2924     }
2925   }
2926 
2927   if(!bDropSomething && GetBurdenState() == STRESSED){
2928     if(clock()%100<5){ //5% chance to drop something weighty randomly every turn
2929       bDropSomething=true; DBGLN;
2930     }
2931   }
2932 
2933   if(!bDropSomething && GetBurdenState() == OVER_LOADED){
2934     bDropSomething=true;
2935   }
2936 
2937   if(bDropSomething){ DBG1("DropSomething");
2938     item* dropMe=NULL;
2939     if(eqBroken!=NULL)dropMe=eqBroken;
2940 
2941     item* heaviest=NULL;
2942     item* cheapest=NULL;
2943 
2944 //    bool bFound=false;
2945 //    for(int k=0;k<2;k++){
2946 //      if(dropMe!=NULL)break;
2947 //    static item* eqDropChk=NULL;
2948 //    for(int i=0;i<GetEquipments();i++){
2949 //      eqDropChk=GetEquipment(i);
2950 //      if(eqDropChk!=NULL && eqDropChk->IsBroken()){
2951 //        dropMe=eqDropChk;
2952 //        break;
2953 //      }
2954 //    }
2955 
2956     if(dropMe==NULL){
2957       static itemvector vit;vit.clear();GetStack()->FillItemVector(vit);
2958       for(int i=0;i<vit.size();i++){ DBG4("CurrentChkToDrop",vit[i]->GetName(DEFINITE).CStr(),vit[i]->GetTruePrice(),vit[i]->GetWeight());
2959         if(vit[i]->IsEncryptedScroll())continue;
2960 //        if(!bPlayerHasLantern && dynamic_cast<lantern*>(vit[i])!=NULL){
2961 //          bPlayerHasLantern=true; //will keep only the 1st lantern
2962 //          continue;
2963 //        }
2964 
2965         if(vit[i]->IsBroken()){ //TODO use repair scroll?
2966           dropMe=vit[i];
2967           break;
2968         }
2969 
2970         if(heaviest==NULL)heaviest=vit[i];
2971         if(cheapest==NULL)cheapest=vit[i];
2972 
2973 //        switch(k){
2974 //        case 0: //better not implement this as a user function as that will remove the doubt about items values what is another fun challenge :)
2975           if(vit[i]->GetTruePrice() < cheapest->GetTruePrice()) //cheapest
2976             cheapest=vit[i];
2977 //          break;
2978 //        case 1: //this could be added as user function to avoid browsing the drop list, but may not be that good...
2979           if(vit[i]->GetWeight() > heaviest->GetWeight()) //heaviest
2980             heaviest=vit[i];
2981 //          break;
2982 //        }
2983       }
2984     }
2985 
2986     if(heaviest!=NULL && cheapest!=NULL){
2987       if(dropMe==NULL && heaviest==cheapest)
2988         dropMe=heaviest;
2989 
2990       if(dropMe==NULL && cheapest->GetTruePrice()<=iMaxValueless){ DBG2("DropValueless",cheapest->GetName(DEFINITE).CStr());
2991         dropMe=cheapest;
2992       }
2993 
2994       if(dropMe==NULL){
2995         // the worst price VS weight will be dropped
2996         float fC = cheapest ->GetTruePrice()/(float)cheapest ->GetWeight();
2997         float fW = heaviest->GetTruePrice()/(float)heaviest->GetWeight(); DBG3("PriceVsWeightRatio",fC,fW);
2998         if(fC < fW){
2999           dropMe = cheapest;
3000         }else{
3001           dropMe = heaviest;
3002         }
3003       }
3004 
3005       if(dropMe==NULL)
3006         dropMe = clock()%2==0 ? heaviest : cheapest;
3007     }
3008 
3009     // chose a throw direction
3010     if(dropMe!=NULL){
3011       static std::vector<int> vv2DirBase;static bool bDummyInit = [](){for(int i=0;i<8;i++)vv2DirBase.push_back(i);return true;}();
3012       std::vector<int> vv2Dir(vv2DirBase);
3013       int iDirOk=-1;
3014       v2 v2DropAt(0,0);
3015       lsquare* lsqrDropAt=NULL;
3016       for(int i=0;i<8;i++){
3017         int k = clock()%vv2Dir.size(); //random chose from remaining TODO could be where there is NPC foe
3018         int iDir = vv2Dir[k]; //collect direction value
3019         vv2Dir.erase(vv2Dir.begin() + k); //remove using the chosen index to prepare next random choice
3020 
3021         v2 v2Dir = game::GetMoveVector(iDir);
3022         v2 v2Chk = GetPos() + v2Dir;
3023         if(game::GetCurrentLevel()->IsValidPos(v2Chk)){
3024           lsquare* lsqrChk=game::GetCurrentLevel()->GetLSquare(v2Chk);
3025           if(lsqrChk->IsFlyable()){
3026             iDirOk = iDir;
3027             v2DropAt = v2Chk;
3028             lsqrDropAt=lsqrChk;
3029             break;
3030           }
3031         }
3032       };DBGLN;
3033 
3034       if(iDirOk==-1){iDirOk=clock()%8;DBG2("RandomDir",iDirOk);}DBGLN; //TODO should just drop may be? unless hitting w/e is there could help
3035 
3036       if(iDirOk>-1){DBG2("KickOrThrow",iDirOk);
3037         static itemcontainer* itc;itc = dynamic_cast<itemcontainer*>(dropMe);DBGLN;
3038         static humanoid* h;h = dynamic_cast<humanoid*>(this);DBGLN;
3039         DBG8("CanKickLockedChest",lsqrDropAt,itc,itc?itc->IsLocked():-1,CanKick(),h,h?h->GetLeftLeg():0,h?h->GetRightLeg():0);
3040         if(lsqrDropAt && itc && itc->IsLocked() && CanKick() && h && h->GetLeftLeg() && h->GetRightLeg()){DBGLN;
3041           dropMe->MoveTo(lsqrDropAt->GetStack());DBGLN; //drop in front..
3042           Kick(lsqrDropAt,iDirOk,true);DBGLN; // ..to kick it
3043         }else{DBGLN;
3044           ThrowItem(iDirOk, dropMe); DBG5("DropThrow",iDirOk,dropMe->GetName(DEFINITE).CStr(),dropMe->GetTruePrice(),dropMe->GetWeight());
3045         }
3046       }else{DBGLN;
3047         dropMe->MoveTo(GetLSquareUnder()->GetStack());DBGLN; //just drop
3048       }
3049 
3050       v2LastDropPlayerWasAt=GetPos();DBGSV2(v2LastDropPlayerWasAt);
3051 
3052       return true;
3053     }
3054 
3055     DBG1("AutoPlayNeedsImprovement:DropItem");
3056     ADD_MESSAGE("%s says \"I need more intelligence to drop trash...\"", CHAR_NAME(DEFINITE)); // improve the dropping AI
3057     //TODO stop autoplay mode? if not, something random may happen some time and wont reach here ex.: spoil, fire, etc..
3058   }
3059 
3060   return false;
3061 }
3062 
IsAutoplayAICanPickup(item * it,bool bPlayerHasLantern)3063 bool character::IsAutoplayAICanPickup(item* it,bool bPlayerHasLantern)
3064 {
3065   if(!it->CanBeSeenBy(this))return false;
3066   if(!it->IsPickable(this))return false;
3067   if(it->GetSquaresUnder()!=1)return false; //avoid big corpses 2x2
3068 
3069   if(!bPlayerHasLantern && it->IsOnFire(this)){
3070     //ok
3071   }else{
3072     if(it->IsBroken())return false;
3073     if(it->GetTruePrice()<=iMaxValueless)return false; //mainly to avoid all rocks from broken walls
3074     if(clock()%3!=0 && it->GetSpoilLevel()>0)return false; //some spoiled may be consumed to randomly test diseases flows
3075   }
3076 
3077   return true;
3078 }
3079 
AutoPlayAIEquipAndPickup(bool bPlayerHasLantern)3080 truth character::AutoPlayAIEquipAndPickup(bool bPlayerHasLantern)
3081 {
3082   static humanoid* h;h = dynamic_cast<humanoid*>(this);
3083   if(h==NULL)return false;
3084 
3085   if(h->AutoPlayAIequip())
3086     return true;
3087 
3088   if(GetBurdenState()!=OVER_LOADED){ DBG4(CommandFlags&DONT_CHANGE_EQUIPMENT,this,GetNameSingular().CStr(),GetSquareUnder());
3089     if(v2LastDropPlayerWasAt!=GetPos()){
3090       static bool bHoarder=true; //TODO wizard autoplay AI config exclusive felist
3091 
3092       if(CheckForUsefulItemsOnGround(false))
3093         if(!bHoarder)
3094           return true;
3095 
3096       //just pick up any useful stuff
3097       static itemvector vit;vit.clear();GetStackUnder()->FillItemVector(vit);
3098       for(uint c = 0; c < vit.size(); ++c){
3099         if(!IsAutoplayAICanPickup(vit[c],bPlayerHasLantern))continue;
3100 
3101         static itemcontainer* itc;itc = dynamic_cast<itemcontainer*>(vit[c]);
3102         if(itc && !itc->IsLocked()){ //get items from unlocked container
3103           static itemvector vitItc;vitItc.clear();itc->GetContained()->FillItemVector(vitItc);
3104           for(uint d = 0; d < vitItc.size(); ++d)
3105             vitItc[d]->MoveTo(itc->GetLSquareUnder()->GetStack());
3106           continue;
3107         }
3108 
3109         vit[c]->MoveTo(GetStack()); DBG2("pickup",vit[c]->GetNameSingular().CStr());
3110 //          if(GetBurdenState()==OVER_LOADED)ThrowItem(clock()%8,ItemVector[c]);
3111 //          return true;
3112         if(!bHoarder)
3113           return true;
3114       }
3115     }
3116   }
3117 
3118   return false;
3119 }
3120 
3121 static const int iMoreThanMaxDist=10000000; //TODO should be max integer but this will do for now in 2018 :)
AutoPlayAITestValidPathTo(v2 v2To)3122 truth character::AutoPlayAITestValidPathTo(v2 v2To)
3123 {
3124   return AutoPlayAIFindWalkDist(v2To) < iMoreThanMaxDist;
3125 }
3126 
AutoPlayAIFindWalkDist(v2 v2To)3127 int character::AutoPlayAIFindWalkDist(v2 v2To)
3128 {
3129   static bool bUseSimpleDirectDist=false; //very bad navigation this is
3130   if(bUseSimpleDirectDist)return (v2To - GetPos()).GetLengthSquare();
3131 
3132   static v2 GoingToBkp;GoingToBkp = GoingTo; //IsGoingSomeWhere() ? GoingTo : v2(0,0);
3133 
3134   SetGoingTo(v2To);
3135   CreateRoute();
3136   static int iDist;iDist=Route.size();
3137   TerminateGoingTo();
3138 
3139   if(GoingToBkp!=ERROR_V2){ DBG2("Warning:WrongUsage:ShouldBeGoingNoWhere",DBGAV2(GoingToBkp));
3140     SetGoingTo(GoingToBkp);
3141     CreateRoute();
3142   }
3143 
3144   return iDist>0?iDist:iMoreThanMaxDist;
3145 }
3146 
AutoPlayAINavigateDungeon(bool bPlayerHasLantern)3147 truth character::AutoPlayAINavigateDungeon(bool bPlayerHasLantern)
3148 {
3149   /**
3150    * navigate the unknown dungeon
3151    */
3152   std::vector<v2> v2Exits;
3153   if(v2KeepGoingTo.Is0()){ DBG1("TryNewMoveTarget");
3154     // target undiscovered squares to explore
3155     v2 v2PreferedTarget(0,0);
3156 
3157     int iNearestLanterOnFloorDist = iMoreThanMaxDist;
3158     v2 v2PreferedLanternOnFloorTarget(0,0);
3159 
3160     v2 v2NearestUndiscovered(0,0);
3161     int iNearestUndiscoveredDist=iMoreThanMaxDist;
3162     std::vector<v2> vv2UndiscoveredValidPathSquares;
3163 
3164     lsquare* lsqrNearestSquareWithWallLantern=NULL;
3165     lsquare* lsqrNearestDropWallLanternAt=NULL;
3166     stack* stkNearestDropWallLanternAt = NULL;
3167     int iNearestSquareWithWallLanternDist=iMoreThanMaxDist;
3168     item* itNearestWallLantern=NULL;
3169 
3170     /***************************************************************
3171      * scan whole dungeon squares
3172      */
3173     for(int iY=0;iY<game::GetCurrentLevel()->GetYSize();iY++){ for(int iX=0;iX<game::GetCurrentLevel()->GetXSize();iX++){
3174       lsquare* lsqr = game::GetCurrentLevel()->GetLSquare(iX,iY);
3175 
3176       olterrain* olt = lsqr->GetOLTerrain();
3177       if(olt && (olt->GetConfig() == STAIRS_UP || olt->GetConfig() == STAIRS_DOWN)){
3178         v2Exits.push_back(v2(lsqr->GetPos())); DBGSV2(v2Exits[v2Exits.size()-1]);
3179       }
3180 
3181       stack* stkSqr = lsqr->GetStack();
3182       static itemvector vit;vit.clear();stkSqr->FillItemVector(vit);
3183       bool bAddValidTargetSquare=true;
3184 
3185       // find nearest wall lantern
3186       if(!bPlayerHasLantern && olt && olt->IsWall()){
3187         for(int n=0;n<vit.size();n++){
3188           if(vit[n]->IsLanternOnWall() && !vit[n]->IsBroken()){
3189             static stack* stkDropWallLanternAt;stkDropWallLanternAt = lsqr->GetStackOfAdjacentSquare(vit[n]->GetSquarePosition());
3190             static lsquare* lsqrDropWallLanternAt;lsqrDropWallLanternAt =
3191               stkDropWallLanternAt?stkDropWallLanternAt->GetLSquareUnder():NULL;
3192 
3193             if(stkDropWallLanternAt && lsqrDropWallLanternAt && CanTheoreticallyMoveOn(lsqrDropWallLanternAt)){
3194               int iDist = AutoPlayAIFindWalkDist(lsqrDropWallLanternAt->GetPos()); //(lsqr->GetPos() - GetPos()).GetLengthSquare();
3195               if(lsqrNearestSquareWithWallLantern==NULL || iDist<iNearestSquareWithWallLanternDist){
3196                 iNearestSquareWithWallLanternDist=iDist;
3197 
3198                 lsqrNearestSquareWithWallLantern=lsqr;
3199                 itNearestWallLantern=vit[n]; DBG3(iNearestSquareWithWallLanternDist,DBGAV2(lsqr->GetPos()),DBGAV2(GetPos()));
3200                 lsqrNearestDropWallLanternAt=lsqrDropWallLanternAt;
3201                 stkNearestDropWallLanternAt=stkDropWallLanternAt;
3202               }
3203             }
3204 
3205             break;
3206           }
3207         }
3208       }
3209 
3210       if(bAddValidTargetSquare && !CanTheoreticallyMoveOn(lsqr))
3211         bAddValidTargetSquare=false;
3212 
3213       bool bIsFailToTravelSquare=false;
3214       if(bAddValidTargetSquare){
3215         for(int j=0;j<vv2FailTravelToTargets.size();j++)
3216           if(vv2FailTravelToTargets[j]==lsqr->GetPos()){
3217             bAddValidTargetSquare=false;
3218             bIsFailToTravelSquare=true;
3219             break;
3220           }
3221       }
3222 
3223       if(!bIsFailToTravelSquare){
3224 
3225 //          if(bAddValidTargetSquare && v2PreferedTarget.Is0() && (lsqr->HasBeenSeen() || !bPlayerHasLantern)){
3226         if(bAddValidTargetSquare && (lsqr->HasBeenSeen() || !bPlayerHasLantern)){
3227           bool bVisitAgain=false;
3228           if(iVisitAgainCount>0 || !bPlayerHasLantern){
3229             if(stkSqr!=NULL && stkSqr->GetItems()>0){
3230               for(int n=0;n<vit.size();n++){ DBG1(vit[n]);DBG1(vit[n]->GetID());DBG1(vit[n]->GetType());DBG3("VisitAgain:ChkItem",vit[n]->GetNameSingular().CStr(),vit.size());
3231                 if(vit[n]->IsBroken())continue; DBGLN;
3232 
3233                 static bool bIsLanternOnFloor;bIsLanternOnFloor = dynamic_cast<lantern*>(vit[n])!=NULL;// || vit[n]->IsOnFire(this); DBGLN;
3234 
3235                 if( // if is useful to the AutoPlay AI endless tests
3236                   vit[n]->IsShield  (this) ||
3237                   vit[n]->IsWeapon  (this) ||
3238                   vit[n]->IsArmor   (this) ||
3239                   vit[n]->IsAmulet  (this) ||
3240                   vit[n]->IsZappable(this) ||
3241                   vit[n]->IsRing    (this) ||
3242                   bIsLanternOnFloor
3243                 )
3244                   if(IsAutoplayAICanPickup(vit[n],bPlayerHasLantern))
3245                   {
3246                     bVisitAgain=true;
3247 
3248                     if(bIsLanternOnFloor && !bPlayerHasLantern){
3249                       static int iDist;iDist = AutoPlayAIFindWalkDist(lsqr->GetPos()); //(lsqr->GetPos() - GetPos()).GetLengthSquare();
3250                       if(iDist<iNearestLanterOnFloorDist){
3251                         iNearestLanterOnFloorDist=iDist;
3252                         v2PreferedLanternOnFloorTarget = lsqr->GetPos(); DBG2("PreferLanternAt",DBGAV2(lsqr->GetPos()))
3253                       }
3254                     }else{
3255                       iVisitAgainCount--;
3256                     }
3257 
3258                     DBG4(bVisitAgain,DBGAV2(lsqr->GetPos()),iVisitAgainCount,bIsLanternOnFloor);
3259                     break;
3260                   }
3261               }
3262             }
3263           }
3264 
3265           if(!bVisitAgain)bAddValidTargetSquare=false;
3266         }
3267 
3268       }
3269 
3270       if(bAddValidTargetSquare)
3271         if(!CanTheoreticallyMoveOn(lsqr)) //if(olt && !CanMoveOn(olt))
3272           bAddValidTargetSquare=false;
3273 
3274       if(bAddValidTargetSquare){ DBG2("addValidSqr",DBGAV2(lsqr->GetPos()));
3275         static int iDist;iDist=AutoPlayAIFindWalkDist(lsqr->GetPos()); //(lsqr->GetPos() - GetPos()).GetLengthSquare();
3276 
3277         if(iDist<iMoreThanMaxDist) //add only valid paths
3278           vv2UndiscoveredValidPathSquares.push_back(lsqr->GetPos());
3279 
3280         if(iDist<iNearestUndiscoveredDist){
3281           iNearestUndiscoveredDist=iDist;
3282           v2NearestUndiscovered=lsqr->GetPos(); DBG3(iNearestUndiscoveredDist,DBGAV2(lsqr->GetPos()),DBGAV2(GetPos()));
3283         }
3284       }
3285     }} DBG2(DBGAV2(v2PreferedTarget),vv2UndiscoveredValidPathSquares.size());
3286 
3287     /***************************************************************
3288      * define prefered navigation target
3289      */
3290     if(!bPlayerHasLantern && v2PreferedTarget.Is0()){
3291       bool bUseWallLantern=false;
3292       if(!v2PreferedLanternOnFloorTarget.Is0() && lsqrNearestSquareWithWallLantern!=NULL){
3293         if(iNearestLanterOnFloorDist <= iNearestSquareWithWallLanternDist){
3294           v2PreferedTarget=v2PreferedLanternOnFloorTarget;
3295         }else{
3296           bUseWallLantern=true;
3297         }
3298       }else if(!v2PreferedLanternOnFloorTarget.Is0()){
3299         v2PreferedTarget=v2PreferedLanternOnFloorTarget;
3300       }else if(lsqrNearestSquareWithWallLantern!=NULL){
3301         bUseWallLantern=true;
3302       }
3303 
3304       if(bUseWallLantern){
3305         /**
3306          * target to nearest wall lantern
3307          * check for lanterns on walls of adjacent squares if none found on floors
3308          */
3309         itNearestWallLantern->MoveTo(stkNearestDropWallLanternAt); // the AI is prepared to get things from the floor only so "magically" drop it :)
3310         v2PreferedTarget = lsqrNearestDropWallLanternAt->GetPos(); DBG2("PreferWallLanternAt",DBGAV2(lsqrNearestDropWallLanternAt->GetPos()))
3311       }
3312 
3313     }
3314 
3315     /***************************************************************
3316      * validate and set new navigation target
3317      */
3318 //    DBG9("AllNavigatePossibilities",DBGAV2(v2PreferedTarget),DBGAV2(v2PreferedLanternOnFloorTarget),DBGAV2(),DBGAV2(),DBGAV2(),DBGAV2(),DBGAV2(),DBGAV2(),DBGAV2(),DBGAV2());
3319     v2 v2NewKGTo=v2(0,0);
3320 
3321     if(v2NewKGTo.Is0()){
3322       //TODO if(!v2PreferedTarget.Is0){ // how can this not be compiled? error: cannot convert ‘v2::Is0’ from type ‘truth (v2::)() const {aka bool (v2::)() const}’ to type ‘bool’
3323       if(v2PreferedTarget.GetLengthSquare()>0)
3324         if(AutoPlayAITestValidPathTo(v2PreferedTarget))
3325           v2NewKGTo=v2PreferedTarget; DBGSV2(v2PreferedTarget);
3326     }
3327 
3328     if(v2NewKGTo.Is0()){
3329       if(bAutoPlayUseRandomNavTargetOnce){ //these targets were already path validated and are safe to use!
3330         v2NewKGTo=vv2UndiscoveredValidPathSquares[clock()%vv2UndiscoveredValidPathSquares.size()]; DBG2("RandomTarget",DBGAV2(v2NewKGTo));
3331         bAutoPlayUseRandomNavTargetOnce=false;
3332       }else{    //find nearest
3333         if(!v2NearestUndiscovered.Is0()){
3334           v2NewKGTo=v2NearestUndiscovered; DBGSV2(v2NearestUndiscovered);
3335         }
3336       }
3337     }
3338 
3339     if(v2NewKGTo.Is0()){ //no new destination: fully explored
3340       if(v2Exits.size()>0){
3341         if(game::GetCurrentDungeonTurnsCount()==0){ DBG1("Dungeon:FullyExplored:FirstTurn");
3342           iWanderTurns=100+clock()%300; DBG2("WanderALotOnFullyExploredLevel",iWanderTurns); //just move around a lot, some NPC may spawn
3343         }else{
3344           // travel between dungeons if current fully explored
3345           v2 v2Try = v2Exits[clock()%v2Exits.size()];
3346           if(AutoPlayAITestValidPathTo(v2Try))
3347             v2NewKGTo = v2TravelingToAnotherDungeon = v2Try; DBGSV2(v2TravelingToAnotherDungeon);
3348         }
3349       }else{
3350         DBG1("AutoPlayNeedsImprovement:Navigation")
3351         ADD_MESSAGE("%s says \"I need more intelligence to move around...\"", CHAR_NAME(DEFINITE)); // improve the dropping AI
3352         //TODO stop autoplay mode?
3353       }
3354     }
3355 
3356     if(v2NewKGTo.Is0()){ DBG1("Desperately:TryAnyRandomTargetNavWithValidPath");
3357       std::vector<lsquare*> vlsqrChk(vv2AllDungeonSquares);
3358 
3359       while(vlsqrChk.size()>0){
3360         static int i;i=clock()%vlsqrChk.size();
3361         static v2 v2Chk; v2Chk = vlsqrChk[i]->GetPos();
3362 
3363         if(!AutoPlayAITestValidPathTo(v2Chk)){
3364           vlsqrChk.erase(vlsqrChk.begin()+i);
3365         }else{
3366           v2NewKGTo=v2Chk;
3367           break;
3368         }
3369       }
3370     }
3371 
3372     if(!v2NewKGTo.Is0()){
3373       AutoPlayAISetAndValidateKeepGoingTo(v2NewKGTo);
3374     }else{
3375       DBG1("TODO:too complex paths are failing... improve CreateRoute()?");
3376     }
3377   }
3378 
3379   if(!v2KeepGoingTo.Is0()){
3380     if(v2KeepGoingTo==GetPos()){ DBG3("ReachedDestination",DBGAV2(v2KeepGoingTo),DBGAV2(GoingTo));
3381       //wander a bit before following new target destination
3382       iWanderTurns=(clock()%iMaxWanderTurns)+iMinWanderTurns; DBG2("WanderAroundAtReachedDestination",iWanderTurns);
3383 
3384 //      v2KeepGoingTo=v2(0,0);
3385 //      TerminateGoingTo();
3386       AutoPlayAIReset(false);
3387       return true;
3388     }
3389 
3390 //    CheckForUsefulItemsOnGround(false); DBGSV2(GoingTo);
3391 //    CheckForEnemies(false, true, false, false); DBGSV2(GoingTo);
3392 
3393 //    if(!IsGoingSomeWhere() || v2KeepGoingTo!=GoingTo){ DBG3("ForceKeepGoingTo",DBGAV2(v2KeepGoingTo),DBGAV2(GoingTo));
3394 //      SetGoingTo(v2KeepGoingTo);
3395 //    }
3396     static int iForceGoingToCountDown=10;
3397     static v2 v2GoingToBkp;v2GoingToBkp=GoingTo;
3398     if(!v2KeepGoingTo.IsAdjacent(GoingTo)){
3399       if(iForceGoingToCountDown==0){
3400         DBG4("ForceKeepGoingTo",DBGAV2(v2KeepGoingTo),DBGAV2(GoingTo),DBGAV2(GetPos()));
3401 
3402         if(!AutoPlayAISetAndValidateKeepGoingTo(v2KeepGoingTo)){
3403           static int iSetFailTeleportCountDown=10;
3404           iSetFailTeleportCountDown--;
3405           vv2WrongGoingTo.push_back(v2GoingToBkp);
3406           if(iSetFailTeleportCountDown==0){
3407             AutoPlayAITeleport(false);
3408             AutoPlayAIReset(true); //refresh to test/try it all again
3409             iSetFailTeleportCountDown=10;
3410           }
3411         }
3412         DBGSV2(GoingTo);
3413         return true;
3414       }else{
3415         iForceGoingToCountDown--; DBG1(iForceGoingToCountDown);
3416       }
3417     }else{
3418       iForceGoingToCountDown=10;
3419     }
3420 
3421     /**
3422      * Determinedly blindly moves towards target, the goal is to Navigate!
3423      *
3424      * this has several possible status if returning false...
3425      * so better do not decide anything based on it?
3426      */
3427     MoveTowardsTarget(false);
3428 
3429 //    if(!MoveTowardsTarget(false)){ DBG3("OrFailedGoingTo,OrReachedDestination...",DBGAV2(GoingTo),DBGAV2(GetPos())); // MoveTowardsTarget may break the GoingTo EVEN if it succeeds?????
3430 //      TerminateGoingTo();
3431 //      v2KeepGoingTo=v2(0,0); //reset only this one to try again
3432 //      GetAICommand(); //wander once for randomicity
3433 //    }
3434 
3435     return true;
3436   }
3437 
3438   return false;
3439 }
3440 
AutoPlayAIChkInconsistency()3441 bool character::AutoPlayAIChkInconsistency()
3442 {
3443   if(GetSquareUnder()==NULL){
3444     DBG9(this,GetNameSingular().CStr(),IsPolymorphed(),IsHuman(),IsHumanoid(),IsPolymorphable(),IsPlayerKind(),IsTemporary(),IsPet());
3445     DBG6("GetSquareUnderIsNULLhow?",IsHeadless(),IsPlayer(),game::GetAutoPlayMode(),IsPlayerAutoPlay(),GetName(DEFINITE).CStr());
3446     return true; //to just ignore this turn expecting on next it will be ok.
3447   }
3448   return false;
3449 }
3450 
AutoPlayAIPray()3451 truth character::AutoPlayAIPray()
3452 {
3453   bool bSPO = bSafePrayOnce;
3454   bSafePrayOnce=false;
3455 
3456   if(bSPO){}
3457   else if(StateIsActivated(PANIC) && clock()%10==0){
3458     iWanderTurns=1; DBG1("Wandering:InPanic"); // to regain control as soon it is a ghost anymore as it can break navigation when inside walls
3459   }else return false;
3460 
3461   // check for known gods
3462   int aiKGods[GODS];
3463   int iKGTot=0;
3464   int aiKGodsP[GODS];
3465   int iKGTotP=0;
3466   static int iPleased=50; //see god::PrintRelation()
3467   for(int c = 1; c <= GODS; ++c){
3468     if(!game::GetGod(c)->IsKnown())continue;
3469     // even known, praying to these extreme ones will be messy if Relation<1000
3470     if(dynamic_cast<valpurus*>(game::GetGod(c))!=NULL && game::GetGod(c)->GetRelation()<1000)continue;
3471     if(dynamic_cast<mortifer*>(game::GetGod(c))!=NULL && game::GetGod(c)->GetRelation()<1000)continue;
3472 
3473     aiKGods[iKGTot++]=c;
3474 
3475     if(game::GetGod(c)->GetRelation() > iPleased){
3476 //      //TODO could this help?
3477 //      switch(game::GetGod(c)->GetBasicAlignment()){ //game::GetGod(c)->GetAlignment();
3478 //        case GOOD:
3479 //          if(game::GetPlayerAlignment()>=2){}else continue;
3480 //          break;
3481 //        case NEUTRAL:
3482 //          if(game::GetPlayerAlignment()<2 && game::GetPlayerAlignment()>-2){}else continue;
3483 //          break;
3484 //        case EVIL:
3485 //          if(game::GetPlayerAlignment()<=-2){}else continue;
3486 //          break;
3487 //      }
3488       aiKGodsP[iKGTotP++] = c;
3489     }
3490   }
3491   if(iKGTot==0)return false;
3492 //  if(bSPO && iKGTotP==0)return false;
3493 
3494   // chose and pray to one god
3495   god* g = NULL;
3496   if(iKGTotP>0 && (bSPO || clock()%10!=0))
3497     g = game::GetGod(aiKGodsP[clock()%iKGTotP]);
3498   else
3499     g = game::GetGod(aiKGods[clock()%iKGTot]);
3500 
3501   if(bSPO || clock()%10!=0){ //it may not recover some times to let pray unsafely
3502     int iRecover=0;
3503     if(iKGTotP==0){
3504       if(iRecover==0 && g->GetRelation()==-1000)iRecover=1000; //to test all relation range
3505       if(iRecover==0 && g->GetRelation() <= iPleased)iRecover=iPleased; //to alternate tests on many with low good relation
3506     }
3507     if(iRecover>0)
3508       g->SetRelation(iRecover);
3509 
3510     g->AdjustTimer(-1000000000); //TODO filter gods using timer too instead of this reset?
3511   }
3512 
3513   g->Pray(); DBG2("PrayingTo",g->GetName());
3514 
3515   return true;
3516 }
3517 
AutoPlayAICommand(int & rKey)3518 truth character::AutoPlayAICommand(int& rKey)
3519 {
3520   DBGLN;if(AutoPlayAIChkInconsistency())return true;
3521   DBGSV2(GetPos());
3522 
3523   if(AutoPlayLastChar!=this){
3524     AutoPlayAIReset(true);
3525     AutoPlayLastChar=this;
3526   }
3527 
3528   DBGLN;if(AutoPlayAIChkInconsistency())return true;
3529   if(AutoPlayAICheckAreaLevelChangedAndReset())
3530     AutoPlayAIReset(true);
3531 
3532   static bool bDummy_initDbg = [](){game::AddDebugDrawOverlayFunction(&AutoPlayAIDebugDrawOverlay);return true;}();
3533 
3534   truth bPlayerHasLantern=false;
3535   static itemvector vit;vit.clear();GetStack()->FillItemVector(vit);
3536   for(uint i=0;i<vit.size();i++){
3537     if(dynamic_cast<lantern*>(vit[i])!=NULL || vit[i]->IsOnFire(this)){
3538       bPlayerHasLantern=true; //will keep only the 1st lantern
3539       break;
3540     }
3541   }
3542 
3543   DBGLN;if(AutoPlayAIChkInconsistency())return true;
3544   AutoPlayAIPray();
3545 
3546   //TODO this doesnt work??? -> if(IsPolymorphed()){ //to avoid some issues TODO but could just check if is a ghost
3547 //  if(dynamic_cast<humanoid*>(this) == NULL){ //this avoid some issues TODO but could just check if is a ghost
3548 //  if(StateIsActivated(ETHEREAL_MOVING)){ //this avoid many issues
3549   static bool bPreviousTurnWasGhost=false;
3550   if(dynamic_cast<ghost*>(this) != NULL){ DBG1("Wandering:Ghost"); //this avoid many issues mainly related to navigation
3551     iWanderTurns=1; // to regain control as soon it is a ghost anymore as it can break navigation when inside walls
3552     bPreviousTurnWasGhost=true;
3553   }else{
3554     if(bPreviousTurnWasGhost){
3555       AutoPlayAIReset(true); //this may help on navigation
3556       bPreviousTurnWasGhost=false;
3557       return true;
3558     }
3559   }
3560 
3561   DBGLN;if(AutoPlayAIChkInconsistency())return true;
3562   if(AutoPlayAIDropThings())
3563     return true;
3564 
3565   DBGLN;if(AutoPlayAIChkInconsistency())return true;
3566   if(AutoPlayAIEquipAndPickup(bPlayerHasLantern))
3567     return true;
3568 
3569   if(iWanderTurns>0){
3570     if(!IsPlayer() || game::GetAutoPlayMode()==0 || !IsPlayerAutoPlay()){ //redundancy: yep
3571       DBG9(this,GetNameSingular().CStr(),IsPolymorphed(),IsHuman(),IsHumanoid(),IsPolymorphable(),IsPlayerKind(),IsTemporary(),IsPet());
3572       DBG5(IsHeadless(),IsPlayer(),game::GetAutoPlayMode(),IsPlayerAutoPlay(),GetName(DEFINITE).CStr());
3573       ABORT("autoplay is inconsistent %d %d %d %d %d %s %d %s %d %d %d %d %d",
3574         IsPolymorphed(),IsHuman(),IsHumanoid(),IsPolymorphable(),IsPlayerKind(),
3575         GetNameSingular().CStr(),game::GetAutoPlayMode(),GetName(DEFINITE).CStr(),
3576         IsTemporary(),IsPet(),IsHeadless(),IsPlayer(),IsPlayerAutoPlay());
3577     }
3578     GetAICommand(); DBG2("Wandering",iWanderTurns); //fallback to default TODO never reached?
3579     iWanderTurns--;
3580     return true;
3581   }
3582 
3583   /***************************************************************************************************
3584    * WANDER above here
3585    * NAVIGATE below here
3586    ***************************************************************************************************/
3587 
3588   /**
3589    * travel between dungeons
3590    */
3591   if(!v2TravelingToAnotherDungeon.Is0() && GetPos() == v2TravelingToAnotherDungeon){
3592     bool bTravel=false;
3593     lsquare* lsqr = game::GetCurrentLevel()->GetLSquare(v2TravelingToAnotherDungeon);
3594 //    square* sqr = Area->GetSquare(v2TravelingToAnotherDungeon);
3595     olterrain* ot = lsqr->GetOLTerrain();
3596 //    oterrain* ot = sqr->GetOTerrain();
3597     if(ot){
3598       if(ot->GetConfig() == STAIRS_UP){
3599         rKey='<';
3600         bTravel=true;
3601       }
3602 
3603       if(ot->GetConfig() == STAIRS_DOWN){
3604         rKey='>';
3605         bTravel=true;
3606       }
3607     }
3608 
3609     if(bTravel){ DBG3("travel",DBGAV2(v2TravelingToAnotherDungeon),rKey);
3610       AutoPlayAIReset(true);
3611       return false; //so the new/changed key will be used as command, otherwise it would be ignored
3612     }
3613   }
3614 
3615   static const int iDesperateResetCountDownDefault=10;
3616   static const int iDesperateEarthQuakeCountDownDefault=iDesperateResetCountDownDefault*5;
3617   static int iDesperateEarthQuakeCountDown=iDesperateEarthQuakeCountDownDefault;
3618   if(AutoPlayAINavigateDungeon(bPlayerHasLantern)){
3619     iDesperateEarthQuakeCountDown=iDesperateEarthQuakeCountDownDefault;
3620     return true;
3621   }else{
3622     if(iDesperateEarthQuakeCountDown==0){
3623       iDesperateEarthQuakeCountDown=iDesperateEarthQuakeCountDownDefault;
3624       scrollofearthquake::Spawn()->FinishReading(this);
3625       DBG1("UsingTerribleEarthquakeSolution"); // xD
3626     }else{
3627       iDesperateEarthQuakeCountDown--;
3628       DBG1(iDesperateEarthQuakeCountDown);
3629     }
3630   }
3631 
3632   /****************************************
3633    * Twighlight zone
3634    */
3635 
3636   ADD_MESSAGE("%s says \"I need more intelligence to do things by myself...\"", CHAR_NAME(DEFINITE)); DBG1("TODO: AI needs improvement");
3637 
3638   static int iDesperateResetCountDown=iDesperateResetCountDownDefault;
3639   if(iDesperateResetCountDown==0){
3640     iDesperateResetCountDown=iDesperateResetCountDownDefault;
3641 
3642     AutoPlayAIReset(true);
3643 
3644     // AFTER THE RESET!!!
3645     iWanderTurns=iMaxWanderTurns; DBG2("DesperateResetToSeeIfAIWorksAgain",iWanderTurns);
3646   }else{
3647     GetAICommand(); DBG2("WanderingDesperatelyNotKnowingWhatToDo",iDesperateResetCountDown); // :)
3648     iDesperateResetCountDown--;
3649   }
3650 
3651   return true;
3652 }
3653 
GetPlayerCommand()3654 void character::GetPlayerCommand()
3655 {
3656   truth HasActed = false;
3657 
3658   while(!HasActed)
3659   {
3660     graphics::SetAllowStretchedBlit(); //overall great/single location to re-enable stretched blit!
3661 
3662     game::DrawEverything();
3663 
3664     if(!StateIsActivated(FEARLESS) && game::GetDangerFound())
3665     {
3666       if(game::GetDangerFound() > 500.)
3667       {
3668         if(game::GetCausePanicFlag())
3669         {
3670           game::SetCausePanicFlag(false);
3671           BeginTemporaryState(PANIC, 500 + RAND_N(500));
3672         }
3673 
3674         game::AskForKeyPress(CONST_S("You are horrified by your situation! [press any key to continue]"));
3675       }
3676       else if(ivanconfig::GetWarnAboutDanger())
3677       {
3678         if(game::GetDangerFound() > 50.)
3679           game::AskForKeyPress(CONST_S("You sense great danger! [press any key to continue]"));
3680         else
3681           game::AskForKeyPress(CONST_S("You sense danger! [press any key to continue]"));
3682       }
3683 
3684       game::SetDangerFound(0);
3685     }
3686 
3687     game::SetIsInGetCommand(true);
3688     int Key = GET_KEY();
3689     game::SetIsInGetCommand(false);
3690 
3691     if(Key != '+' && Key != '-' && Key != 'M') // gum (these are the messages keys M=ShowHistory +-=ScrollHistUpDown)
3692       msgsystem::ThyMessagesAreNowOld();
3693 
3694     truth ValidKeyPressed = false;
3695     int c;
3696 
3697 #ifdef WIZARD
3698     if(IsPlayerAutoPlay()){
3699       bool bForceStop = false;
3700       if(game::GetAutoPlayMode()>=2)
3701         bForceStop = globalwindowhandler::IsKeyPressed(SDL_SCANCODE_ESCAPE);
3702 
3703       if(!bForceStop && Key=='.'){ // pressed or simulated
3704         if(game::IsInWilderness()){
3705           Key='>'; //blindly tries to go back to the dungeon safety :) TODO target and move to other dungeons/towns in the wilderness
3706         }else{
3707           HasActed = AutoPlayAICommand(Key); DBG2("Simulated",Key);
3708           if(HasActed)ValidKeyPressed = true; //valid simulated action
3709         }
3710       }else{
3711         /**
3712          * if the user hits any key during the autoplay mode that runs by itself, it will be disabled.
3713          * at non auto mode, can be moved around but cannot rest or will move by itself
3714          */
3715         if(game::GetAutoPlayMode()>=2 && (Key!='~' || bForceStop)){
3716           game::DisableAutoPlayMode();
3717           AutoPlayAIReset(true); // this will help on re-randomizing things, mainly paths
3718         }
3719       }
3720     }
3721 #endif
3722 
3723     if(!HasActed){
3724 
3725       for(c = 0; c < DIRECTION_COMMAND_KEYS; ++c)
3726         if(Key == game::GetMoveCommandKey(c))
3727         {
3728           bool bWaitNeutralMove=false;
3729           HasActed = TryMove(ApplyStateModification(game::GetMoveVector(c)), true, game::PlayerIsRunning(), &bWaitNeutralMove);
3730           if(HasActed){
3731             game::CheckAddAutoMapNote();
3732             game::CheckAutoPickup();
3733           }
3734           if(!HasActed && bWaitNeutralMove){
3735             //cant access.. HasActed = commandsystem::NOP(this);
3736             Key = '.'; //TODO request NOP()'s key instead of this '.' hardcoded here. how?
3737           }
3738           ValidKeyPressed = true;
3739         }
3740 
3741       for(c = 1; commandsystem::GetCommand(c); ++c)
3742         if(Key == commandsystem::GetCommand(c)->GetKey())
3743         {
3744           if(game::IsInWilderness() && !commandsystem::GetCommand(c)->IsUsableInWilderness())
3745             ADD_MESSAGE("This function cannot be used while in wilderness.");
3746           else
3747             if(!game::WizardModeIsActive() && commandsystem::GetCommand(c)->IsWizardModeFunction())
3748               ADD_MESSAGE("Activate wizardmode to use this function.");
3749             else{
3750               game::RegionListItemEnable(commandsystem::IsForRegionListItem(c));
3751               game::RegionSilhouetteEnable(commandsystem::IsForRegionSilhouette(c));
3752               HasActed = commandsystem::GetCommand(c)->GetLinkedFunction()(this);
3753               game::RegionListItemEnable(false);
3754               game::RegionSilhouetteEnable(false);
3755             }
3756 
3757           ValidKeyPressed = true;
3758           break;
3759         }
3760 
3761     }
3762 
3763     if(!ValidKeyPressed)
3764       ADD_MESSAGE("Unknown key. Press '?' for a list of commands.");
3765   }
3766 
3767   game::IncreaseTurn();
3768 }
3769 
Vomit(v2 Pos,int Amount,truth ShowMsg)3770 void character::Vomit(v2 Pos, int Amount, truth ShowMsg)
3771 {
3772   if(!CanVomit())
3773     return;
3774 
3775   if(ShowMsg)
3776   {
3777     if(IsPlayer())
3778       ADD_MESSAGE("You vomit.");
3779     else if(CanBeSeenByPlayer())
3780       ADD_MESSAGE("%s vomits.", CHAR_NAME(DEFINITE));
3781   }
3782 
3783   if(VomittingIsUnhealthy())
3784   {
3785     EditExperience(ARM_STRENGTH, -75, 1 << 9);
3786     EditExperience(LEG_STRENGTH, -75, 1 << 9);
3787   }
3788 
3789   if(IsPlayer())
3790   {
3791     EditNP(-2500 - RAND() % 2501);
3792     CheckStarvationDeath(CONST_S("vomited himself to death"));
3793   }
3794 
3795   if(StateIsActivated(PARASITE_TAPE_WORM) && !(RAND() & 7))
3796   {
3797     if(IsPlayer())
3798       ADD_MESSAGE("You notice a dead broad tapeworm among your former stomach contents.");
3799 
3800     DeActivateTemporaryState(PARASITE_TAPE_WORM);
3801   }
3802 
3803   if(!game::IsInWilderness())
3804     GetNearLSquare(Pos)->ReceiveVomit(this,
3805                                       liquid::Spawn(GetMyVomitMaterial(), long(sqrt(GetBodyVolume()) * Amount / 1000)));
3806 }
3807 
Polymorph(character * NewForm,int Counter)3808 truth character::Polymorph(character* NewForm, int Counter)
3809 {
3810   if(NewForm == NULL)
3811     ABORT("Unable to polymorph into NULL!");
3812 
3813   if(!IsPolymorphable() || (!IsPlayer() && game::IsInWilderness()))
3814   {
3815     delete NewForm;
3816     return false;
3817   }
3818 
3819   RemoveTraps();
3820 
3821   if(GetAction())
3822     GetAction()->Terminate(false);
3823 
3824   NewForm->SetAssignedName("");
3825 
3826   if(IsPlayer())
3827     ADD_MESSAGE("Your body glows in a crimson light. You transform into %s!", NewForm->CHAR_NAME(INDEFINITE));
3828   else if(CanBeSeenByPlayer())
3829     ADD_MESSAGE("%s glows in a crimson light and %s transforms into %s!",
3830                 CHAR_NAME(DEFINITE), GetPersonalPronoun().CStr(), NewForm->CHAR_NAME(INDEFINITE));
3831 
3832   Flags |= C_IN_NO_MSG_MODE;
3833   NewForm->Flags |= C_IN_NO_MSG_MODE;
3834   NewForm->ChangeTeam(GetTeam());
3835   NewForm->GenerationDanger = GenerationDanger;
3836 
3837   if(GetTeam()->GetLeader() == this)
3838     GetTeam()->SetLeader(NewForm);
3839 
3840   v2 Pos = GetPos();
3841   Remove();
3842   NewForm->PutToOrNear(Pos);
3843   NewForm->SetAssignedName(GetAssignedName());
3844   NewForm->ActivateTemporaryState(POLYMORPHED);
3845   NewForm->SetTemporaryStateCounter(POLYMORPHED, Counter);
3846 
3847   if(TemporaryStateIsActivated(POLYMORPHED))
3848   {
3849     NewForm->SetPolymorphBackup(GetPolymorphBackup());
3850     SetPolymorphBackup(0);
3851     SendToHell();
3852   }
3853   else
3854   {
3855     NewForm->SetPolymorphBackup(this);
3856     Flags |= C_POLYMORPHED;
3857     Disable();
3858   }
3859 
3860   GetStack()->MoveItemsTo(NewForm->GetStack());
3861   NewForm->SetMoney(GetMoney());
3862   DonateEquipmentTo(NewForm);
3863   Flags &= ~C_IN_NO_MSG_MODE;
3864   NewForm->Flags &= ~C_IN_NO_MSG_MODE;
3865   NewForm->CalculateAll();
3866 
3867   if(IsPlayer())
3868   {
3869     Flags &= ~C_PLAYER;
3870     game::SetPlayer(NewForm);
3871     game::SendLOSUpdateRequest();
3872     UpdateESPLOS();
3873   }
3874 
3875   NewForm->TestWalkability();
3876   return true;
3877 }
3878 
BeKicked(character * Kicker,item * Boot,bodypart * Leg,v2 HitPos,double KickDamage,double ToHitValue,int Success,int Direction,truth Critical,truth ForceHit)3879 void character::BeKicked(character* Kicker, item* Boot, bodypart* Leg, v2 HitPos, double KickDamage,
3880                          double ToHitValue, int Success, int Direction, truth Critical, truth ForceHit)
3881 {
3882   switch(TakeHit(Kicker, Boot, Leg, HitPos, KickDamage, ToHitValue,
3883                  Success, KICK_ATTACK, Direction, Critical, ForceHit))
3884   {
3885    case HAS_HIT:
3886    case HAS_BLOCKED:
3887    case DID_NO_DAMAGE:
3888     if(IsEnabled())
3889     {
3890       if(Boot && (Boot->GetConfig() == BOOT_OF_DISPLACEMENT) && Kicker->Displace(this, true))
3891         return;
3892 
3893       if(!CheckBalance(KickDamage) || (Boot && (Boot->GetConfig() == BOOT_OF_KICKING)))
3894       {
3895         if(IsPlayer())
3896           ADD_MESSAGE("The kick throws you off balance.");
3897         else if(Kicker->IsPlayer())
3898           ADD_MESSAGE("The kick throws %s off balance.", CHAR_DESCRIPTION(DEFINITE));
3899 
3900         v2 FallToPos = GetPos() + game::GetMoveVector(Direction);
3901         FallTo(Kicker, FallToPos);
3902       }
3903     }
3904   }
3905 }
3906 
3907 /* Return true if still in balance */
3908 
CheckBalance(double KickDamage)3909 truth character::CheckBalance(double KickDamage)
3910 {
3911   return !CanMove()
3912     || IsStuck()
3913     || !KickDamage
3914     || (!IsFlying()
3915         && KickDamage * 5 < RAND() % GetSize());
3916 }
3917 
FallTo(character * GuiltyGuy,v2 Where)3918 void character::FallTo(character* GuiltyGuy, v2 Where)
3919 {
3920   EditAP(-500);
3921   lsquare* MoveToSquare[MAX_SQUARES_UNDER];
3922   int Squares = CalculateNewSquaresUnder(MoveToSquare, Where);
3923 
3924   if(Squares)
3925   {
3926     truth NoRoom = false;
3927 
3928     for(int c = 0; c < Squares; ++c)
3929     {
3930       olterrain* Terrain = MoveToSquare[c]->GetOLTerrain();
3931 
3932       if(Terrain && !CanMoveOn(Terrain))
3933       {
3934         NoRoom = true;
3935         break;
3936       }
3937     }
3938 
3939     if(NoRoom)
3940     {
3941       if(HasHead())
3942       {
3943         if(IsPlayer())
3944           ADD_MESSAGE("You hit your head on the wall.");
3945         else if(CanBeSeenByPlayer())
3946           ADD_MESSAGE("%s hits %s head on the wall.", CHAR_NAME(DEFINITE), GetPossessivePronoun().CStr());
3947       }
3948 
3949       ReceiveDamage(GuiltyGuy, 1 + RAND() % 5, PHYSICAL_DAMAGE, HEAD);
3950       CheckDeath(CONST_S("killed by hitting a wall due to being kicked @bk"), GuiltyGuy);
3951     }
3952     else
3953     {
3954       if(IsFreeForMe(MoveToSquare[0]))
3955         Move(Where, true);
3956 
3957       // Place code that handles characters bouncing to each other here
3958     }
3959   }
3960 }
3961 
CheckCannibalism(cmaterial * What) const3962 truth character::CheckCannibalism(cmaterial* What) const
3963 {
3964   return GetTorso()->GetMainMaterial()->IsSameAs(What);
3965 }
3966 
StandIdleAI()3967 void character::StandIdleAI()
3968 {
3969   SeekLeader(GetLeader());
3970 
3971   if(CheckForEnemies(true, true, true))
3972     return;
3973 
3974   if(CheckForUsefulItemsOnGround())
3975     return;
3976 
3977   if(FollowLeader(GetLeader()))
3978     return;
3979 
3980   if(CheckForDoors())
3981     return;
3982 
3983   if(MoveTowardsHomePos())
3984     return;
3985 
3986   if(CheckSadism())
3987     return;
3988 
3989   EditAP(-1000);
3990 }
3991 
LoseConsciousness(int Counter,truth HungerFaint)3992 truth character::LoseConsciousness(int Counter, truth HungerFaint)
3993 {
3994   if(!AllowUnconsciousness())
3995     return false;
3996 
3997   action* Action = GetAction();
3998 
3999   if(Action)
4000   {
4001     if(HungerFaint && !Action->AllowUnconsciousness())
4002       return false;
4003 
4004     if(Action->IsUnconsciousness())
4005     {
4006       static_cast<unconsciousness*>(Action)->RaiseCounterTo(Counter);
4007       return true;
4008     }
4009     else
4010       Action->Terminate(false);
4011   }
4012 
4013   if(IsPlayer())
4014     ADD_MESSAGE("You lose consciousness.");
4015   else if(CanBeSeenByPlayer())
4016     ADD_MESSAGE("%s loses consciousness.", CHAR_NAME(DEFINITE));
4017 
4018   unconsciousness* Unconsciousness = unconsciousness::Spawn(this);
4019   Unconsciousness->SetCounter(Counter);
4020   SetAction(Unconsciousness);
4021   return true;
4022 }
4023 
DeActivateTemporaryState(long What)4024 void character::DeActivateTemporaryState(long What)
4025 {
4026   if(PolymorphBackup)
4027     PolymorphBackup->TemporaryState &= ~What;
4028 
4029   TemporaryState &= ~What;
4030 }
4031 
DeActivateVoluntaryAction(cfestring & Reason)4032 void character::DeActivateVoluntaryAction(cfestring& Reason)
4033 {
4034   if(GetAction() && GetAction()->IsVoluntary())
4035   {
4036     if(IsPlayer())
4037     {
4038       if(Reason.GetSize())
4039         ADD_MESSAGE("%s", Reason.CStr());
4040 
4041       if(game::TruthQuestion(CONST_S("Continue ") + GetAction()->GetDescription() + "? [y/N]"))
4042         GetAction()->ActivateInDNDMode();
4043       else
4044         GetAction()->Terminate(false);
4045     }
4046     else
4047       GetAction()->Terminate(false);
4048   }
4049 }
4050 
ActionAutoTermination()4051 void character::ActionAutoTermination()
4052 {
4053   if(!GetAction() || !GetAction()->IsVoluntary() || GetAction()->InDNDMode())
4054     return;
4055 
4056   v2 Pos = GetPos();
4057 
4058   for(int c = 0; c < game::GetTeams(); ++c)
4059     if(GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE)
4060       for(character* p : game::GetTeam(c)->GetMember())
4061         if(p->IsEnabled()
4062            && p->CanBeSeenBy(this, false, true)
4063            && (p->CanMove() || p->GetPos().IsAdjacent(Pos))
4064            && p->CanAttack())
4065         {
4066           if(IsPlayer())
4067           {
4068             ADD_MESSAGE("%s seems to be hostile.", p->CHAR_NAME(DEFINITE));
4069 
4070             if(game::TruthQuestion(CONST_S("Continue ") + GetAction()->GetDescription() + "? [y/N]"))
4071               GetAction()->ActivateInDNDMode();
4072             else
4073               GetAction()->Terminate(false);
4074           }
4075           else
4076             GetAction()->Terminate(false);
4077 
4078           return;
4079         }
4080 }
4081 
CheckForEnemies(truth CheckDoors,truth CheckGround,truth MayMoveRandomly,truth RunTowardsTarget)4082 truth character::CheckForEnemies(truth CheckDoors, truth CheckGround, truth MayMoveRandomly, truth RunTowardsTarget)
4083 {
4084   if(!IsEnabled())
4085     return false;
4086 
4087   truth HostileCharsNear = false;
4088   character* NearestChar = 0;
4089   long NearestDistance = 0x7FFFFFFF;
4090   v2 Pos = GetPos();
4091 
4092   for(int c = 0; c < game::GetTeams(); ++c)
4093     if(GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE)
4094       for(character* p : game::GetTeam(c)->GetMember())
4095         if(p->IsEnabled() && GetAttribute(WISDOM) < p->GetAttackWisdomLimit())
4096         {
4097           long ThisDistance = Max<long>(abs(p->GetPos().X - Pos.X), abs(p->GetPos().Y - Pos.Y));
4098 
4099           if(ThisDistance <= GetLOSRangeSquare())
4100             HostileCharsNear = true;
4101 
4102           if((ThisDistance < NearestDistance
4103               || (ThisDistance == NearestDistance && !(RAND() % 3)))
4104              && p->CanBeSeenBy(this, false, IsGoingSomeWhere())
4105              && (!IsGoingSomeWhere() || HasClearRouteTo(p->GetPos())))
4106           {
4107             NearestChar = p;
4108             NearestDistance = ThisDistance;
4109           }
4110         }
4111 
4112   if(NearestChar)
4113   {
4114     if(GetAttribute(INTELLIGENCE) >= 10 || IsSpy())
4115       game::CallForAttention(GetPos(), 100);
4116 
4117     if(SpecialEnemySightedReaction(NearestChar))
4118       return true;
4119 
4120     if(IsExtraCoward() && !StateIsActivated(PANIC) && NearestChar->GetRelativeDanger(this) >= 0.5 && !StateIsActivated(FEARLESS))
4121     {
4122       if(CanBeSeenByPlayer())
4123         ADD_MESSAGE("%s sees %s.", CHAR_NAME(DEFINITE), NearestChar->CHAR_DESCRIPTION(DEFINITE));
4124 
4125       BeginTemporaryState(PANIC, 500 + RAND() % 500);
4126     }
4127 
4128     if(!IsRetreating())
4129     {
4130       if(CheckGround && NearestDistance > 2 && CheckForUsefulItemsOnGround(false))
4131         return true;
4132 
4133       SetGoingTo(NearestChar->GetPos());
4134     }
4135     else
4136       SetGoingTo(Pos - ((NearestChar->GetPos() - Pos) << 4));
4137 
4138     return MoveTowardsTarget(true);
4139   }
4140   else
4141   {
4142     character* Leader = GetLeader();
4143 
4144     if(Leader == this)
4145       Leader = 0;
4146 
4147     if(!Leader && IsGoingSomeWhere())
4148     {
4149       if(!MoveTowardsTarget(RunTowardsTarget))
4150       {
4151         TerminateGoingTo();
4152         return false;
4153       }
4154       else
4155       {
4156         if(!IsEnabled())
4157           return true;
4158 
4159         if(GetPos() == GoingTo)
4160           TerminateGoingTo();
4161 
4162         return true;
4163       }
4164     }
4165     else
4166     {
4167       if((!Leader || (Leader && !IsGoingSomeWhere())) && HostileCharsNear)
4168       {
4169         if(CheckDoors && CheckForDoors())
4170           return true;
4171 
4172         if(CheckGround && CheckForUsefulItemsOnGround())
4173           return true;
4174 
4175         if(!Leader || Leader!=PLAYER || (Leader==PLAYER && ivanconfig::GetHoldPosMaxDist()==0)){ // this lets all pets stay put if hold pos is > 0
4176           if(MayMoveRandomly){
4177             if(MoveRandomly()) // one has heard that an enemy is near but doesn't know where
4178               return true;
4179           }
4180         }
4181       }
4182 
4183       return false;
4184     }
4185   }
4186 }
4187 
CheckForDoors()4188 truth character::CheckForDoors()
4189 {
4190   if(!CanOpen() || !IsEnabled())
4191     return false;
4192 
4193   for(int d = 0; d < GetNeighbourSquares(); ++d)
4194   {
4195     lsquare* Square = GetNeighbourLSquare(d);
4196 
4197     if(Square && Square->GetOLTerrain() && Square->GetOLTerrain()->Open(this))
4198       return true;
4199   }
4200 
4201   return false;
4202 }
4203 
CheckForUsefulItemsOnGround(truth CheckFood)4204 truth character::CheckForUsefulItemsOnGround(truth CheckFood)
4205 {
4206   if(StateIsActivated(PANIC) || !IsEnabled())
4207     return false;
4208 
4209   itemvector ItemVector;
4210   GetStackUnder()->FillItemVector(ItemVector);
4211 
4212   for(uint c = 0; c < ItemVector.size(); ++c)
4213     if(ItemVector[c]->CanBeSeenBy(this) && ItemVector[c]->IsPickable(this))
4214     {
4215       if(!(CommandFlags & DONT_CHANGE_EQUIPMENT)
4216          && TryToEquip(ItemVector[c]))
4217         return true;
4218 
4219       if(CheckFood && UsesNutrition() && !CheckIfSatiated()
4220          && TryToConsume(ItemVector[c]))
4221         return true;
4222     }
4223 
4224   return false;
4225 }
4226 
FollowLeader(character * Leader)4227 truth character::FollowLeader(character* Leader)
4228 {
4229   if(!Leader || Leader == this || !IsEnabled()) { DBG1(GetNameSingular().CStr());
4230     return false;
4231   }
4232 
4233   if(CommandFlags & FOLLOW_LEADER && Leader->CanBeSeenBy(this) && Leader->SquareUnderCanBeSeenBy(this, true)){ DBG1(GetNameSingular().CStr());
4234     v2HoldPos = GoingTo; //will keep the last reference position possible
4235 
4236     v2 Distance = GetPos() - GoingTo; //set by SeekLeader()
4237     if(abs(Distance.X) <= 2 && abs(Distance.Y) <= 2)
4238       return false;
4239     else
4240       return MoveTowardsTarget(false);
4241   }
4242 
4243   if(IsGoingSomeWhere()){
4244     if(!MoveTowardsTarget(true)){ DBG1(GetNameSingular().CStr());
4245       TerminateGoingTo();
4246       return false;
4247     }else{ DBG1(GetNameSingular().CStr());
4248       return true;
4249     }
4250   }else{
4251     // just hold near position
4252     if(v2HoldPos.Is0())
4253       v2HoldPos=GetPos(); //when the game is loaded keep current pos TODO could be savegamed tho
4254     if(ivanconfig::GetHoldPosMaxDist()>0){
4255       v2 v2HoldDist = GetPos() - v2HoldPos;
4256       if(abs(v2HoldDist.X) < ivanconfig::GetHoldPosMaxDist() && abs(v2HoldDist.Y) < ivanconfig::GetHoldPosMaxDist()){ DBG1(GetNameSingular().CStr());
4257         // will do other things
4258         return false;
4259       }else{
4260         SetGoingTo(v2HoldPos); DBG4(GetNameSingular().CStr(),DBGAV2(v2HoldPos),DBGAV2(Leader->GetPos()),DBGAV2(v2HoldDist));
4261         return true;
4262       }
4263     }
4264   }
4265 
4266   return false;
4267 }
4268 
SeekLeader(ccharacter * Leader)4269 void character::SeekLeader(ccharacter* Leader)
4270 {
4271   if(Leader && Leader != this)
4272   {
4273     if(Leader->CanBeSeenBy(this) && (Leader->SquareUnderCanBeSeenBy(this, true) || !IsGoingSomeWhere()))
4274     {
4275       if(CommandFlags & FOLLOW_LEADER)
4276         SetGoingTo(Leader->GetPos());
4277     }
4278     else if(!IsGoingSomeWhere())
4279     {
4280       team* Team = GetTeam();
4281 
4282       for(character* p : Team->GetMember()){
4283         if(p->IsEnabled()
4284            && p->GetID() != GetID()
4285            && (CommandFlags & FOLLOW_LEADER)
4286            == (p->CommandFlags & FOLLOW_LEADER)
4287            && p->CanBeSeenBy(this))
4288         {
4289           v2 Pos = p->GetPos();
4290           v2 Distance = GetPos() - Pos;
4291 
4292           if(abs(Distance.X) > 2 && abs(Distance.Y) > 2)
4293           {
4294             SetGoingTo(Pos);
4295             break;
4296           }
4297         }
4298       }
4299 
4300 //      if(!IsGoingSomeWhere()){ // couldnt follow leader neather team mate
4301 //      }
4302     }
4303   }
4304 }
4305 
GetMoveEase() const4306 int character::GetMoveEase() const
4307 {
4308   switch(BurdenState)
4309   {
4310    case OVER_LOADED:
4311    case STRESSED: return 50;
4312    case BURDENED: return 75;
4313    case UNBURDENED: return 100;
4314   }
4315 
4316   return 666;
4317 }
4318 
GetLOSRange() const4319 int character::GetLOSRange() const
4320 {
4321   if(!game::IsInWilderness())
4322     return GetAttribute(PERCEPTION) * GetLevel()->GetLOSModifier() / 48;
4323   else
4324     return 3;
4325 }
4326 
Displace(character * Who,truth Forced)4327 truth character::Displace(character* Who, truth Forced)
4328 {
4329   if(GetBurdenState() == OVER_LOADED)
4330   {
4331     if(IsPlayer())
4332     {
4333       cchar* CrawlVerb = StateIsActivated(LEVITATION) ? "float" : "crawl";
4334       ADD_MESSAGE("You try very hard to %s forward. But your load is too heavy.", CrawlVerb);
4335       EditAP(-1000);
4336       return true;
4337     }
4338     else
4339       return false;
4340   }
4341 
4342   double Danger = GetRelativeDanger(Who);
4343   int PriorityDifference = Limit(GetDisplacePriority() - Who->GetDisplacePriority(), -31, 31);
4344 
4345   if(IsPlayer())
4346     ++PriorityDifference;
4347   else if(Who->IsPlayer())
4348     --PriorityDifference;
4349 
4350   if(PriorityDifference >= 0)
4351     Danger *= 1 << PriorityDifference;
4352   else
4353     Danger /= 1 << -PriorityDifference;
4354 
4355   if(IsSmall() && Who->IsSmall()
4356      && (Forced || Danger > 1. || !(Who->IsPlayer() || Who->IsBadPath(GetPos())))
4357      && !IsStuck() && !Who->IsStuck()
4358      && (!Who->GetAction() || Who->GetAction()->TryDisplace())
4359      && CanMove() && Who->CanMove() && Who->CanMoveOn(GetLSquareUnder()))
4360   {
4361     if(IsPlayer())
4362       ADD_MESSAGE("You displace %s!", Who->CHAR_DESCRIPTION(DEFINITE));
4363     else if(Who->IsPlayer())
4364       ADD_MESSAGE("%s displaces you!", CHAR_DESCRIPTION(DEFINITE));
4365     else if(CanBeSeenByPlayer() || Who->CanBeSeenByPlayer())
4366       ADD_MESSAGE("%s displaces %s!", CHAR_DESCRIPTION(DEFINITE), Who->CHAR_DESCRIPTION(DEFINITE));
4367 
4368     lsquare* OldSquareUnder1[MAX_SQUARES_UNDER];
4369     lsquare* OldSquareUnder2[MAX_SQUARES_UNDER];
4370     int c;
4371 
4372     for(c = 0; c < GetSquaresUnder(); ++c)
4373       OldSquareUnder1[c] = GetLSquareUnder(c);
4374 
4375     for(c = 0; c < Who->GetSquaresUnder(); ++c)
4376       OldSquareUnder2[c] = Who->GetLSquareUnder(c);
4377 
4378     v2 Pos = GetPos();
4379     v2 WhoPos = Who->GetPos();
4380     Remove();
4381     Who->Remove();
4382     PutTo(WhoPos);
4383     Who->PutTo(Pos);
4384     EditAP(-GetMoveAPRequirement(GetSquareUnder()->GetEntryDifficulty()) - 500);
4385     EditNP(-12 * GetSquareUnder()->GetEntryDifficulty());
4386     EditExperience(AGILITY, 75, GetSquareUnder()->GetEntryDifficulty() << 7);
4387 
4388     if(IsPlayer())
4389       ShowNewPosInfo();
4390 
4391     if(Who->IsPlayer())
4392       Who->ShowNewPosInfo();
4393 
4394     SignalStepFrom(OldSquareUnder1);
4395     Who->SignalStepFrom(OldSquareUnder2);
4396     return true;
4397   }
4398   else
4399   {
4400     if(IsPlayer())
4401     {
4402       ADD_MESSAGE("%s resists!", Who->CHAR_DESCRIPTION(DEFINITE));
4403       EditAP(-1000);
4404       return true;
4405     }
4406     else
4407       return false;
4408   }
4409 }
4410 
SetNP(long What)4411 void character::SetNP(long What)
4412 {
4413   int OldState = GetHungerState();
4414   NP = What;
4415 
4416   if(IsPlayer())
4417   {
4418     int NewState = GetHungerState();
4419 
4420     if(NewState == STARVING && OldState > STARVING)
4421       DeActivateVoluntaryAction(CONST_S("You are getting really hungry."));
4422     else if(NewState == VERY_HUNGRY && OldState > VERY_HUNGRY)
4423       DeActivateVoluntaryAction(CONST_S("You are getting very hungry."));
4424     else if(NewState == HUNGRY && OldState > HUNGRY)
4425       DeActivateVoluntaryAction(CONST_S("You are getting hungry."));
4426   }
4427 }
4428 
ShowNewPosInfo() const4429 void character::ShowNewPosInfo() const
4430 {
4431   msgsystem::EnterBigMessageMode();
4432   v2 Pos = GetPos();
4433 
4434   if(Pos.X < game::GetCamera().X + 3 || Pos.X >= game::GetCamera().X + game::GetScreenXSize() - 3)
4435     game::UpdateCameraX();
4436 
4437   if(Pos.Y < game::GetCamera().Y + 3 || Pos.Y >= game::GetCamera().Y + game::GetScreenYSize() - 3)
4438     game::UpdateCameraY();
4439 
4440   game::SendLOSUpdateRequest();
4441   game::DrawEverythingNoBlit();
4442   UpdateESPLOS();
4443 
4444   if(!game::IsInWilderness())
4445   {
4446     if(GetLSquareUnder()->IsDark() && !game::GetSeeWholeMapCheatMode())
4447       ADD_MESSAGE("It's dark in here!");
4448 
4449     GetLSquareUnder()->ShowSmokeMessage();
4450     itemvectorvector PileVector;
4451     GetStackUnder()->Pile(PileVector, this, CENTER);
4452 
4453     if(PileVector.size())
4454     {
4455       truth Feel = !GetLSquareUnder()->IsTransparent() || GetLSquareUnder()->IsDark();
4456 
4457       if(PileVector.size() == 1)
4458       {
4459         if(Feel)
4460           ADD_MESSAGE("You feel %s lying here.",
4461                       PileVector[0][0]->GetName(INDEFINITE, PileVector[0].size()).CStr());
4462         else
4463           ADD_MESSAGE("%s %s lying here.",
4464                       PileVector[0][0]->GetName(INDEFINITE, PileVector[0].size()).CStr(),
4465                       PileVector[0].size() == 1 ? "is" : "are");
4466       }
4467       else
4468       {
4469         int Items = 0;
4470 
4471         for(uint c = 0; c < PileVector.size(); ++c)
4472           if((Items += PileVector[c].size()) > 3)
4473             break;
4474 
4475         if(Items > 3)
4476         {
4477           if(Feel)
4478             ADD_MESSAGE("You feel several items lying here.");
4479           else
4480             ADD_MESSAGE("Several items are lying here.");
4481         }
4482         else if(Items)
4483         {
4484           if(Feel)
4485             ADD_MESSAGE("You feel a few items lying here.");
4486           else
4487             ADD_MESSAGE("A few items are lying here.");
4488         }
4489       }
4490     }
4491 
4492     festring SideItems;
4493     GetLSquareUnder()->GetSideItemDescription(SideItems);
4494 
4495     if(!SideItems.IsEmpty())
4496       ADD_MESSAGE("There is %s.", SideItems.CStr());
4497 
4498     if(GetLSquareUnder()->HasEngravings())
4499     {
4500       cchar* Text = GetLSquareUnder()->GetEngraved();
4501 
4502       if(Text[0] != '#') // Prevent displaying map notes.
4503       {
4504         if(CanRead())
4505           ADD_MESSAGE("Something has been engraved here: \"%s\"", Text);
4506         else
4507           ADD_MESSAGE("Something has been engraved here.");
4508       }
4509     }
4510   }
4511 
4512   msgsystem::LeaveBigMessageMode();
4513 }
4514 
Hostility(character * Enemy)4515 void character::Hostility(character* Enemy)
4516 {
4517   if(Enemy == this || !Enemy || !Team || !Enemy->Team)
4518     return;
4519 
4520   if(Enemy->IsMasochist() && GetRelation(Enemy) == FRIEND)
4521     return;
4522 
4523   if(!IsAlly(Enemy))
4524     GetTeam()->Hostility(Enemy->GetTeam());
4525   else if(IsPlayer() && !Enemy->IsPlayer()) // I believe both may be players due to polymorph feature...
4526   {
4527     if(Enemy->CanBeSeenByPlayer())
4528       ADD_MESSAGE("%s becomes enraged.", Enemy->CHAR_NAME(DEFINITE));
4529 
4530     Enemy->ChangeTeam(game::GetTeam(BETRAYED_TEAM));
4531   }
4532 }
4533 
GetGiftStack() const4534 stack* character::GetGiftStack() const
4535 {
4536   if(GetLSquareUnder()->GetRoomIndex() && !GetLSquareUnder()->GetRoom()->AllowDropGifts())
4537     return GetStack();
4538   else
4539     return GetStackUnder();
4540 }
4541 
MoveRandomlyInRoom()4542 truth character::MoveRandomlyInRoom()
4543 {
4544   for(int c = 0; c < 10; ++c)
4545   {
4546     v2 ToTry = game::GetMoveVector(RAND() & 7);
4547 
4548     if(GetLevel()->IsValidPos(GetPos() + ToTry))
4549     {
4550       lsquare* Square = GetNearLSquare(GetPos() + ToTry);
4551 
4552       if(!Square->IsDangerous(this)
4553          && !Square->IsScary(this)
4554          && (!Square->GetOLTerrain()
4555              || !Square->GetOLTerrain()->IsDoor())
4556          && TryMove(ToTry, false, false))
4557         return true;
4558     }
4559   }
4560 
4561   return false;
4562 }
4563 
IsAboveUsefulItem()4564 truth character::IsAboveUsefulItem()
4565 {
4566   if(GetStackUnder()->GetVisibleItems(this))
4567   {
4568     bool bUseless=false,bTooCheap=false,bEncumbering=false;
4569 
4570     switch(ivanconfig::GetGoOnStopMode()){
4571     case 0: return true;
4572     case 1:bUseless=true;break;
4573     case 2:bTooCheap=true;break;
4574     case 3:bEncumbering=true;break;
4575     default:
4576       ABORT("unsupported go on stop mode %ld",ivanconfig::GetGoOnStopMode());
4577       break;
4578     }
4579 
4580     itemvector vit;
4581     GetStackUnder()->FillItemVector(vit);
4582     for(int i=0;i<vit.size();i++){
4583       DBG9(vit[i]->GetNameSingular().CStr(), vit[i]->AllowEquip(), vit[i]->IsAppliable(this), vit[i]->IsConsumable(),
4584         vit[i]->IsEatable(this), vit[i]->IsDrinkable(this), vit[i]->IsOpenable(this), vit[i]->IsReadable(this), vit[i]->IsZappable(this) );
4585       DBG8(vit[i]->GetNameSingular().CStr(),vit[i]->IsWeapon(this),vit[i]->IsArmor(this),vit[i]->IsBodyArmor(this),vit[i]->IsHelmet(this),
4586         vit[i]->IsGauntlet(this),vit[i]->IsBoot(this),vit[i]->IsBelt(this) );
4587       if( //TODO ? vit[i]->GetSpoilLevel()==0
4588           vit[i]->IsOpenable(this) //doors are not items, but works for chests too
4589           ||
4590           (bUseless &&
4591             (
4592               vit[i]->IsAppliable(this) ||
4593               vit[i]->IsZappable(this)  ||
4594 
4595               // bad! keep as info! vit[i]->IsConsumable() ||
4596               vit[i]->IsEatable(this) ||
4597               vit[i]->IsDrinkable(this) ||
4598 
4599               // bad! keep as info! vit[i]->AllowEquip() ||
4600               vit[i]->IsWeapon(this) ||
4601               vit[i]->IsArmor(this) || //all armor slots
4602 
4603               vit[i]->IsAmulet(this) ||
4604               vit[i]->IsRing(this) ||
4605 
4606               vit[i]->IsReadable(this)
4607             )
4608           ) ||
4609           (bTooCheap &&
4610             (vit[i]->GetTruePrice() > iMaxValueless)
4611           ) ||
4612           (bEncumbering && //calc in float price vs weight
4613             (vit[i]->GetTruePrice()/(vit[i]->GetWeight()/1000.0)) > (iMaxValueless*2)
4614           )
4615       ){
4616         return true;
4617       }
4618     }
4619   }
4620 
4621   return false;
4622 }
4623 
GoOn(go * Go,truth FirstStep)4624 void character::GoOn(go* Go, truth FirstStep)
4625 {
4626   v2 MoveVector = ApplyStateModification(game::GetMoveVector(Go->GetDirection()));
4627   if(MoveVector.Is0()) //this is rare and may happen while confuse state is active
4628   {
4629     Go->Terminate(false);
4630     return;
4631   }
4632 
4633   lsquare* MoveToSquare[MAX_SQUARES_UNDER];
4634   int Squares = CalculateNewSquaresUnder(MoveToSquare, GetPos() + MoveVector);
4635 
4636   if(!Squares || !CanMoveOn(MoveToSquare[0]))
4637   {
4638     Go->Terminate(false);
4639     return;
4640   }
4641 
4642   uint OldRoomIndex = GetLSquareUnder()->GetRoomIndex();
4643   uint CurrentRoomIndex = MoveToSquare[0]->GetRoomIndex();
4644 
4645   if(!Go->IsRouteMode())
4646     if((OldRoomIndex && (CurrentRoomIndex != OldRoomIndex)) && !FirstStep)
4647     {
4648       Go->Terminate(false);
4649       return;
4650     }
4651 
4652   for(int c = 0; c < Squares; ++c)
4653     if((MoveToSquare[c]->GetCharacter() && GetTeam() != MoveToSquare[c]->GetCharacter()->GetTeam())
4654        || MoveToSquare[c]->IsDangerous(this))
4655     {
4656       Go->Terminate(false);
4657       return;
4658     }
4659 
4660   if(!Go->IsRouteMode()){
4661     int OKDirectionsCounter = 0;
4662 
4663     for(int d = 0; d < GetNeighbourSquares(); ++d)
4664     {
4665       lsquare* Square = GetNeighbourLSquare(d);
4666 
4667       if(Square && CanMoveOn(Square))
4668         ++OKDirectionsCounter;
4669     }
4670 
4671     if(!Go->IsWalkingInOpen())
4672     {
4673       if(OKDirectionsCounter > 2)
4674       {
4675         Go->Terminate(false);
4676         return;
4677       }
4678     }
4679     else
4680       if(OKDirectionsCounter <= 2)
4681         Go->SetIsWalkingInOpen(false);
4682   }
4683 
4684   square* BeginSquare = GetSquareUnder();
4685 
4686   if(!TryMove(MoveVector, true, game::PlayerIsRunning())
4687      || BeginSquare == GetSquareUnder()
4688      || (!Go->IsRouteMode() && CurrentRoomIndex && (OldRoomIndex != CurrentRoomIndex)))
4689   {
4690     Go->Terminate(false);
4691     return;
4692   }
4693 
4694   if(IsAboveUsefulItem())
4695   {
4696     Go->Terminate(false);
4697     return;
4698   }
4699 
4700   game::DrawEverything();
4701 }
4702 
SetTeam(team * What)4703 void character::SetTeam(team* What)
4704 {
4705   Team = What;
4706   SetTeamIterator(What->Add(this));
4707 }
4708 
ChangeTeam(team * What)4709 void character::ChangeTeam(team* What)
4710 {
4711   if(Team)
4712     Team->Remove(GetTeamIterator());
4713 
4714   Team = What;
4715   SendNewDrawRequest();
4716 
4717   if(Team)
4718     SetTeamIterator(Team->Add(this));
4719 }
4720 
ChangeRandomAttribute(int HowMuch)4721 truth character::ChangeRandomAttribute(int HowMuch)
4722 {
4723   for(int c = 0; c < 50; ++c)
4724   {
4725     int AttribID = RAND() % ATTRIBUTES;
4726 
4727     if(EditAttribute(AttribID, HowMuch))
4728       return true;
4729   }
4730 
4731   return false;
4732 }
4733 
RandomizeReply(long & Said,int Replies)4734 int character::RandomizeReply(long& Said, int Replies)
4735 {
4736   truth NotSaid = false;
4737 
4738   for(int c = 0; c < Replies; ++c)
4739     if(!(Said & (1 << c)))
4740     {
4741       NotSaid = true;
4742       break;
4743     }
4744 
4745   if(!NotSaid)
4746     Said = 0;
4747 
4748   long ToSay;
4749   while(Said & 1 << (ToSay = RAND() % Replies));
4750   Said |= 1 << ToSay;
4751   return ToSay;
4752 }
4753 
DisplayInfo(festring & Msg)4754 void character::DisplayInfo(festring& Msg)
4755 {
4756   if(IsPlayer())
4757   {
4758     Msg << " You are " << GetStandVerb() << " here.";
4759     if(IsBurning())
4760       Msg << " You are on fire.";
4761   }
4762   else
4763   {
4764     Msg << ' ' << GetName(INDEFINITE).CapitalizeCopy() << " is "
4765         << GetStandVerb() << " here. ";
4766 
4767     if(PLAYER->GetAttribute(WISDOM) > 11)
4768     {
4769       Msg << GetPersonalPronoun().CapitalizeCopy() << " is "
4770           << GetHitPointDescription() << ". ";
4771     }
4772 
4773     Msg << GetPersonalPronoun().CapitalizeCopy();
4774     cchar* Separator1 = GetAction() ? "," : " and";
4775     cchar* Separator2 = " and";
4776 
4777     if(GetTeam() == PLAYER->GetTeam())
4778       Msg << " is tame";
4779     else
4780     {
4781       int Relation = GetRelation(PLAYER);
4782 
4783       if(Relation == HOSTILE)
4784         Msg << " is hostile";
4785       else if(Relation == UNCARING)
4786       {
4787         Msg << " does not care about you";
4788         Separator1 = Separator2 = " and is";
4789       }
4790       else
4791         Msg << " is friendly";
4792     }
4793 
4794     if(StateIsActivated(PANIC))
4795     {
4796       Msg << Separator1 << " panicked";
4797       Separator2 = " and";
4798     }
4799 
4800     if(IsBurning())
4801     {
4802       Msg << Separator1 << " is on fire";
4803       Separator2 = " and";
4804     }
4805 
4806     if(GetAction())
4807       Msg << Separator2 << ' ' << GetAction()->GetDescription();
4808 
4809     Msg << '.';
4810   }
4811 }
4812 
TestWalkability()4813 void character::TestWalkability()
4814 {
4815   if(!IsEnabled())
4816     return;
4817 
4818   square* SquareUnder = !game::IsInWilderness()
4819                         ? GetSquareUnder() : PLAYER->GetSquareUnder();
4820 
4821   if(SquareUnder->IsFatalToStay() && !CanMoveOn(SquareUnder))
4822   {
4823     truth Alive = false;
4824 
4825     if(!game::IsInWilderness() || IsPlayer())
4826       for(int d = 0; d < GetNeighbourSquares(); ++d)
4827       {
4828         square* Square = GetNeighbourSquare(d);
4829 
4830         if(Square && CanMoveOn(Square) && IsFreeForMe(Square))
4831         {
4832           if(IsPlayer())
4833             ADD_MESSAGE("%s.", SquareUnder->SurviveMessage(this));
4834           else if(CanBeSeenByPlayer())
4835             ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE), SquareUnder->MonsterSurviveMessage(this));
4836 
4837           Move(Square->GetPos(), true); // actually, this shouldn't be a teleport move
4838           SquareUnder->SurviveEffect(this);
4839           Alive = true;
4840           break;
4841         }
4842       }
4843 
4844     if(!Alive)
4845     {
4846       if(IsPlayer())
4847       {
4848         Remove();
4849         SendToHell();
4850         festring DeathMsg = festring(SquareUnder->DeathMessage(this));
4851         game::AskForKeyPress(DeathMsg + ". [press any key to continue]");
4852         festring Msg = SquareUnder->ScoreEntry(this);
4853         PLAYER->AddScoreEntry(Msg);
4854         game::End(Msg);
4855       }
4856       else
4857       {
4858         if(CanBeSeenByPlayer())
4859           ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE), SquareUnder->MonsterDeathVerb(this));
4860 
4861         Die(0, SquareUnder->ScoreEntry(this), DISALLOW_MSG);
4862       }
4863     }
4864   }
4865 }
4866 
GetSize() const4867 int character::GetSize() const
4868 {
4869   return GetTorso()->GetSize();
4870 }
4871 
4872 // NOTE: Do not reuse the old materials!
SetMainMaterial(material * NewMaterial,int SpecialFlags)4873 material* character::SetMainMaterial(material* NewMaterial, int SpecialFlags)
4874 {
4875   NewMaterial->SetVolume(GetBodyPart(0)->GetMainMaterial()->GetVolume());
4876   delete GetBodyPart(0)->SetMainMaterial(NewMaterial, SpecialFlags);
4877 
4878   for(int c = 1; c < BodyParts; ++c)
4879   {
4880     NewMaterial = NewMaterial->SpawnMore(GetBodyPart(c)->GetMainMaterial()->GetVolume());
4881     delete GetBodyPart(c)->SetMainMaterial(NewMaterial, SpecialFlags);
4882   }
4883 
4884   return NULL;
4885 }
4886 
ChangeMainMaterial(material * NewMaterial,int SpecialFlags)4887 void character::ChangeMainMaterial(material* NewMaterial, int SpecialFlags)
4888 {
4889   delete SetMainMaterial(NewMaterial, SpecialFlags);
4890 }
4891 
SetSecondaryMaterial(material *,int)4892 material* character::SetSecondaryMaterial(material*, int)
4893 {
4894   ABORT("Illegal character::SetSecondaryMaterial call!");
4895   return NULL;
4896 }
4897 
TeleportRandomly(truth Intentional)4898 void character::TeleportRandomly(truth Intentional)
4899 {
4900   v2 TelePos = ERROR_V2;
4901   lsquare* FromSquare = GetLSquareUnder();
4902 
4903   if(StateIsActivated(TELEPORT_LOCK))
4904   {
4905     if(IsPlayer())
4906       ADD_MESSAGE("You flicker for a second.");
4907     else if(CanBeSeenByPlayer())
4908       ADD_MESSAGE("%s flickers for a second.", CHAR_NAME(DEFINITE));
4909 
4910     return;
4911   }
4912 
4913   if(StateIsActivated(TELEPORT_CONTROL))
4914   {
4915     if(IsPlayer())
4916     {
4917       v2 Input = game::PositionQuestion(CONST_S("Where do you wish to teleport? "
4918                                                 "[direction keys move cursor, space accepts]"),
4919                                         GetPos(), &game::TeleportHandler, 0, false);
4920 
4921       if(Input == ERROR_V2) // esc pressed
4922         Input = GetPos();
4923 
4924       lsquare* Square = GetNearLSquare(Input);
4925 
4926       if(CanMoveOn(Square) || game::GoThroughWallsCheatIsActive())
4927       {
4928         if(Square->GetPos() == GetPos())
4929         {
4930           ADD_MESSAGE("You disappear and reappear.");
4931           return;
4932         }
4933 
4934         if(IsFreeForMe(Square))
4935         {
4936           if((Input - GetPos()).GetLengthSquare() <= GetTeleportRangeSquare())
4937           {
4938             EditExperience(INTELLIGENCE, 100, 1 << 10);
4939             TelePos = Input;
4940           }
4941           else
4942             ADD_MESSAGE("You cannot concentrate yourself enough to control a teleport that far.");
4943         }
4944         else
4945         {
4946           character* C = Square->GetCharacter();
4947 
4948           if(C)
4949             ADD_MESSAGE("For a moment you feel very much like %s.", C->CHAR_NAME(INDEFINITE));
4950           else
4951             ADD_MESSAGE("You feel that something weird has happened, but can't really tell what exactly.");
4952         }
4953       }
4954       else
4955         ADD_MESSAGE("You feel like having been hit by something really hard from the inside.");
4956     }
4957     else if(!Intentional)
4958     {
4959       if(IsGoingSomeWhere() && GetLevel()->IsValidPos(GoingTo))
4960       {
4961         v2 Where = GetLevel()->GetNearestFreeSquare(this, GoingTo);
4962 
4963         if(Where != ERROR_V2 && (Where - GetPos()).GetLengthSquare() <= GetTeleportRangeSquare())
4964         {
4965           EditExperience(INTELLIGENCE, 100, 1 << 10);
4966           Where = TelePos;
4967         }
4968       }
4969     }
4970   }
4971   else if(IsPlayer())
4972   {
4973     // This is to prevent uncontrolled teleportation from going unnoticed by players.
4974     game::AskForKeyPress(CONST_S("You teleport! [press any key to continue]"));
4975   }
4976 
4977   if(IsPlayer())
4978     ADD_MESSAGE("A rainbow-colored whirlpool twists the existence around you. "
4979                 "You are sucked through a tunnel piercing a myriad of surreal "
4980                 "universes. Luckily you return to this dimension in one piece.");
4981 
4982   if(TelePos != ERROR_V2)
4983     Move(TelePos, true);
4984   else
4985     Move(GetLevel()->GetRandomSquare(this), true);
4986 
4987   if(!IsPlayer() && CanBeSeenByPlayer())
4988     ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE));
4989 
4990   if(GetAction() && GetAction()->IsVoluntary())
4991     GetAction()->Terminate(false);
4992 
4993   if(IsPlayerAutoPlay())
4994     AutoPlayAIReset(true);
4995 
4996   // There's a small chance that some warp gas/fluid is left behind.
4997   if(FromSquare->IsFlyable() && !RAND_N(1000))
4998   {
4999     if(!RAND_N(100))
5000       FromSquare->AddSmoke(gas::Spawn(TELEPORT_GAS, 50 + RAND() % 100));
5001     else
5002       FromSquare->SpillFluid(this, liquid::Spawn(TELEPORT_FLUID, 50 + RAND() % 50));
5003   }
5004 }
5005 
IsPlayerAutoPlay()5006 truth character::IsPlayerAutoPlay()
5007 {
5008   return IsPlayer() && game::GetAutoPlayMode()>0;
5009 }
5010 
DoDetecting()5011 void character::DoDetecting()
5012 {
5013   if(IsPlayerAutoPlay() || !IsPlayer())
5014     return;
5015 
5016   material* TempMaterial;
5017 
5018   for(;;)
5019   {
5020     festring Temp;
5021 
5022     if(game::DefaultQuestion(Temp, CONST_S("What material do you want to detect?"),
5023                              game::GetDefaultDetectMaterial(), true) == ABORTED)
5024     {
5025       if(game::TruthQuestion(CONST_S("Really cancel? [y/N]")))
5026         return;
5027       else
5028         continue;
5029     }
5030 
5031     TempMaterial = protosystem::CreateMaterialForDetection(Temp);
5032 
5033     if(TempMaterial)
5034       break;
5035     else
5036       game::DrawEverythingNoBlit();
5037   }
5038 
5039   level* Level = GetLevel();
5040   int Squares = Level->DetectMaterial(TempMaterial);
5041 
5042   if(Squares > GetAttribute(INTELLIGENCE) * (25 + RAND() % 51))
5043   {
5044     ADD_MESSAGE("An enormous burst of geographical information overwhelms your consciousness. Your mind cannot cope with it and your memories blur.");
5045     Level->BlurMemory();
5046     BeginTemporaryState(CONFUSED, 1000 + RAND() % 1000);
5047     EditExperience(INTELLIGENCE, -100, 1 << 12);
5048   }
5049   else if(!Squares)
5050   {
5051     ADD_MESSAGE("You feel a sudden urge to imagine the dark void of a starless night sky.");
5052     EditExperience(INTELLIGENCE, 20, 1 << 12);
5053   }
5054   else
5055   {
5056     ADD_MESSAGE("You feel attracted to all things made of %s.", TempMaterial->GetName(false, false).CStr());
5057     game::SetDrawMapOverlay(ivanconfig::IsShowMapAtDetectMaterial());
5058     game::PositionQuestion(CONST_S("Detecting material [direction keys move cursor, space exits]"), GetPos(), 0, 0, false);
5059     game::SetDrawMapOverlay(false);
5060     EditExperience(INTELLIGENCE, 30, 1 << 12);
5061   }
5062 
5063   delete TempMaterial;
5064   Level->CalculateLuminances();
5065   game::SendLOSUpdateRequest();
5066 }
5067 
RestoreHP()5068 void character::RestoreHP()
5069 {
5070   doforbodyparts()(this, &bodypart::FastRestoreHP);
5071   HP = MaxHP;
5072 }
5073 
RestoreLivingHP()5074 void character::RestoreLivingHP()
5075 {
5076   HP = 0;
5077 
5078   for(int c = 0; c < BodyParts; ++c)
5079   {
5080     bodypart* BodyPart = GetBodyPart(c);
5081 
5082     if(BodyPart && BodyPart->CanRegenerate())
5083     {
5084       BodyPart->FastRestoreHP();
5085       HP += BodyPart->GetHP();
5086     }
5087   }
5088 }
5089 
IsBurnt() const5090 truth character::IsBurnt() const
5091 {
5092   for(int c = 0; c < BodyParts; ++c)
5093   {
5094     bodypart* BodyPart = GetBodyPart(c);
5095 
5096     if(BodyPart && BodyPart->IsBurnt())
5097     {
5098       return true;
5099     }
5100   }
5101   return false;
5102 }
5103 
AllowDamageTypeBloodSpill(int Type)5104 truth character::AllowDamageTypeBloodSpill(int Type)
5105 {
5106   switch(Type&0xFFF)
5107   {
5108    case PHYSICAL_DAMAGE:
5109    case SOUND:
5110    case ENERGY:
5111     return true;
5112    case ACID:
5113    case FIRE:
5114    case DRAIN:
5115    case POISON:
5116    case ELECTRICITY:
5117    case MUSTARD_GAS_DAMAGE:
5118    case PSI:
5119     return false;
5120   }
5121 
5122   ABORT("Unknown blood effect destroyed the dungeon!");
5123   return false;
5124 }
5125 
GetPosSafely() const5126 v2 character::GetPosSafely() const
5127 {
5128   square* sqr = GetSquareUnderSafely();
5129   if(sqr!=NULL)return sqr->GetPos();
5130   return v2();
5131 }
5132 
GetSquareUnderSafely() const5133 square* character::GetSquareUnderSafely() const
5134 { //prevents crash if polymorphed (here at least)
5135   if(SquareUnder[0]!=NULL){DBGLN;
5136     return SquareUnder[0];
5137   }DBGLN;
5138 
5139   if(IsPolymorphed()){DBGLN;
5140     character* pb = GetPolymorphBackup();
5141     if(pb!=NULL && pb->SquareUnder[0]!=NULL){ DBG1(pb->GetNameSingular().CStr()); //TODO to use square under index here may cause inconsistencies?
5142       return pb->SquareUnder[0];
5143     }
5144   }DBGLN;
5145 
5146   return NULL;
5147 }
5148 
GetStackUnderSafely() const5149 stack* character::GetStackUnderSafely() const
5150 {
5151   square* sqr = GetSquareUnderSafely();
5152   lsquare* lsqr = dynamic_cast<lsquare*>(sqr);
5153   if(lsqr!=NULL)return lsqr->GetStack();
5154   return NULL;
5155 }
5156 
5157 
5158 /* Returns truly done damage */
5159 
ReceiveBodyPartDamage(character * Damager,int Damage,int Type,int BodyPartIndex,int Direction,truth PenetrateResistance,truth Critical,truth ShowNoDamageMsg,truth CaptureBodyPart)5160 int character::ReceiveBodyPartDamage(character* Damager, int Damage, int Type, int BodyPartIndex,
5161                                      int Direction, truth PenetrateResistance, truth Critical,
5162                                      truth ShowNoDamageMsg, truth CaptureBodyPart)
5163 {
5164   bodypart* BodyPart = GetBodyPart(BodyPartIndex);
5165 
5166   if(!BodyPart)
5167     return 0;
5168 
5169   if(!Damager || Damager->AttackMayDamageArmor())
5170     BodyPart->DamageArmor(Damager, Damage, Type);
5171 
5172   if(!PenetrateResistance)
5173     Damage -= (BodyPart->GetTotalResistance(Type) >> 1) + RAND() % ((BodyPart->GetTotalResistance(Type) >> 1) + 1);
5174 
5175   if(Damage < 1)
5176   {
5177     if(Critical)
5178       Damage = 1;
5179     else
5180     {
5181       if(ShowNoDamageMsg)
5182       {
5183         if(IsPlayer())
5184           ADD_MESSAGE("You are not hurt.");
5185         else if(CanBeSeenByPlayer())
5186           ADD_MESSAGE("%s is not hurt.", GetPersonalPronoun().CStr());
5187       }
5188 
5189       return 0;
5190     }
5191   }
5192 
5193   if(BodyPart->GetMainMaterial())
5194   {
5195     if(BodyPart->CanBeBurned()
5196        && (BodyPart->GetMainMaterial()->GetInteractionFlags() & CAN_BURN)
5197        && !BodyPart->IsBurning()
5198        && Type & FIRE)
5199     {
5200       BodyPart->TestActivationEnergy(Damage);
5201     }
5202     else if(BodyPart->IsBurning() && Type & FIRE)
5203       BodyPart->GetMainMaterial()->AddToThermalEnergy(Damage);
5204   }
5205 
5206   if(Critical && AllowDamageTypeBloodSpill(Type) && !game::IsInWilderness())
5207   {
5208     BodyPart->SpillBlood(2 + (RAND() & 1));
5209 
5210     for(int d = 0; d < GetNeighbourSquares(); ++d)
5211     {
5212       lsquare* Square = GetNeighbourLSquare(d);
5213 
5214       if(Square && Square->IsFlyable())
5215         BodyPart->SpillBlood(1, Square->GetPos());
5216     }
5217   }
5218 
5219   if(BodyPart->ReceiveDamage(Damager, Damage, Type, Direction)
5220      && BodyPartCanBeSevered(BodyPartIndex))
5221   {
5222     if(DamageTypeDestroysBodyPart(Type))
5223     {
5224       if(IsPlayer())
5225         ADD_MESSAGE("Your %s is destroyed!", BodyPart->GetBodyPartName().CStr());
5226       else if(CanBeSeenByPlayer())
5227         ADD_MESSAGE("%s %s is destroyed!", GetPossessivePronoun().CStr(), BodyPart->GetBodyPartName().CStr());
5228 
5229       GetBodyPart(BodyPartIndex)->DropEquipment();
5230       item* Severed = SevereBodyPart(BodyPartIndex);
5231 
5232       if(Severed)
5233         Severed->DestroyBodyPart(!game::IsInWilderness() ? GetStackUnder() : GetStack());
5234 
5235       SendNewDrawRequest();
5236 
5237       if(IsPlayer())
5238         game::AskForKeyPress(CONST_S("Bodypart destroyed! [press any key to continue]"));
5239     }
5240     else
5241     {
5242       if(IsPlayer())
5243         ADD_MESSAGE("Your %s is severed off!", BodyPart->GetBodyPartName().CStr());
5244       else if(CanBeSeenByPlayer())
5245         ADD_MESSAGE("%s %s is severed off!", GetPossessivePronoun().CStr(), BodyPart->GetBodyPartName().CStr());
5246 
5247       item* Severed = SevereBodyPart(BodyPartIndex);
5248       SendNewDrawRequest();
5249 
5250       if(Severed)
5251       {
5252         if(CaptureBodyPart)
5253           Damager->GetLSquareUnder()->AddItem(Severed);
5254         else if(!game::IsInWilderness())
5255         {
5256           /** No multi-tile humanoid support! */
5257 
5258           stack* su = GetStackUnderSafely();
5259           if(su){
5260             su->AddItem(Severed);
5261 
5262             if(Direction != YOURSELF)
5263               Severed->Fly(0, Direction, Damage);
5264           }else{
5265             /**
5266              * this may happen when polymorphing (tests were made as a snake) during an explosion that severe body parts
5267              */
5268             GetStack()->AddItem(Severed); DBGLN;
5269           }
5270         }
5271         else
5272           GetStack()->AddItem(Severed);
5273 
5274         Severed->DropEquipment();
5275       }
5276       else if(IsPlayer() || CanBeSeenByPlayer())
5277         ADD_MESSAGE("It vanishes.");
5278 
5279       if(IsPlayer())
5280         game::AskForKeyPress(CONST_S("Bodypart severed! [press any key to continue]"));
5281     }
5282 
5283     if(CanPanicFromSeveredBodyPart()
5284        && RAND() % 100 < GetPanicLevel()
5285        && !StateIsActivated(PANIC)
5286        && !StateIsActivated(FEARLESS)
5287        && !IsDead())
5288       BeginTemporaryState(PANIC, 1000 + RAND() % 1001);
5289 
5290     SpecialBodyPartSeverReaction();
5291   }
5292 
5293   if(!IsDead())
5294     CheckPanic(500);
5295 
5296   return Damage;
5297 }
5298 
5299 /* Returns 0 if bodypart disappears */
5300 
SevereBodyPart(int BodyPartIndex,truth ForceDisappearance,stack * EquipmentDropStack)5301 item* character::SevereBodyPart(int BodyPartIndex, truth ForceDisappearance, stack* EquipmentDropStack)
5302 {
5303   bodypart* BodyPart = GetBodyPart(BodyPartIndex);
5304 
5305   if(StateIsActivated(LEPROSY))
5306     BodyPart->GetMainMaterial()->SetIsInfectedByLeprosy(true);
5307 
5308   if(BodyPartIndex == HEAD_INDEX && StateIsActivated(PARASITE_MIND_WORM))
5309     DeActivateTemporaryState(PARASITE_MIND_WORM);
5310 
5311   if(ForceDisappearance
5312      || BodyPartsDisappearWhenSevered()
5313      || StateIsActivated(POLYMORPHED)
5314      || game::AllBodyPartsVanish())
5315   {
5316     BodyPart->DropEquipment(EquipmentDropStack);
5317     BodyPart->RemoveFromSlot();
5318     CalculateAttributeBonuses();
5319     CalculateBattleInfo();
5320     BodyPart->SendToHell();
5321     SignalPossibleTransparencyChange();
5322     RemoveTraps(BodyPartIndex);
5323     return 0;
5324   }
5325   else
5326   {
5327     BodyPart->SetOwnerDescription("of " + GetName(INDEFINITE));
5328     BodyPart->SetIsUnique(LeftOversAreUnique());
5329     UpdateBodyPartPicture(BodyPartIndex, true);
5330     BodyPart->RemoveFromSlot();
5331     BodyPart->RandomizePosition();
5332     CalculateAttributeBonuses();
5333     CalculateBattleInfo();
5334     BodyPart->Enable();
5335     SignalPossibleTransparencyChange();
5336     RemoveTraps(BodyPartIndex);
5337     return BodyPart;
5338   }
5339 }
5340 
IgniteBodyPart(int BodyPartIndex,int Damage)5341 void character::IgniteBodyPart(int BodyPartIndex, int Damage)
5342 {
5343   bodypart* BodyPart = GetBodyPart(BodyPartIndex);
5344   BodyPart->TestActivationEnergy(Damage);
5345 }
5346 
5347 /* The second int is actually TargetFlags, which is not used here, but seems to be used
5348    in humanoid::ReceiveDamage. Returns true if the character really receives damage */
5349 
ReceiveDamage(character * Damager,int Damage,int Type,int,int Direction,truth,truth PenetrateArmor,truth Critical,truth ShowMsg)5350 truth character::ReceiveDamage(character* Damager, int Damage, int Type, int, int Direction,
5351                                truth, truth PenetrateArmor, truth Critical, truth ShowMsg)
5352 {
5353   truth Affected = ReceiveBodyPartDamage(Damager, Damage, Type, 0, Direction, PenetrateArmor, Critical, ShowMsg);
5354 
5355   if(DamageTypeAffectsInventory(Type))
5356   {
5357     for(int c = 0; c < GetEquipments(); ++c)
5358     {
5359       item* Equipment = GetEquipment(c);
5360 
5361       if(Equipment)
5362         Equipment->ReceiveDamage(Damager, Damage, Type);
5363     }
5364 
5365     GetStack()->ReceiveDamage(Damager, Damage, Type);
5366   }
5367 
5368   return Affected;
5369 }
5370 
GetDescription(int Case) const5371 festring character::GetDescription(int Case) const
5372 {
5373   if(IsPlayer())
5374     return CONST_S("you");
5375   else if(CanBeSeenByPlayer())
5376     return GetName(Case);
5377   else
5378     return CONST_S("something");
5379 }
5380 
GetPersonalPronoun(truth PlayersView) const5381 festring character::GetPersonalPronoun(truth PlayersView) const
5382 {
5383   if(IsPlayer() && PlayersView)
5384     return CONST_S("you");
5385   else if(GetSex() == UNDEFINED
5386           || (PlayersView && !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode()))
5387     return CONST_S("it");
5388   else if(GetSex() == MALE)
5389     return CONST_S("he");
5390   else
5391     return CONST_S("she");
5392 }
5393 
GetPossessivePronoun(truth PlayersView) const5394 festring character::GetPossessivePronoun(truth PlayersView) const
5395 {
5396   if(IsPlayer() && PlayersView)
5397     return CONST_S("your");
5398   else if(GetSex() == UNDEFINED
5399           || (PlayersView && !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode()))
5400     return CONST_S("its");
5401   else if(GetSex() == MALE)
5402     return CONST_S("his");
5403   else
5404     return CONST_S("her");
5405 }
5406 
GetObjectPronoun(truth PlayersView) const5407 festring character::GetObjectPronoun(truth PlayersView) const
5408 {
5409   if(IsPlayer() && PlayersView)
5410     return CONST_S("you");
5411   else if(GetSex() == UNDEFINED
5412           || (PlayersView && !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode()))
5413     return CONST_S("it");
5414   else if(GetSex() == MALE)
5415     return CONST_S("him");
5416   else
5417     return CONST_S("her");
5418 }
5419 
AddName(festring & String,int Case) const5420 void character::AddName(festring& String, int Case) const
5421 {
5422   if(AssignedName.IsEmpty())
5423     id::AddName(String, Case);
5424   else if(!(Case & PLURAL))
5425   {
5426     if(!ShowClassDescription())
5427       String << AssignedName;
5428     else
5429     {
5430       String << AssignedName << ' ';
5431       id::AddName(String, (Case|ARTICLE_BIT)&~INDEFINE_BIT);
5432     }
5433   }
5434   else
5435   {
5436     id::AddName(String, Case);
5437     String << " named " << AssignedName;
5438   }
5439 }
5440 
GetWorldShapeDescription() const5441 festring character::GetWorldShapeDescription() const
5442 {
5443   int Shape = game::GetWorldShape();
5444 
5445   if(Shape == 0)
5446     return CONST_S("square pancake"); // Flat
5447   else if(Shape == 1)
5448     return CONST_S("square brandy snap"); // Cylinder
5449   else if(Shape == 2)
5450     return CONST_S("square doughnut"); // Toroidal
5451   else
5452     return CONST_S("square pancake"); // Default
5453 }
5454 
GetHungerState() const5455 int character::GetHungerState() const
5456 {
5457   if(!UsesNutrition())
5458     return NOT_HUNGRY;
5459   else if(GetNP() > OVER_FED_LEVEL)
5460     return OVER_FED;
5461   else if(GetNP() > BLOATED_LEVEL)
5462     return BLOATED;
5463   else if(GetNP() > SATIATED_LEVEL)
5464     return SATIATED;
5465   else if(GetNP() > NOT_HUNGER_LEVEL)
5466     return NOT_HUNGRY;
5467   else if(GetNP() > HUNGER_LEVEL)
5468     return HUNGRY;
5469   else if(GetNP() > VERY_HUNGER_LEVEL)
5470     return VERY_HUNGRY;
5471   else
5472     return STARVING;
5473 }
5474 
CanConsume(material * Material) const5475 truth character::CanConsume(material* Material) const
5476 {
5477   return GetConsumeFlags() & Material->GetConsumeType();
5478 }
5479 
SetTemporaryStateCounter(long State,int What)5480 void character::SetTemporaryStateCounter(long State, int What)
5481 {
5482   for(int c = 0; c < STATES; ++c)
5483     if((1 << c) & State)
5484       TemporaryStateCounter[c] = What;
5485 }
5486 
EditTemporaryStateCounter(long State,int What)5487 void character::EditTemporaryStateCounter(long State, int What)
5488 {
5489   for(int c = 0; c < STATES; ++c)
5490     if((1 << c) & State)
5491       TemporaryStateCounter[c] += What;
5492 }
5493 
GetTemporaryStateCounter(long State) const5494 int character::GetTemporaryStateCounter(long State) const
5495 {
5496   for(int c = 0; c < STATES; ++c)
5497     if((1 << c) & State)
5498       return TemporaryStateCounter[c];
5499 
5500   ABORT("Illegal GetTemporaryStateCounter request!");
5501   return 0;
5502 }
5503 
CheckKick() const5504 truth character::CheckKick() const
5505 {
5506   if(!CanKick())
5507   {
5508     if(IsPlayer())
5509       ADD_MESSAGE("This race can't kick.");
5510 
5511     return false;
5512   }
5513   else
5514     return true;
5515 }
5516 
GetResistance(int Type) const5517 int character::GetResistance(int Type) const
5518 {
5519   switch(Type&0xFFF)
5520   {
5521    case PHYSICAL_DAMAGE:
5522    case DRAIN:
5523    case MUSTARD_GAS_DAMAGE:
5524    case PSI:
5525     return 0;
5526    case ENERGY: return GetEnergyResistance();
5527    case FIRE: return GetFireResistance();
5528    case POISON: return GetPoisonResistance();
5529    case ELECTRICITY: return GetElectricityResistance();
5530    case ACID: return GetAcidResistance();
5531    case SOUND: return GetSoundResistance();
5532   }
5533 
5534   ABORT("Resistance lack detected!");
5535   return 0;
5536 }
5537 
Regenerate()5538 void character::Regenerate()
5539 {
5540   if(StateIsActivated(REGENERATION) && !(RAND() % 3000))
5541   {
5542     bodypart* NewBodyPart = GenerateRandomBodyPart();
5543 
5544     if(NewBodyPart)
5545     {
5546       NewBodyPart->SetHP(1);
5547 
5548       if(IsPlayer())
5549         ADD_MESSAGE("You grow a new %s.", NewBodyPart->GetBodyPartName().CStr());
5550       else if(CanBeSeenByPlayer())
5551         ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE), NewBodyPart->GetBodyPartName().CStr());
5552     }
5553   }
5554 
5555   if(HP == MaxHP)
5556     return;
5557 
5558   long RegenerationBonus = 0;
5559   truth NoHealableBodyParts = true;
5560 
5561   for(int c = 0; c < BodyParts; ++c)
5562   {
5563     bodypart* BodyPart = GetBodyPart(c);
5564 
5565     if(BodyPart && BodyPart->CanRegenerate())
5566     {
5567       RegenerationBonus += BodyPart->GetMaxHP();
5568 
5569       if(NoHealableBodyParts && BodyPart->GetHP() < BodyPart->GetMaxHP())
5570         NoHealableBodyParts = false;
5571     }
5572   }
5573 
5574   if(!RegenerationBonus || NoHealableBodyParts)
5575     return;
5576 
5577   RegenerationBonus *= (50 + GetAttribute(ENDURANCE));
5578 
5579   if(StateIsActivated(REGENERATION))
5580   {
5581     RegenerationBonus *= GetAttribute(ENDURANCE) /*<< 1*/;
5582   }
5583 
5584   if(Action && Action->IsRest())
5585   {
5586     if(SquaresUnder == 1)
5587       RegenerationBonus *= GetSquareUnder()->GetRestModifier() << 1;
5588     else
5589     {
5590       int Lowest = GetSquareUnder(0)->GetRestModifier();
5591 
5592       for(int c = 1; c < GetSquaresUnder(); ++c)
5593       {
5594         int Mod = GetSquareUnder(c)->GetRestModifier();
5595 
5596         if(Mod < Lowest)
5597           Lowest = Mod;
5598       }
5599 
5600       RegenerationBonus *= Lowest << 1;
5601     }
5602   }
5603 
5604   RegenerationCounter += RegenerationBonus;
5605 
5606   while(RegenerationCounter > 1250000)
5607   {
5608     bodypart* BodyPart = HealHitPoint();
5609 
5610     if(!BodyPart)
5611       break;
5612 
5613     EditNP(-Max(7500 / MaxHP, 1));
5614     RegenerationCounter -= 1250000;
5615     int HP = BodyPart->GetHP();
5616     EditExperience(ENDURANCE, Min(1000 * BodyPart->GetMaxHP() / (HP * HP), 300), 1000);
5617   }
5618 }
5619 
PrintInfo() const5620 void character::PrintInfo() const
5621 {
5622   felist Info(CONST_S("Information about ") + GetName(DEFINITE));
5623 
5624   for(int c = 0; c < GetEquipments(); ++c)
5625   {
5626     item* Equipment = GetEquipment(c);
5627 
5628     if((EquipmentEasilyRecognized(c) || game::WizardModeIsActive()) && Equipment)
5629     {
5630       int ImageKey = game::AddToItemDrawVector(itemvector(1, Equipment));
5631       Info.AddEntry(festring(GetEquipmentName(c)) + ": " + Equipment->GetName(INDEFINITE),
5632                     LIGHT_GRAY, 0, ImageKey, true);
5633     }
5634   }
5635 
5636   if(Info.IsEmpty())
5637     ADD_MESSAGE("There's nothing special to tell about %s.", CHAR_NAME(DEFINITE));
5638   else
5639   {
5640     game::SetStandardListAttributes(Info);
5641     Info.SetEntryDrawer(game::ItemEntryDrawer);
5642     Info.Draw();
5643   }
5644 
5645   game::ClearItemDrawVector();
5646 }
5647 
TryToRiseFromTheDead()5648 truth character::TryToRiseFromTheDead()
5649 {
5650   for(int c = 0; c < BodyParts; ++c)
5651   {
5652     bodypart* BodyPart = GetBodyPart(c);
5653 
5654     if(BodyPart)
5655     {
5656       BodyPart->ResetSpoiling();
5657 
5658       if(BodyPart->CanRegenerate() || BodyPart->GetHP() < 1)
5659         BodyPart->SetHP(1);
5660     }
5661   }
5662 
5663   ResetStates();
5664   return true;
5665 }
5666 
RaiseTheDead(character *)5667 truth character::RaiseTheDead(character*)
5668 {
5669   truth Useful = false;
5670 
5671   for(int c = 0; c < BodyParts; ++c)
5672   {
5673     bodypart* BodyPart = GetBodyPart(c);
5674 
5675     if(!BodyPart && CanCreateBodyPart(c))
5676     {
5677       CreateBodyPart(c)->SetHP(1);
5678 
5679       if(IsPlayer())
5680         ADD_MESSAGE("Suddenly you grow a new %s.", GetBodyPartName(c).CStr());
5681       else if(CanBeSeenByPlayer())
5682         ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE), GetBodyPartName(c).CStr());
5683 
5684       Useful = true;
5685     }
5686     else if(BodyPart && BodyPart->CanRegenerate() && BodyPart->GetHP() < 1)
5687       BodyPart->SetHP(1);
5688   }
5689 
5690   if(!Useful)
5691   {
5692     if(IsPlayer())
5693       ADD_MESSAGE("You shudder.");
5694     else if(CanBeSeenByPlayer())
5695       ADD_MESSAGE("%s shudders.", CHAR_NAME(DEFINITE));
5696   }
5697 
5698   return Useful;
5699 }
5700 
SetSize(int Size)5701 void character::SetSize(int Size)
5702 {
5703   for(int c = 0; c < BodyParts; ++c)
5704   {
5705     bodypart* BodyPart = GetBodyPart(c);
5706 
5707     if(BodyPart)
5708       BodyPart->SetSize(GetBodyPartSize(c, Size));
5709   }
5710 }
5711 
GetBodyPartSize(int I,int TotalSize) const5712 long character::GetBodyPartSize(int I, int TotalSize) const
5713 {
5714   if(I == TORSO_INDEX)
5715     return TotalSize;
5716   else
5717   {
5718     ABORT("Weird bodypart size request for a character!");
5719     return 0;
5720   }
5721 }
5722 
GetBodyPartVolume(int I) const5723 long character::GetBodyPartVolume(int I) const
5724 {
5725   if(I == TORSO_INDEX)
5726     return GetTotalVolume();
5727   else
5728   {
5729     ABORT("Weird bodypart volume request for a character!");
5730     return 0;
5731   }
5732 }
5733 
CreateBodyParts(int SpecialFlags)5734 void character::CreateBodyParts(int SpecialFlags)
5735 {
5736   for(int c = 0; c < BodyParts; ++c)
5737     if(CanCreateBodyPart(c))
5738       CreateBodyPart(c, SpecialFlags);
5739 }
5740 
RestoreBodyParts()5741 void character::RestoreBodyParts()
5742 {
5743   for(int c = 0; c < BodyParts; ++c)
5744     if(!GetBodyPart(c) && CanCreateBodyPart(c))
5745       CreateBodyPart(c);
5746 }
5747 
UpdatePictures()5748 void character::UpdatePictures()
5749 {
5750   if(!PictureUpdatesAreForbidden())
5751     for(int c = 0; c < BodyParts; ++c)
5752       UpdateBodyPartPicture(c, false);
5753 }
5754 
MakeBodyPart(int I) const5755 bodypart* character::MakeBodyPart(int I) const
5756 {
5757   if(I == TORSO_INDEX)
5758     return normaltorso::Spawn(0, NO_MATERIALS);
5759   else
5760   {
5761     ABORT("Weird bodypart to make for a character!");
5762     return 0;
5763   }
5764 }
5765 
CreateBodyPart(int I,int SpecialFlags)5766 bodypart* character::CreateBodyPart(int I, int SpecialFlags)
5767 {
5768   bodypart* BodyPart = MakeBodyPart(I);
5769   material* Material = CreateBodyPartMaterial(I, GetBodyPartVolume(I));
5770   BodyPart->InitMaterials(Material, false);
5771   BodyPart->SetSize(GetBodyPartSize(I, GetTotalSize()));
5772   BodyPart->SetBloodMaterial(GetBloodMaterial());
5773   BodyPart->SetNormalMaterial(Material->GetConfig());
5774   SetBodyPart(I, BodyPart);
5775   BodyPart->InitSpecialAttributes();
5776 
5777   if(!(SpecialFlags & NO_PIC_UPDATE))
5778     UpdateBodyPartPicture(I, false);
5779 
5780   if(!IsInitializing())
5781   {
5782     CalculateBattleInfo();
5783     SendNewDrawRequest();
5784     SignalPossibleTransparencyChange();
5785   }
5786 
5787   return BodyPart;
5788 }
5789 
GetBodyPartBitmapPos(int I,truth) const5790 v2 character::GetBodyPartBitmapPos(int I, truth) const
5791 {
5792   if(I == TORSO_INDEX)
5793     return GetTorsoBitmapPos();
5794   else
5795   {
5796     ABORT("Weird bodypart BitmapPos request for a character!");
5797     return v2();
5798   }
5799 }
5800 
UpdateBodyPartPicture(int I,truth Severed)5801 void character::UpdateBodyPartPicture(int I, truth Severed)
5802 {
5803   bodypart* BP = GetBodyPart(I);
5804 
5805   if(BP)
5806   {
5807     BP->SetBitmapPos(GetBodyPartBitmapPos(I, Severed));
5808     BP->GetMainMaterial()->SetSkinColor(GetBodyPartColorA(I, Severed));
5809     BP->GetMainMaterial()->SetSkinColorIsSparkling(GetBodyPartSparkleFlags(I) & SPARKLING_A);
5810     BP->SetMaterialColorB(GetBodyPartColorB(I, Severed));
5811     BP->SetMaterialColorC(GetBodyPartColorC(I, Severed));
5812     BP->SetMaterialColorD(GetBodyPartColorD(I, Severed));
5813     BP->SetSparkleFlags(GetBodyPartSparkleFlags(I));
5814     BP->SetSpecialFlags(GetSpecialBodyPartFlags(I));
5815     BP->SetWobbleData(GetBodyPartWobbleData(I));
5816     BP->UpdatePictures();
5817   }
5818 }
5819 
LoadDataBaseStats()5820 void character::LoadDataBaseStats()
5821 {
5822   for(int c = 0; c < BASE_ATTRIBUTES; ++c)
5823   {
5824     BaseExperience[c] = DataBase->NaturalExperience[c];
5825 
5826     if(BaseExperience[c])
5827       LimitRef(BaseExperience[c], MIN_EXP, MAX_EXP);
5828   }
5829 
5830   SetMoney(GetDefaultMoney());
5831   SetNewVomitMaterial(GetVomitMaterial());
5832   const fearray<long>& Skills = GetKnownCWeaponSkills();
5833 
5834   if(Skills.Size)
5835   {
5836     const fearray<long>& Hits = GetCWeaponSkillHits();
5837 
5838     if(Hits.Size == 1)
5839     {
5840       for(uint c = 0; c < Skills.Size; ++c)
5841         if(Skills[c] < AllowedWeaponSkillCategories)
5842           CWeaponSkill[Skills[c]].AddHit(Hits[0] * 100);
5843     }
5844     else if(Hits.Size == Skills.Size)
5845     {
5846       for(uint c = 0; c < Skills.Size; ++c)
5847         if(Skills[c] < AllowedWeaponSkillCategories)
5848           CWeaponSkill[Skills[c]].AddHit(Hits[c] * 100);
5849     }
5850     else
5851       ABORT("Illegal weapon skill hit array size detected!");
5852   }
5853 }
5854 
SpawnAndLoad(inputfile & SaveFile) const5855 character* characterprototype::SpawnAndLoad(inputfile& SaveFile) const
5856 {
5857   character* Char = Spawner(0, LOAD);
5858   Char->Load(SaveFile);
5859   Char->CalculateAll();
5860   return Char;
5861 }
5862 
Initialize(int NewConfig,int SpecialFlags)5863 void character::Initialize(int NewConfig, int SpecialFlags)
5864 {
5865   Flags |= C_INITIALIZING|C_IN_NO_MSG_MODE;
5866   CalculateBodyParts();
5867   CalculateAllowedWeaponSkillCategories();
5868   CalculateSquaresUnder();
5869   BodyPartSlot = new bodypartslot[BodyParts];
5870   OriginalBodyPartID = new std::list<ulong>[BodyParts];
5871   CWeaponSkill = new cweaponskill[AllowedWeaponSkillCategories];
5872   SquareUnder = new square*[SquaresUnder];
5873 
5874   if(SquaresUnder == 1)
5875     *SquareUnder = 0;
5876   else
5877     memset(SquareUnder, 0, SquaresUnder * sizeof(square*));
5878 
5879   int c;
5880 
5881   for(c = 0; c < BodyParts; ++c)
5882     BodyPartSlot[c].SetMaster(this);
5883 
5884   if(!(SpecialFlags & LOAD))
5885   {
5886     ID = game::CreateNewCharacterID(this);
5887     databasecreator<character>::InstallDataBase(this, NewConfig);
5888     LoadDataBaseStats();
5889     TemporaryState |= GetClassStates();
5890 
5891     if(TemporaryState)
5892       for(c = 0; c < STATES; ++c)
5893         if(TemporaryState & (1 << c))
5894           TemporaryStateCounter[c] = PERMANENT;
5895 
5896     CreateBodyParts(SpecialFlags | NO_PIC_UPDATE);
5897     InitSpecialAttributes();
5898     CommandFlags = GetDefaultCommandFlags();
5899 
5900     if(GetAttribute(INTELLIGENCE, false) < 8) // gum
5901       CommandFlags &= ~DONT_CONSUME_ANYTHING_VALUABLE;
5902 
5903     if(!GetDefaultName().IsEmpty())
5904       SetAssignedName(GetDefaultName());
5905   }
5906 
5907   if(!(SpecialFlags & LOAD))
5908     PostConstruct();
5909 
5910   if(!(SpecialFlags & LOAD))
5911   {
5912     if(!(SpecialFlags & NO_EQUIPMENT))
5913       CreateInitialEquipment((SpecialFlags & NO_EQUIPMENT_PIC_UPDATE) >> 1);
5914 
5915     if(!(SpecialFlags & NO_PIC_UPDATE))
5916       UpdatePictures();
5917 
5918     CalculateAll();
5919     RestoreHP();
5920     RestoreStamina();
5921   }
5922 
5923   Flags &= ~(C_INITIALIZING|C_IN_NO_MSG_MODE);
5924 }
5925 
TeleportNear(character * Caller)5926 truth character::TeleportNear(character* Caller)
5927 {
5928   v2 Where = GetLevel()->GetNearestFreeSquare(this, Caller->GetPos());
5929 
5930   if(Where == ERROR_V2)
5931     return false;
5932 
5933   Move(Where, true);
5934   return true;
5935 }
5936 
ReceiveHeal(long Amount)5937 void character::ReceiveHeal(long Amount)
5938 {
5939   int c;
5940 
5941   if(IsBurnt())
5942     HealBurntBodyParts(Amount);
5943 
5944   for(c = 0; c < Amount / 10; ++c)
5945     if(!HealHitPoint())
5946       break;
5947 
5948   Amount -= c * 10;
5949 
5950   if(RAND() % 10 < Amount)
5951     HealHitPoint();
5952 
5953   if(Amount >= 1000 || RAND() % 1000 < Amount)
5954   {
5955     bodypart* NewBodyPart = GenerateRandomBodyPart();
5956 
5957     if(!NewBodyPart)
5958       return;
5959 
5960     NewBodyPart->SetHP(1);
5961 
5962     if(IsPlayer())
5963       ADD_MESSAGE("You grow a new %s.", NewBodyPart->GetBodyPartName().CStr());
5964     else if(CanBeSeenByPlayer())
5965       ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE), NewBodyPart->GetBodyPartName().CStr());
5966   }
5967 }
5968 
AddHealingLiquidConsumeEndMessage() const5969 void character::AddHealingLiquidConsumeEndMessage() const
5970 {
5971   if(IsPlayer())
5972     ADD_MESSAGE("You feel better.");
5973   else if(CanBeSeenByPlayer())
5974     ADD_MESSAGE("%s looks healthier.", CHAR_NAME(DEFINITE));
5975 }
5976 
ReceiveSchoolFood(long SizeOfEffect)5977 void character::ReceiveSchoolFood(long SizeOfEffect)
5978 {
5979   SizeOfEffect += RAND() % SizeOfEffect;
5980 
5981   if(SizeOfEffect >= 250)
5982     VomitAtRandomDirection(SizeOfEffect);
5983 
5984   if(!(RAND() % 3) && SizeOfEffect >= 500
5985      && EditAttribute(ENDURANCE, SizeOfEffect / 500))
5986   {
5987     if(IsPlayer())
5988       ADD_MESSAGE("You gain a little bit of toughness for surviving this stuff.");
5989     else if(CanBeSeenByPlayer())
5990       ADD_MESSAGE("Suddenly %s looks tougher.", CHAR_NAME(DEFINITE));
5991   }
5992 
5993   BeginTemporaryState(POISONED, (SizeOfEffect >> 1));
5994 }
5995 
AddSchoolFoodConsumeEndMessage() const5996 void character::AddSchoolFoodConsumeEndMessage() const
5997 {
5998   if(IsPlayer())
5999     ADD_MESSAGE("Yuck! This stuff tasted like vomit and old mousepads.");
6000 }
6001 
AddSchoolFoodHitMessage() const6002 void character::AddSchoolFoodHitMessage() const
6003 {
6004   if(IsPlayer())
6005     ADD_MESSAGE("Yuck! This stuff feels like vomit and old mousepads.");
6006 }
6007 
ReceiveNutrition(long SizeOfEffect)6008 void character::ReceiveNutrition(long SizeOfEffect)
6009 {
6010   EditNP(SizeOfEffect);
6011 }
6012 
ReceiveOmmelBlood(long Amount)6013 void character::ReceiveOmmelBlood(long Amount)
6014 {
6015   EditExperience(WILL_POWER, 500, Amount << 4);
6016   EditExperience(MANA, 500, Amount << 4);
6017 
6018   if(IsPlayer())
6019     game::DoEvilDeed(Amount / 25);
6020 }
6021 
ReceiveOmmelUrine(long Amount)6022 void character::ReceiveOmmelUrine(long Amount)
6023 {
6024   EditExperience(ARM_STRENGTH, 500, Amount << 4);
6025   EditExperience(LEG_STRENGTH, 500, Amount << 4);
6026 
6027   if(IsPlayer())
6028     game::DoEvilDeed(Amount / 25);
6029 }
6030 
ReceiveOmmelCerumen(long Amount)6031 void character::ReceiveOmmelCerumen(long Amount)
6032 {
6033   EditExperience(INTELLIGENCE, 500, Amount << 5);
6034   EditExperience(WISDOM, 500, Amount << 5);
6035 
6036   if(IsPlayer())
6037     game::DoEvilDeed(Amount / 25);
6038 }
6039 
ReceiveOmmelSweat(long Amount)6040 void character::ReceiveOmmelSweat(long Amount)
6041 {
6042   EditExperience(AGILITY, 500, Amount << 4);
6043   EditExperience(DEXTERITY, 500, Amount << 4);
6044   RestoreStamina();
6045 
6046   if(IsPlayer())
6047     game::DoEvilDeed(Amount / 25);
6048 }
6049 
ReceiveOmmelTears(long Amount)6050 void character::ReceiveOmmelTears(long Amount)
6051 {
6052   EditExperience(PERCEPTION, 500, Amount << 4);
6053   EditExperience(CHARISMA, 500, Amount << 4);
6054 
6055   if(IsPlayer())
6056     game::DoEvilDeed(Amount / 25);
6057 }
6058 
ReceiveOmmelSnot(long Amount)6059 void character::ReceiveOmmelSnot(long Amount)
6060 {
6061   EditExperience(ENDURANCE, 500, Amount << 5);
6062   RestoreLivingHP();
6063 
6064   if(IsPlayer())
6065     game::DoEvilDeed(Amount / 25);
6066 }
6067 
ReceiveOmmelBone(long Amount)6068 void character::ReceiveOmmelBone(long Amount)
6069 {
6070   EditExperience(ARM_STRENGTH, 500, Amount << 6);
6071   EditExperience(LEG_STRENGTH, 500, Amount << 6);
6072   EditExperience(DEXTERITY, 500, Amount << 6);
6073   EditExperience(AGILITY, 500, Amount << 6);
6074   EditExperience(ENDURANCE, 500, Amount << 6);
6075   EditExperience(PERCEPTION, 500, Amount << 6);
6076   EditExperience(INTELLIGENCE, 500, Amount << 6);
6077   EditExperience(WISDOM, 500, Amount << 6);
6078   EditExperience(CHARISMA, 500, Amount << 6);
6079   RestoreLivingHP();
6080   RestoreStamina();
6081 
6082   if(IsPlayer())
6083     game::DoEvilDeed(Amount / 25);
6084 }
6085 
AddOmmelConsumeEndMessage() const6086 void character::AddOmmelConsumeEndMessage() const
6087 {
6088   if(IsPlayer())
6089     ADD_MESSAGE("You feel a primitive force coursing through your veins.");
6090   else if(CanBeSeenByPlayer())
6091     ADD_MESSAGE("Suddenly %s looks more powerful.", CHAR_NAME(DEFINITE));
6092 }
6093 
ReceivePepsi(long Amount)6094 void character::ReceivePepsi(long Amount)
6095 {
6096   ReceiveDamage(0, Amount / 100, POISON, TORSO);
6097   EditExperience(PERCEPTION, Amount, 1 << 14);
6098 
6099   if(CheckDeath(CONST_S("was poisoned by pepsi"), 0))
6100     return;
6101 
6102   if(IsPlayer())
6103     game::DoEvilDeed(Amount / 10);
6104 }
6105 
AddPepsiConsumeEndMessage() const6106 void character::AddPepsiConsumeEndMessage() const
6107 {
6108   if(IsPlayer())
6109     ADD_MESSAGE("Urgh. You feel your guruism fading away.");
6110   else if(CanBeSeenByPlayer())
6111     ADD_MESSAGE("%s looks very lame.", CHAR_NAME(DEFINITE));
6112 }
6113 
AddCocaColaConsumeEndMessage() const6114 void character::AddCocaColaConsumeEndMessage() const
6115 {
6116   if(IsPlayer())
6117     ADD_MESSAGE("You feel your guruism rising!");
6118   else if(CanBeSeenByPlayer())
6119     ADD_MESSAGE("%s looks awesome.", CHAR_NAME(DEFINITE));
6120 }
6121 
ReceiveDarkness(long Amount)6122 void character::ReceiveDarkness(long Amount)
6123 {
6124   // A bit of a gum solution, but spiders and frogs are immune to prevent
6125   // Lobh-se and dark frogs from poisoning themselves.
6126   if(!(IsSpider() || IsFrog()))
6127     EditExperience(RAND() % BASE_ATTRIBUTES, -Amount, 1 << 14);
6128 
6129   if(IsPlayer())
6130     game::DoEvilDeed(int(Amount / 50));
6131 }
6132 
AddFrogFleshConsumeEndMessage() const6133 void character::AddFrogFleshConsumeEndMessage() const
6134 {
6135   if(IsPlayer())
6136     ADD_MESSAGE("Arg. You feel the fate of a navastater placed upon you...");
6137   else if(CanBeSeenByPlayer())
6138     ADD_MESSAGE("Suddenly %s looks like a navastater.", CHAR_NAME(DEFINITE));
6139 }
6140 
ReceiveKoboldFlesh(long)6141 void character::ReceiveKoboldFlesh(long)
6142 {
6143   /* As it is commonly known, the possibility of fainting per 500 cubic
6144      centimeters of kobold flesh is exactly 5%. */
6145 
6146   if(!(RAND() % 20))
6147   {
6148     if(IsPlayer())
6149       ADD_MESSAGE("You lose control of your legs and fall down.");
6150 
6151     LoseConsciousness(250 + RAND_N(250));
6152   }
6153 }
6154 
AddKoboldFleshConsumeEndMessage() const6155 void character::AddKoboldFleshConsumeEndMessage() const
6156 {
6157   if(IsPlayer())
6158     ADD_MESSAGE("This stuff tasted really funny.");
6159 }
6160 
AddKoboldFleshHitMessage() const6161 void character::AddKoboldFleshHitMessage() const
6162 {
6163   if(IsPlayer())
6164     ADD_MESSAGE("You feel very funny.");
6165 }
6166 
AddBoneConsumeEndMessage() const6167 void character::AddBoneConsumeEndMessage() const
6168 {
6169   if(IsPlayer())
6170     ADD_MESSAGE("You feel like a hippie.");
6171   else if(CanBeSeenByPlayer())
6172     // This suspects that nobody except dogs can eat bones.
6173     // Necromancers can now eat bones, too. --red_kangaroo
6174     ADD_MESSAGE("%s seems happy.", CHAR_NAME(DEFINITE));
6175 }
6176 
RawEditAttribute(double & Experience,int Amount) const6177 truth character::RawEditAttribute(double& Experience, int Amount) const
6178 {
6179   /* Check if the attribute is disabled for creature */
6180 
6181   if(!Experience)
6182     return false;
6183 
6184   if((Amount < 0 && Experience < 2 * EXP_MULTIPLIER)
6185      || (Amount > 0 && Experience > 999 * EXP_MULTIPLIER))
6186     return false;
6187 
6188   Experience += Amount * EXP_MULTIPLIER;
6189   LimitRef<double>(Experience, MIN_EXP, MAX_EXP);
6190   return true;
6191 }
6192 
DrawPanel(truth AnimationDraw) const6193 void character::DrawPanel(truth AnimationDraw) const
6194 {
6195   if(AnimationDraw)
6196   {
6197     DrawStats(true);
6198     return;
6199   }
6200 
6201   igraph::BlitBackGround(v2(19 + (game::GetMaxScreenXSize() << 4), 0),
6202                          v2(RES.X - 19 - (game::GetMaxScreenXSize() << 4), RES.Y));
6203   igraph::BlitBackGround(v2(16, 45 + (game::GetMaxScreenYSize() << 4)),
6204                          v2(game::GetMaxScreenXSize() << 4, 9));
6205   FONT->Printf(DOUBLE_BUFFER, v2(16, 45 + (game::GetMaxScreenYSize() << 4)), WHITE, "%s", GetPanelName().CStr());
6206   game::UpdateAttributeMemory();
6207   int PanelPosX = RES.X - 96;
6208   int PanelPosY = DrawStats(false);
6209   PrintAttribute("End", ENDURANCE, PanelPosX, PanelPosY++);
6210   PrintAttribute("Per", PERCEPTION, PanelPosX, PanelPosY++);
6211   PrintAttribute("Int", INTELLIGENCE, PanelPosX, PanelPosY++);
6212   PrintAttribute("Wis", WISDOM, PanelPosX, PanelPosY++);
6213   PrintAttribute("Will", WILL_POWER, PanelPosX, PanelPosY++);
6214   PrintAttribute("Cha", CHARISMA, PanelPosX, PanelPosY++);
6215   PrintAttribute("Mana", MANA, PanelPosX, PanelPosY++);
6216   FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Ht   %d cm", GetSize());
6217   FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Wt   %d kg", GetTotalCharacterWeight());
6218   ++PanelPosY;
6219   if(ivanconfig::UseDescriptiveHP())
6220   {
6221     // Display health level description.
6222     festring DescHP = GetHitPointDescription().CapitalizeCopy().CStr();
6223 
6224     if(DescHP.GetSize() > 11)
6225     {
6226       // Description doesn't fit on the sidebar, so split it on two lines.
6227       festring Desc2;
6228       festring::SplitString(DescHP, Desc2, 11);
6229 
6230       FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10),
6231                    IsInBadCondition() ? RED : WHITE, "%s", Desc2.CStr());
6232       FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10),
6233                    IsInBadCondition() ? RED : WHITE, " %s", DescHP.CStr());
6234     }
6235     else
6236       FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10),
6237                    IsInBadCondition() ? RED : WHITE, "%s", DescHP.CStr());
6238   }
6239   else // Normal numeric HP.
6240   {
6241     FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10),
6242                  IsInBadCondition() ? RED : WHITE, "HP: %d/%d", GetHP(), GetMaxHP());
6243   }
6244   ++PanelPosY;
6245   FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Gold: %ld", GetMoney());
6246   ++PanelPosY;
6247 
6248   if(game::IsInWilderness())
6249     FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Wilderness");
6250   else
6251     FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "%s",
6252                  game::GetCurrentDungeon()->GetShortLevelDescription(game::GetCurrentLevelIndex()).CapitalizeCopy().CStr());
6253 
6254   ivantime Time;
6255   game::GetTime(Time);
6256   FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Day  %d", Time.Day);
6257   FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Time %d:%s%d", Time.Hour,
6258                                                                                       Time.Min < 10 ? "0" : "",
6259                                                                                       Time.Min);
6260   FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Turn %ld", game::GetTurn());
6261 
6262   ++PanelPosY;
6263 
6264   if(GetAction())
6265     FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "%s",
6266                  festring(GetAction()->GetDescription()).CapitalizeCopy().CStr());
6267 
6268   for(int c = 0; c < STATES; ++c)
6269     if(!(StateData[c].Flags & SECRET)
6270        && StateIsActivated(1 << c)
6271        && (1 << c != HASTE || !StateIsActivated(SLOW))
6272        && (1 << c != SLOW || !StateIsActivated(HASTE)))
6273       FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10),
6274                    (1 << c) & EquipmentState || TemporaryStateCounter[c] >= PERMANENT ? BLUE : WHITE,
6275                    "%s", StateData[c].Description);
6276 
6277   static cchar* HungerStateStrings[] = { "Starving!", "Very hungry", "Hungry", "", "Satiated", "Bloated", "Overfed!" };
6278   static cpackcol16 HungerStateColors[] = { RED, RED, YELLOW, 0, WHITE, WHITE, YELLOW };
6279   int HungerState = GetHungerState();
6280   if(HungerState != NOT_HUNGRY)
6281     FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10),
6282                  HungerStateColors[HungerState], HungerStateStrings[HungerState]);
6283 
6284   static cchar* BurdenStateStrings[] = { "Overload!", "Stressed", "Burdened" };
6285   static cpackcol16 BurdenStateColors[] = { RED, RED, YELLOW };
6286   int BurdenState = GetBurdenState();
6287   if(BurdenState != UNBURDENED)
6288     FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10),
6289                  BurdenStateColors[BurdenState], BurdenStateStrings[BurdenState]);
6290 
6291   static cchar* TirednessStateStrings[] = { "Fainting!", "Exhausted" };
6292   static cpackcol16 TirednessStateColors[] = { RED, YELLOW };
6293   int TirednessState = GetTirednessState();
6294   if(TirednessState != UNTIRED)
6295     FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10),
6296                  TirednessStateColors[TirednessState], TirednessStateStrings[TirednessState]);
6297 
6298   if(game::IsInWilderness() && game::PlayerHasBoat() && IsSwimming())
6299   {
6300     FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "On Ship");
6301   }
6302 
6303   if(game::PlayerIsRunning())
6304   {
6305     FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, GetRunDescriptionLine(0));
6306     cchar* SecondLine = GetRunDescriptionLine(1);
6307 
6308     if(SecondLine[0] != '\0')
6309       FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, SecondLine);
6310   }
6311 }
6312 
CalculateDodgeValue()6313 void character::CalculateDodgeValue()
6314 {
6315   DodgeValue = 0.05 * GetMoveEase() * GetAttribute(AGILITY) / sqrt(GetSize());
6316 
6317   if(IsFlying())
6318     DodgeValue *= 2;
6319 
6320   if(DodgeValue < 1)
6321     DodgeValue = 1;
6322 }
6323 
DamageTypeAffectsInventory(int Type)6324 truth character::DamageTypeAffectsInventory(int Type)
6325 {
6326   switch(Type&0xFFF)
6327   {
6328    case SOUND:
6329    case ENERGY:
6330    case ACID:
6331    case FIRE:
6332    case ELECTRICITY:
6333     return true;
6334    case PHYSICAL_DAMAGE:
6335    case POISON:
6336    case DRAIN:
6337    case MUSTARD_GAS_DAMAGE:
6338    case PSI:
6339     return false;
6340   }
6341 
6342   ABORT("Unknown reaping effect destroyed dungeon!");
6343   return false;
6344 }
6345 
CheckForBlockWithArm(character * Enemy,item * Weapon,arm * Arm,double WeaponToHitValue,int Damage,int Success,int Type)6346 int character::CheckForBlockWithArm(character* Enemy, item* Weapon, arm* Arm,
6347                                     double WeaponToHitValue, int Damage, int Success, int Type)
6348 {
6349   int BlockStrength = Arm->GetBlockCapability();
6350   double BlockValue = Arm->GetBlockValue();
6351 
6352   if(BlockStrength && BlockValue)
6353   {
6354     item* Blocker = Arm->GetWielded();
6355 
6356     if(RAND() % int(100 + WeaponToHitValue / BlockValue / (1 << BlocksSinceLastTurn) * (100 + Success)) < 100)
6357     {
6358       int NewDamage = BlockStrength < Damage ? Damage - BlockStrength : 0;
6359 
6360       switch(Type)
6361       {
6362        case UNARMED_ATTACK:
6363         AddBlockMessage(Enemy, Blocker, Enemy->UnarmedHitNoun(), NewDamage);
6364         break;
6365        case WEAPON_ATTACK:
6366         AddBlockMessage(Enemy, Blocker, "attack", NewDamage);
6367         break;
6368        case KICK_ATTACK:
6369         AddBlockMessage(Enemy, Blocker, Enemy->KickNoun(), NewDamage);
6370         break;
6371        case BITE_ATTACK:
6372         AddBlockMessage(Enemy, Blocker, Enemy->BiteNoun(), NewDamage);
6373         break;
6374       }
6375 
6376       long Weight = Blocker->GetWeight();
6377       long StrExp = Limit(15 * Weight / 200L, 75L, 300L);
6378       long DexExp = Weight ? Limit(75000L / Weight, 75L, 300L) : 300;
6379       Arm->EditExperience(ARM_STRENGTH, StrExp, 1 << 8);
6380       Arm->EditExperience(DEXTERITY, DexExp, 1 << 8);
6381       EditStamina(GetAdjustedStaminaCost(-1000, GetAttribute(ARM_STRENGTH)), false);
6382 
6383       if(Arm->TwoHandWieldIsActive())
6384       {
6385         arm* PairArm = Arm->GetPairArm();
6386         PairArm->EditExperience(ARM_STRENGTH, StrExp, 1 << 8);
6387         PairArm->EditExperience(DEXTERITY, DexExp, 1 << 8);
6388       }
6389 
6390       Blocker->WeaponSkillHit(Enemy->CalculateWeaponSkillHits(this));
6391       Blocker->ReceiveDamage(this, Damage, PHYSICAL_DAMAGE);
6392 
6393       Blocker->BlockEffect(this, Enemy, Weapon, Type);
6394 
6395       if(Weapon)
6396         Weapon->ReceiveDamage(Enemy, Damage - NewDamage, PHYSICAL_DAMAGE);
6397 
6398       if(BlocksSinceLastTurn < 16)
6399         ++BlocksSinceLastTurn;
6400 
6401       return NewDamage;
6402     }
6403   }
6404 
6405   return Damage;
6406 }
6407 
GetStateAPGain(long BaseAPGain) const6408 long character::GetStateAPGain(long BaseAPGain) const
6409 {
6410   if(!StateIsActivated(HASTE) == !StateIsActivated(SLOW))
6411     return BaseAPGain;
6412   else if(StateIsActivated(HASTE))
6413     return (BaseAPGain * 5) >> 2;
6414   else
6415     return (BaseAPGain << 2) / 5;
6416 }
6417 
SignalEquipmentAdd(int EquipmentIndex)6418 void character::SignalEquipmentAdd(int EquipmentIndex)
6419 {
6420   item* Equipment = GetEquipment(EquipmentIndex);
6421 
6422   if(Equipment->IsInCorrectSlot(EquipmentIndex))
6423   {
6424     long AddedStates = Equipment->GetGearStates();
6425 
6426     if(AddedStates)
6427       for(int c = 0; c < STATES; ++c)
6428         if(AddedStates & (1 << c))
6429         {
6430           if(!StateIsActivated(1 << c))
6431           {
6432             if(!IsInNoMsgMode())
6433               (this->*StateData[c].PrintBeginMessage)();
6434 
6435             EquipmentState |= 1 << c;
6436 
6437             if(StateData[c].BeginHandler)
6438               (this->*StateData[c].BeginHandler)();
6439           }
6440           else
6441             EquipmentState |= 1 << c;
6442         }
6443   }
6444 
6445   if(!IsInitializing() && Equipment->IsInCorrectSlot(EquipmentIndex))
6446     ApplyEquipmentAttributeBonuses(Equipment);
6447 }
6448 
SignalEquipmentRemoval(int,citem * Item)6449 void character::SignalEquipmentRemoval(int, citem* Item)
6450 {
6451   CalculateEquipmentState();
6452 
6453   if(CalculateAttributeBonuses())
6454     CheckDeath(festring("lost ") + GetPossessivePronoun(false) + " vital " + Item->GetName(INDEFINITE));
6455 }
6456 
CalculateEquipmentState()6457 void character::CalculateEquipmentState()
6458 {
6459   int c;
6460   long Back = EquipmentState;
6461   EquipmentState = 0;
6462 
6463   for(c = 0; c < GetEquipments(); ++c)
6464   {
6465     item* Equipment = GetEquipment(c);
6466 
6467     if(Equipment && Equipment->IsInCorrectSlot(c))
6468       EquipmentState |= Equipment->GetGearStates();
6469   }
6470 
6471   for(c = 0; c < STATES; ++c)
6472     if(Back & (1 << c) && !StateIsActivated(1 << c))
6473     {
6474       if(StateData[c].EndHandler)
6475       {
6476         (this->*StateData[c].EndHandler)();
6477 
6478         if(!IsEnabled())
6479           return;
6480       }
6481 
6482       if(!IsInNoMsgMode())
6483         (this->*StateData[c].PrintEndMessage)();
6484     }
6485 }
6486 
6487 /* Counter = duration in ticks */
6488 
BeginTemporaryState(long State,int Counter)6489 void character::BeginTemporaryState(long State, int Counter)
6490 {
6491   if(!Counter)
6492     return;
6493 
6494   int Index;
6495 
6496   if(State == POLYMORPHED)
6497     ABORT("No Polymorphing with BeginTemporaryState!");
6498 
6499   for(Index = 0; Index < STATES; ++Index)
6500     if(1 << Index == State)
6501       break;
6502 
6503   if(Index == STATES)
6504     ABORT("BeginTemporaryState works only when State == 2 ^ n!");
6505 
6506   if(TemporaryStateIsActivated(State))
6507   {
6508     int OldCounter = GetTemporaryStateCounter(State);
6509 
6510     if(OldCounter != PERMANENT)
6511       EditTemporaryStateCounter(State, Max(Counter, 50 - OldCounter));
6512   }
6513   else if(StateData[Index].IsAllowed == 0 || (this->*StateData[Index].IsAllowed)())
6514   {
6515     SetTemporaryStateCounter(State, Max(Counter, 50));
6516 
6517     if(!EquipmentStateIsActivated(State))
6518     {
6519       if(!IsInNoMsgMode())
6520         (this->*StateData[Index].PrintBeginMessage)();
6521 
6522       ActivateTemporaryState(State);
6523 
6524       if(StateData[Index].BeginHandler)
6525         (this->*StateData[Index].BeginHandler)();
6526     }
6527     else
6528       ActivateTemporaryState(State);
6529   }
6530 }
6531 
HandleStates()6532 void character::HandleStates()
6533 {
6534   if(!TemporaryState && !EquipmentState)
6535     return;
6536 
6537   for(int c = 0; c < STATES; ++c)
6538   {
6539     if(TemporaryState & (1 << c) && TemporaryStateCounter[c] != PERMANENT)
6540     {
6541       if(!--TemporaryStateCounter[c])
6542       {
6543         TemporaryState &= ~(1 << c);
6544 
6545         if(!(EquipmentState & (1 << c)))
6546         {
6547           if(StateData[c].EndHandler)
6548           {
6549             (this->*StateData[c].EndHandler)();
6550 
6551             if(!IsEnabled())
6552               return;
6553           }
6554 
6555           if(!TemporaryStateCounter[c])
6556             (this->*StateData[c].PrintEndMessage)();
6557         }
6558       }
6559     }
6560 
6561     if(StateIsActivated(1 << c))
6562       if(StateData[c].Handler)
6563         (this->*StateData[c].Handler)();
6564 
6565     if(!IsEnabled())
6566       return;
6567   }
6568 }
6569 
PrintBeginPolymorphControlMessage() const6570 void character::PrintBeginPolymorphControlMessage() const
6571 {
6572   if(IsPlayer())
6573     ADD_MESSAGE("You feel your mind has total control over your body.");
6574 }
6575 
PrintEndPolymorphControlMessage() const6576 void character::PrintEndPolymorphControlMessage() const
6577 {
6578   if(IsPlayer())
6579     ADD_MESSAGE("You are somehow uncertain of your willpower.");
6580 }
6581 
PrintBeginLifeSaveMessage() const6582 void character::PrintBeginLifeSaveMessage() const
6583 {
6584   if(IsPlayer())
6585     ADD_MESSAGE("You hear Hell's gates being locked just now.");
6586 }
6587 
PrintEndLifeSaveMessage() const6588 void character::PrintEndLifeSaveMessage() const
6589 {
6590   if(IsPlayer())
6591     ADD_MESSAGE("You feel the Afterlife is welcoming you once again.");
6592 }
6593 
PrintBeginLycanthropyMessage() const6594 void character::PrintBeginLycanthropyMessage() const
6595 {
6596   if(IsPlayer())
6597     ADD_MESSAGE("You suddenly notice you've always loved full moons.");
6598 }
6599 
PrintEndLycanthropyMessage() const6600 void character::PrintEndLycanthropyMessage() const
6601 {
6602   if(IsPlayer())
6603     ADD_MESSAGE("You feel the wolf inside you has had enough of your bad habits.");
6604 }
6605 
PrintBeginVampirismMessage() const6606 void character::PrintBeginVampirismMessage() const
6607 {
6608   if(IsPlayer())
6609     ADD_MESSAGE("You suddenly decide you have always hated garlic.");
6610 }
6611 
PrintEndVampirismMessage() const6612 void character::PrintEndVampirismMessage() const
6613 {
6614   if(IsPlayer())
6615     ADD_MESSAGE("You recall your delight of the morning sunshine back in New Attnam.");
6616 }
6617 
PrintBeginFearlessMessage() const6618 void character::PrintBeginFearlessMessage () const
6619 {
6620   if(IsPlayer())
6621     ADD_MESSAGE("You shout: \"I am invincible!\"");
6622   else if(CanBeSeenByPlayer())
6623     ADD_MESSAGE("%s looks very calm.", CHAR_NAME(DEFINITE));
6624 }
6625 
PrintEndFearlessMessage() const6626 void character::PrintEndFearlessMessage () const
6627 {
6628   if(IsPlayer())
6629     ADD_MESSAGE("Everything looks much more dangerous now.");
6630   else if(CanBeSeenByPlayer())
6631     ADD_MESSAGE("%s seems to have lost all confidence.", CHAR_NAME(DEFINITE));
6632 }
6633 
PrintBeginFastingMessage() const6634 void character::PrintBeginFastingMessage() const
6635 {
6636   if(IsPlayer())
6637     ADD_MESSAGE("You feel your life-force invigorating your entire body.");
6638 }
6639 
PrintEndFastingMessage() const6640 void character::PrintEndFastingMessage() const
6641 {
6642   if(IsPlayer())
6643     ADD_MESSAGE("Your stomach growls in discontent.");
6644 }
6645 
PrintBeginInvisibilityMessage() const6646 void character::PrintBeginInvisibilityMessage() const
6647 {
6648   if((PLAYER->StateIsActivated(INFRA_VISION) && IsWarm())
6649      || (PLAYER->StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5))
6650   {
6651     if(IsPlayer())
6652       ADD_MESSAGE("You seem somehow transparent.");
6653     else if(CanBeSeenByPlayer())
6654       ADD_MESSAGE("%s seems somehow transparent.", CHAR_NAME(DEFINITE));
6655   }
6656   else
6657   {
6658     if(IsPlayer())
6659       ADD_MESSAGE("You fade away.");
6660     else if(CanBeSeenByPlayer())
6661       ADD_MESSAGE("%s disappears!", CHAR_NAME(DEFINITE));
6662   }
6663 }
6664 
PrintEndInvisibilityMessage() const6665 void character::PrintEndInvisibilityMessage() const
6666 {
6667   if((PLAYER->StateIsActivated(INFRA_VISION) && IsWarm())
6668      || (PLAYER->StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5))
6669   {
6670     if(IsPlayer())
6671       ADD_MESSAGE("Your notice your transparency has ended.");
6672     else if(CanBeSeenByPlayer())
6673       ADD_MESSAGE("The appearance of %s seems far more solid now.", CHAR_NAME(INDEFINITE));
6674   }
6675   else
6676   {
6677     if(IsPlayer())
6678       ADD_MESSAGE("You reappear.");
6679     else if(CanBeSeenByPlayer())
6680       ADD_MESSAGE("Suddenly %s appears from nowhere!", CHAR_NAME(INDEFINITE));
6681   }
6682 }
6683 
PrintBeginEtherealityMessage() const6684 void character::PrintBeginEtherealityMessage() const
6685 {
6686   if(IsPlayer())
6687     ADD_MESSAGE("You feel like many miscible droplets of ether.");
6688   else if(CanBeSeenByPlayer())
6689     ADD_MESSAGE("%s melds into the surroundings.", CHAR_NAME(DEFINITE));
6690 }
6691 
PrintEndEtherealityMessage() const6692 void character::PrintEndEtherealityMessage() const
6693 {
6694   if(IsPlayer())
6695     ADD_MESSAGE("You drop out of the firmament, feeling suddenly quite dense.");
6696   else if(CanBeSeenByPlayer())
6697     ADD_MESSAGE("Suddenly %s displaces the air with a puff.", CHAR_NAME(INDEFINITE));
6698 }
6699 
PrintBeginSwimmingMessage() const6700 void character::PrintBeginSwimmingMessage() const
6701 {
6702   if(IsPlayer())
6703     ADD_MESSAGE("You fondly remember the sound of ocean waves.");
6704   else if(CanBeSeenByPlayer())
6705     ADD_MESSAGE("%s looks wet.", CHAR_NAME(DEFINITE));
6706 }
6707 
PrintEndSwimmingMessage() const6708 void character::PrintEndSwimmingMessage() const
6709 {
6710   if(IsPlayer())
6711     ADD_MESSAGE("You suddenly remember how you nearly drowned as a child.");
6712   else if(CanBeSeenByPlayer())
6713     ADD_MESSAGE("Suddenly %s looks less wet.", CHAR_NAME(INDEFINITE));
6714 }
6715 
PrintBeginInfraVisionMessage() const6716 void character::PrintBeginInfraVisionMessage() const
6717 {
6718   if(IsPlayer())
6719   {
6720     if(StateIsActivated(INVISIBLE) && IsWarm() && !(StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5))
6721       ADD_MESSAGE("You reappear.");
6722     else
6723       ADD_MESSAGE("You feel your perception being magically altered.");
6724   }
6725 }
6726 
PrintEndInfraVisionMessage() const6727 void character::PrintEndInfraVisionMessage() const
6728 {
6729   if(IsPlayer())
6730   {
6731     if(StateIsActivated(INVISIBLE) && IsWarm() && !(StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5))
6732       ADD_MESSAGE("You disappear.");
6733     else
6734       ADD_MESSAGE("You feel your perception returning to normal.");
6735   }
6736 }
6737 
PrintBeginESPMessage() const6738 void character::PrintBeginESPMessage() const
6739 {
6740   if(IsPlayer())
6741     ADD_MESSAGE("You suddenly feel like being only a tiny part of a great network of intelligent minds.");
6742 }
6743 
PrintEndESPMessage() const6744 void character::PrintEndESPMessage() const
6745 {
6746   if(IsPlayer())
6747     ADD_MESSAGE("You are filled with desire to be just yourself from now on.");
6748 }
6749 
PrintBeginHasteMessage() const6750 void character::PrintBeginHasteMessage() const
6751 {
6752   if(IsPlayer())
6753     ADD_MESSAGE("Time slows down to a crawl.");
6754   else if(CanBeSeenByPlayer())
6755     ADD_MESSAGE("%s looks faster!", CHAR_NAME(DEFINITE));
6756 }
6757 
PrintEndHasteMessage() const6758 void character::PrintEndHasteMessage() const
6759 {
6760   if(IsPlayer())
6761     ADD_MESSAGE("Everything seems to move much faster now.");
6762   else if(CanBeSeenByPlayer())
6763     ADD_MESSAGE("%s looks slower!", CHAR_NAME(DEFINITE));
6764 }
6765 
PrintBeginSlowMessage() const6766 void character::PrintBeginSlowMessage() const
6767 {
6768   if(IsPlayer())
6769     ADD_MESSAGE("Everything seems to move much faster now.");
6770   else if(CanBeSeenByPlayer())
6771     ADD_MESSAGE("%s looks slower!", CHAR_NAME(DEFINITE));
6772 }
6773 
PrintEndSlowMessage() const6774 void character::PrintEndSlowMessage() const
6775 {
6776   if(IsPlayer())
6777     ADD_MESSAGE("Time slows down to a crawl.");
6778   else if(CanBeSeenByPlayer())
6779     ADD_MESSAGE("%s looks faster!", CHAR_NAME(DEFINITE));
6780 }
6781 
EndPolymorph()6782 void character::EndPolymorph()
6783 {
6784   ForceEndPolymorph();
6785 }
6786 
ForceEndPolymorph()6787 character* character::ForceEndPolymorph()
6788 {
6789   if(IsPlayer())
6790     ADD_MESSAGE("You return to your true form.");
6791   else if(game::IsInWilderness())
6792   {
6793     ActivateTemporaryState(POLYMORPHED);
6794     SetTemporaryStateCounter(POLYMORPHED, 10);
6795     return this; // fast gum solution, state ends when the player enters a dungeon
6796   }
6797   else if(CanBeSeenByPlayer())
6798     ADD_MESSAGE("%s returns to %s true form.", CHAR_NAME(DEFINITE), GetPossessivePronoun().CStr());
6799 
6800   RemoveTraps();
6801 
6802   if(GetAction())
6803     GetAction()->Terminate(false);
6804 
6805   v2 Pos = GetPos();
6806   SendToHell();
6807   Remove();
6808   character* Char = GetPolymorphBackup();
6809   Flags |= C_IN_NO_MSG_MODE;
6810   Char->Flags |= C_IN_NO_MSG_MODE;
6811   Char->ChangeTeam(GetTeam());
6812 
6813   if(GetTeam()->GetLeader() == this)
6814     GetTeam()->SetLeader(Char);
6815 
6816   SetPolymorphBackup(0);
6817   Char->PutToOrNear(Pos);
6818   Char->Enable();
6819   Char->Flags &= ~C_POLYMORPHED;
6820   GetStack()->MoveItemsTo(Char->GetStack());
6821   DonateEquipmentTo(Char);
6822   Char->SetMoney(GetMoney());
6823   Flags &= ~C_IN_NO_MSG_MODE;
6824   Char->Flags &= ~C_IN_NO_MSG_MODE;
6825   Char->CalculateAll();
6826   Char->SetAssignedName(GetAssignedName());
6827 
6828   if(IsPlayer())
6829   {
6830     Flags &= ~C_PLAYER;
6831     game::SetPlayer(Char);
6832     game::SendLOSUpdateRequest();
6833     UpdateESPLOS();
6834   }
6835 
6836   Char->TestWalkability();
6837   return Char;
6838 }
6839 
LycanthropyHandler()6840 void character::LycanthropyHandler()
6841 {
6842   if(StateIsActivated(POLYMORPH_LOCK))
6843     return;
6844 
6845   if(GetType() == werewolfwolf::ProtoType.GetIndex())
6846     return;
6847 
6848   if(!(RAND() % 2000))
6849   {
6850     if(StateIsActivated(POLYMORPH_CONTROL)
6851        && (IsPlayer() ? !game::TruthQuestion(CONST_S("Do you wish to change into a werewolf? [y/N]")) : false))
6852       return;
6853 
6854     Polymorph(werewolfwolf::Spawn(), 1000 + RAND() % 2000);
6855   }
6856 }
6857 
SaveLife()6858 void character::SaveLife()
6859 {
6860   if(TemporaryStateIsActivated(LIFE_SAVED))
6861   {
6862     if(IsPlayer())
6863       ADD_MESSAGE("But wait! You glow briefly red and seem to be in a better shape!");
6864     else if(CanBeSeenByPlayer())
6865       ADD_MESSAGE("But wait, suddenly %s glows briefly red and seems to be in a better shape!",
6866                   GetPersonalPronoun().CStr());
6867 
6868     DeActivateTemporaryState(LIFE_SAVED);
6869   }
6870   else
6871   {
6872     item* LifeSaver = 0;
6873 
6874     for(int c = 0; c < GetEquipments(); ++c)
6875     {
6876       item* Equipment = GetEquipment(c);
6877 
6878       if(Equipment && Equipment->IsInCorrectSlot(c) && Equipment->GetGearStates() & LIFE_SAVED)
6879         LifeSaver = Equipment;
6880     }
6881 
6882     if(!LifeSaver)
6883       ABORT("The Universe can only kill you once!");
6884 
6885     if(IsPlayer())
6886       ADD_MESSAGE("But wait! Your %s glows briefly red and disappears and you seem to be in a better shape!",
6887                   LifeSaver->CHAR_NAME(UNARTICLED));
6888     else if(CanBeSeenByPlayer())
6889       ADD_MESSAGE("But wait, suddenly %s %s glows briefly red and disappears and %s seems to be in a better shape!",
6890                   GetPossessivePronoun().CStr(), LifeSaver->CHAR_NAME(UNARTICLED), GetPersonalPronoun().CStr());
6891 
6892     LifeSaver->RemoveFromSlot();
6893     LifeSaver->SendToHell();
6894   }
6895 
6896   if(IsPlayer())
6897     game::AskForKeyPress(CONST_S("Life saved! [press any key to continue]"));
6898 
6899   RestoreBodyParts();
6900   ResetSpoiling();
6901   RestoreHP();
6902   RestoreStamina();
6903   ResetStates();
6904 
6905   if(GetNP() < SATIATED_LEVEL)
6906     SetNP(SATIATED_LEVEL);
6907 
6908   SendNewDrawRequest();
6909 
6910   if(GetAction())
6911     GetAction()->Terminate(false);
6912 }
6913 
PolymorphRandomly(int MinDanger,int MaxDanger,int Time)6914 character* character::PolymorphRandomly(int MinDanger, int MaxDanger, int Time)
6915 {DBG1(GetNameSingular().CStr());
6916   character* NewForm = NULL;
6917 
6918   if(StateIsActivated(POLYMORPH_LOCK))
6919   {DBGLN;
6920     if(IsPlayer())
6921       ADD_MESSAGE("You feel uncertain about your body for a moment.");
6922     return NULL;
6923   }
6924 
6925   if(StateIsActivated(POLYMORPH_CONTROL))
6926   {DBGLN;
6927     if(IsPlayer() && !IsPlayerAutoPlay())
6928     {DBGLN;
6929       if(!GetNewFormForPolymorphWithControl(NewForm)){DBG1(NewForm);
6930         return NULL;
6931       }
6932     }else{DBGLN;
6933       NewForm = protosystem::CreateMonster(MinDanger * 10, MaxDanger * 10, NO_EQUIPMENT);DBG1(NewForm);
6934     }
6935   }else{DBGLN;
6936     NewForm = protosystem::CreateMonster(MinDanger, MaxDanger, NO_EQUIPMENT);DBG1(NewForm);
6937   }DBGLN;
6938 
6939   if(NewForm != NULL && Polymorph(NewForm, Time))
6940   {
6941     DBG1(NewForm);
6942     return NewForm;
6943   }
6944   return NULL;
6945 }
6946 
6947 /* In reality, the reading takes Time / (Intelligence * 10) turns */
6948 
StartReading(item * Item,long Time)6949 void character::StartReading(item* Item, long Time)
6950 {
6951   study* Read = study::Spawn(this);
6952   Read->SetLiteratureID(Item->GetID());
6953 
6954   if(game::WizardModeIsActive())
6955     Time = 1;
6956 
6957   Read->SetCounter(Time);
6958   SetAction(Read);
6959 
6960   if(IsPlayer())
6961     ADD_MESSAGE("You start reading %s.", Item->CHAR_NAME(DEFINITE));
6962   else if(CanBeSeenByPlayer())
6963     ADD_MESSAGE("%s starts reading %s.", CHAR_NAME(DEFINITE), Item->CHAR_NAME(DEFINITE));
6964 }
6965 
6966 /* Call when one makes something with his/her/its hands.
6967  * Difficulty of 5 takes about one turn, so it's the most common to use. */
6968 
DexterityAction(int Difficulty)6969 void character::DexterityAction(int Difficulty)
6970 {
6971   EditAP(-20000 * Difficulty / APBonus(GetAttribute(DEXTERITY)));
6972   EditExperience(DEXTERITY, Difficulty * 15, 1 << 7);
6973 }
6974 
6975 /* If Theoretically != false, range is not a factor. */
6976 
CanBeSeenByPlayer(truth Theoretically,truth IgnoreESP) const6977 truth character::CanBeSeenByPlayer(truth Theoretically, truth IgnoreESP) const
6978 {
6979   if(IsEnabled() && !game::IsGenerating() && (Theoretically || GetSquareUnder()))
6980   {
6981     truth MayBeESPSeen = PLAYER->IsEnabled() && !IgnoreESP && PLAYER->StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5 && !IsESPBlockedByEquipment();
6982     truth MayBeInfraSeen = PLAYER->IsEnabled() && PLAYER->StateIsActivated(INFRA_VISION) && IsWarm();
6983     truth Visible = !StateIsActivated(INVISIBLE) || MayBeESPSeen || MayBeInfraSeen;
6984 
6985     if(game::IsInWilderness())
6986       return Visible;
6987 
6988     if(MayBeESPSeen
6989        && (Theoretically
6990            || GetDistanceSquareFrom(PLAYER) <= PLAYER->GetESPRangeSquare()))
6991       return true;
6992     else if(!Visible)
6993       return false;
6994     else
6995       return Theoretically || SquareUnderCanBeSeenByPlayer(MayBeInfraSeen);
6996   }
6997   else
6998     return false;
6999 }
7000 
CanBeSeenBy(ccharacter * Who,truth Theoretically,truth IgnoreESP) const7001 truth character::CanBeSeenBy(ccharacter* Who, truth Theoretically, truth IgnoreESP) const
7002 {
7003   if(Who->IsPlayer())
7004     return CanBeSeenByPlayer(Theoretically, IgnoreESP);
7005   else
7006   {
7007     if(IsEnabled() && !game::IsGenerating() && (Theoretically || GetSquareUnder()))
7008     {
7009       truth MayBeESPSeen = Who->IsEnabled() && !IgnoreESP && Who->StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5;
7010       truth MayBeInfraSeen = Who->IsEnabled() && Who->StateIsActivated(INFRA_VISION) && IsWarm();
7011       truth Visible = !StateIsActivated(INVISIBLE) || MayBeESPSeen || MayBeInfraSeen || Who->StateIsActivated(DETECTING);
7012       // Let monsters also make use of Detecting status effect. Here we simulate that they always ask to detect player flesh.
7013 
7014       if(game::IsInWilderness())
7015         return Visible;
7016 
7017       if(MayBeESPSeen
7018          && (Theoretically
7019              || GetDistanceSquareFrom(Who) <= Who->GetESPRangeSquare()))
7020         return true;
7021       else if(!Visible)
7022         return false;
7023       else
7024         return Theoretically || SquareUnderCanBeSeenBy(Who, MayBeInfraSeen);
7025     }
7026     else
7027       return false;
7028   }
7029 }
7030 
SquareUnderCanBeSeenByPlayer(truth IgnoreDarkness) const7031 truth character::SquareUnderCanBeSeenByPlayer(truth IgnoreDarkness) const
7032 {
7033   if(!GetSquareUnder())
7034     return false;
7035 
7036   int S1 = SquaresUnder, S2 = PLAYER->SquaresUnder;
7037 
7038   if(S1 == 1 && S2 == 1)
7039   {
7040     if(GetSquareUnder()->CanBeSeenByPlayer(IgnoreDarkness))
7041       return true;
7042 
7043     if(IgnoreDarkness)
7044     {
7045       int LOSRangeSquare = PLAYER->GetLOSRangeSquare();
7046 
7047       if((GetPos() - PLAYER->GetPos()).GetLengthSquare() <= LOSRangeSquare)
7048       {
7049         eyecontroller::Map = GetLevel()->GetMap();
7050         return mapmath<eyecontroller>::DoLine(PLAYER->GetPos().X, PLAYER->GetPos().Y,
7051                                               GetPos().X, GetPos().Y, SKIP_FIRST);
7052       }
7053     }
7054 
7055     return false;
7056   }
7057   else
7058   {
7059     for(int c1 = 0; c1 < S1; ++c1)
7060     {
7061       lsquare* Square = GetLSquareUnder(c1);
7062 
7063       if(Square->CanBeSeenByPlayer(IgnoreDarkness))
7064         return true;
7065       else if(IgnoreDarkness)
7066       {
7067         v2 Pos = Square->GetPos();
7068         int LOSRangeSquare = PLAYER->GetLOSRangeSquare();
7069 
7070         for(int c2 = 0; c2 < S2; ++c2)
7071         {
7072           v2 PlayerPos = PLAYER->GetPos(c2);
7073 
7074           if((Pos - PlayerPos).GetLengthSquare() <= LOSRangeSquare)
7075           {
7076             eyecontroller::Map = GetLevel()->GetMap();
7077 
7078             if(mapmath<eyecontroller>::DoLine(PlayerPos.X, PlayerPos.Y, Pos.X, Pos.Y, SKIP_FIRST))
7079               return true;
7080           }
7081         }
7082       }
7083     }
7084 
7085     return false;
7086   }
7087 }
7088 
SquareUnderCanBeSeenBy(ccharacter * Who,truth IgnoreDarkness) const7089 truth character::SquareUnderCanBeSeenBy(ccharacter* Who, truth IgnoreDarkness) const
7090 {
7091   int S1 = SquaresUnder, S2 = Who->SquaresUnder;
7092   int LOSRangeSquare = Who->GetLOSRangeSquare();
7093 
7094   if(S1 == 1 && S2 == 1)
7095     return GetSquareUnder()->CanBeSeenFrom(Who->GetPos(), LOSRangeSquare, IgnoreDarkness);
7096   else
7097   {
7098     for(int c1 = 0; c1 < S1; ++c1)
7099     {
7100       lsquare* Square = GetLSquareUnder(c1);
7101 
7102       for(int c2 = 0; c2 < S2; ++c2)
7103         if(Square->CanBeSeenFrom(Who->GetPos(c2), LOSRangeSquare, IgnoreDarkness))
7104           return true;
7105     }
7106 
7107     return false;
7108   }
7109 }
7110 
GetDistanceSquareFrom(ccharacter * Who) const7111 int character::GetDistanceSquareFrom(ccharacter* Who) const
7112 {
7113   int S1 = SquaresUnder, S2 = Who->SquaresUnder;
7114 
7115   if(S1 == 1 && S2 == 1)
7116     return (GetPos() - Who->GetPos()).GetLengthSquare();
7117   else
7118   {
7119     v2 MinDist(0x7FFF, 0x7FFF);
7120     int MinLength = 0xFFFF;
7121 
7122     for(int c1 = 0; c1 < S1; ++c1)
7123       for(int c2 = 0; c2 < S2; ++c2)
7124       {
7125         v2 Dist = GetPos(c1) - Who->GetPos(c2);
7126 
7127         if(Dist.X < 0)
7128           Dist.X = -Dist.X;
7129 
7130         if(Dist.Y < 0)
7131           Dist.Y = -Dist.Y;
7132 
7133         if(Dist.X <= MinDist.X && Dist.Y <= MinDist.Y)
7134         {
7135           MinDist = Dist;
7136           MinLength = Dist.GetLengthSquare();
7137         }
7138         else if(Dist.X < MinDist.X || Dist.Y < MinDist.Y)
7139         {
7140           int Length = Dist.GetLengthSquare();
7141 
7142           if(Length < MinLength)
7143           {
7144             MinDist = Dist;
7145             MinLength = Length;
7146           }
7147         }
7148       }
7149 
7150     return MinLength;
7151   }
7152 }
7153 
AttachBodyPart(bodypart * BodyPart)7154 void character::AttachBodyPart(bodypart* BodyPart)
7155 {
7156   SetBodyPart(BodyPart->GetBodyPartIndex(), BodyPart);
7157 
7158   if(!AllowSpoil())
7159     BodyPart->ResetSpoiling();
7160 
7161   BodyPart->ResetPosition();
7162   BodyPart->UpdatePictures();
7163   CalculateAttributeBonuses();
7164   CalculateBattleInfo();
7165   SendNewDrawRequest();
7166   SignalPossibleTransparencyChange();
7167 }
7168 
7169 /* Returns true if the character has all bodyparts, false if not. */
7170 
HasAllBodyParts() const7171 truth character::HasAllBodyParts() const
7172 {
7173   for(int c = 0; c < BodyParts; ++c)
7174     if(!GetBodyPart(c) && CanCreateBodyPart(c))
7175       return false;
7176 
7177   return true;
7178 }
7179 
HasFire() const7180 truth character::HasFire() const
7181 {
7182   for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
7183     if(i->IsBurning())
7184       return true;
7185 
7186   for(int c = 0; c < BodyParts; ++c)
7187   {
7188     bodypart* BodyPart = GetBodyPart(c);
7189     if(BodyPart)
7190       if(BodyPart->IsBurning())
7191         return true;
7192   }
7193 
7194   return false;
7195 }
7196 
IsBurning() const7197 truth character::IsBurning() const
7198 {
7199   for(int c = 0; c < BodyParts; ++c)
7200   {
7201     bodypart* BodyPart = GetBodyPart(c);
7202     if(BodyPart)
7203       if(BodyPart->IsBurning())
7204         return true;
7205   }
7206   return false;
7207 }
7208 
GenerateRandomBodyPart()7209 bodypart* character::GenerateRandomBodyPart()
7210 {
7211   int NeededBodyPart[MAX_BODYPARTS];
7212   int Index = 0;
7213 
7214   for(int c = 0; c < BodyParts; ++c)
7215     if(!GetBodyPart(c) && CanCreateBodyPart(c))
7216       NeededBodyPart[Index++] = c;
7217 
7218   return Index ? CreateBodyPart(NeededBodyPart[RAND() % Index]) : 0;
7219 }
7220 
7221 /* Searches the character's Stack and if it find some bodyparts there that are
7222    the character's old bodyparts returns a stackiterator to one of them (choosen
7223    in random). If no fitting bodyparts are found the function returns 0 */
7224 
FindRandomOwnBodyPart(truth AllowNonLiving) const7225 bodypart* character::FindRandomOwnBodyPart(truth AllowNonLiving) const
7226 {
7227   itemvector LostAndFound;
7228 
7229   for(int c = 0; c < BodyParts; ++c)
7230     if(!GetBodyPart(c))
7231       for(ulong ID : OriginalBodyPartID[c])
7232       {
7233         bodypart* Found = static_cast<bodypart*>(SearchForItem(ID));
7234 
7235         if(Found && (AllowNonLiving || Found->CanRegenerate()))
7236           LostAndFound.push_back(Found);
7237       }
7238 
7239   if(LostAndFound.empty())
7240     return 0;
7241   else
7242     return static_cast<bodypart*>(LostAndFound[RAND() % LostAndFound.size()]);
7243 }
7244 
PrintBeginPoisonedMessage() const7245 void character::PrintBeginPoisonedMessage() const
7246 {
7247   if(IsPlayer())
7248     ADD_MESSAGE("You seem to be very ill.");
7249   else if(CanBeSeenByPlayer())
7250     ADD_MESSAGE("%s looks very ill.", CHAR_NAME(DEFINITE));
7251 }
7252 
PrintEndPoisonedMessage() const7253 void character::PrintEndPoisonedMessage() const
7254 {
7255   if(IsPlayer())
7256     ADD_MESSAGE("You feel better again.");
7257   else if(CanBeSeenByPlayer())
7258     ADD_MESSAGE("%s looks better.", CHAR_NAME(DEFINITE));
7259 }
7260 
PoisonedHandler()7261 void character::PoisonedHandler()
7262 {
7263   if(!(RAND() % 100))
7264     VomitAtRandomDirection(500 + RAND_N(250));
7265 
7266   int Damage = 0;
7267 
7268   for(int Used = 0; Used < GetTemporaryStateCounter(POISONED); Used += 100)
7269     if(!(RAND() % 100))
7270       ++Damage;
7271 
7272   if(Damage)
7273   {
7274     ReceiveDamage(0, Damage, POISON, ALL, 8, false, false, false, false);
7275     CheckDeath(CONST_S("died of acute poisoning"), 0);
7276   }
7277 }
7278 
IsWarm() const7279 truth character::IsWarm() const
7280 {
7281   return combinebodypartpredicates()(this, &bodypart::IsWarm, 1);
7282 }
7283 
IsWarmBlooded() const7284 truth character::IsWarmBlooded() const
7285 {
7286   return combinebodypartpredicates()(this, &bodypart::IsWarmBlooded, 1);
7287 }
7288 
BeginInvisibility()7289 void character::BeginInvisibility()
7290 {
7291   UpdatePictures();
7292   SendNewDrawRequest();
7293   SignalPossibleTransparencyChange();
7294 }
7295 
BeginInfraVision()7296 void character::BeginInfraVision()
7297 {
7298   if(IsPlayer())
7299     GetArea()->SendNewDrawRequest();
7300 }
7301 
BeginESP()7302 void character::BeginESP()
7303 {
7304   if(IsPlayer())
7305     GetArea()->SendNewDrawRequest();
7306 }
7307 
BeginEthereality()7308 void character::BeginEthereality()
7309 {
7310 }
7311 
BeginSwimming()7312 void character::BeginSwimming()
7313 {
7314 }
7315 
EndInvisibility()7316 void character::EndInvisibility()
7317 {
7318   UpdatePictures();
7319   SendNewDrawRequest();
7320   SignalPossibleTransparencyChange();
7321 }
7322 
EndInfraVision()7323 void character::EndInfraVision()
7324 {
7325   if(IsPlayer() && IsEnabled())
7326     GetArea()->SendNewDrawRequest();
7327 }
7328 
EndESP()7329 void character::EndESP()
7330 {
7331   if(IsPlayer() && IsEnabled())
7332     GetArea()->SendNewDrawRequest();
7333 }
7334 
EndEthereality()7335 void character::EndEthereality()
7336 {
7337 }
7338 
EndSwimming()7339 void character::EndSwimming()
7340 {
7341 }
7342 
Draw(blitdata & BlitData) const7343 void character::Draw(blitdata& BlitData) const
7344 {
7345   col24 L = BlitData.Luminance;
7346 
7347   if(PLAYER->IsEnabled()
7348      && ((PLAYER->StateIsActivated(ESP)
7349           && GetAttribute(INTELLIGENCE) >= 5
7350           && (PLAYER->GetPos() - GetPos()).GetLengthSquare() <= PLAYER->GetESPRangeSquare())
7351          || (PLAYER->StateIsActivated(INFRA_VISION) && IsWarm())))
7352     BlitData.Luminance = ivanconfig::GetContrastLuminance();
7353 
7354   DrawBodyParts(BlitData);
7355   BlitData.Luminance = ivanconfig::GetContrastLuminance();
7356   BlitData.Src.Y = 16;
7357   cint SquareIndex = BlitData.CustomData & SQUARE_INDEX_MASK;
7358 
7359   if(GetTeam() == PLAYER->GetTeam() && !IsPlayer()
7360      && SquareIndex == GetTameSymbolSquareIndex())
7361   {
7362     BlitData.Src.X = 32;
7363     igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData);
7364   }
7365 
7366   if(IsFlying() && SquareIndex == GetFlySymbolSquareIndex())
7367   {
7368     BlitData.Src.X = 128;
7369     igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData);
7370   }
7371 
7372   if(IsSwimming() && SquareIndex == GetSwimmingSymbolSquareIndex())
7373   {
7374     BlitData.Src.X = 240;
7375     igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData);
7376   }
7377 
7378   if(GetAction() && GetAction()->IsUnconsciousness()
7379      && SquareIndex == GetUnconsciousSymbolSquareIndex())
7380   {
7381     BlitData.Src.X = 224;
7382     igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData);
7383   }
7384 
7385   BlitData.Src.X = BlitData.Src.Y = 0;
7386   BlitData.Luminance = L;
7387 }
7388 
DrawBodyParts(blitdata & BlitData) const7389 void character::DrawBodyParts(blitdata& BlitData) const
7390 {
7391   GetTorso()->Draw(BlitData);
7392 }
7393 
PrintBeginTeleportMessage() const7394 void character::PrintBeginTeleportMessage() const
7395 {
7396   if(IsPlayer())
7397     ADD_MESSAGE("You feel jumpy.");
7398 }
7399 
PrintEndTeleportMessage() const7400 void character::PrintEndTeleportMessage() const
7401 {
7402   if(IsPlayer())
7403     ADD_MESSAGE("You suddenly realize you've always preferred walking to jumping.");
7404 }
7405 
PrintBeginDetectMessage() const7406 void character::PrintBeginDetectMessage() const
7407 {
7408   if(IsPlayer())
7409     ADD_MESSAGE("You feel curious about your surroundings.");
7410 }
7411 
PrintEndDetectMessage() const7412 void character::PrintEndDetectMessage() const
7413 {
7414   if(IsPlayer())
7415     ADD_MESSAGE("You decide to rely on your own sight from now on.");
7416 }
7417 
TeleportHandler()7418 void character::TeleportHandler()
7419 {
7420   if(!(RAND() % 1500) && !game::IsInWilderness())
7421   {
7422     if(IsPlayer())
7423       ADD_MESSAGE("You feel an urgent spatial relocation is now appropriate.");
7424     else if(CanBeSeenByPlayer())
7425       ADD_MESSAGE("%s disappears.", CHAR_NAME(DEFINITE));
7426 
7427     TeleportRandomly();
7428   }
7429 }
7430 
DetectHandler()7431 void character::DetectHandler()
7432 {
7433   if(IsPlayer()) //the AI can't be asked position questions! So only the player can have this state really.
7434   {
7435     if(!(RAND() % 3000) && !game::IsInWilderness())
7436     {
7437       ADD_MESSAGE("Your mind wanders in search of something.");
7438       DoDetecting();
7439     }
7440   }
7441   else
7442     return;
7443 }
7444 
PrintBeginPolymorphMessage() const7445 void character::PrintBeginPolymorphMessage() const
7446 {
7447   if(IsPlayer())
7448     ADD_MESSAGE("An uncomfortable uncertainty of who you really are overwhelms you.");
7449 }
7450 
PrintEndPolymorphMessage() const7451 void character::PrintEndPolymorphMessage() const
7452 {
7453   if(IsPlayer())
7454     ADD_MESSAGE("You feel you are you and no one else.");
7455 }
7456 
PolymorphHandler()7457 void character::PolymorphHandler()
7458 {
7459   if(!(RAND() % 1500))
7460     PolymorphRandomly(1, 999999, 200 + RAND() % 800);
7461 }
7462 
PrintBeginPolymorphLockMessage() const7463 void character::PrintBeginPolymorphLockMessage() const
7464 {
7465   if(IsPlayer())
7466     ADD_MESSAGE("You feel incredibly stubborn about who you are.");
7467 }
7468 
PrintEndPolymorphLockMessage() const7469 void character::PrintEndPolymorphLockMessage() const
7470 {
7471   if(IsPlayer())
7472     ADD_MESSAGE("You feel more open to new ideas.");
7473 }
7474 
PolymorphLockHandler()7475 void character::PolymorphLockHandler()
7476 {
7477   if (TemporaryStateIsActivated(POLYMORPHED))
7478   {
7479       EditTemporaryStateCounter(POLYMORPHED, 1);
7480       if (GetTemporaryStateCounter(POLYMORPHED) < 1000)
7481         EditTemporaryStateCounter(POLYMORPHED, 1);
7482   }
7483 }
7484 
PrintBeginTeleportControlMessage() const7485 void character::PrintBeginTeleportControlMessage() const
7486 {
7487   if(IsPlayer())
7488     ADD_MESSAGE("You feel very controlled.");
7489 }
7490 
PrintEndTeleportControlMessage() const7491 void character::PrintEndTeleportControlMessage() const
7492 {
7493   if(IsPlayer())
7494     ADD_MESSAGE("You feel your control slipping.");
7495 }
7496 
PrintBeginRegenerationMessage() const7497 void character::PrintBeginRegenerationMessage() const
7498 {
7499   if(IsPlayer())
7500     ADD_MESSAGE("Your heart races.");
7501 }
7502 
PrintEndRegenerationMessage() const7503 void character::PrintEndRegenerationMessage() const
7504 {
7505   if(IsPlayer())
7506     ADD_MESSAGE("Your rapid heartbeat calms down.");
7507 }
7508 
PrintBeginDiseaseImmunityMessage() const7509 void character::PrintBeginDiseaseImmunityMessage() const
7510 {
7511   if(IsPlayer())
7512     ADD_MESSAGE("You feel especially healthy.");
7513 }
7514 
PrintEndDiseaseImmunityMessage() const7515 void character::PrintEndDiseaseImmunityMessage() const
7516 {
7517   if(IsPlayer())
7518     ADD_MESSAGE("You develop a sudden fear of germs.");
7519 }
7520 
PrintBeginTeleportLockMessage() const7521 void character::PrintBeginTeleportLockMessage() const
7522 {
7523   if(IsPlayer())
7524     ADD_MESSAGE("You feel firmly planted in reality.");
7525 }
7526 
PrintEndTeleportLockMessage() const7527 void character::PrintEndTeleportLockMessage() const
7528 {
7529   if(IsPlayer())
7530     ADD_MESSAGE("Your mind soars far and wide.");
7531 }
7532 
DisplayStethoscopeInfo(character *) const7533 void character::DisplayStethoscopeInfo(character*) const
7534 {
7535   game::RegionListItemEnable(false);
7536   game::RegionSilhouetteEnable(false);
7537   felist Info(CONST_S("Information about ") + GetDescription(DEFINITE));
7538   AddSpecialStethoscopeInfo(Info);
7539   Info.AddEntry(CONST_S("Endurance:    ") + GetAttribute(ENDURANCE), LIGHT_GRAY);
7540   Info.AddEntry(CONST_S("Perception:   ") + GetAttribute(PERCEPTION), LIGHT_GRAY);
7541   Info.AddEntry(CONST_S("Intelligence: ") + GetAttribute(INTELLIGENCE), LIGHT_GRAY);
7542   Info.AddEntry(CONST_S("Wisdom:       ") + GetAttribute(WISDOM), LIGHT_GRAY);
7543   Info.AddEntry(CONST_S("Willpower:    ") + GetAttribute(WILL_POWER), LIGHT_GRAY);
7544   Info.AddEntry(CONST_S("Charisma:     ") + GetAttribute(CHARISMA), LIGHT_GRAY);
7545   Info.AddEntry(CONST_S("Mana:         ") + GetAttribute(MANA), LIGHT_GRAY);
7546   Info.AddEntry(CONST_S(""), LIGHT_GRAY);
7547   Info.AddEntry(CONST_S("Height: ") + GetSize() + " cm", LIGHT_GRAY);
7548   Info.AddEntry(CONST_S("Weight: ") + GetTotalCharacterWeight() + " kg", LIGHT_GRAY);
7549   Info.AddEntry(CONST_S(""), LIGHT_GRAY);
7550   Info.AddEntry(CONST_S("Hit points: ") + GetHP() + "/" + GetMaxHP() + " (" + GetHitPointDescription() + ")",
7551                         IsInBadCondition() ? RED : LIGHT_GRAY);
7552   Info.AddEntry(CONST_S(""), LIGHT_GRAY);
7553 
7554   Info.AddEntry(CONST_S("Body parts:"), LIGHT_GRAY);
7555   festring EntryBP;
7556   for(int c = 0; c < BodyParts; ++c)
7557   {
7558     bodypart* BodyPart = GetBodyPart(c);
7559     if(!BodyPart) continue;
7560 
7561     EntryBP.Empty();
7562     if(BodyPart->GetMainMaterial()->GetConfig() == GetTorso()->GetMainMaterial()->GetConfig())
7563     {
7564       BodyPart->GetMainMaterial()->AddName(EntryBP, UNARTICLED);
7565       EntryBP << " ";
7566     }
7567     BodyPart->AddName(EntryBP, UNARTICLED); //this already says the material if differs from torso
7568     Info.AddEntry(EntryBP, BodyPart->GetMainMaterial()->GetColor());
7569   }
7570 
7571   Info.AddEntry(CONST_S(""), LIGHT_GRAY);
7572   Info.AddEntry(CONST_S("Status effects:"), LIGHT_GRAY);
7573 
7574   if(GetTalent() != GetWeakness())
7575   {
7576     if(GetTalent())
7577     {
7578       switch(GetTalent())
7579       {
7580         case TALENT_STRONG:
7581          Info.AddEntry("Strong", LIGHT_GRAY);
7582          break;
7583         case TALENT_FAST_N_ACCURATE:
7584          Info.AddEntry("Swift", LIGHT_GRAY);
7585          break;
7586         case TALENT_HEALTHY:
7587          Info.AddEntry("Healthy", LIGHT_GRAY);
7588          break;
7589         case TALENT_CLEVER:
7590          Info.AddEntry("Clever", LIGHT_GRAY);
7591          break;
7592       }
7593     }
7594     if(GetWeakness())
7595     {
7596       switch(GetWeakness())
7597       {
7598         case TALENT_STRONG:
7599          Info.AddEntry("Weak", LIGHT_GRAY);
7600          break;
7601         case TALENT_FAST_N_ACCURATE:
7602          Info.AddEntry("Clumsy", LIGHT_GRAY);
7603          break;
7604         case TALENT_HEALTHY:
7605          Info.AddEntry("Frail", LIGHT_GRAY);
7606          break;
7607         case TALENT_CLEVER:
7608          Info.AddEntry("Dim", LIGHT_GRAY);
7609          break;
7610       }
7611     }
7612   }
7613 
7614   if(GetAction())
7615     Info.AddEntry(festring(GetAction()->GetDescription()).CapitalizeCopy(), LIGHT_GRAY);
7616 
7617   for(int c = 0; c < STATES; ++c)
7618     if(StateIsActivated(1 << c)
7619        && (1 << c != HASTE || !StateIsActivated(SLOW))
7620        && (1 << c != SLOW || !StateIsActivated(HASTE)))
7621       Info.AddEntry(StateData[c].Description, LIGHT_GRAY);
7622 
7623   switch(GetTirednessState())
7624   {
7625    case FAINTING:
7626     Info.AddEntry("Fainting", RED);
7627     break;
7628    case EXHAUSTED:
7629     Info.AddEntry("Exhausted", LIGHT_GRAY);
7630     break;
7631   }
7632 
7633   if(IsPlayer() && game::PlayerHasBoat())
7634     Info.AddEntry("Ship Owned", LIGHT_GRAY);
7635 
7636   game::SetStandardListAttributes(Info);
7637   Info.Draw();
7638 }
7639 
CanUseStethoscope(truth PrintReason) const7640 truth character::CanUseStethoscope(truth PrintReason) const
7641 {
7642   if(PrintReason)
7643     ADD_MESSAGE("This type of monster can't use a stethoscope.");
7644 
7645   return false;
7646 }
7647 
7648 /* Effect used by at least Sophos.
7649  * NOTICE: Doesn't check for death! */
7650 
TeleportSomePartsAway(int NumberToTeleport)7651 void character::TeleportSomePartsAway(int NumberToTeleport)
7652 {
7653   if(StateIsActivated(TELEPORT_LOCK))
7654   {
7655     if(IsPlayer())
7656       ADD_MESSAGE("You feel very itchy for a moment.");
7657 
7658     return;
7659   }
7660 
7661   for(int c = 0; c < NumberToTeleport; ++c)
7662   {
7663     int RandomBodyPart = GetRandomNonVitalBodyPart();
7664 
7665     if(RandomBodyPart == NONE_INDEX)
7666     {
7667       for(; c < NumberToTeleport; ++c)
7668       {
7669         GetTorso()->SetHP((GetTorso()->GetHP() << 2) / 5);
7670         long TorsosVolume = GetTorso()->GetMainMaterial()->GetVolume() / 10;
7671 
7672         if(!TorsosVolume)
7673           break;
7674 
7675         long Amount = (RAND() % TorsosVolume) + 1;
7676         item* Lump = GetTorso()->GetMainMaterial()->CreateNaturalForm(Amount);
7677         GetTorso()->GetMainMaterial()->EditVolume(-Amount);
7678         Lump->MoveTo(GetNearLSquare(GetLevel()->GetRandomSquare())->GetStack());
7679 
7680         if(IsPlayer())
7681           ADD_MESSAGE("Parts of you teleport away.");
7682         else if(CanBeSeenByPlayer())
7683           ADD_MESSAGE("Parts of %s teleport away.", CHAR_NAME(DEFINITE));
7684       }
7685     }
7686     else
7687     {
7688       item* SeveredBodyPart = SevereBodyPart(RandomBodyPart);
7689 
7690       if(SeveredBodyPart)
7691       {
7692         GetNearLSquare(GetLevel()->GetRandomSquare())->AddItem(SeveredBodyPart);
7693         SeveredBodyPart->DropEquipment();
7694 
7695         if(IsPlayer())
7696           ADD_MESSAGE("Your %s teleports away.", GetBodyPartName(RandomBodyPart).CStr());
7697         else if(CanBeSeenByPlayer())
7698           ADD_MESSAGE("%s %s teleports away.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart).CStr());
7699       }
7700       else
7701       {
7702         if(IsPlayer())
7703           ADD_MESSAGE("Your %s disappears.", GetBodyPartName(RandomBodyPart).CStr());
7704         else if(CanBeSeenByPlayer())
7705           ADD_MESSAGE("%s %s disappears.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart).CStr());
7706       }
7707     }
7708   }
7709 }
7710 
7711 /* Returns an index of a random bodypart that is not vital. If no non-vital bodypart is found returns NONE_INDEX */
7712 
GetRandomNonVitalBodyPart() const7713 int character::GetRandomNonVitalBodyPart() const
7714 {
7715   int OKBodyPart[MAX_BODYPARTS];
7716   int OKBodyParts = 0;
7717 
7718   for(int c = 0; c < BodyParts; ++c)
7719     if(GetBodyPart(c) && !BodyPartIsVital(c))
7720       OKBodyPart[OKBodyParts++] = c;
7721 
7722   return OKBodyParts ? OKBodyPart[RAND() % OKBodyParts] : NONE_INDEX;
7723 }
7724 
GetTotalCharacterWeight() const7725 int character::GetTotalCharacterWeight() const
7726 {
7727   int CharacterWeight = 0;
7728 
7729   for(int c = 0; c < BodyParts; ++c)
7730   {
7731     bodypart* BodyPart = GetBodyPart(c);
7732 
7733     if(BodyPart)
7734     {
7735       CharacterWeight += BodyPart->GetWeight();
7736     }
7737   }
7738 
7739   return (floor(CharacterWeight / 1000));
7740 }
7741 
CalculateVolumeAndWeight()7742 void character::CalculateVolumeAndWeight()
7743 {
7744   Volume = Stack->GetVolume();
7745   Weight = Stack->GetWeight();
7746   BodyVolume = 0;
7747   CarriedWeight = Weight;
7748 
7749   for(int c = 0; c < BodyParts; ++c)
7750   {
7751     bodypart* BodyPart = GetBodyPart(c);
7752 
7753     if(BodyPart)
7754     {
7755       BodyVolume += BodyPart->GetBodyPartVolume();
7756       Volume += BodyPart->GetVolume();
7757       CarriedWeight += BodyPart->GetCarriedWeight();
7758       Weight += BodyPart->GetWeight();
7759     }
7760   }
7761 }
7762 
SignalVolumeAndWeightChange()7763 void character::SignalVolumeAndWeightChange()
7764 {
7765   if(!IsInitializing())
7766   {
7767     CalculateVolumeAndWeight();
7768 
7769     if(IsEnabled())
7770       CalculateBurdenState();
7771 
7772     if(MotherEntity)
7773       MotherEntity->SignalVolumeAndWeightChange();
7774   }
7775 }
7776 
SignalEmitationIncrease(col24 EmitationUpdate)7777 void character::SignalEmitationIncrease(col24 EmitationUpdate)
7778 {
7779   if(game::CompareLights(EmitationUpdate, Emitation) > 0)
7780   {
7781     game::CombineLights(Emitation, EmitationUpdate);
7782 
7783     if(MotherEntity)
7784       MotherEntity->SignalEmitationIncrease(EmitationUpdate);
7785     else if(SquareUnder[0] && !game::IsInWilderness())
7786       for(int c = 0; c < GetSquaresUnder(); ++c)
7787         GetLSquareUnder()->SignalEmitationIncrease(EmitationUpdate);
7788   }
7789 }
7790 
SignalEmitationDecrease(col24 EmitationUpdate)7791 void character::SignalEmitationDecrease(col24 EmitationUpdate)
7792 {
7793   if(game::CompareLights(EmitationUpdate, Emitation) >= 0 && Emitation)
7794   {
7795     col24 Backup = Emitation;
7796     CalculateEmitation();
7797 
7798     if(Backup != Emitation)
7799     {
7800       if(MotherEntity)
7801         MotherEntity->SignalEmitationDecrease(EmitationUpdate);
7802       else if(SquareUnder[0] && !game::IsInWilderness())
7803         for(int c = 0; c < GetSquaresUnder(); ++c)
7804           GetLSquareUnder(c)->SignalEmitationDecrease(EmitationUpdate);
7805     }
7806   }
7807 }
7808 
CalculateEmitation()7809 void character::CalculateEmitation()
7810 {
7811   Emitation = GetBaseEmitation();
7812 
7813   for(int c = 0; c < BodyParts; ++c)
7814   {
7815     bodypart* BodyPart = GetBodyPart(c);
7816 
7817     if(BodyPart)
7818       game::CombineLights(Emitation, BodyPart->GetEmitation());
7819   }
7820 
7821   game::CombineLights(Emitation, Stack->GetEmitation());
7822 }
7823 
CalculateAll()7824 void character::CalculateAll()
7825 {
7826   Flags |= C_INITIALIZING;
7827   CalculateAttributeBonuses();
7828   CalculateVolumeAndWeight();
7829   CalculateEmitation();
7830   CalculateBodyPartMaxHPs(0);
7831   CalculateMaxStamina();
7832   CalculateBurdenState();
7833   CalculateBattleInfo();
7834   Flags &= ~C_INITIALIZING;
7835 }
7836 
CalculateHP()7837 void character::CalculateHP()
7838 {
7839   HP = sumbodypartproperties()(this, &bodypart::GetHP);
7840 }
7841 
CalculateMaxHP()7842 void character::CalculateMaxHP()
7843 {
7844   MaxHP = sumbodypartproperties()(this, &bodypart::GetMaxHP);
7845 }
7846 
CalculateBodyPartMaxHPs(ulong Flags)7847 void character::CalculateBodyPartMaxHPs(ulong Flags)
7848 {
7849   doforbodypartswithparam<ulong>()(this, &bodypart::CalculateMaxHP, Flags);
7850   CalculateMaxHP();
7851   CalculateHP();
7852 }
7853 
EditAttribute(int Identifier,int Value)7854 truth character::EditAttribute(int Identifier, int Value)
7855 {
7856   if(Identifier == ENDURANCE && UseMaterialAttributes())
7857     return false;
7858 
7859   if(RawEditAttribute(BaseExperience[Identifier], Value))
7860   {
7861     if(!IsInitializing())
7862     {
7863       if(Identifier == LEG_STRENGTH)
7864         CalculateBurdenState();
7865       else if(Identifier == ENDURANCE)
7866         CalculateBodyPartMaxHPs();
7867       else if(IsPlayer() && Identifier == PERCEPTION)
7868         game::SendLOSUpdateRequest();
7869       else if(IsPlayerKind() && (Identifier == INTELLIGENCE || Identifier == WISDOM || Identifier == CHARISMA))
7870         UpdatePictures();
7871 
7872       CalculateBattleInfo();
7873     }
7874 
7875     return true;
7876   }
7877   else
7878     return false;
7879 }
7880 
ActivateRandomState(int Flags,int Time,long Seed)7881 truth character::ActivateRandomState(int Flags, int Time, long Seed)
7882 {
7883   femath::SaveSeed();
7884 
7885   if(Seed)
7886     femath::SetSeed(Seed);
7887 
7888   long ToBeActivated = GetRandomState(Flags|DUR_TEMPORARY);
7889   femath::LoadSeed();
7890 
7891   if(!ToBeActivated)
7892     return false;
7893 
7894   BeginTemporaryState(ToBeActivated, Time);
7895   return true;
7896 }
7897 
GainRandomIntrinsic(int Flags)7898 truth character::GainRandomIntrinsic(int Flags)
7899 {
7900   long ToBeActivated = GetRandomState(Flags|DUR_PERMANENT);
7901 
7902   if(!ToBeActivated)
7903     return false;
7904 
7905   GainIntrinsic(ToBeActivated);
7906   return true;
7907 }
7908 
7909 /* Returns 0 if state not found */
7910 
GetRandomState(int Flags) const7911 long character::GetRandomState(int Flags) const
7912 {
7913   long OKStates[STATES];
7914   int NumberOfOKStates = 0;
7915 
7916   for(int c = 0; c < STATES; ++c)
7917     if(StateData[c].Flags & Flags & DUR_FLAGS && StateData[c].Flags & Flags & SRC_FLAGS)
7918       OKStates[NumberOfOKStates++] = 1 << c;
7919 
7920   return NumberOfOKStates ? OKStates[RAND() % NumberOfOKStates] : 0;
7921 }
7922 
CreateSpecialConfigurations(characterdatabase ** TempConfig,int Configs,int Level)7923 int characterprototype::CreateSpecialConfigurations(characterdatabase** TempConfig, int Configs, int Level)
7924 {
7925   if(Level == 0 && TempConfig[0]->CreateDivineConfigurations)
7926     Configs = databasecreator<character>::CreateDivineConfigurations(this, TempConfig, Configs);
7927 
7928   if(Level == 1 && TempConfig[0]->CreateUndeadConfigurations)
7929     for(int c = 1; c < protocontainer<character>::GetSize(); ++c)
7930     {
7931       const character::prototype* Proto = protocontainer<character>::GetProto(c);
7932       const character::database*const* CharacterConfigData = Proto->GetConfigData();
7933       const character::database*const* End = CharacterConfigData + Proto->GetConfigSize();
7934 
7935       for(++CharacterConfigData; CharacterConfigData != End; ++CharacterConfigData)
7936       {
7937         const character::database* CharacterDataBase = *CharacterConfigData;
7938 
7939         if(CharacterDataBase->UndeadVersions)
7940         {
7941           character::database* ConfigDataBase = new character::database(**TempConfig);
7942           ConfigDataBase->InitDefaults(this, (c << 8) | CharacterDataBase->Config);
7943           ConfigDataBase->PostFix << "of ";
7944 
7945           if(CharacterDataBase->Adjective.GetSize())
7946           {
7947             if(CharacterDataBase->UsesLongAdjectiveArticle)
7948               ConfigDataBase->PostFix << "an ";
7949             else
7950               ConfigDataBase->PostFix << "a ";
7951 
7952             ConfigDataBase->PostFix << CharacterDataBase->Adjective << ' ';
7953           }
7954           else
7955           {
7956             if(CharacterDataBase->UsesLongArticle)
7957               ConfigDataBase->PostFix << "an ";
7958             else
7959               ConfigDataBase->PostFix << "a ";
7960           }
7961 
7962           ConfigDataBase->PostFix << CharacterDataBase->NameSingular;
7963 
7964           if(CharacterDataBase->PostFix.GetSize())
7965             ConfigDataBase->PostFix << ' ' << CharacterDataBase->PostFix;
7966 
7967           int P1 = TempConfig[0]->UndeadAttributeModifier;
7968           int P2 = TempConfig[0]->UndeadVolumeModifier;
7969           int c2;
7970 
7971           for(c2 = 0; c2 < ATTRIBUTES; ++c2)
7972               ConfigDataBase->*ExpPtr[c2] = CharacterDataBase->*ExpPtr[c2] * P1 / 100;
7973 
7974           for(c2 = 0; c2 < EQUIPMENT_DATAS; ++c2)
7975               ConfigDataBase->*EquipmentDataPtr[c2] = contentscript<item>();
7976 
7977           ConfigDataBase->DefaultIntelligence = 5;
7978           ConfigDataBase->DefaultWisdom = 5;
7979           ConfigDataBase->DefaultCharisma = 5;
7980           ConfigDataBase->TotalSize = CharacterDataBase->TotalSize;
7981           ConfigDataBase->Sex = CharacterDataBase->Sex;
7982           ConfigDataBase->AttributeBonus = CharacterDataBase->AttributeBonus;
7983           ConfigDataBase->TotalVolume = CharacterDataBase->TotalVolume * P2 / 100;
7984 
7985           if(TempConfig[0]->UndeadCopyMaterials)
7986           {
7987             ConfigDataBase->HeadBitmapPos = CharacterDataBase->HeadBitmapPos;
7988             ConfigDataBase->HairColor = CharacterDataBase->HairColor;
7989             ConfigDataBase->EyeColor = CharacterDataBase->EyeColor;
7990             ConfigDataBase->CapColor = CharacterDataBase->CapColor;
7991             ConfigDataBase->FleshMaterial = CharacterDataBase->FleshMaterial;
7992             ConfigDataBase->BloodMaterial = CharacterDataBase->BloodMaterial;
7993             ConfigDataBase->VomitMaterial = CharacterDataBase->VomitMaterial;
7994             ConfigDataBase->SweatMaterial = CharacterDataBase->SweatMaterial;
7995           }
7996 
7997           if(TempConfig[0]->GhostCopyMaterials)
7998           {
7999             ConfigDataBase->HeadBitmapPos = CharacterDataBase->HeadBitmapPos;
8000             ConfigDataBase->RightArmBitmapPos = CharacterDataBase->RightArmBitmapPos;
8001             ConfigDataBase->LeftArmBitmapPos = CharacterDataBase->LeftArmBitmapPos;
8002             ConfigDataBase->TorsoBitmapPos = CharacterDataBase->TorsoBitmapPos;
8003             ConfigDataBase->RightLegBitmapPos = CharacterDataBase->RightLegBitmapPos;
8004             ConfigDataBase->LeftLegBitmapPos = CharacterDataBase->LeftLegBitmapPos;
8005             ConfigDataBase->HairColor = CharacterDataBase->HairColor;
8006             ConfigDataBase->EyeColor = CharacterDataBase->EyeColor;
8007           }
8008 
8009           ConfigDataBase->KnownCWeaponSkills = CharacterDataBase->KnownCWeaponSkills;
8010           ConfigDataBase->CWeaponSkillHits = CharacterDataBase->CWeaponSkillHits;
8011           ConfigDataBase->PostProcess();
8012           TempConfig[Configs++] = ConfigDataBase;
8013         }
8014       }
8015     }
8016 
8017   if(Level == 0 && TempConfig[0]->CreateGolemMaterialConfigurations)
8018     for(int c = 1; c < protocontainer<material>::GetSize(); ++c)
8019     {
8020       const material::prototype* Proto = protocontainer<material>::GetProto(c);
8021       const material::database*const* MaterialConfigData = Proto->GetConfigData();
8022       const material::database*const* End = MaterialConfigData + Proto->GetConfigSize();
8023 
8024       for(++MaterialConfigData; MaterialConfigData != End; ++MaterialConfigData)
8025       {
8026         const material::database* MaterialDataBase = *MaterialConfigData;
8027 
8028         if(MaterialDataBase->CategoryFlags & IS_GOLEM_MATERIAL)
8029         {
8030           character::database* ConfigDataBase = new character::database(**TempConfig);
8031           ConfigDataBase->InitDefaults(this, MaterialDataBase->Config);
8032           ConfigDataBase->Adjective = MaterialDataBase->NameStem;
8033           ConfigDataBase->UsesLongAdjectiveArticle = MaterialDataBase->NameFlags & USE_AN;
8034           ConfigDataBase->AttachedGod = MaterialDataBase->AttachedGod;
8035           TempConfig[Configs++] = ConfigDataBase;
8036         }
8037       }
8038     }
8039 
8040   return Configs;
8041 }
8042 
GetTimeToDie(ccharacter * Enemy,int Damage,double ToHitValue,truth AttackIsBlockable,truth UseMaxHP) const8043 double character::GetTimeToDie(ccharacter* Enemy, int Damage, double ToHitValue,
8044                                truth AttackIsBlockable, truth UseMaxHP) const
8045 {
8046   double DodgeValue = GetDodgeValue();
8047 
8048   if(!Enemy->CanBeSeenBy(this, true))
8049     ToHitValue *= 2;
8050 
8051   if(!CanBeSeenBy(Enemy, true))
8052     DodgeValue *= 2;
8053 
8054   double MinHits = 1000;
8055   truth First = true;
8056 
8057   for(int c = 0; c < BodyParts; ++c)
8058     if(BodyPartIsVital(c) && GetBodyPart(c))
8059     {
8060       double Hits = GetBodyPart(c)->GetTimeToDie(Damage, ToHitValue, DodgeValue, AttackIsBlockable, UseMaxHP);
8061 
8062       if(First)
8063       {
8064         MinHits = Hits;
8065         First = false;
8066       }
8067       else
8068         MinHits = 1 / (1 / MinHits + 1 / Hits);
8069     }
8070 
8071   return MinHits;
8072 }
8073 
GetRelativeDanger(ccharacter * Enemy,truth UseMaxHP) const8074 double character::GetRelativeDanger(ccharacter* Enemy, truth UseMaxHP) const
8075 {
8076   double Danger = Enemy->GetTimeToKill(this, UseMaxHP) / GetTimeToKill(Enemy, UseMaxHP);
8077   int EnemyAP = Enemy->GetMoveAPRequirement(1);
8078   int ThisAP = GetMoveAPRequirement(1);
8079 
8080   if(EnemyAP > ThisAP)
8081     Danger *= 1.25;
8082   else if(ThisAP > EnemyAP)
8083     Danger *= 0.80;
8084 
8085   if(!Enemy->CanBeSeenBy(this, true))
8086     Danger *= Enemy->IsPlayer() ? 0.2 : 0.5;
8087 
8088   if(!CanBeSeenBy(Enemy, true))
8089     Danger *= IsPlayer() ? 5. : 2.;
8090 
8091   if(GetAttribute(INTELLIGENCE) < 10 && !IsPlayer())
8092     Danger *= 0.80;
8093 
8094   if(Enemy->GetAttribute(INTELLIGENCE) < 10 && !Enemy->IsPlayer())
8095     Danger *= 1.25;
8096 
8097   return Limit(Danger, 0.001, 1000.);
8098 }
8099 
GetBodyPartName(int I,truth Articled) const8100 festring character::GetBodyPartName(int I, truth Articled) const
8101 {
8102   if(I == TORSO_INDEX)
8103     return Articled ? CONST_S("a torso") : CONST_S("torso");
8104   else
8105   {
8106     ABORT("Illegal character bodypart name request!");
8107     return "";
8108   }
8109 }
8110 
SearchForItem(ulong ID) const8111 item* character::SearchForItem(ulong ID) const
8112 {
8113   item* Equipment = findequipment<ulong>()(this, &item::HasID, ID);
8114 
8115   if(Equipment)
8116     return Equipment;
8117 
8118   for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
8119     if(i->GetID() == ID)
8120       return *i;
8121 
8122   return 0;
8123 }
8124 
ContentsCanBeSeenBy(ccharacter * Viewer) const8125 truth character::ContentsCanBeSeenBy(ccharacter* Viewer) const
8126 {
8127   return Viewer == this;
8128 }
8129 
HitEffect(character * Enemy,item * Weapon,v2 HitPos,int Type,int BodyPartIndex,int Direction,truth BlockedByArmour,truth Critical,int DoneDamage)8130 truth character::HitEffect(character* Enemy, item* Weapon, v2 HitPos, int Type,
8131                            int BodyPartIndex, int Direction, truth BlockedByArmour, truth Critical, int DoneDamage)
8132 {
8133   if(Weapon)
8134     return Weapon->HitEffect(this, Enemy, HitPos, BodyPartIndex, Direction, BlockedByArmour);
8135 
8136   switch(Type)
8137   {
8138    case UNARMED_ATTACK:
8139     return Enemy->SpecialUnarmedEffect(this, HitPos, BodyPartIndex, Direction, BlockedByArmour);
8140    case KICK_ATTACK:
8141     return Enemy->SpecialKickEffect(this, HitPos, BodyPartIndex, Direction, BlockedByArmour);
8142    case BITE_ATTACK:
8143     return Enemy->SpecialBiteEffect(this, HitPos, BodyPartIndex, Direction, BlockedByArmour, Critical, DoneDamage);
8144   }
8145 
8146   return false;
8147 }
8148 
WeaponSkillHit(item * Weapon,int Type,int Hits)8149 void character::WeaponSkillHit(item* Weapon, int Type, int Hits)
8150 {
8151   int Category;
8152 
8153   switch(Type)
8154   {
8155    case UNARMED_ATTACK:
8156     Category = UNARMED;
8157     break;
8158    case WEAPON_ATTACK:
8159     Weapon->WeaponSkillHit(Hits);
8160     return;
8161    case KICK_ATTACK:
8162     Category = KICK;
8163     break;
8164    case BITE_ATTACK:
8165     Category = BITE;
8166     break;
8167    case THROW_ATTACK:
8168     if(!IsHumanoid()) return;
8169     Category = Weapon->GetWeaponCategory();
8170     break;
8171    default:
8172     ABORT("Illegal Type %d passed to character::WeaponSkillHit()!", Type);
8173     return;
8174   }
8175 
8176   if(GetCWeaponSkill(Category)->AddHit(Hits))
8177   {
8178     CalculateBattleInfo();
8179 
8180     if(IsPlayer())
8181       GetCWeaponSkill(Category)->AddLevelUpMessage(Category);
8182   }
8183 }
8184 
8185 /* Returns 0 if character cannot be duplicated */
8186 
Duplicate(ulong Flags)8187 character* character::Duplicate(ulong Flags)
8188 {
8189   if(!(Flags & IGNORE_PROHIBITIONS) && !CanBeCloned())
8190     return 0;
8191 
8192   character* Char = GetProtoType()->Clone(this);
8193 
8194   if(Flags & MIRROR_IMAGE)
8195   {
8196     DuplicateEquipment(Char, Flags & ~IGNORE_PROHIBITIONS);
8197     Char->SetLifeExpectancy(Flags >> LE_BASE_SHIFT & LE_BASE_RANGE,
8198                             Flags >> LE_RAND_SHIFT & LE_RAND_RANGE);
8199   }
8200 
8201   Char->CalculateAll();
8202   Char->CalculateEmitation();
8203   Char->UpdatePictures();
8204   Char->Flags &= ~(C_INITIALIZING|C_IN_NO_MSG_MODE);
8205   return Char;
8206 }
8207 
TryToEquip(item * Item,truth onlyIfEmpty,int onlyAt)8208 truth character::TryToEquip(item* Item, truth onlyIfEmpty, int onlyAt)
8209 {
8210   if(!Item->AllowEquip()
8211      || !CanUseEquipment()
8212      || GetAttribute(WISDOM) >= Item->GetWearWisdomLimit()
8213      || Item->GetSquaresUnder() != 1)
8214     return false;
8215 
8216   for(int e = 0; e < GetEquipments(); ++e){
8217     if(onlyAt>-1 && e!=onlyAt)continue;
8218 
8219     if(GetBodyPartOfEquipment(e) && EquipmentIsAllowed(e))
8220     {
8221       sorter Sorter = EquipmentSorter(e);
8222 
8223       if((Sorter == 0 || (Item->*Sorter)(this))
8224          && ((e != RIGHT_WIELDED_INDEX && e != LEFT_WIELDED_INDEX)
8225              || Item->IsWeapon(this)
8226              || Item->IsShield(this))
8227          && AllowEquipment(Item, e))
8228       {
8229         item* OldEquipment = GetEquipment(e);
8230         if(onlyIfEmpty && OldEquipment!=NULL)continue;
8231 
8232         if(BoundToUse(OldEquipment, e))
8233           continue;
8234 
8235         lsquare* LSquareUnder = GetLSquareUnder();
8236         stack* StackUnder = LSquareUnder->GetStack();
8237         msgsystem::DisableMessages();
8238         Flags |= C_PICTURE_UPDATES_FORBIDDEN;
8239         LSquareUnder->Freeze();
8240         StackUnder->Freeze();
8241         double Danger = GetRelativeDanger(PLAYER);
8242 
8243         if(OldEquipment)
8244           OldEquipment->RemoveFromSlot();
8245 
8246         Item->RemoveFromSlot();
8247         SetEquipment(e, Item);
8248         double NewDanger = GetRelativeDanger(PLAYER);
8249         Item->RemoveFromSlot();
8250         StackUnder->AddItem(Item);
8251 
8252         if(OldEquipment)
8253           SetEquipment(e, OldEquipment);
8254 
8255         msgsystem::EnableMessages();
8256         Flags &= ~C_PICTURE_UPDATES_FORBIDDEN;
8257         LSquareUnder->UnFreeze();
8258         StackUnder->UnFreeze();
8259 
8260         if(OldEquipment)
8261         {
8262           if(NewDanger > Danger || BoundToUse(Item, e))
8263           {
8264             room* Room = GetRoom();
8265 
8266             if(!Room || Room->PickupItem(this, Item, 1))
8267             {
8268               if(CanBeSeenByPlayer())
8269                 ADD_MESSAGE("%s drops %s %s and equips %s instead.",
8270                             CHAR_NAME(DEFINITE), CHAR_POSSESSIVE_PRONOUN,
8271                             OldEquipment->CHAR_NAME(UNARTICLED), Item->CHAR_NAME(INDEFINITE));
8272 
8273               if(Room)
8274                 Room->DropItem(this, OldEquipment, 1);
8275 
8276               OldEquipment->MoveTo(StackUnder);
8277               Item->RemoveFromSlot();
8278               SetEquipment(e, Item);
8279               DexterityAction(5);
8280               return true;
8281             }
8282           }
8283         }
8284         else
8285         {
8286           if(NewDanger > Danger
8287              || (NewDanger == Danger
8288                  && e != RIGHT_WIELDED_INDEX && e != LEFT_WIELDED_INDEX)
8289              || BoundToUse(Item, e))
8290           {
8291             room* Room = GetRoom();
8292 
8293             if(!Room || Room->PickupItem(this, Item, 1))
8294             {
8295               if(CanBeSeenByPlayer())
8296                 ADD_MESSAGE("%s picks up and equips %s.", CHAR_NAME(DEFINITE), Item->CHAR_NAME(INDEFINITE));
8297 
8298               Item->RemoveFromSlot();
8299               SetEquipment(e, Item);
8300               DexterityAction(5);
8301               return true;
8302             }
8303           }
8304         }
8305       }
8306     }
8307   }
8308 
8309   return false;
8310 }
8311 
TryToConsume(item * Item)8312 truth character::TryToConsume(item* Item)
8313 {
8314   return Item->CanBeEatenByAI(this) && ConsumeItem(Item, Item->GetConsumeMaterial(this)->GetConsumeVerb());
8315 }
8316 
UpdateESPLOS() const8317 void character::UpdateESPLOS() const
8318 {
8319   if(StateIsActivated(ESP) && !game::IsInWilderness())
8320     for(int c = 0; c < game::GetTeams(); ++c)
8321       for(character* p : game::GetTeam(c)->GetMember())
8322         if(p->IsEnabled())
8323           p->SendNewDrawRequest();
8324 }
8325 
GetCWeaponSkillLevel(citem * Item) const8326 int character::GetCWeaponSkillLevel(citem* Item) const
8327 {
8328   if(Item->GetWeaponCategory() < GetAllowedWeaponSkillCategories())
8329     return GetCWeaponSkill(Item->GetWeaponCategory())->GetLevel();
8330   else
8331     return 0;
8332 }
8333 
PrintBeginPanicMessage() const8334 void character::PrintBeginPanicMessage() const
8335 {
8336   if(IsPlayer())
8337     ADD_MESSAGE("You panic!");
8338   else if(CanBeSeenByPlayer())
8339     ADD_MESSAGE("%s panics.", CHAR_NAME(DEFINITE));
8340 }
8341 
PrintEndPanicMessage() const8342 void character::PrintEndPanicMessage() const
8343 {
8344   if(IsPlayer())
8345     ADD_MESSAGE("You finally calm down.");
8346   else if(CanBeSeenByPlayer())
8347     ADD_MESSAGE("%s calms down.", CHAR_NAME(DEFINITE));
8348 }
8349 
CheckPanic(int Ticks)8350 void character::CheckPanic(int Ticks)
8351 {
8352   if(GetPanicLevel() > 1 && !StateIsActivated(PANIC)
8353      && GetHP() * 100 < RAND() % (GetPanicLevel() * GetMaxHP() << 1) && !StateIsActivated(FEARLESS))
8354     BeginTemporaryState(PANIC, ((Ticks * 3) >> 2) + RAND() % ((Ticks >> 1) + 1)); // 25% randomness to ticks...
8355 }
8356 
8357 /* returns 0 if fails else the newly created character */
8358 
DuplicateToNearestSquare(character * Cloner,ulong Flags)8359 character* character::DuplicateToNearestSquare(character* Cloner, ulong Flags)
8360 {
8361   character* NewlyCreated = Duplicate(Flags);
8362 
8363   if(!NewlyCreated)
8364     return 0;
8365 
8366   if(Flags & CHANGE_TEAM && Cloner)
8367     NewlyCreated->ChangeTeam(Cloner->GetTeam());
8368 
8369   NewlyCreated->PutNear(GetPos());
8370   return NewlyCreated;
8371 }
8372 
SignalSpoil()8373 void character::SignalSpoil()
8374 {
8375   if(GetMotherEntity())
8376     GetMotherEntity()->SignalSpoil(0);
8377   else
8378     Disappear(0, "spoil", &item::IsVeryCloseToSpoiling);
8379 }
8380 // never call this function!
SignalBurn()8381 void character::SignalBurn()
8382 {
8383   if(GetMotherEntity())
8384     GetMotherEntity()->SignalBurn(0);
8385   else
8386     Disappear(0, "burn", &item::IsVeryCloseToBurning);
8387 }
8388 
Extinguish(truth SendMessages)8389 void character::Extinguish(truth SendMessages)
8390 {
8391   doforbodypartswithparam<truth>()(this, &bodypart::Extinguish, SendMessages);
8392 }
8393 
CanHeal() const8394 truth character::CanHeal() const
8395 {
8396   for(int c = 0; c < BodyParts; ++c)
8397   {
8398     bodypart* BodyPart = GetBodyPart(c);
8399 
8400     if(BodyPart && BodyPart->CanRegenerate() && BodyPart->GetHP() < BodyPart->GetMaxHP())
8401       return true;
8402   }
8403 
8404   return false;
8405 }
8406 
GetRelation(ccharacter * Who) const8407 int character::GetRelation(ccharacter* Who) const
8408 {
8409   return GetTeam()->GetRelation(Who->GetTeam());
8410 }
8411 
8412 truth (item::*AffectTest[BASE_ATTRIBUTES])() const =
8413 {
8414   &item::AffectsEndurance,
8415   &item::AffectsPerception,
8416   &item::AffectsIntelligence,
8417   &item::AffectsWisdom,
8418   &item::AffectsWillPower,
8419   &item::AffectsCharisma,
8420   &item::AffectsMana
8421 };
8422 
8423 /* Returns nonzero if endurance has decreased and death may occur */
8424 
CalculateAttributeBonuses()8425 truth character::CalculateAttributeBonuses()
8426 {
8427   doforbodyparts()(this, &bodypart::CalculateAttributeBonuses);
8428   int BackupBonus[BASE_ATTRIBUTES];
8429   int BackupCarryingBonus = CarryingBonus;
8430   CarryingBonus = 0;
8431   int c1;
8432 
8433   for(c1 = 0; c1 < BASE_ATTRIBUTES; ++c1)
8434   {
8435     BackupBonus[c1] = AttributeBonus[c1];
8436     AttributeBonus[c1] = 0;
8437   }
8438 
8439   for(c1 = 0; c1 < GetEquipments(); ++c1)
8440   {
8441     item* Equipment = GetEquipment(c1);
8442 
8443     if(!Equipment || !Equipment->IsInCorrectSlot(c1))
8444       continue;
8445 
8446     for(int c2 = 0; c2 < BASE_ATTRIBUTES; ++c2)
8447       if((Equipment->*AffectTest[c2])())
8448         AttributeBonus[c2] += Equipment->GetEnchantment();
8449 
8450     if(Equipment->AffectsCarryingCapacity())
8451       CarryingBonus += Equipment->GetCarryingBonus();
8452   }
8453 
8454   ApplySpecialAttributeBonuses();
8455 
8456   if(IsPlayer() && !IsInitializing() && AttributeBonus[PERCEPTION] != BackupBonus[PERCEPTION])
8457     game::SendLOSUpdateRequest();
8458 
8459   if(IsPlayer() && !IsInitializing() && AttributeBonus[INTELLIGENCE] != BackupBonus[INTELLIGENCE])
8460     UpdateESPLOS();
8461 
8462   if(!IsInitializing() && CarryingBonus != BackupCarryingBonus)
8463     CalculateBurdenState();
8464 
8465   if(!IsInitializing() && AttributeBonus[ENDURANCE] != BackupBonus[ENDURANCE])
8466   {
8467     CalculateBodyPartMaxHPs();
8468     CalculateMaxStamina();
8469     return AttributeBonus[ENDURANCE] < BackupBonus[ENDURANCE];
8470   }
8471 
8472   return false;
8473 }
8474 
ApplyEquipmentAttributeBonuses(item * Equipment)8475 void character::ApplyEquipmentAttributeBonuses(item* Equipment)
8476 {
8477   if(Equipment->AffectsEndurance())
8478   {
8479     AttributeBonus[ENDURANCE] += Equipment->GetEnchantment();
8480     CalculateBodyPartMaxHPs();
8481     CalculateMaxStamina();
8482   }
8483 
8484   if(Equipment->AffectsPerception())
8485   {
8486     AttributeBonus[PERCEPTION] += Equipment->GetEnchantment();
8487 
8488     if(IsPlayer())
8489       game::SendLOSUpdateRequest();
8490   }
8491 
8492   if(Equipment->AffectsIntelligence())
8493   {
8494     AttributeBonus[INTELLIGENCE] += Equipment->GetEnchantment();
8495 
8496     if(IsPlayer())
8497       UpdateESPLOS();
8498   }
8499 
8500   if(Equipment->AffectsWisdom())
8501     AttributeBonus[WISDOM] += Equipment->GetEnchantment();
8502 
8503   if(Equipment->AffectsWillPower())
8504     AttributeBonus[WILL_POWER] += Equipment->GetEnchantment();
8505 
8506   if(Equipment->AffectsCharisma())
8507     AttributeBonus[CHARISMA] += Equipment->GetEnchantment();
8508 
8509   if(Equipment->AffectsMana())
8510     AttributeBonus[MANA] += Equipment->GetEnchantment();
8511 
8512   if(Equipment->AffectsCarryingCapacity())
8513   {
8514     CarryingBonus += Equipment->GetCarryingBonus();
8515     CalculateBurdenState();
8516   }
8517 }
8518 
ReceiveAntidote(long Amount)8519 void character::ReceiveAntidote(long Amount)
8520 {
8521   if(StateIsActivated(POISONED))
8522   {
8523     if(GetTemporaryStateCounter(POISONED) > Amount)
8524     {
8525       EditTemporaryStateCounter(POISONED, -Amount);
8526       Amount = 0;
8527     }
8528     else
8529     {
8530       if(IsPlayer())
8531         ADD_MESSAGE("Aaaah... You feel much better.");
8532 
8533       Amount -= GetTemporaryStateCounter(POISONED);
8534       DeActivateTemporaryState(POISONED);
8535     }
8536   }
8537 
8538   if((Amount > 500 || RAND_N(1000) < Amount) && StateIsActivated(PARASITE_TAPE_WORM))
8539   {
8540     if(IsPlayer())
8541       ADD_MESSAGE("Something in your belly didn't seem to like this stuff.");
8542 
8543     DeActivateTemporaryState(PARASITE_TAPE_WORM);
8544     Amount -= Min(100L, Amount);
8545   }
8546 
8547   if((Amount > 500 || RAND_N(1000) < Amount) && StateIsActivated(PARASITE_MIND_WORM))
8548   {
8549     if(IsPlayer())
8550       ADD_MESSAGE("Something in your head screeches in pain.");
8551 
8552     DeActivateTemporaryState(PARASITE_MIND_WORM);
8553     Amount -= Min(100L, Amount);
8554   }
8555 
8556   if((Amount > 500 || RAND_N(1000) < Amount) && StateIsActivated(LEPROSY))
8557   {
8558     if(IsPlayer())
8559       ADD_MESSAGE("You are not falling to pieces anymore.");
8560 
8561     DeActivateTemporaryState(LEPROSY);
8562     Amount -= Min(100L, Amount);
8563   }
8564 }
8565 
AddAntidoteConsumeEndMessage() const8566 void character::AddAntidoteConsumeEndMessage() const
8567 {
8568   if(StateIsActivated(POISONED)) // true only if the antidote didn't cure the poison completely
8569   {
8570     if(IsPlayer())
8571       ADD_MESSAGE("Your body processes the poison in your veins with rapid speed.");
8572   }
8573 }
8574 
IsDead() const8575 truth character::IsDead() const
8576 {
8577   for(int c = 0; c < BodyParts; ++c)
8578   {
8579     bodypart* BodyPart = GetBodyPart(c);
8580 
8581     if(BodyPartIsVital(c) && (!BodyPart || BodyPart->GetHP() < 1))
8582       return true;
8583   }
8584 
8585   return false;
8586 }
8587 
SignalSpoilLevelChange()8588 void character::SignalSpoilLevelChange()
8589 {
8590   if(GetMotherEntity())
8591     GetMotherEntity()->SignalSpoilLevelChange(0);
8592   else
8593     UpdatePictures();
8594 }
8595 
SignalBurnLevelChange()8596 void character::SignalBurnLevelChange()
8597 {
8598 //  if(GetMotherEntity())
8599 //    GetMotherEntity()->SignalBurnLevelChange();
8600 //  else
8601     UpdatePictures();
8602 }
8603 
AddOriginalBodyPartID(int I,ulong What)8604 void character::AddOriginalBodyPartID(int I, ulong What)
8605 {
8606   if(std::find(OriginalBodyPartID[I].begin(), OriginalBodyPartID[I].end(), What) == OriginalBodyPartID[I].end())
8607   {
8608     OriginalBodyPartID[I].push_back(What);
8609 
8610     if(OriginalBodyPartID[I].size() > 100)
8611       OriginalBodyPartID[I].erase(OriginalBodyPartID[I].begin());
8612   }
8613 }
8614 
AddToInventory(const fearray<contentscript<item>> & ItemArray,int SpecialFlags)8615 void character::AddToInventory(const fearray<contentscript<item>>& ItemArray, int SpecialFlags)
8616 {
8617   for(uint c1 = 0; c1 < ItemArray.Size; ++c1)
8618     if(ItemArray[c1].IsValid())
8619     {
8620       const interval* TimesPtr = ItemArray[c1].GetTimes();
8621       int Times = TimesPtr ? TimesPtr->Randomize() : 1;
8622 
8623       for(int c2 = 0; c2 < Times; ++c2)
8624       {
8625         item* Item = ItemArray[c1].Instantiate(SpecialFlags);
8626 
8627         if(Item)
8628         {
8629           Stack->AddItem(Item);
8630           Item->SpecialGenerationHandler();
8631         }
8632       }
8633     }
8634 }
8635 
HasHadBodyPart(citem * Item) const8636 truth character::HasHadBodyPart(citem* Item) const
8637 {
8638   for(int c = 0; c < BodyParts; ++c)
8639     if(std::find(OriginalBodyPartID[c].begin(),
8640                  OriginalBodyPartID[c].end(),
8641                  Item->GetID()) != OriginalBodyPartID[c].end())
8642       return true;
8643 
8644   return GetPolymorphBackup() && GetPolymorphBackup()->HasHadBodyPart(Item);
8645 }
8646 
ProcessMessage(festring & Msg) const8647 festring& character::ProcessMessage(festring& Msg) const
8648 {
8649   SEARCH_N_REPLACE(Msg, "@nu", GetName(UNARTICLED));
8650   SEARCH_N_REPLACE(Msg, "@ni", GetName(INDEFINITE));
8651   SEARCH_N_REPLACE(Msg, "@nd", GetName(DEFINITE));
8652   SEARCH_N_REPLACE(Msg, "@du", GetDescription(UNARTICLED));
8653   SEARCH_N_REPLACE(Msg, "@di", GetDescription(INDEFINITE));
8654   SEARCH_N_REPLACE(Msg, "@dd", GetDescription(DEFINITE));
8655   SEARCH_N_REPLACE(Msg, "@pp", GetPersonalPronoun());
8656   SEARCH_N_REPLACE(Msg, "@sp", GetPossessivePronoun());
8657   SEARCH_N_REPLACE(Msg, "@op", GetObjectPronoun());
8658   SEARCH_N_REPLACE(Msg, "@Nu", GetName(UNARTICLED).CapitalizeCopy());
8659   SEARCH_N_REPLACE(Msg, "@Ni", GetName(INDEFINITE).CapitalizeCopy());
8660   SEARCH_N_REPLACE(Msg, "@Nd", GetName(DEFINITE).CapitalizeCopy());
8661   SEARCH_N_REPLACE(Msg, "@Du", GetDescription(UNARTICLED).CapitalizeCopy());
8662   SEARCH_N_REPLACE(Msg, "@Di", GetDescription(INDEFINITE).CapitalizeCopy());
8663   SEARCH_N_REPLACE(Msg, "@Dd", GetDescription(DEFINITE).CapitalizeCopy());
8664   SEARCH_N_REPLACE(Msg, "@Pp", GetPersonalPronoun().CapitalizeCopy());
8665   SEARCH_N_REPLACE(Msg, "@Sp", GetPossessivePronoun().CapitalizeCopy());
8666   SEARCH_N_REPLACE(Msg, "@Op", GetObjectPronoun().CapitalizeCopy());
8667   SEARCH_N_REPLACE(Msg, "@Gd", GetMasterGod()->GetName());
8668   SEARCH_N_REPLACE(Msg, "@ws", GetWorldShapeDescription());
8669   return Msg;
8670 }
8671 
ProcessAndAddMessage(festring Msg) const8672 void character::ProcessAndAddMessage(festring Msg) const
8673 {
8674   ADD_MESSAGE("%s", ProcessMessage(Msg).CStr());
8675 }
8676 
BeTalkedTo()8677 void character::BeTalkedTo()
8678 {
8679   static long Said;
8680 
8681   if(GetRelation(PLAYER) == HOSTILE)
8682     ProcessAndAddMessage(GetHostileReplies()[RandomizeReply(Said, GetHostileReplies().Size)]);
8683   else
8684     ProcessAndAddMessage(GetFriendlyReplies()[RandomizeReply(Said, GetFriendlyReplies().Size)]);
8685 }
8686 
CheckZap()8687 truth character::CheckZap()
8688 {
8689   if(!CanZap())
8690   {
8691     ADD_MESSAGE("This monster type can't zap.");
8692     return false;
8693   }
8694   if(GetAttribute(INTELLIGENCE) < 5)
8695   {
8696     ADD_MESSAGE("You are too dumb to operate any delicate magical devices.");
8697     return false;
8698   }
8699 
8700   return true;
8701 }
8702 
DamageAllItems(character * Damager,int Damage,int Type)8703 void character::DamageAllItems(character* Damager, int Damage, int Type)
8704 {
8705   GetStack()->ReceiveDamage(Damager, Damage, Type);
8706 
8707   for(int c = 0; c < GetEquipments(); ++c)
8708   {
8709     item* Equipment = GetEquipment(c);
8710 
8711     if(Equipment)
8712       Equipment->ReceiveDamage(Damager, Damage, Type);
8713   }
8714 }
8715 
Equips(citem * Item) const8716 truth character::Equips(citem* Item) const
8717 {
8718   return combineequipmentpredicateswithparam<ulong>()(this, &item::HasID, Item->GetID(), 1);
8719 }
8720 
PrintBeginConfuseMessage() const8721 void character::PrintBeginConfuseMessage() const
8722 {
8723   if(IsPlayer())
8724     ADD_MESSAGE("You feel quite happy.");
8725 }
8726 
PrintEndConfuseMessage() const8727 void character::PrintEndConfuseMessage() const
8728 {
8729   if(IsPlayer())
8730     ADD_MESSAGE("The world is boring again.");
8731 }
8732 
ApplyStateModification(v2 TryDirection) const8733 v2 character::ApplyStateModification(v2 TryDirection) const
8734 {
8735   if(!StateIsActivated(CONFUSED) || RAND() & 15 || game::IsInWilderness())
8736     return TryDirection;
8737   else
8738   {
8739     v2 To = GetLevel()->GetFreeAdjacentSquare(this, GetPos(), true);
8740 
8741     if(To == ERROR_V2)
8742       return TryDirection;
8743     else
8744     {
8745       To -= GetPos();
8746 
8747       if(To != TryDirection && IsPlayer())
8748         ADD_MESSAGE("Whoa! You somehow don't manage to walk straight.");
8749 
8750       return To;
8751     }
8752   }
8753 }
8754 
AddConfuseHitMessage() const8755 void character::AddConfuseHitMessage() const
8756 {
8757   if(IsPlayer())
8758     ADD_MESSAGE("This stuff is confusing.");
8759 }
8760 
SelectFromPossessions(cfestring & Topic,sorter Sorter)8761 item* character::SelectFromPossessions(cfestring& Topic, sorter Sorter)
8762 {
8763   itemvector ReturnVector;
8764   SelectFromPossessions(ReturnVector, Topic, NO_MULTI_SELECT, Sorter);
8765   return !ReturnVector.empty() ? ReturnVector[0] : 0;
8766 }
8767 
SelectFromPossessions(itemvector & ReturnVector,cfestring & Topic,int Flags,sorter Sorter)8768 truth character::SelectFromPossessions(itemvector& ReturnVector, cfestring& Topic, int Flags, sorter Sorter)
8769 {
8770   felist List(Topic);
8771   truth InventoryPossible = GetStack()->SortedItems(this, Sorter);
8772 
8773   if(InventoryPossible)
8774     List.AddEntry(CONST_S("choose from inventory"), LIGHT_GRAY, 20, game::AddToItemDrawVector(itemvector()));
8775 
8776   truth Any = false;
8777   itemvector Item;
8778   festring Entry;
8779   int c;
8780 
8781   for(c = 0; c < BodyParts; ++c)
8782   {
8783     bodypart* BodyPart = GetBodyPart(c);
8784 
8785     if(BodyPart && (Sorter == 0 || (BodyPart->*Sorter)(this)))
8786     {
8787       Item.push_back(BodyPart);
8788       Entry.Empty();
8789       BodyPart->AddName(Entry, UNARTICLED);
8790       int ImageKey = game::AddToItemDrawVector(itemvector(1, BodyPart));
8791       List.AddEntry(Entry, LIGHT_GRAY, 20, ImageKey, true);
8792       Any = true;
8793     }
8794   }
8795 
8796   for(c = 0; c < GetEquipments(); ++c)
8797   {
8798     item* Equipment = GetEquipment(c);
8799 
8800     if(Equipment && (Sorter == 0 || (Equipment->*Sorter)(this)))
8801     {
8802       Item.push_back(Equipment);
8803       Entry = GetEquipmentName(c);
8804       Entry << ':';
8805       Entry.Resize(20);
8806       Equipment->AddInventoryEntry(this, Entry, 1, true);
8807       AddSpecialEquipmentInfo(Entry, c);
8808       int ImageKey = game::AddToItemDrawVector(itemvector(1, Equipment));
8809       List.AddEntry(Entry, LIGHT_GRAY, 20, ImageKey, true);
8810       Any = true;
8811     }
8812   }
8813 
8814   if(Any)
8815   {
8816     game::SetStandardListAttributes(List);
8817     List.SetFlags(SELECTABLE|DRAW_BACKGROUND_AFTERWARDS);
8818     List.SetEntryDrawer(game::ItemEntryDrawer);
8819     game::DrawEverythingNoBlit();
8820     game::RegionListItemEnable(true);
8821     game::RegionSilhouetteEnable(true);
8822     int Chosen = List.Draw();
8823     game::RegionListItemEnable(false);
8824     game::RegionSilhouetteEnable(false);
8825     game::ClearItemDrawVector();
8826 
8827     if(Chosen != ESCAPED)
8828     {
8829       if((InventoryPossible && !Chosen) || Chosen & FELIST_ERROR_BIT)
8830         GetStack()->DrawContents(ReturnVector, this, Topic, Flags, Sorter);
8831       else
8832       {
8833         ReturnVector.push_back(Item[InventoryPossible ? Chosen - 1 : Chosen]);
8834 
8835         if(Flags & SELECT_PAIR && ReturnVector[0]->HandleInPairs())
8836         {
8837           item* PairEquipment = GetPairEquipment(ReturnVector[0]->GetEquipmentIndex());
8838 
8839           if(PairEquipment && PairEquipment->CanBePiledWith(ReturnVector[0], this))
8840             ReturnVector.push_back(PairEquipment);
8841         }
8842       }
8843     }
8844   }
8845   else
8846   {
8847     if(!GetStack()->SortedItems(this, Sorter))
8848       return false;
8849 
8850     game::ClearItemDrawVector();
8851     GetStack()->DrawContents(ReturnVector, this, Topic, Flags, Sorter);
8852   }
8853 
8854   return true;
8855 }
8856 
EquipsSomething(sorter Sorter)8857 truth character::EquipsSomething(sorter Sorter)
8858 {
8859   for(int c = 0; c < GetEquipments(); ++c)
8860   {
8861     item* Equipment = GetEquipment(c);
8862 
8863     if(Equipment && (Sorter == 0 || (Equipment->*Sorter)(this)))
8864       return true;
8865   }
8866 
8867   return false;
8868 }
8869 
CreateBodyPartMaterial(int,long Volume) const8870 material* character::CreateBodyPartMaterial(int, long Volume) const
8871 {
8872   return MAKE_MATERIAL(GetFleshMaterial(), Volume);
8873 }
8874 
CheckTalk()8875 truth character::CheckTalk()
8876 {
8877   if(!CanTalk())
8878   {
8879     ADD_MESSAGE("This monster does not know the art of talking.");
8880     return false;
8881   }
8882 
8883   return true;
8884 }
8885 
MoveTowardsHomePos()8886 truth character::MoveTowardsHomePos()
8887 {
8888   if(HomeDataIsValid() && IsEnabled())
8889   {
8890     SetGoingTo(HomeData->Pos);
8891     return MoveTowardsTarget(false)
8892       || (!GetPos().IsAdjacent(HomeData->Pos) && MoveRandomly());
8893   }
8894   else
8895     return false;
8896 }
8897 
TryToChangeEquipment(stack * MainStack,stack * SecStack,int Chosen)8898 truth character::TryToChangeEquipment(stack* MainStack, stack* SecStack, int Chosen)
8899 {
8900   if(!GetBodyPartOfEquipment(Chosen))
8901   {
8902     ADD_MESSAGE("Bodypart missing!");
8903     return false;
8904   }
8905 
8906   item* OldEquipment = GetEquipment(Chosen);
8907 
8908   if(!IsPlayer() && BoundToUse(OldEquipment, Chosen))
8909   {
8910     ADD_MESSAGE("%s refuses to unequip %s.", CHAR_DESCRIPTION(DEFINITE), OldEquipment->CHAR_NAME(DEFINITE));
8911     return false;
8912   }
8913 
8914   if(OldEquipment)
8915   {
8916     if(!OldEquipment->CanBeUnEquipped(Chosen))
8917     {
8918       if(IsPlayer())
8919         ADD_MESSAGE("You fail to unequip %s.", OldEquipment->CHAR_NAME(DEFINITE));
8920       else
8921         ADD_MESSAGE("%s fails to unequip %s.", CHAR_DESCRIPTION(DEFINITE), OldEquipment->CHAR_NAME(DEFINITE));
8922 
8923       return false;
8924     }
8925     else
8926       OldEquipment->MoveTo(MainStack);
8927   }
8928 
8929   sorter Sorter = EquipmentSorter(Chosen);
8930 
8931   if(!MainStack->SortedItems(this, Sorter)
8932      && (!SecStack || !SecStack->SortedItems(this, Sorter)))
8933   {
8934     ADD_MESSAGE("You haven't got any item that could be used for this purpose.");
8935     return false;
8936   }
8937   else
8938   {
8939     game::DrawEverythingNoBlit();
8940     itemvector ItemVector;
8941     int Return = MainStack->DrawContents(ItemVector,
8942                                          SecStack,
8943                                          this,
8944                                          CONST_S("Choose ") + GetEquipmentName(Chosen) + ':',
8945                                          SecStack ? CONST_S("Items in your inventory") : CONST_S(""),
8946                                          SecStack ? festring(CONST_S("Items in ") + GetPossessivePronoun()
8947                                                              + " inventory") : CONST_S(""),
8948                                          SecStack ? festring(GetDescription(DEFINITE) + " is "
8949                                                              + GetVerbalBurdenState()) : CONST_S(""),
8950                                          GetVerbalBurdenStateColor(),
8951                                          NONE_AS_CHOICE|NO_MULTI_SELECT,
8952                                          Sorter);
8953 
8954     if(Return == ESCAPED)
8955     {
8956       if(OldEquipment)
8957       {
8958         OldEquipment->RemoveFromSlot();
8959         SetEquipment(Chosen, OldEquipment);
8960       }
8961 
8962       return false;
8963     }
8964 
8965     item* Item = ItemVector.empty() ? 0 : ItemVector[0];
8966 
8967     if(Item)
8968     {
8969       if(!IsPlayer() && !AllowEquipment(Item, Chosen))
8970       {
8971         ADD_MESSAGE("%s refuses to equip %s.", CHAR_DESCRIPTION(DEFINITE), Item->CHAR_NAME(DEFINITE));
8972         return false;
8973       }
8974 
8975       if(!Item->CanBeEquipped(Chosen))
8976       {
8977         if(IsPlayer())
8978           ADD_MESSAGE("You fail to equip %s.", Item->CHAR_NAME(DEFINITE));
8979         else
8980           ADD_MESSAGE("%s fails to equip %s.", CHAR_DESCRIPTION(DEFINITE), Item->CHAR_NAME(DEFINITE));
8981 
8982         return false;
8983       }
8984 
8985       Item->RemoveFromSlot();
8986       SetEquipment(Chosen, Item);
8987 
8988       if(CheckIfEquipmentIsNotUsable(Chosen))
8989         Item->MoveTo(MainStack); // small bug?
8990     }
8991 
8992     return Item != OldEquipment;
8993   }
8994 }
8995 
PrintBeginParasitizedMessage() const8996 void character::PrintBeginParasitizedMessage() const
8997 {
8998   if(IsPlayer())
8999     ADD_MESSAGE("You feel you are no longer alone.");
9000 }
9001 
PrintEndParasitizedMessage() const9002 void character::PrintEndParasitizedMessage() const
9003 {
9004   if(IsPlayer())
9005     ADD_MESSAGE("A feeling of long welcome emptiness overwhelms you.");
9006 }
9007 
ParasitizedHandler()9008 void character::ParasitizedHandler()
9009 {
9010   EditNP(-10);
9011 
9012   if(!(RAND() % 250))
9013   {
9014     if(IsPlayer())
9015       ADD_MESSAGE("Ugh. You feel something violently carving its way through your intestines.");
9016 
9017     ReceiveDamage(0, 1, DRAIN, TORSO, 8, false, false, false, false); // Use DRAIN here so that resistances do not apply.
9018     CheckDeath(CONST_S("killed by a vile parasite"), 0);
9019   }
9020 }
9021 
CanFollow() const9022 truth character::CanFollow() const
9023 {
9024   return CanMove() && !StateIsActivated(PANIC) && !IsStuck();
9025 }
9026 
GetKillName() const9027 festring character::GetKillName() const
9028 {
9029   if(!GetPolymorphBackup())
9030     return GetName(INDEFINITE);
9031   else
9032   {
9033     festring KillName;
9034     GetPolymorphBackup()->AddName(KillName, INDEFINITE);
9035     KillName << " polymorphed into ";
9036     id::AddName(KillName, INDEFINITE);
9037     return KillName;
9038   }
9039 }
9040 
GetPanelName() const9041 festring character::GetPanelName() const
9042 {
9043   festring Name;
9044   Name << AssignedName << " the " << game::GetVerbalPlayerAlignment() << ' ';
9045   id::AddName(Name, UNARTICLED);
9046 
9047   festring PanelName;
9048   if(!game::IsInWilderness()){
9049     PanelName << Name;
9050     if(ivanconfig::IsShowFullDungeonName()){
9051       PanelName << " (at " << game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex(), true) << ')';
9052     }
9053   }
9054 
9055   return PanelName;
9056 }
9057 
GetMoveAPRequirement(int Difficulty) const9058 long character::GetMoveAPRequirement(int Difficulty) const
9059 {
9060   return (!StateIsActivated(PANIC) ? 10000000 : 8000000) * Difficulty
9061          / (APBonus(GetAttribute(AGILITY)) * GetMoveEase());
9062 }
9063 
HealHitPoint()9064 bodypart* character::HealHitPoint()
9065 {
9066   int NeedHeal = 0, NeedHealIndex[MAX_BODYPARTS];
9067 
9068   for(int c = 0; c < BodyParts; ++c)
9069   {
9070     bodypart* BodyPart = GetBodyPart(c);
9071 
9072     if(BodyPart && BodyPart->CanRegenerate() && BodyPart->GetHP() < BodyPart->GetMaxHP())
9073       NeedHealIndex[NeedHeal++] = c;
9074   }
9075 
9076   if(NeedHeal)
9077   {
9078     bodypart* BodyPart = GetBodyPart(NeedHealIndex[RAND() % NeedHeal]);
9079     BodyPart->IncreaseHP();
9080     ++HP;
9081     return BodyPart;
9082   }
9083   else
9084     return 0;
9085 }
9086 
HealBurntBodyParts(long Amount)9087 void character::HealBurntBodyParts(long Amount)
9088 {
9089   if(!Amount)
9090     return;
9091 
9092   for(int c = 0; c < BodyParts; ++c)
9093   {
9094     bodypart* BodyPart = GetBodyPart(c);
9095 
9096     if(Amount > 0)
9097     {
9098       if(BodyPart && BodyPart->CanRegenerate() && BodyPart->IsBurnt())
9099       {
9100         if(IsPlayer())
9101           ADD_MESSAGE("Your %s is healed of its burns.", BodyPart->GetBodyPartName().CStr());
9102         else if(CanBeSeenByPlayer())
9103           ADD_MESSAGE("The %s of %s is healed of its burns.", BodyPart->GetBodyPartName().CStr(),
9104                       CHAR_NAME(DEFINITE));
9105 
9106         BodyPart->GetMainMaterial()->ResetBurning();
9107         Amount = Amount - 250;
9108       }
9109     }
9110     else
9111       break;
9112   }
9113   return;
9114 }
9115 
CreateHomeData()9116 void character::CreateHomeData()
9117 {
9118   HomeData = new homedata;
9119   lsquare* Square = GetLSquareUnder();
9120   HomeData->Pos = Square->GetPos();
9121   HomeData->Dungeon = Square->GetDungeonIndex();
9122   HomeData->Level = Square->GetLevelIndex();
9123   HomeData->Room = Square->GetRoomIndex();
9124 }
9125 
GetHomeRoom() const9126 room* character::GetHomeRoom() const
9127 {
9128   if(HomeDataIsValid() && HomeData->Room)
9129     return GetLevel()->GetRoom(HomeData->Room);
9130   else
9131     return 0;
9132 }
9133 
RemoveHomeData()9134 void character::RemoveHomeData()
9135 {
9136   delete HomeData;
9137   HomeData = 0;
9138 }
9139 
AddESPConsumeMessage() const9140 void character::AddESPConsumeMessage() const
9141 {
9142   if(IsPlayer())
9143     ADD_MESSAGE("You feel a strange mental activity.");
9144 }
9145 
SetBodyPart(int I,bodypart * What)9146 void character::SetBodyPart(int I, bodypart* What)
9147 {
9148   BodyPartSlot[I].PutInItem(What);
9149 
9150   if(What)
9151   {
9152     What->SignalPossibleUsabilityChange();
9153     What->Disable();
9154     AddOriginalBodyPartID(I, What->GetID());
9155 
9156     if(What->GetMainMaterial()->IsInfectedByLeprosy())
9157       GainIntrinsic(LEPROSY);
9158     else if(StateIsActivated(LEPROSY))
9159       What->GetMainMaterial()->SetIsInfectedByLeprosy(true);
9160   }
9161 }
9162 
ConsumeItem(item * Item,cfestring & ConsumeVerb,truth nibbling)9163 truth character::ConsumeItem(item* Item, cfestring& ConsumeVerb, truth nibbling)
9164 {
9165   if(Item->IsQuestItem() || !Item->IsDestroyable(this))
9166   {
9167     if(IsPlayer())
9168       ADD_MESSAGE("You cannot eat that!");
9169     return false;
9170   }
9171 
9172   if(IsPlayer()
9173      && HasHadBodyPart(Item)
9174      && !game::TruthQuestion(CONST_S("Are you sure? You may be able to put it back... [y/N]")))
9175     return false;
9176 
9177   if(Item->IsOnGround() && GetRoom() && !GetRoom()->ConsumeItem(this, Item, 1))
9178     return false;
9179 
9180   if(IsPlayer())
9181     ADD_MESSAGE("You begin %s %s.", ConsumeVerb.CStr(), Item->CHAR_NAME(DEFINITE));
9182   else if(CanBeSeenByPlayer())
9183     ADD_MESSAGE("%s begins %s %s.", CHAR_NAME(DEFINITE), ConsumeVerb.CStr(), Item->CHAR_NAME(DEFINITE));
9184 
9185   consume* Consume = consume::Spawn(this);
9186   Consume->SetDescription(ConsumeVerb);
9187   Consume->SetConsumingID(Item->GetID());
9188   Consume->SetNibbling(nibbling);
9189   SetAction(Consume);
9190   DexterityAction(5);
9191   return true;
9192 }
9193 
CheckThrow() const9194 truth character::CheckThrow() const
9195 {
9196   if(!CanThrow())
9197   {
9198     ADD_MESSAGE("This monster type cannot throw.");
9199     return false;
9200   }
9201 
9202   return true;
9203 }
9204 
GetHitByExplosion(const explosion * Explosion,int Damage)9205 void character::GetHitByExplosion(const explosion* Explosion, int Damage)
9206 {
9207   int DamageDirection = GetPos() == Explosion->Pos ?
9208                         RANDOM_DIR : game::CalculateRoughDirection(GetPos() - Explosion->Pos);
9209 
9210   if(!IsPet() && Explosion->Terrorist && Explosion->Terrorist->IsPet())
9211     Explosion->Terrorist->Hostility(this);
9212 
9213   if(!Explosion->FireOnly)
9214     GetTorso()->SpillBlood((8 - Explosion->Size + RAND() % (8 - Explosion->Size)) >> 1);
9215 
9216   if(DamageDirection == RANDOM_DIR)
9217     DamageDirection = RAND() & 7;
9218 
9219   v2 SpillPos = GetPos() + game::GetMoveVector(DamageDirection);
9220 
9221   truth WasUnconscious = GetAction() && GetAction()->IsUnconsciousness();
9222   truth Burned = ReceiveDamage(Explosion->Terrorist, Explosion->FireOnly ? Damage : (Damage >> 1),
9223                                FIRE, ALL, DamageDirection, true, false, false, false);
9224   truth Pummeled = ReceiveDamage(Explosion->Terrorist, Explosion->FireOnly ? 0 : (Damage >> 1),
9225                                  PHYSICAL_DAMAGE, ALL, DamageDirection, true, false, false, false);
9226 
9227   // The ReceiveDamage calls above might cause 'this' to be polymorphed, in which case
9228   // SquareUnder[0] is null and calling GetArea or SpillBlood will crash.
9229   // See https://github.com/Attnam/ivan/issues/237 for details.
9230   if(SquareUnder[0] && Pummeled && GetArea()->IsValidPos(SpillPos))
9231     GetTorso()->SpillBlood((8 - Explosion->Size + RAND() % (8 - Explosion->Size)) >> 1, SpillPos);
9232 
9233   festring Msg;
9234   if(IsPlayer())
9235     Msg << "You are ";
9236   else
9237     Msg << CHAR_NAME(DEFINITE) << " is ";
9238 
9239   if (Burned && Pummeled)
9240     Msg << "blasted ";
9241   else if (Burned)
9242     Msg << "burned ";
9243   else if (Pummeled)
9244     Msg << "pummeled ";
9245   else
9246     Msg << "unharmed ";
9247 
9248   Msg << "by the " << (Explosion->FireOnly ? "fireball" : "explosion") << "!";
9249 
9250   if(IsPlayer() || CanBeSeenByPlayer())
9251     ADD_MESSAGE(Msg.CStr());
9252 
9253   if(IsEnabled())
9254     CheckDeath(Explosion->DeathMsg, Explosion->Terrorist, !WasUnconscious ? IGNORE_UNCONSCIOUSNESS : 0);
9255 }
9256 
SortAllItems(const sortdata & SortData)9257 void character::SortAllItems(const sortdata& SortData)
9258 {
9259   GetStack()->SortAllItems(SortData);
9260   doforequipmentswithparam<const sortdata&>()(this, &item::SortAllItems, SortData);
9261 }
9262 
PrintBeginSearchingMessage() const9263 void character::PrintBeginSearchingMessage() const
9264 {
9265   if(IsPlayer())
9266     ADD_MESSAGE("You feel you can now notice even the very smallest details around you.");
9267 }
9268 
PrintEndSearchingMessage() const9269 void character::PrintEndSearchingMessage() const
9270 {
9271   if(IsPlayer())
9272     ADD_MESSAGE("You feel less perceptive.");
9273 }
9274 
SearchingHandler()9275 void character::SearchingHandler()
9276 {
9277   if(!game::IsInWilderness())
9278     Search(15);
9279 }
9280 
Search(int Perception)9281 void character::Search(int Perception)
9282 {
9283   for(int d = 0; d < GetExtendedNeighbourSquares(); ++d)
9284   {
9285     lsquare* LSquare = GetNeighbourLSquare(d);
9286 
9287     if(LSquare)
9288       LSquare->GetStack()->Search(this, Min(Perception, 200));
9289   }
9290 }
9291 
9292 // surprisingly returns 0 if fails
9293 
GetRandomNeighbour(int RelationFlags) const9294 character* character::GetRandomNeighbour(int RelationFlags) const
9295 {
9296   character* Chars[MAX_NEIGHBOUR_SQUARES];
9297   int Index = 0;
9298 
9299   for(int d = 0; d < GetNeighbourSquares(); ++d)
9300   {
9301     lsquare* LSquare = GetNeighbourLSquare(d);
9302 
9303     if(LSquare)
9304     {
9305       character* Char = LSquare->GetCharacter();
9306 
9307       if(Char && (GetRelation(Char) & RelationFlags))
9308         Chars[Index++] = Char;
9309     }
9310   }
9311 
9312   return Index ? Chars[RAND() % Index] : 0;
9313 }
9314 
ResetStates()9315 void character::ResetStates()
9316 {
9317   for(int c = 0; c < STATES; ++c){
9318     if(
9319         1 << c != POLYMORPHED
9320         &&
9321         TemporaryStateIsActivated(1 << c)
9322         &&
9323         (IsPlayerAutoPlay() || TemporaryStateCounter[c] != PERMANENT) //autoplay will be messed if not removing some things like leprosy or worms
9324     ){
9325       TemporaryState &= ~(1 << c);
9326 
9327       if(StateData[c].EndHandler)
9328       {
9329         (this->*StateData[c].EndHandler)();
9330 
9331         if(!IsEnabled())
9332           return;
9333       }
9334     }
9335   }
9336 }
9337 
InitDefaults(const characterprototype * NewProtoType,int NewConfig)9338 void characterdatabase::InitDefaults(const characterprototype* NewProtoType, int NewConfig)
9339 {
9340   IsAbstract = false;
9341   ProtoType = NewProtoType;
9342   Config = NewConfig;
9343   Alias.Clear();
9344 }
9345 
PrintBeginGasImmunityMessage() const9346 void character::PrintBeginGasImmunityMessage() const
9347 {
9348   if(IsPlayer())
9349     ADD_MESSAGE("All smells fade away.");
9350 }
9351 
PrintEndGasImmunityMessage() const9352 void character::PrintEndGasImmunityMessage() const
9353 {
9354   if(IsPlayer())
9355     ADD_MESSAGE("Yuck! The world smells bad again.");
9356 }
9357 
inventoryInfo(const character * pC)9358 void inventoryInfo(const character* pC){
9359   game::RegionListItemEnable(true);
9360   game::RegionSilhouetteEnable(true);
9361 
9362   pC->GetStack()->DrawContents(pC, CONST_S("Your inventory"), REMEMBER_SELECTED);
9363 
9364   for(stackiterator i = pC->GetStack()->GetBottom(); i.HasItem(); ++i)
9365     i->DrawContents(pC);
9366 
9367   doforequipmentswithparam<ccharacter*>()(pC, &item::DrawContents, pC);
9368 
9369   game::RegionListItemEnable(false);
9370   game::RegionSilhouetteEnable(false);
9371 }
9372 
ShowAdventureInfo() const9373 void character::ShowAdventureInfo() const
9374 {
9375   graphics::SetAllowStretchedBlit();
9376 
9377   if(ivanconfig::IsAltAdentureInfo())
9378   {
9379     ShowAdventureInfoAlt();
9380   }
9381   else
9382   {
9383     if(GetStack()->GetItems()
9384        && game::TruthQuestion(CONST_S("Do you want to see your inventory? [y/n]"), REQUIRES_ANSWER))
9385     {
9386       inventoryInfo(this);
9387     }
9388 
9389     if(game::TruthQuestion(CONST_S("Do you want to see your message history? [y/n]"), REQUIRES_ANSWER))
9390       msgsystem::DrawMessageHistory();
9391 
9392     if(!game::MassacreListsEmpty()
9393        && game::TruthQuestion(CONST_S("Do you want to see your massacre history? [y/n]"), REQUIRES_ANSWER))
9394     {
9395       game::DisplayMassacreLists();
9396     }
9397   }
9398 
9399   graphics::SetDenyStretchedBlit(); //back to menu
9400 }
9401 
ShowAdventureInfoAlt() const9402 void character::ShowAdventureInfoAlt() const
9403 {
9404   while(true) {
9405 #ifdef WIZARD
9406     int Answer =
9407      game::KeyQuestion(
9408        CONST_S("See (i)nventory, (m)essage history, (k)ill list, (s)tats, (l)ook around or (n)othing?"), // ESC implicit
9409          'z', 13, 'i','I', 'm','M', 'k','K', 's', 'S', 'l','L', 'x','X', 'n','N', KEY_ESC); //default answer 'z' is ignored
9410 #else
9411     int Answer =
9412      game::KeyQuestion(
9413        CONST_S("See (i)nventory, (m)essage history, (k)ill list, (s)tats, (l)ook around or (n)othing?"), // ESC implicit
9414          'z', 11, 'i','I', 'm','M', 'k','K', 's', 'S', 'l','L', 'n','N', KEY_ESC); //default answer 'z' is ignored
9415 #endif
9416 
9417     if(Answer == 'i' || Answer == 'I'){
9418       inventoryInfo(this);
9419     }else if(Answer == 'm' || Answer == 'M'){
9420       msgsystem::DrawMessageHistory();
9421     }else if(Answer == 'k' || Answer == 'K'){
9422       game::DisplayMassacreLists();
9423 #ifdef WIZARD
9424     }else if(Answer == 'l' || Answer == 'L' || Answer == 'x' || Answer == 'X'){
9425       commandsystem::PlayerDiedLookMode(Answer == 'x' || Answer == 'X');
9426 #else
9427     }else if(Answer == 'l' || Answer == 'L'){
9428       commandsystem::PlayerDiedLookMode();
9429 #endif
9430 }else if(Answer == 's' || Answer == 'S'){
9431   DisplayStethoscopeInfo(NULL);
9432   commandsystem::PlayerDiedWeaponSkills();
9433 }else if(Answer == 'n' || Answer == 'N' || Answer == KEY_ESC){
9434       return;
9435     }
9436   }
9437 }
9438 
9439 
EditAllAttributes(int Amount)9440 truth character::EditAllAttributes(int Amount)
9441 {
9442   if(!Amount)
9443     return true;
9444 
9445   int c;
9446   truth MayEditMore = false;
9447 
9448   for(c = 0; c < BodyParts; ++c)
9449   {
9450     bodypart* BodyPart = GetBodyPart(c);
9451 
9452     if(BodyPart && BodyPart->EditAllAttributes(Amount))
9453       MayEditMore = true;
9454   }
9455 
9456   for(c = 0; c < BASE_ATTRIBUTES; ++c)
9457     if(BaseExperience[c])
9458     {
9459       BaseExperience[c] += Amount * EXP_MULTIPLIER;
9460       LimitRef(BaseExperience[c], MIN_EXP, MAX_EXP);
9461 
9462       if((Amount < 0 && BaseExperience[c] != MIN_EXP)
9463          || (Amount > 0 && BaseExperience[c] != MAX_EXP))
9464         MayEditMore = true;
9465     }
9466 
9467   CalculateAll();
9468   RestoreHP();
9469   RestoreStamina();
9470 
9471   if(IsPlayer())
9472   {
9473     game::SendLOSUpdateRequest();
9474     UpdateESPLOS();
9475   }
9476 
9477   if(IsPlayerKind())
9478     UpdatePictures();
9479 
9480   return MayEditMore;
9481 }
9482 
9483 #ifdef WIZARD
9484 
AddAttributeInfo(festring & Entry) const9485 void character::AddAttributeInfo(festring& Entry) const
9486 {
9487   Entry.Resize(54);
9488   Entry << GetAttribute(ENDURANCE);
9489   Entry.Resize(57);
9490   Entry << GetAttribute(PERCEPTION);
9491   Entry.Resize(60);
9492   Entry << GetAttribute(INTELLIGENCE);
9493   Entry.Resize(63);
9494   Entry << GetAttribute(WISDOM);
9495   Entry.Resize(66);
9496   Entry << GetAttribute(WILL_POWER);
9497   Entry.Resize(69);
9498   Entry << GetAttribute(CHARISMA);
9499   Entry.Resize(72);
9500   Entry << GetAttribute(MANA);
9501 }
9502 
AddDefenceInfo(felist & List) const9503 void character::AddDefenceInfo(felist& List) const
9504 {
9505   festring Entry;
9506 
9507   for(int c = 0; c < BodyParts; ++c)
9508   {
9509     bodypart* BodyPart = GetBodyPart(c);
9510 
9511     if(BodyPart)
9512     {
9513       Entry = CONST_S("   ");
9514       BodyPart->AddName(Entry, UNARTICLED);
9515       Entry.Resize(60);
9516       Entry << BodyPart->GetMaxHP();
9517       Entry.Resize(70);
9518       Entry << BodyPart->GetTotalResistance(PHYSICAL_DAMAGE);
9519       List.AddEntry(Entry, LIGHT_GRAY);
9520     }
9521   }
9522 }
9523 
DetachBodyPart()9524 void character::DetachBodyPart()
9525 {
9526   ADD_MESSAGE("You haven't got any extra bodyparts.");
9527 }
9528 
SetFireToBodyPart()9529 void character::SetFireToBodyPart()
9530 {
9531   ADD_MESSAGE("You haven't got any extra bodyparts.");
9532 }
9533 
9534 #endif
9535 
ReceiveHolyBanana(long Amount)9536 void character::ReceiveHolyBanana(long Amount)
9537 {
9538   Amount <<= 1;
9539   EditExperience(ARM_STRENGTH, Amount, 1 << 13);
9540   EditExperience(LEG_STRENGTH, Amount, 1 << 13);
9541   EditExperience(DEXTERITY, Amount, 1 << 13);
9542   EditExperience(AGILITY, Amount, 1 << 13);
9543   EditExperience(ENDURANCE, Amount, 1 << 13);
9544   EditExperience(PERCEPTION, Amount, 1 << 13);
9545   EditExperience(INTELLIGENCE, Amount, 1 << 13);
9546   EditExperience(WISDOM, Amount, 1 << 13);
9547   EditExperience(WILL_POWER, Amount, 1 << 13);
9548   EditExperience(CHARISMA, Amount, 1 << 13);
9549   EditExperience(MANA, Amount, 1 << 13);
9550   RestoreLivingHP();
9551 }
9552 
AddHolyBananaConsumeEndMessage() const9553 void character::AddHolyBananaConsumeEndMessage() const
9554 {
9555   if(IsPlayer())
9556     ADD_MESSAGE("You feel a mysterious strengthening fire coursing through your body.");
9557   else if(CanBeSeenByPlayer())
9558     ADD_MESSAGE("For a moment %s is surrounded by a swirling fire aura.", CHAR_NAME(DEFINITE));
9559 }
9560 
PreProcessForBone()9561 truth character::PreProcessForBone()
9562 {
9563   if(IsPet() && IsEnabled())
9564   {
9565     Die(0, CONST_S(""), FORBID_REINCARNATION);
9566     return true;
9567   }
9568 
9569   if(GetAction())
9570     GetAction()->Terminate(false);
9571 
9572   if(TemporaryStateIsActivated(POLYMORPHED))
9573   {
9574     character* PolymorphBackup = GetPolymorphBackup();
9575     EndPolymorph();
9576     PolymorphBackup->PreProcessForBone();
9577     return true;
9578   }
9579 
9580   if(MustBeRemovedFromBone())
9581     return false;
9582   else if(IsUnique() && !CanBeGenerated())
9583     game::SignalQuestMonsterFound();
9584 
9585   RestoreLivingHP();
9586   ResetStates();
9587   RemoveTraps();
9588   GetStack()->PreProcessForBone();
9589   doforequipments()(this, &item::PreProcessForBone);
9590   doforbodyparts()(this, &bodypart::PreProcessForBone);
9591   game::RemoveCharacterID(ID);
9592   ID = -ID;
9593   game::AddCharacterID(this, ID);
9594   return true;
9595 }
9596 
_BugWorkaround_PlayerDup(ulong key)9597 void character::_BugWorkaround_PlayerDup(ulong key){
9598   ID=key;
9599   // brute force empty the inv list leaving objects untracked in volatile memory. TODO really untracked?
9600   Stack = new stack(0, this, HIDDEN); //like constructor init
9601 }
9602 
PostProcessForBone(double & DangerSum,int & Enemies)9603 truth character::PostProcessForBone(double& DangerSum, int& Enemies)
9604 {
9605   if(PostProcessForBone())
9606   {
9607     if(GetRelation(PLAYER) == HOSTILE)
9608     {
9609       double Danger = GetRelativeDanger(PLAYER, true);
9610 
9611       if(Danger > 99.)
9612         game::SetTooGreatDangerFound(true);
9613       else if(!IsUnique() && !IgnoreDanger())
9614       {
9615         DangerSum += Danger;
9616         ++Enemies;
9617       }
9618     }
9619 
9620     return true;
9621   }
9622   else
9623     return false;
9624 }
9625 
PostProcessForBone()9626 truth character::PostProcessForBone()
9627 {
9628   ulong NewID = game::CreateNewCharacterID(this);
9629   game::GetBoneCharacterIDMap().insert(std::make_pair(-ID, NewID));
9630   game::RemoveCharacterID(ID);
9631   ID = NewID;
9632 
9633   if(IsUnique() && CanBeGenerated())
9634   {
9635     if(DataBase->Flags & HAS_BEEN_GENERATED)
9636       return false;
9637     else
9638       SignalGeneration();
9639   }
9640 
9641   GetStack()->PostProcessForBone();
9642   doforequipments()(this, &item::PostProcessForBone);
9643   doforbodyparts()(this, &bodypart::PostProcessForBone);
9644   return true;
9645 }
9646 
FinalProcessForBone()9647 void character::FinalProcessForBone()
9648 {
9649   Flags &= ~C_PLAYER;
9650   GetStack()->FinalProcessForBone();
9651   doforequipments()(this, &item::FinalProcessForBone);
9652   int c;
9653 
9654   for(c = 0; c < BodyParts; ++c)
9655   {
9656     for(std::list<ulong>::iterator i = OriginalBodyPartID[c].begin(); i != OriginalBodyPartID[c].end();)
9657     {
9658       boneidmap::iterator BI = game::GetBoneItemIDMap().find(*i);
9659 
9660       if(BI == game::GetBoneItemIDMap().end())
9661       {
9662         std::list<ulong>::iterator Dirt = i++;
9663         OriginalBodyPartID[c].erase(Dirt);
9664       }
9665       else
9666       {
9667         *i = BI->second;
9668         ++i;
9669       }
9670     }
9671   }
9672 }
9673 
SetSoulID(ulong What)9674 void character::SetSoulID(ulong What)
9675 {
9676   if(GetPolymorphBackup())
9677     GetPolymorphBackup()->SetSoulID(What);
9678 }
9679 
SearchForItem(citem * Item) const9680 truth character::SearchForItem(citem* Item) const
9681 {
9682   if(combineequipmentpredicateswithparam<ulong>()(this, &item::HasID, Item->GetID(), 1))
9683     return true;
9684 
9685   for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
9686     if(*i == Item)
9687       return true;
9688 
9689   return false;
9690 }
9691 
SearchForItem(const sweaponskill * SWeaponSkill) const9692 item* character::SearchForItem(const sweaponskill* SWeaponSkill) const
9693 {
9694   for(int c = 0; c < GetEquipments(); ++c)
9695   {
9696     item* Equipment = GetEquipment(c);
9697 
9698     if(Equipment && SWeaponSkill->IsSkillOf(Equipment))
9699       return Equipment;
9700   }
9701 
9702   for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
9703     if(SWeaponSkill->IsSkillOf(*i))
9704       return *i;
9705 
9706   return 0;
9707 }
9708 
PutNear(v2 Pos)9709 void character::PutNear(v2 Pos)
9710 {
9711   v2 NewPos = game::GetCurrentLevel()->GetNearestFreeSquare(this, Pos, false);
9712 
9713   if(NewPos == ERROR_V2)
9714     do
9715     {
9716       NewPos = game::GetCurrentLevel()->GetRandomSquare(this);
9717     }
9718     while(NewPos == Pos);
9719 
9720   PutTo(NewPos);
9721 }
9722 
PutToOrNear(v2 Pos)9723 void character::PutToOrNear(v2 Pos)
9724 {
9725   if(game::IsInWilderness() || (CanMoveOn(game::GetCurrentLevel()->GetLSquare(Pos))
9726                                 && IsFreeForMe(game::GetCurrentLevel()->GetLSquare(Pos))))
9727     PutTo(Pos);
9728   else
9729     PutNear(Pos);
9730 }
9731 
PutTo(v2 Pos)9732 void character::PutTo(v2 Pos)
9733 {
9734   SquareUnder[0] = game::GetCurrentArea()->GetSquare(Pos);
9735   SquareUnder[0]->AddCharacter(this);
9736 }
9737 
Remove()9738 void character::Remove()
9739 {
9740   SquareUnder[0]->RemoveCharacter();
9741   SquareUnder[0] = 0;
9742 }
9743 
SendNewDrawRequest() const9744 void character::SendNewDrawRequest() const
9745 {
9746   for(int c = 0; c < SquaresUnder; ++c)
9747   {
9748     square* Square = GetSquareUnder(c);
9749 
9750     if(Square)
9751       Square->SendNewDrawRequest();
9752   }
9753 }
9754 
IsOver(v2 Pos) const9755 truth character::IsOver(v2 Pos) const
9756 {
9757   for(int c = 0; c < SquaresUnder; ++c)
9758   {
9759     square* Square = GetSquareUnder(c);
9760 
9761     if(Square && Square->GetPos() == Pos)
9762       return true;
9763   }
9764 
9765   return false;
9766 }
9767 
CanTheoreticallyMoveOn(const lsquare * LSquare) const9768 truth character::CanTheoreticallyMoveOn(const lsquare* LSquare) const
9769 {
9770   return GetMoveType() & LSquare->GetTheoreticalWalkability();
9771 }
9772 
CanMoveOn(const lsquare * LSquare) const9773 truth character::CanMoveOn(const lsquare* LSquare) const
9774 {
9775   return GetMoveType() & LSquare->GetWalkability();
9776 }
9777 
CanMoveOn(const square * Square) const9778 truth character::CanMoveOn(const square* Square) const
9779 {
9780   return GetMoveType() & Square->GetSquareWalkability();
9781 }
9782 
CanMoveOn(const olterrain * OLTerrain) const9783 truth character::CanMoveOn(const olterrain* OLTerrain) const
9784 {
9785   return GetMoveType() & OLTerrain->GetWalkability();
9786 }
9787 
CanMoveOn(const oterrain * OTerrain) const9788 truth character::CanMoveOn(const oterrain* OTerrain) const
9789 {
9790   return GetMoveType() & OTerrain->GetWalkability();
9791 }
9792 
IsFreeForMe(square * Square) const9793 truth character::IsFreeForMe(square* Square) const
9794 {
9795   return !Square->GetCharacter() || Square->GetCharacter() == this;
9796 }
9797 
LoadSquaresUnder()9798 void character::LoadSquaresUnder()
9799 {
9800   SquareUnder[0] = game::GetSquareInLoad();
9801 }
9802 
AttackAdjacentEnemyAI()9803 truth character::AttackAdjacentEnemyAI()
9804 {
9805   if(!IsEnabled())
9806     return false;
9807 
9808   character* Char[MAX_NEIGHBOUR_SQUARES];
9809   v2 Pos[MAX_NEIGHBOUR_SQUARES];
9810   int Dir[MAX_NEIGHBOUR_SQUARES];
9811   int Index = 0;
9812 
9813   for(int d = 0; d < GetNeighbourSquares(); ++d)
9814   {
9815     square* Square = GetNeighbourSquare(d);
9816 
9817     if(Square)
9818     {
9819       character* Enemy = Square->GetCharacter();
9820 
9821       if(Enemy && (GetRelation(Enemy) == HOSTILE || StateIsActivated(CONFUSED)))
9822       {
9823         Dir[Index] = d;
9824         Pos[Index] = Square->GetPos();
9825         Char[Index++] = Enemy;
9826       }
9827     }
9828   }
9829 
9830   if(Index)
9831   {
9832     int ChosenIndex = RAND() % Index;
9833     Hit(Char[ChosenIndex], Pos[ChosenIndex], Dir[ChosenIndex]);
9834     return true;
9835   }
9836 
9837   return false;
9838 }
9839 
SignalStepFrom(lsquare ** OldSquareUnder)9840 void character::SignalStepFrom(lsquare** OldSquareUnder)
9841 {
9842   int c;
9843   lsquare* NewSquareUnder[MAX_SQUARES_UNDER];
9844 
9845   for(c = 0; c < GetSquaresUnder(); ++c)
9846     NewSquareUnder[c] = GetLSquareUnder(c);
9847 
9848   for(c = 0; c < GetSquaresUnder(); ++c)
9849     if(IsEnabled() && GetLSquareUnder(c) == NewSquareUnder[c])
9850       NewSquareUnder[c]->StepOn(this, OldSquareUnder);
9851 }
9852 
GetSumOfAttributes() const9853 int character::GetSumOfAttributes() const
9854 {
9855   return GetAttribute(ENDURANCE)
9856        + GetAttribute(PERCEPTION)
9857        + GetAttribute(INTELLIGENCE)
9858        + GetAttribute(WISDOM)
9859        + GetAttribute(CHARISMA)
9860        + GetAttribute(ARM_STRENGTH)
9861        + GetAttribute(AGILITY);
9862 }
9863 
IntelligenceAction(int Difficulty)9864 void character::IntelligenceAction(int Difficulty)
9865 {
9866   EditAP(-20000 * Difficulty / APBonus(GetAttribute(INTELLIGENCE)));
9867   EditExperience(INTELLIGENCE, Difficulty * 15, 1 << 7);
9868 }
9869 
9870 struct walkabilitycontroller
9871 {
Handlerwalkabilitycontroller9872   static truth Handler(int x, int y)
9873   {
9874     return x >= 0 && y >= 0 && x < LevelXSize && y < LevelYSize
9875                 && Map[x][y]->GetTheoreticalWalkability() & MoveType;
9876   }
9877   static lsquare*** Map;
9878   static int LevelXSize, LevelYSize;
9879   static int MoveType;
9880 };
9881 
9882 lsquare*** walkabilitycontroller::Map;
9883 int walkabilitycontroller::LevelXSize, walkabilitycontroller::LevelYSize;
9884 int walkabilitycontroller::MoveType;
9885 
CreateRoute()9886 truth character::CreateRoute()
9887 {
9888   Route.clear();
9889 
9890   if(GetAttribute(INTELLIGENCE) >= 10 && !StateIsActivated(CONFUSED))
9891   {
9892     v2 Pos = GetPos();
9893     walkabilitycontroller::Map = GetLevel()->GetMap();
9894     walkabilitycontroller::LevelXSize = GetLevel()->GetXSize();
9895     walkabilitycontroller::LevelYSize = GetLevel()->GetYSize();
9896     walkabilitycontroller::MoveType = GetMoveType();
9897     node* Node;
9898 
9899     for(int c = 0; c < game::GetTeams(); ++c)
9900       for(character* Char : game::GetTeam(c)->GetMember())
9901       {
9902         if(Char->IsEnabled()
9903            && !Char->Route.empty()
9904            && (Char->GetMoveType() & GetMoveType()) == Char->GetMoveType())
9905         {
9906           v2 CharGoingTo = Char->Route[0];
9907           v2 iPos = Char->Route.back();
9908 
9909           if((GoingTo - CharGoingTo).GetLengthSquare() <= 100
9910              && (Pos - iPos).GetLengthSquare() <= 100
9911              && mapmath<walkabilitycontroller>::DoLine(CharGoingTo.X, CharGoingTo.Y, GoingTo.X, GoingTo.Y, SKIP_FIRST)
9912              && mapmath<walkabilitycontroller>::DoLine(Pos.X, Pos.Y, iPos.X, iPos.Y, SKIP_FIRST))
9913           {
9914             if(!Illegal.empty() && Illegal.find(Char->Route.back()) != Illegal.end())
9915               continue;
9916 
9917             Node = GetLevel()->FindRoute(CharGoingTo, GoingTo, Illegal, GetMoveType());
9918 
9919             if(Node)
9920               while(Node->Last)
9921               {
9922                 Route.push_back(Node->Pos);
9923                 Node = Node->Last;
9924               }
9925             else
9926             {
9927               Route.clear();
9928               continue;
9929             }
9930 
9931             Route.insert(Route.end(), Char->Route.begin(), Char->Route.end());
9932             Node = GetLevel()->FindRoute(Pos, iPos, Illegal, GetMoveType());
9933 
9934             if(Node)
9935               while(Node->Last)
9936               {
9937                 Route.push_back(Node->Pos);
9938                 Node = Node->Last;
9939               }
9940             else
9941             {
9942               Route.clear();
9943               continue;
9944             }
9945 
9946             IntelligenceAction(1);
9947             return true;
9948           }
9949         }
9950       }
9951 
9952     Node = GetLevel()->FindRoute(Pos, GoingTo, Illegal, GetMoveType());
9953 
9954     if(Node)
9955       while(Node->Last)
9956       {
9957         Route.push_back(Node->Pos);
9958         Node = Node->Last;
9959       }
9960     else
9961       TerminateGoingTo();
9962 
9963     IntelligenceAction(5);
9964     return true;
9965   }
9966   else
9967     return false;
9968 }
9969 
SetGoingTo(v2 What)9970 void character::SetGoingTo(v2 What)
9971 {
9972   if(GoingTo != What)
9973   {
9974     GoingTo = What;
9975     Route.clear();
9976     Illegal.clear();
9977   }
9978 }
9979 
TerminateGoingTo()9980 void character::TerminateGoingTo()
9981 {
9982   GoingTo = ERROR_V2;
9983   Route.clear();
9984   Illegal.clear();
9985 }
9986 
CheckForFood(int Radius)9987 truth character::CheckForFood(int Radius)
9988 {
9989   if(StateIsActivated(PANIC) || !UsesNutrition() || !IsEnabled())
9990     return false;
9991 
9992   v2 Pos = GetPos();
9993   int x, y;
9994 
9995   for(int r = 1; r <= Radius; ++r)
9996   {
9997     x = Pos.X - r;
9998 
9999     if(x >= 0)
10000       for(y = Pos.Y - r; y <= Pos.Y + r; ++y)
10001         if(CheckForFoodInSquare(v2(x, y)))
10002           return true;
10003 
10004     x = Pos.X + r;
10005 
10006     if(x < GetLevel()->GetXSize())
10007       for(y = Pos.Y - r; y <= Pos.Y + r; ++y)
10008         if(CheckForFoodInSquare(v2(x, y)))
10009           return true;
10010 
10011     y = Pos.Y - r;
10012 
10013     if(y >= 0)
10014       for(x = Pos.X - r; x <= Pos.X + r; ++x)
10015         if(CheckForFoodInSquare(v2(x, y)))
10016           return true;
10017 
10018     y = Pos.Y + r;
10019 
10020     if(y < GetLevel()->GetYSize())
10021       for(x = Pos.X - r; x <= Pos.X + r; ++x)
10022         if(CheckForFoodInSquare(v2(x, y)))
10023           return true;
10024   }
10025 
10026   return false;
10027 }
10028 
CheckForFoodInSquare(v2 Pos)10029 truth character::CheckForFoodInSquare(v2 Pos)
10030 {
10031   level* Level = GetLevel();
10032 
10033   if(Level->IsValidPos(Pos))
10034   {
10035     lsquare* Square = Level->GetLSquare(Pos);
10036     stack* Stack = Square->GetStack();
10037 
10038     if(Stack->GetItems())
10039       for(stackiterator i = Stack->GetBottom(); i.HasItem(); ++i)
10040         if(i->IsPickable(this)
10041            && i->CanBeSeenBy(this)
10042            && i->CanBeEatenByAI(this)
10043            && (!Square->GetRoomIndex()
10044                || Square->GetRoom()->AllowFoodSearch()))
10045         {
10046           SetGoingTo(Pos);
10047           return MoveTowardsTarget(false);
10048         }
10049   }
10050 
10051   return false;
10052 }
10053 
SetConfig(int NewConfig,int SpecialFlags)10054 void character::SetConfig(int NewConfig, int SpecialFlags)
10055 {
10056   databasecreator<character>::InstallDataBase(this, NewConfig);
10057   CalculateAll();
10058   CheckIfSeen();
10059 
10060   if(!(SpecialFlags & NO_PIC_UPDATE))
10061     UpdatePictures();
10062 }
10063 
IsOver(citem * Item) const10064 truth character::IsOver(citem* Item) const
10065 {
10066   for(int c1 = 0; c1 < Item->GetSquaresUnder(); ++c1)
10067     for(int c2 = 0; c2 < SquaresUnder; ++c2)
10068       if(Item->GetPos(c1) == GetPos(c2))
10069         return true;
10070 
10071   return false;
10072 }
10073 
CheckConsume(cfestring & Verb) const10074 truth character::CheckConsume(cfestring& Verb) const
10075 {
10076   if(!UsesNutrition())
10077   {
10078     if(IsPlayer())
10079       ADD_MESSAGE("In this form you can't and don't need to %s.", Verb.CStr());
10080 
10081     return false;
10082   }
10083 
10084   return true;
10085 }
10086 
PutTo(lsquare * To)10087 void character::PutTo(lsquare* To)
10088 {
10089   PutTo(To->GetPos());
10090 }
10091 
RandomizeBabyExperience(double SumE)10092 double character::RandomizeBabyExperience(double SumE)
10093 {
10094   if(!SumE)
10095     return 0;
10096 
10097   double E = (SumE / 4) - (SumE / 32) + (double(RAND()) / MAX_RAND) * (SumE / 16 + 1);
10098   return Limit(E, MIN_EXP, MAX_EXP);
10099 }
10100 
CreateBlood(long Volume) const10101 liquid* character::CreateBlood(long Volume) const
10102 {
10103   return liquid::Spawn(GetBloodMaterial(), Volume);
10104 }
10105 
SpillFluid(character * Spiller,liquid * Liquid,int SquareIndex)10106 void character::SpillFluid(character* Spiller, liquid* Liquid, int SquareIndex)
10107 {
10108   long ReserveVolume = Liquid->GetVolume() >> 1;
10109   Liquid->EditVolume(-ReserveVolume);
10110   GetStack()->SpillFluid(Spiller, Liquid,
10111                          long(Liquid->GetVolume() * sqrt(double(GetStack()->GetVolume()) / GetVolume())));
10112   Liquid->EditVolume(ReserveVolume);
10113   int c;
10114   long Modifier[MAX_BODYPARTS], ModifierSum = 0;
10115 
10116   for(c = 0; c < BodyParts; ++c)
10117     if(GetBodyPart(c))
10118     {
10119       Modifier[c] = long(sqrt(GetBodyPart(c)->GetVolume()));
10120 
10121       if(Modifier[c])
10122         Modifier[c] *= 1 + (RAND() & 3);
10123 
10124       ModifierSum += Modifier[c];
10125     }
10126     else
10127       Modifier[c] = 0;
10128 
10129   for(c = 1; c < GetBodyParts(); ++c)
10130     if(GetBodyPart(c) && IsEnabled())
10131       GetBodyPart(c)->SpillFluid(Spiller,
10132                                  Liquid->SpawnMoreLiquid(Liquid->GetVolume() * Modifier[c] / ModifierSum),
10133                                  SquareIndex);
10134 
10135   if(IsEnabled())
10136   {
10137     Liquid->SetVolume(Liquid->GetVolume() * Modifier[TORSO_INDEX] / ModifierSum);
10138     GetTorso()->SpillFluid(Spiller, Liquid, SquareIndex);
10139   }
10140 }
10141 
StayOn(liquid * Liquid)10142 void character::StayOn(liquid* Liquid)
10143 {
10144   Liquid->TouchEffect(this, TORSO_INDEX);
10145 }
10146 
IsAlly(ccharacter * Char) const10147 truth character::IsAlly(ccharacter* Char) const
10148 {
10149   return Char->GetTeam()->GetID() == GetTeam()->GetID();
10150 }
10151 
ResetSpoiling()10152 void character::ResetSpoiling()
10153 {
10154   doforbodyparts()(this, &bodypart::ResetSpoiling);
10155 }
10156 
ResetBurning()10157 void character::ResetBurning()
10158 {
10159   doforbodyparts()(this, &bodypart::ResetBurning);
10160 }
10161 
ResetLivingBurning()10162 void character::ResetLivingBurning()
10163 {
10164   for(int c = 0; c < BodyParts; ++c)
10165   {
10166     bodypart* BodyPart = GetBodyPart(c);
10167 
10168     if(BodyPart && BodyPart->CanRegenerate() && BodyPart->GetMainMaterial())
10169     {
10170       BodyPart->GetMainMaterial()->ResetBurning();
10171     }
10172   }
10173 }
10174 
RemoveBurns()10175 void character::RemoveBurns()
10176 {
10177   doforbodyparts()(this, &bodypart::RemoveBurns);
10178 }
10179 
ResetThermalEnergies()10180 void character::ResetThermalEnergies()
10181 {
10182   doforbodyparts()(this, &bodypart::ResetThermalEnergies);
10183 }
10184 
SearchForItem(ccharacter * Char,sorter Sorter) const10185 item* character::SearchForItem(ccharacter* Char, sorter Sorter) const
10186 {
10187   item* Equipment = findequipment<ccharacter*>()(this, Sorter, Char);
10188 
10189   if(Equipment)
10190     return Equipment;
10191 
10192   for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
10193     if(((*i)->*Sorter)(Char))
10194       return *i;
10195 
10196   return 0;
10197 }
10198 
DetectMaterial(cmaterial * Material) const10199 truth character::DetectMaterial(cmaterial* Material) const
10200 {
10201   return GetStack()->DetectMaterial(Material)
10202     || combinebodypartpredicateswithparam<cmaterial*>()(this, &bodypart::DetectMaterial, Material, 1)
10203     || combineequipmentpredicateswithparam<cmaterial*>()(this, &item::DetectMaterial, Material, 1);
10204 }
10205 
DamageTypeDestroysBodyPart(int Type)10206 truth character::DamageTypeDestroysBodyPart(int Type)
10207 {
10208   return (Type&0xFFF) != PHYSICAL_DAMAGE;
10209 }
10210 
CheckIfTooScaredToHit(ccharacter * Enemy) const10211 truth character::CheckIfTooScaredToHit(ccharacter* Enemy) const
10212 {
10213   if(IsPlayer() && StateIsActivated(PANIC))
10214     for(int d = 0; d < GetNeighbourSquares(); ++d)
10215     {
10216       square* Square = GetNeighbourSquare(d);
10217 
10218       if(Square)
10219       {
10220         if(CanMoveOn(Square)
10221            && (!Square->GetCharacter()
10222                || Square->GetCharacter()->IsPet()))
10223         {
10224           ADD_MESSAGE("You are too scared to attack %s.", Enemy->CHAR_DESCRIPTION(DEFINITE));
10225           return true;
10226         }
10227       }
10228     }
10229 
10230   return false;
10231 }
10232 
PrintBeginLevitationMessage() const10233 void character::PrintBeginLevitationMessage() const
10234 {
10235   if(!IsFlying())
10236   {
10237     if(IsPlayer())
10238       ADD_MESSAGE("You rise into the air like a small hot-air balloon.");
10239     else if(CanBeSeenByPlayer())
10240       ADD_MESSAGE("%s begins to float.", CHAR_NAME(DEFINITE));
10241   }
10242 }
10243 
PrintEndLevitationMessage() const10244 void character::PrintEndLevitationMessage() const
10245 {
10246   if(!IsFlying())
10247   {
10248     if(IsPlayer())
10249       ADD_MESSAGE("You descend gently onto the ground.");
10250     else if(CanBeSeenByPlayer())
10251       ADD_MESSAGE("%s drops onto the ground.", CHAR_NAME(DEFINITE));
10252   }
10253 }
10254 
IsLimbIndex(int I)10255 truth character::IsLimbIndex(int I)
10256 {
10257   switch(I)
10258   {
10259    case RIGHT_ARM_INDEX:
10260    case LEFT_ARM_INDEX:
10261    case RIGHT_LEG_INDEX:
10262    case LEFT_LEG_INDEX:
10263     return true;
10264   }
10265 
10266   return false;
10267 }
10268 
EditExperience(int Identifier,double Value,double Speed)10269 void character::EditExperience(int Identifier, double Value, double Speed)
10270 {
10271   if(!AllowExperience()
10272      || (Identifier == ENDURANCE && UseMaterialAttributes()))
10273     return;
10274 
10275   int Change = RawEditExperience(BaseExperience[Identifier],
10276                                  GetNaturalExperience(Identifier),
10277                                  Value, Speed);
10278 
10279   if(!Change)
10280     return;
10281 
10282   cchar* PlayerMsg = 0, * NPCMsg = 0;
10283 
10284   switch(Identifier)
10285   {
10286    case ENDURANCE:
10287     if(Change > 0)
10288     {
10289       PlayerMsg = "You feel tougher than anything!";
10290 
10291       if(IsPet())
10292         NPCMsg = "Suddenly %s looks tougher.";
10293     }
10294     else
10295     {
10296       PlayerMsg = "You feel less healthy.";
10297 
10298       if(IsPet())
10299         NPCMsg = "Suddenly %s looks less healthy.";
10300     }
10301 
10302     CalculateBodyPartMaxHPs();
10303     CalculateMaxStamina();
10304     break;
10305    case PERCEPTION:
10306     if(IsPlayer())
10307     {
10308       if(Change > 0)
10309         PlayerMsg = "You now see the world in much better detail than before.";
10310       else
10311       {
10312         PlayerMsg = "You feel very guru.";
10313         game::GetGod(VALPURUS)->AdjustRelation(100);
10314       }
10315 
10316       game::SendLOSUpdateRequest();
10317     }
10318     break;
10319    case INTELLIGENCE:
10320     if(IsPlayer())
10321     {
10322       if(Change > 0)
10323         PlayerMsg = "Suddenly the inner structure of the Multiverse around you looks quite simple.";
10324       else
10325         PlayerMsg = "It surely is hard to think today.";
10326 
10327       UpdateESPLOS();
10328     }
10329 
10330     if(IsPlayerKind())
10331       UpdatePictures();
10332 
10333     break;
10334    case WISDOM:
10335     if(IsPlayer())
10336     {
10337       if(Change > 0)
10338         PlayerMsg = "You feel your life experience increasing all the time.";
10339       else
10340         PlayerMsg = "You feel like having done something unwise.";
10341     }
10342 
10343     if(IsPlayerKind())
10344       UpdatePictures();
10345 
10346     break;
10347    case WILL_POWER:
10348     if(Change > 0)
10349     {
10350       PlayerMsg = "You feel incredibly stubborn.";
10351       NPCMsg = "You notice %s looks much more determined.";
10352     }
10353     else
10354     {
10355       PlayerMsg = "You loose your determination.";
10356       NPCMsg = "You notice how wimpy %s looks.";
10357     }
10358     break;
10359    case CHARISMA:
10360     if(Change > 0)
10361     {
10362       PlayerMsg = "You feel very confident of your social skills.";
10363 
10364       if(IsPet())
10365       {
10366         if(GetAttribute(CHARISMA) <= 15)
10367           NPCMsg = "%s looks less ugly.";
10368         else
10369           NPCMsg = "%s looks more attractive.";
10370       }
10371     }
10372     else
10373     {
10374       PlayerMsg = "You feel somehow disliked.";
10375 
10376       if(IsPet())
10377       {
10378         if(GetAttribute(CHARISMA) < 15)
10379           NPCMsg = "%s looks more ugly.";
10380         else
10381           NPCMsg = "%s looks less attractive.";
10382       }
10383     }
10384 
10385     if(IsPlayerKind())
10386       UpdatePictures();
10387 
10388     break;
10389    case MANA:
10390     if(Change > 0)
10391     {
10392       PlayerMsg = "You feel magical forces coursing through your body!";
10393       NPCMsg = "You notice an odd glow around %s.";
10394     }
10395     else
10396     {
10397       PlayerMsg = "You feel your magical abilities withering slowly.";
10398       NPCMsg = "You notice strange vibrations in the air around %s. But they disappear rapidly.";
10399     }
10400     break;
10401    default:
10402     if(Change > 0)
10403     {
10404       PlayerMsg = "You feel more awesome!";
10405       NPCMsg = "You notice how cool %s looks.";
10406     }
10407     else
10408     {
10409       PlayerMsg = "You feel a strange sensation, but then it passes.";
10410       NPCMsg = "You notice there is something different about %s.";
10411     }
10412   }
10413 
10414   if(IsPlayer())
10415     ADD_MESSAGE(PlayerMsg);
10416   else if(NPCMsg && CanBeSeenByPlayer())
10417     ADD_MESSAGE(NPCMsg, CHAR_NAME(DEFINITE));
10418 
10419   CalculateBattleInfo();
10420 }
10421 
RawEditExperience(double & Exp,double NaturalExp,double Value,double Speed) const10422 int character::RawEditExperience(double& Exp, double NaturalExp, double Value, double Speed) const
10423 {
10424   double OldExp = Exp;
10425 
10426   if(Speed < 0)
10427   {
10428     Speed = -Speed;
10429     Value = -Value;
10430   }
10431 
10432   if(!OldExp || !Value
10433      || (Value > 0 && OldExp >= NaturalExp * (100 + Value) / 100)
10434      || (Value < 0 && OldExp <= NaturalExp * (100 + Value) / 100))
10435     return 0;
10436 
10437   if(IsPlayer() && !IsPlayerKind())
10438   {
10439     Speed *= 0.5; // Slow down attribute gain of polymorphed player.
10440   }
10441   else if(!IsPlayer())
10442   {
10443     Speed *= 1.5;
10444   }
10445 
10446   Exp += (NaturalExp * (100 + Value) - 100 * OldExp) * Speed * EXP_DIVISOR;
10447   LimitRef(Exp, MIN_EXP, MAX_EXP);
10448   int NewA = int(Exp * EXP_DIVISOR);
10449   int OldA = int(OldExp * EXP_DIVISOR);
10450   int Delta = NewA - OldA;
10451 
10452   if(Delta > 0)
10453     Exp = Max(Exp, (NewA + 0.05) * EXP_MULTIPLIER);
10454   else if(Delta < 0)
10455     Exp = Min(Exp, (NewA + 0.95) * EXP_MULTIPLIER);
10456 
10457   LimitRef(Exp, MIN_EXP, MAX_EXP);
10458   return Delta;
10459 }
10460 
GetAttribute(int Identifier,truth AllowBonus) const10461 int character::GetAttribute(int Identifier, truth AllowBonus) const
10462 {
10463   int A = int(BaseExperience[Identifier] * EXP_DIVISOR);
10464 
10465   if(AllowBonus && Identifier == INTELLIGENCE && BrainsHurt())
10466     return Max((A + AttributeBonus[INTELLIGENCE]) / 3, 1);
10467 
10468   if(AllowBonus && Identifier == PERCEPTION && IsHeadless())
10469     return 0;
10470 
10471   return A && AllowBonus ? Max(A + AttributeBonus[Identifier], 1) : A;
10472 }
10473 
PostProcess()10474 void characterdatabase::PostProcess()
10475 {
10476   double AM = (100 + AttributeBonus) * EXP_MULTIPLIER / 100;
10477 
10478   for(int c = 0; c < ATTRIBUTES; ++c)
10479     NaturalExperience[c] = this->*ExpPtr[c] * AM;
10480 }
10481 
EditDealExperience(long Price)10482 void character::EditDealExperience(long Price)
10483 {
10484   EditExperience(CHARISMA, sqrt(Price) * 10, 15000);
10485 }
10486 
PrintBeginLeprosyMessage() const10487 void character::PrintBeginLeprosyMessage() const
10488 {
10489   if(IsPlayer())
10490     ADD_MESSAGE("You feel you're falling in pieces.");
10491 }
10492 
PrintEndLeprosyMessage() const10493 void character::PrintEndLeprosyMessage() const
10494 {
10495   if(IsPlayer())
10496     ADD_MESSAGE("You feel your limbs are stuck in place tightly."); // CHANGE OR DIE
10497 }
10498 
TryToInfectWithLeprosy(ccharacter * Infector)10499 void character::TryToInfectWithLeprosy(ccharacter* Infector)
10500 {
10501   if(!IsImmuneToLeprosy()
10502      && ((GetRelation(Infector) == HOSTILE
10503           && !RAND_N(50 * GetAttribute(ENDURANCE)))
10504          || !RAND_N(500 * GetAttribute(ENDURANCE))))
10505     GainIntrinsic(LEPROSY);
10506 }
10507 
SignalGeneration()10508 void character::SignalGeneration()
10509 {
10510   const_cast<database*>(DataBase)->Flags |= HAS_BEEN_GENERATED;
10511 }
10512 
CheckIfSeen()10513 void character::CheckIfSeen()
10514 {
10515   if(IsPlayer() || CanBeSeenByPlayer())
10516     SignalSeen();
10517 }
10518 
SignalSeen()10519 void character::SignalSeen()
10520 {
10521   if(!(WarnFlags & WARNED)
10522      && GetRelation(PLAYER) == HOSTILE
10523      && !StateIsActivated(FEARLESS))
10524   {
10525     double Danger = GetRelativeDanger(PLAYER);
10526 
10527     if(Danger > 5.)
10528     {
10529       game::SetDangerFound(Max(game::GetDangerFound(), Danger));
10530 
10531       if(Danger > 500. && !(WarnFlags & HAS_CAUSED_PANIC))
10532       {
10533         WarnFlags |= HAS_CAUSED_PANIC;
10534         game::SetCausePanicFlag(true);
10535       }
10536 
10537       WarnFlags |= WARNED;
10538     }
10539   }
10540 
10541   const_cast<database*>(DataBase)->Flags |= HAS_BEEN_SEEN;
10542 }
10543 
GetPolymorphIntelligenceRequirement() const10544 int character::GetPolymorphIntelligenceRequirement() const
10545 {
10546   if(DataBase->PolymorphIntelligenceRequirement == DEPENDS_ON_ATTRIBUTES)
10547     return Max(GetAttributeAverage() - 5, 0);
10548   else
10549     return DataBase->PolymorphIntelligenceRequirement;
10550 }
10551 
RemoveAllItems()10552 void character::RemoveAllItems()
10553 {
10554   GetStack()->Clean();
10555 
10556   for(int c = 0; c < GetEquipments(); ++c)
10557   {
10558     item* Equipment = GetEquipment(c);
10559 
10560     if(Equipment)
10561     {
10562       Equipment->RemoveFromSlot();
10563       Equipment->SendToHell();
10564     }
10565   }
10566 }
10567 
CalculateWeaponSkillHits(ccharacter * Enemy) const10568 int character::CalculateWeaponSkillHits(ccharacter* Enemy) const
10569 {
10570   if(Enemy->IsPlayer())
10571   {
10572     configid ConfigID(GetType(), GetConfig());
10573     const dangerid& DangerID = game::GetDangerMap().find(ConfigID)->second;
10574     return Min(int(DangerID.EquippedDanger * 2000), 1000);
10575   }
10576   else
10577     return Min(int(GetRelativeDanger(Enemy, true) * 2000), 1000);
10578 }
10579 
CanUseEquipment(int I) const10580 truth character::CanUseEquipment(int I) const
10581 {
10582   return CanUseEquipment() && I < GetEquipments() && GetBodyPartOfEquipment(I) && EquipmentIsAllowed(I);
10583 }
10584 
MemorizeEquipedItems()10585 void character::MemorizeEquipedItems(){DBGCHAR(this,"");
10586   int iEqCount=0;
10587   for(int c = 0; c < MAX_EQUIPMENT_SLOTS; ++c)
10588   {
10589     item* Item = NULL;
10590     if(CanUseEquipment(c))Item=GetEquipment(c);
10591 
10592     if(Item!=NULL){
10593       MemorizedEquippedItemIDs[c]=Item->GetID(); DBGSI(MemorizedEquippedItemIDs[c]);
10594       iEqCount++;
10595     }else{
10596       MemorizedEquippedItemIDs[c]=0;
10597     }
10598   }
10599 }
10600 
addPolymorphBkpToVector(character * CharIni,std::vector<character * > * pvCharMemory)10601 void addPolymorphBkpToVector(character* CharIni,std::vector<character*>* pvCharMemory){
10602   character* CharMemory = CharIni;
10603 
10604   while(CharMemory!=NULL){
10605     if(std::find(pvCharMemory->begin(), pvCharMemory->end(), CharMemory) == pvCharMemory->end()){ //if not already in vector
10606       pvCharMemory->push_back(CharMemory); DBGCHAR(CharMemory,"");
10607     }
10608 
10609     CharMemory=CharMemory->GetPolymorphBackup();
10610 
10611     if(CharMemory==CharIni){DBG1("weirdRecurrentPolymorphBkp?");break;} //TODO keep this? just in case some weird thing happens out of here? it probably would mean inconsistency?
10612   }
10613 }
10614 /* Target mustn't have any equipment */
DonateEquipmentTo(character * Character)10615 void character::DonateEquipmentTo(character* Character)
10616 {
10617   if(IsPlayer())
10618   {
10619     ulong* EquipmentMemory = game::GetEquipmentMemory();
10620 
10621     for(int c = 0; c < MAX_EQUIPMENT_SLOTS; ++c)
10622     {
10623       item* Item = GetEquipment(c);
10624 
10625       if(Item)
10626       {
10627         if(Character->CanUseEquipment(c))
10628         {
10629           Item->RemoveFromSlot();
10630           Character->SetEquipment(c, Item);
10631         }
10632         else
10633         {
10634           EquipmentMemory[c] = Item->GetID();
10635           Item->MoveTo(Character->GetStack());
10636         }
10637       }
10638       else if(CanUseEquipment(c))
10639         EquipmentMemory[c] = 0;
10640       else if(EquipmentMemory[c]
10641               && Character->CanUseEquipment(c))
10642       {
10643         for(stackiterator i = Character->GetStack()->GetBottom(); i.HasItem(); ++i)
10644           if(i->GetID() == EquipmentMemory[c])
10645           {
10646             item* Item = *i;
10647             Item->RemoveFromSlot();
10648             Character->SetEquipment(c, Item);
10649             break;
10650           }
10651 
10652         EquipmentMemory[c] = 0;
10653       }
10654     }
10655   }
10656   else
10657   {
10658     bool bImprovedEquipping =
10659        ivanconfig::GetMemorizeEquipmentMode()==2 ||
10660       (ivanconfig::GetMemorizeEquipmentMode()==1 && IsPet());
10661 
10662     if(bImprovedEquipping)MemorizeEquipedItems();
10663 
10664     for(int c = 0; c < GetEquipments(); ++c)
10665     {
10666       item* Item = GetEquipment(c);
10667 
10668       if(bImprovedEquipping && Item==NULL){
10669         /**
10670          *
10671          * Looks as much far as possible for Equipped memories.
10672          */
10673         std::vector<character*> vCharMemory;
10674         addPolymorphBkpToVector(Character,&vCharMemory);
10675         addPolymorphBkpToVector(this,&vCharMemory); DBGSI(vCharMemory.size());
10676 
10677         /* The target has all items on it's inventory now, so search at it only. */
10678         DBGCHAR(Character,""); DBGCHAR(this,"");
10679         for(int i=0;i<vCharMemory.size();i++){ character* CharMemory=vCharMemory[i]; DBGCHAR(CharMemory,"");
10680           Item=Character->SearchForItem(CharMemory->MemorizedEquippedItemIDs[c]);
10681           if(Item!=NULL){
10682             DBG5("FoundMemEqAtInv",DBGI(Item->GetID()),DBGI(CharMemory->GetID()),DBGI(this->GetID()),DBGI(Character->GetID()));
10683             break;
10684           }
10685         }
10686       }
10687 
10688       if(Item)
10689       {
10690         if(Character->CanUseEquipment(c))
10691         {
10692           Item->RemoveFromSlot();
10693           Character->SetEquipment(c, Item);
10694         }
10695         else
10696         {
10697           if(bImprovedEquipping){
10698             Item->MoveTo(Character->GetStack());
10699           }else{
10700             Item->MoveTo(Character->GetStackUnder());
10701           }
10702         }
10703       }
10704     }
10705   }
10706 }
10707 
ReceivePeaSoup(long)10708 void character::ReceivePeaSoup(long)
10709 {                                            /* don't spawn fart smoke on the world map (smoke objects only
10710                                                 have functions for lsquares, not wsquares) */
10711   if(!game::IsInWilderness() || !IsPlayer()) /* not sure if the AI eats while the player's on the world map,
10712                                                 but smoke should still spawn in the dungeon if they do */
10713   {
10714     lsquare* Square = GetLSquareUnder();
10715 
10716     if(Square->IsFlyable())
10717       Square->AddSmoke(gas::Spawn(FART, 250));
10718   }
10719 }
10720 
AddPeaSoupConsumeEndMessage() const10721 void character::AddPeaSoupConsumeEndMessage() const
10722 {
10723   if(IsPlayer())
10724     ADD_MESSAGE("Mmmh! The soup is very tasty.%s", CanHear() ? " You hear a small puff." : "");
10725   else if(CanBeSeenByPlayer() && PLAYER->CanHear()) // change someday
10726     ADD_MESSAGE("You hear a small puff.");
10727 }
10728 
CalculateMaxStamina()10729 void character::CalculateMaxStamina()
10730 {
10731   MaxStamina = TorsoIsAlive() ? GetAttribute(ENDURANCE) * 10000 : 0;
10732 }
10733 
EditStamina(int Amount,truth CanCauseUnconsciousness)10734 void character::EditStamina(int Amount, truth CanCauseUnconsciousness)
10735 {
10736   if(!TorsoIsAlive())
10737     return;
10738 
10739   int UnconsciousnessStamina = MaxStamina >> 3;
10740 
10741   if(!CanCauseUnconsciousness && Amount < 0)
10742   {
10743     if(Stamina > UnconsciousnessStamina)
10744     {
10745       Stamina += Amount;
10746 
10747       if(Stamina < UnconsciousnessStamina)
10748         Stamina = UnconsciousnessStamina;
10749     }
10750 
10751     return;
10752   }
10753 
10754   int OldStamina = Stamina;
10755   Stamina += Amount;
10756 
10757   if(Stamina > MaxStamina)
10758     Stamina = MaxStamina;
10759   else if(Stamina < 0)
10760   {
10761     Stamina = 0;
10762     LoseConsciousness(250 + RAND_N(250));
10763   }
10764   else if(IsPlayer())
10765   {
10766     if(OldStamina >= MaxStamina >> 2 && Stamina < MaxStamina >> 2)
10767       ADD_MESSAGE("You are getting a little tired.");
10768     else if(OldStamina >= UnconsciousnessStamina
10769             && Stamina < UnconsciousnessStamina)
10770     {
10771       ADD_MESSAGE("You are seriously out of breath!");
10772       game::SetPlayerIsRunning(false);
10773     }
10774   }
10775 
10776   if(IsPlayer() && StateIsActivated(PANIC)
10777      && GetTirednessState() != FAINTING)
10778     game::SetPlayerIsRunning(true);
10779 }
10780 
RegenerateStamina()10781 void character::RegenerateStamina()
10782 {
10783   if(GetTirednessState() != UNTIRED)
10784   {
10785     EditExperience(ENDURANCE, 50, 1);
10786 
10787     if(Sweats() && TorsoIsAlive() && !RAND_N(30) && !game::IsInWilderness())
10788     {
10789       long Volume = long(.05 * sqrt(GetBodyVolume()));
10790 
10791       if(GetTirednessState() == FAINTING)
10792         Volume <<= 1;
10793 
10794       for(int c = 0; c < SquaresUnder; ++c)
10795         GetLSquareUnder(c)->SpillFluid(0, CreateSweat(Volume), false, false);
10796     }
10797   }
10798 
10799   int Bonus = 1;
10800 
10801   if(Action)
10802   {
10803     if(Action->IsRest())
10804     {
10805       if(SquaresUnder == 1)
10806         Bonus = GetSquareUnder()->GetRestModifier() << 1;
10807       else
10808       {
10809         int Lowest = GetSquareUnder(0)->GetRestModifier();
10810 
10811         for(int c = 1; c < GetSquaresUnder(); ++c)
10812         {
10813           int Mod = GetSquareUnder(c)->GetRestModifier();
10814 
10815           if(Mod < Lowest)
10816             Lowest = Mod;
10817         }
10818 
10819         Bonus = Lowest << 1;
10820       }
10821     }
10822     else if(Action->IsUnconsciousness())
10823       Bonus = 2;
10824   }
10825 
10826   int Plus1 = 100;
10827 
10828   switch(GetBurdenState())
10829   {
10830    case OVER_LOADED:
10831     Plus1 = 25;
10832     break;
10833    case STRESSED:
10834     Plus1 = 50;
10835     break;
10836    case BURDENED:
10837     Plus1 = 75;
10838     break;
10839   }
10840 
10841   int Plus2 = 100;
10842 
10843   if(IsPlayer())
10844     switch(GetHungerState())
10845     {
10846      case STARVING:
10847       Plus2 = 25;
10848       break;
10849      case VERY_HUNGRY:
10850       Plus2 = 50;
10851       break;
10852      case HUNGRY:
10853       Plus2 = 75;
10854       break;
10855     }
10856 
10857   Stamina += Plus1 * Plus2 * Bonus / 1000;
10858 
10859   if(Stamina > MaxStamina)
10860     Stamina = MaxStamina;
10861 
10862   if(IsPlayer() && StateIsActivated(PANIC)
10863      && GetTirednessState() != FAINTING)
10864     game::SetPlayerIsRunning(true);
10865 }
10866 
BeginPanic()10867 void character::BeginPanic()
10868 {
10869   if(IsPlayer() && GetTirednessState() != FAINTING)
10870     game::SetPlayerIsRunning(true);
10871 
10872   DeActivateVoluntaryAction();
10873 }
10874 
EndPanic()10875 void character::EndPanic()
10876 {
10877   if(IsPlayer())
10878     game::SetPlayerIsRunning(false);
10879 }
10880 
GetTirednessState() const10881 int character::GetTirednessState() const
10882 {
10883   if(Stamina >= MaxStamina >> 2)
10884     return UNTIRED;
10885   else if(Stamina >= MaxStamina >> 3)
10886     return EXHAUSTED;
10887   else
10888     return FAINTING;
10889 }
10890 
ReceiveBlackUnicorn(long Amount)10891 void character::ReceiveBlackUnicorn(long Amount)
10892 {
10893   if(!(RAND() % 160))
10894     game::DoEvilDeed(Amount / 50);
10895 
10896   BeginTemporaryState(TELEPORT, Amount / 100);
10897 
10898   for(int c = 0; c < STATES; ++c)
10899     if(StateData[c].Flags & DUR_TEMPORARY)
10900     {
10901       BeginTemporaryState(1 << c, Amount / 100);
10902 
10903       if(!IsEnabled())
10904         return;
10905     }
10906     else if(StateData[c].Flags & DUR_PERMANENT)
10907     {
10908       GainIntrinsic(1 << c);
10909 
10910       if(!IsEnabled())
10911         return;
10912     }
10913 }
10914 
ReceiveGrayUnicorn(long Amount)10915 void character::ReceiveGrayUnicorn(long Amount)
10916 {
10917   if(!(RAND() % 80))
10918     game::DoEvilDeed(Amount / 50);
10919 
10920   BeginTemporaryState(TELEPORT, Amount / 100);
10921 
10922   for(int c = 0; c < STATES; ++c)
10923     if(1 << c != TELEPORT)
10924     {
10925       DecreaseStateCounter(1 << c, -Amount / 100);
10926 
10927       if(!IsEnabled())
10928         return;
10929     }
10930 }
10931 
ReceiveWhiteUnicorn(long Amount)10932 void character::ReceiveWhiteUnicorn(long Amount)
10933 {
10934   if(!(RAND() % 40))
10935     game::DoEvilDeed(Amount / 50);
10936 
10937   BeginTemporaryState(TELEPORT, Amount / 100);
10938 
10939   DecreaseStateCounter(LYCANTHROPY, -Amount / 100);
10940   DecreaseStateCounter(POISONED, -Amount / 100);
10941   DecreaseStateCounter(PARASITE_TAPE_WORM, -Amount / 100);
10942   DecreaseStateCounter(PARASITE_MIND_WORM, -Amount / 100);
10943   DecreaseStateCounter(LEPROSY, -Amount / 100);
10944   DecreaseStateCounter(VAMPIRISM, -Amount / 100);
10945 }
10946 
ReceiveSickness(long Amount)10947 void character::ReceiveSickness(long Amount)
10948 {
10949   if(IsPlayer() && !RAND_N(10))
10950     ADD_MESSAGE("You don't feel so good.");
10951 
10952   if(!StateIsActivated(DISEASE_IMMUNITY) && !RAND_N(10))
10953   {
10954     switch(RAND() % 5)
10955     {
10956      case 0: BeginTemporaryState(LYCANTHROPY, Amount); break;
10957      case 1: BeginTemporaryState(VAMPIRISM, Amount); break;
10958      case 2: BeginTemporaryState(PARASITE_TAPE_WORM, Amount); break;
10959      case 3: BeginTemporaryState(PARASITE_MIND_WORM, Amount); break;
10960      case 4: GainIntrinsic(LEPROSY); break;
10961     }
10962   }
10963 
10964   if(!RAND_N(10))
10965     BeginTemporaryState(POISONED, Amount + RAND_N(Amount));
10966 }
10967 
10968 /* Counter should be negative. Removes intrinsics. */
10969 
DecreaseStateCounter(long State,int Counter)10970 void character::DecreaseStateCounter(long State, int Counter)
10971 {
10972   int Index;
10973 
10974   for(Index = 0; Index < STATES; ++Index)
10975     if(1 << Index == State)
10976       break;
10977 
10978   if(Index == STATES)
10979     ABORT("DecreaseTemporaryStateCounter works only when State == 2 ^ n!");
10980 
10981   if(TemporaryState & State)
10982   {
10983     if(TemporaryStateCounter[Index] == PERMANENT
10984        || (TemporaryStateCounter[Index] += Counter) <= 0)
10985     {
10986       TemporaryState &= ~State;
10987 
10988       if(!(EquipmentState & State))
10989       {
10990         if(StateData[Index].EndHandler)
10991         {
10992           (this->*StateData[Index].EndHandler)();
10993 
10994           if(!IsEnabled())
10995             return;
10996         }
10997 
10998         (this->*StateData[Index].PrintEndMessage)();
10999       }
11000     }
11001   }
11002 }
11003 
IsImmuneToLeprosy() const11004 truth character::IsImmuneToLeprosy() const
11005 {
11006   return DataBase->IsImmuneToLeprosy || UseMaterialAttributes() || StateIsActivated(DISEASE_IMMUNITY);
11007 }
11008 
LeprosyHandler()11009 void character::LeprosyHandler()
11010 {
11011   if(!(RAND() % 1000) && IsPlayer())
11012     ADD_MESSAGE("You notice you're covered in sores!");
11013   EditExperience(ARM_STRENGTH, -25, 1 << 1);
11014   EditExperience(LEG_STRENGTH, -25, 1 << 1);
11015   EditExperience(DEXTERITY, -25, 1 << 1);
11016   EditExperience(AGILITY, -25, 1 << 1);
11017   EditExperience(ENDURANCE, -25, 1 << 1);
11018   EditExperience(CHARISMA, -25, 1 << 1);
11019   CheckDeath(CONST_S("killed by leprosy"));
11020 }
11021 
VampirismHandler()11022 void character::VampirismHandler()
11023 {
11024   EditExperience(WISDOM, -25, 1 << 1);
11025   CheckDeath(CONST_S("killed by vampirism"));
11026 }
11027 
SearchForOriginalBodyPart(int I) const11028 bodypart* character::SearchForOriginalBodyPart(int I) const
11029 {
11030   for(stackiterator i = GetStackUnder()->GetBottom(); i.HasItem(); ++i)
11031   {
11032     for(ulong ID : OriginalBodyPartID[I])
11033       if(i->GetID() == ID)
11034         return static_cast<bodypart*>(*i);
11035   }
11036 
11037   return 0;
11038 }
11039 
SetLifeExpectancy(int Base,int RandPlus)11040 void character::SetLifeExpectancy(int Base, int RandPlus)
11041 {
11042   int c;
11043 
11044   for(c = 0; c < BodyParts; ++c)
11045   {
11046     bodypart* BodyPart = GetBodyPart(c);
11047 
11048     if(BodyPart)
11049       BodyPart->SetLifeExpectancy(Base, RandPlus);
11050   }
11051 
11052   for(c = 0; c < GetEquipments(); ++c)
11053   {
11054     item* Equipment = GetEquipment(c);
11055 
11056     if(Equipment)
11057       Equipment->SetLifeExpectancy(Base, RandPlus);
11058   }
11059 }
11060 
11061 /* Receiver should be a fresh duplicate of this */
11062 
DuplicateEquipment(character * Receiver,ulong Flags)11063 void character::DuplicateEquipment(character* Receiver, ulong Flags)
11064 {
11065   for(int c = 0; c < GetEquipments(); ++c)
11066   {
11067     item* Equipment = GetEquipment(c);
11068 
11069     if(Equipment)
11070     {
11071       item* Duplicate = Equipment->Duplicate(Flags);
11072       Receiver->SetEquipment(c, Duplicate);
11073     }
11074   }
11075 }
11076 
Disappear(corpse * Corpse,cchar * Verb,truth (item::* ClosePredicate)()const)11077 void character::Disappear(corpse* Corpse, cchar* Verb, truth (item::*ClosePredicate)() const)
11078 {
11079   truth TorsoDisappeared = false;
11080   truth CanBeSeen = Corpse ? Corpse->CanBeSeenByPlayer() : IsPlayer() || CanBeSeenByPlayer();
11081   int c;
11082 
11083   if((GetTorso()->*ClosePredicate)())
11084   {
11085     if(CanBeSeen)
11086     {
11087       if(Corpse)
11088         ADD_MESSAGE("%s %ss.", Corpse->CHAR_NAME(DEFINITE), Verb);
11089       else if(IsPlayer())
11090         ADD_MESSAGE("You %s.", Verb);
11091       else
11092         ADD_MESSAGE("%s %ss.", CHAR_NAME(DEFINITE), Verb);
11093     }
11094 
11095     TorsoDisappeared = true;
11096 
11097     for(c = 0; c < GetEquipments(); ++c)
11098     {
11099       item* Equipment = GetEquipment(c);
11100 
11101       if(Equipment && (Equipment->*ClosePredicate)())
11102       {
11103         Equipment->RemoveFromSlot();
11104         Equipment->SendToHell();
11105       }
11106     }
11107 
11108     itemvector ItemVector;
11109     GetStack()->FillItemVector(ItemVector);
11110 
11111     for(uint c = 0; c < ItemVector.size(); ++c)
11112       if(ItemVector[c] && (ItemVector[c]->*ClosePredicate)())
11113       {
11114         ItemVector[c]->RemoveFromSlot();
11115         ItemVector[c]->SendToHell();
11116       }
11117   }
11118 
11119   for(c = 1; c < GetBodyParts(); ++c)
11120   {
11121     bodypart* BodyPart = GetBodyPart(c);
11122 
11123     if(BodyPart)
11124     {
11125       if((BodyPart->*ClosePredicate)())
11126       {
11127         if(!TorsoDisappeared && CanBeSeen)
11128         {
11129           if(IsPlayer())
11130             ADD_MESSAGE("Your %s %ss.", GetBodyPartName(c).CStr(), Verb);
11131           else
11132             ADD_MESSAGE("The %s of %s %ss.", GetBodyPartName(c).CStr(), CHAR_NAME(DEFINITE), Verb);
11133         }
11134 
11135         BodyPart->DropEquipment();
11136         item* BodyPart = SevereBodyPart(c);
11137 
11138         if(BodyPart)
11139           BodyPart->SendToHell();
11140       }
11141       else if(TorsoDisappeared)
11142       {
11143         BodyPart->DropEquipment();
11144         item* BodyPart = SevereBodyPart(c);
11145 
11146         if(BodyPart)
11147         {
11148           if(Corpse)
11149             Corpse->GetSlot()->AddFriendItem(BodyPart);
11150           else if(!game::IsInWilderness())
11151             GetStackUnder()->AddItem(BodyPart);
11152           else
11153             BodyPart->SendToHell();
11154         }
11155       }
11156     }
11157   }
11158 
11159   if(TorsoDisappeared)
11160   {
11161     if(Corpse)
11162     {
11163       Corpse->RemoveFromSlot();
11164       Corpse->SendToHell();
11165     }
11166     else
11167       CheckDeath(festring(Verb) + "ed", 0, FORCE_DEATH|DISALLOW_CORPSE|DISALLOW_MSG);
11168   }
11169   else
11170     CheckDeath(festring(Verb) + "ed", 0, DISALLOW_MSG);
11171 }
11172 
SignalDisappearance()11173 void character::SignalDisappearance()
11174 {
11175   if(GetMotherEntity())
11176     GetMotherEntity()->SignalDisappearance();
11177   else
11178     Disappear(0, "disappear", &item::IsVeryCloseToDisappearance);
11179 }
11180 
HornOfFearWorks() const11181 truth character::HornOfFearWorks() const
11182 {
11183   return CanHear() && GetPanicLevel() > RAND() % 33 && !StateIsActivated(FEARLESS);
11184 }
11185 
BeginLeprosy()11186 void character::BeginLeprosy()
11187 {
11188   doforbodypartswithparam<truth>()(this, &bodypart::SetIsInfectedByLeprosy, true);
11189 }
11190 
EndLeprosy()11191 void character::EndLeprosy()
11192 {
11193   doforbodypartswithparam<truth>()(this, &bodypart::SetIsInfectedByLeprosy, false);
11194 }
11195 
IsSameAs(ccharacter * What) const11196 truth character::IsSameAs(ccharacter* What) const
11197 {
11198   return What->GetType() == GetType()
11199     && What->GetConfig() == GetConfig();
11200 }
11201 
GetCommandFlags() const11202 ulong character::GetCommandFlags() const
11203 {
11204   return !StateIsActivated(PANIC)
11205     ? CommandFlags
11206     : CommandFlags|FLEE_FROM_ENEMIES;
11207 }
11208 
GetConstantCommandFlags() const11209 ulong character::GetConstantCommandFlags() const
11210 {
11211   return !StateIsActivated(PANIC)
11212     ? DataBase->ConstantCommandFlags
11213     : DataBase->ConstantCommandFlags|FLEE_FROM_ENEMIES;
11214 }
11215 
GetPossibleCommandFlags() const11216 ulong character::GetPossibleCommandFlags() const
11217 {
11218   int Int = GetAttribute(INTELLIGENCE);
11219   ulong Flags = ALL_COMMAND_FLAGS;
11220 
11221   if(!CanMove() || Int < 4)
11222     Flags &= ~FOLLOW_LEADER;
11223 
11224   if(!CanMove() || Int < 6)
11225     Flags &= ~FLEE_FROM_ENEMIES;
11226 
11227   if(!CanUseEquipment() || Int < 8)
11228     Flags &= ~DONT_CHANGE_EQUIPMENT;
11229 
11230   if(!UsesNutrition() || Int < 8)
11231     Flags &= ~DONT_CONSUME_ANYTHING_VALUABLE;
11232 
11233   return Flags;
11234 }
11235 
IsRetreating() const11236 truth character::IsRetreating() const
11237 {
11238   return StateIsActivated(PANIC)
11239     || (CommandFlags & FLEE_FROM_ENEMIES && IsPet());
11240 }
11241 
ChatMenu()11242 truth character::ChatMenu()
11243 {
11244   if(GetAction() && !GetAction()->CanBeTalkedTo())
11245   {
11246     ADD_MESSAGE("%s is silent.", CHAR_DESCRIPTION(DEFINITE));
11247     PLAYER->EditAP(-200);
11248     return true;
11249   }
11250 
11251   ulong ManagementFlags = GetManagementFlags();
11252 
11253   if(ManagementFlags == CHAT_IDLY || !IsPet())
11254     return ChatIdly();
11255 
11256   static cchar*const ChatMenuEntry[CHAT_MENU_ENTRIES] =
11257     {
11258       "Change equipment",
11259       "Take items",
11260       "Give items",
11261       "Issue commands",
11262       "Chat idly",
11263     };
11264 
11265   static const petmanagementfunction PMF[CHAT_MENU_ENTRIES] =
11266     {
11267       &character::ChangePetEquipment,
11268       &character::TakePetItems,
11269       &character::GivePetItems,
11270       &character::IssuePetCommands,
11271       &character::ChatIdly
11272     };
11273 
11274   felist List(CONST_S("Choose action:"));
11275   game::SetStandardListAttributes(List);
11276   List.AddFlags(SELECTABLE);
11277   int c, i;
11278 
11279   for(c = 0; c < CHAT_MENU_ENTRIES; ++c)
11280     if(1 << c & ManagementFlags)
11281       List.AddEntry(ChatMenuEntry[c], LIGHT_GRAY);
11282 
11283   int Chosen = List.Draw();
11284 
11285   if(Chosen & FELIST_ERROR_BIT)
11286     return false;
11287 
11288   for(c = 0, i = 0; c < CHAT_MENU_ENTRIES; ++c)
11289     if(1 << c & ManagementFlags && i++ == Chosen)
11290       return (this->*PMF[c])();
11291 
11292   return false; // dummy
11293 }
11294 
ChangePetEquipment()11295 truth character::ChangePetEquipment()
11296 {
11297   game::RegionListItemEnable(true);
11298   game::RegionSilhouetteEnable(true);
11299   bool b = EquipmentScreen(PLAYER->GetStack(), GetStack());
11300   game::RegionListItemEnable(false);
11301   game::RegionSilhouetteEnable(false);
11302 
11303   if(b)
11304   {
11305     DexterityAction(3);
11306     return true;
11307   }
11308 
11309   return false;
11310 }
11311 
TakePetItems()11312 truth character::TakePetItems()
11313 {
11314   truth Success = false;
11315   stack::SetSelected(0);
11316 
11317   for(;;)
11318   {
11319     itemvector ToTake;
11320     game::DrawEverythingNoBlit();
11321 
11322     game::RegionListItemEnable(true);
11323     game::RegionSilhouetteEnable(true);
11324     GetStack()->DrawContents(ToTake,
11325                              0,
11326                              PLAYER,
11327                              CONST_S("What do you want to take from ") + CHAR_DESCRIPTION(DEFINITE) + '?',
11328                              CONST_S(""),
11329                              CONST_S(""),
11330                              GetDescription(DEFINITE) + " is " + GetVerbalBurdenState(),
11331                              GetVerbalBurdenStateColor(),
11332                              REMEMBER_SELECTED);
11333     game::RegionListItemEnable(false);
11334     game::RegionSilhouetteEnable(false);
11335 
11336     if(ToTake.empty())
11337       break;
11338 
11339     for(uint c = 0; c < ToTake.size(); ++c)
11340     {
11341       if(ivanconfig::GetRotateTimesPerSquare() > 0)
11342         ToTake[c]->ResetFlyingThrownStep();
11343 
11344       ToTake[c]->MoveTo(PLAYER->GetStack());
11345     }
11346 
11347     ADD_MESSAGE("You take %s.", ToTake[0]->GetName(DEFINITE, ToTake.size()).CStr());
11348     Success = true;
11349   }
11350 
11351   if(Success)
11352   {
11353     DexterityAction(2);
11354     PLAYER->DexterityAction(2);
11355   }
11356 
11357   return Success;
11358 }
11359 
GivePetItems()11360 truth character::GivePetItems()
11361 {
11362   truth Success = false;
11363   stack::SetSelected(0);
11364 
11365   for(;;)
11366   {
11367     itemvector ToGive;
11368     game::DrawEverythingNoBlit();
11369 
11370     game::RegionListItemEnable(true);
11371     game::RegionSilhouetteEnable(true);
11372     PLAYER->GetStack()->DrawContents(ToGive,
11373                                      0,
11374                                      this,
11375                                      CONST_S("What do you want to give to ") + CHAR_DESCRIPTION(DEFINITE) + '?',
11376                                      CONST_S(""),
11377                                      CONST_S(""),
11378                                      GetDescription(DEFINITE) + " is " + GetVerbalBurdenState(),
11379                                      GetVerbalBurdenStateColor(),
11380                                      REMEMBER_SELECTED);
11381     game::RegionListItemEnable(false);
11382     game::RegionSilhouetteEnable(false);
11383 
11384     if(ToGive.empty())
11385       break;
11386 
11387     for(uint c = 0; c < ToGive.size(); ++c)
11388       ToGive[c]->MoveTo(GetStack());
11389 
11390     ADD_MESSAGE("You give %s to %s.", ToGive[0]->GetName(DEFINITE, ToGive.size()).CStr(), CHAR_DESCRIPTION(DEFINITE));
11391     Success = true;
11392   }
11393 
11394   if(Success)
11395   {
11396     DexterityAction(2);
11397     PLAYER->DexterityAction(2);
11398   }
11399 
11400   return Success;
11401 }
11402 
IssuePetCommands()11403 truth character::IssuePetCommands()
11404 {
11405   if(!IsConscious())
11406   {
11407     ADD_MESSAGE("%s is unconscious.", CHAR_DESCRIPTION(DEFINITE));
11408     return false;
11409   }
11410 
11411   ulong PossibleC = GetPossibleCommandFlags();
11412 
11413   if(!PossibleC)
11414   {
11415     ADD_MESSAGE("%s cannot be commanded.", CHAR_DESCRIPTION(DEFINITE));
11416     return false;
11417   }
11418 
11419   ulong OldC = GetCommandFlags();
11420   ulong NewC = OldC, VaryFlags = 0;
11421   game::CommandScreen(CONST_S("Issue commands to ") + GetDescription(DEFINITE),
11422                       PossibleC, GetConstantCommandFlags(), VaryFlags, NewC);
11423 
11424   if(NewC == OldC)
11425     return false;
11426 
11427   SetCommandFlags(NewC);
11428   PLAYER->EditAP(-500);
11429   PLAYER->EditExperience(CHARISMA, 25, 1 << 7);
11430   return true;
11431 }
11432 
ChatIdly()11433 truth character::ChatIdly()
11434 {
11435   if(!TryToTalkAboutScience())
11436   {
11437     BeTalkedTo();
11438     PLAYER->EditExperience(CHARISMA, 75, 1 << 7);
11439   }
11440 
11441   PLAYER->EditAP(-1000);
11442   return true;
11443 }
11444 
EquipmentScreen(stack * MainStack,stack * SecStack)11445 truth character::EquipmentScreen(stack* MainStack, stack* SecStack)
11446 {
11447   if(!CanUseEquipment())
11448   {
11449     ADD_MESSAGE("%s cannot use equipment.", CHAR_DESCRIPTION(DEFINITE));
11450     return false;
11451   }
11452 
11453   int Chosen = 0;
11454   truth EquipmentChanged = false;
11455   felist List(CONST_S("Equipment menu [ESC exits]"));
11456   festring Entry;
11457   long TotalEquippedWeight;
11458 
11459   for(;;)
11460   {
11461     List.Empty();
11462     List.EmptyDescription();
11463 
11464     TotalEquippedWeight = 0;
11465 
11466     for(int c = 0; c < GetEquipments(); ++c) // if equipment exists, add to TotalEquippedWeight
11467     {
11468       item* Equipment = GetEquipment(c);
11469       TotalEquippedWeight += (Equipment) ? Equipment->GetWeight() : 0;
11470     }
11471 
11472     if(IsPlayer())
11473     {
11474       festring Total("Total weight: ");
11475       Total << TotalEquippedWeight;
11476       Total << "g";
11477 
11478       List.AddDescription(CONST_S(""));
11479       List.AddDescription(Total);
11480     }
11481 
11482     if(!IsPlayer())
11483     {
11484       List.AddDescription(CONST_S(""));
11485       List.AddDescription(festring(GetDescription(DEFINITE) + " is " + GetVerbalBurdenState()).CapitalizeCopy(),
11486                           GetVerbalBurdenStateColor());
11487     }
11488 
11489     for(int c = 0; c < GetEquipments(); ++c)
11490     {
11491       Entry = GetEquipmentName(c);
11492       Entry << ':';
11493       Entry.Resize(20);
11494       item* Equipment = GetEquipment(c);
11495 
11496       if(Equipment)
11497       {
11498         Equipment->AddInventoryEntry(this, Entry, 1, true);
11499         AddSpecialEquipmentInfo(Entry, c);
11500         int ImageKey = game::AddToItemDrawVector(itemvector(1, Equipment));
11501         List.AddEntry(Entry, LIGHT_GRAY, 20, ImageKey, true);
11502       }
11503       else
11504       {
11505         Entry << (GetBodyPartOfEquipment(c) ? "-" : "can't use");
11506         List.AddEntry(Entry, LIGHT_GRAY, 20, game::AddToItemDrawVector(itemvector()));
11507       }
11508 
11509       List.SetLastEntryHelp(festring() << "Your equipped arms and armor.");
11510     }
11511 
11512     game::DrawEverythingNoBlit();
11513     game::SetStandardListAttributes(List);
11514     List.SetFlags(SELECTABLE|DRAW_BACKGROUND_AFTERWARDS);
11515     List.SetEntryDrawer(game::ItemEntryDrawer);
11516     Chosen = List.Draw();
11517     game::ClearItemDrawVector();
11518 
11519     if(Chosen >= GetEquipments())
11520       break;
11521 
11522     EquipmentChanged = TryToChangeEquipment(MainStack, SecStack, Chosen);
11523   }
11524 
11525   if(EquipmentChanged)
11526     DexterityAction(5);
11527 
11528   return EquipmentChanged;
11529 }
11530 
GetManagementFlags() const11531 ulong character::GetManagementFlags() const
11532 {
11533   ulong Flags = ALL_MANAGEMENT_FLAGS;
11534 
11535   if(!CanUseEquipment() || !AllowPlayerToChangeEquipment())
11536     Flags &= ~CHANGE_EQUIPMENT;
11537 
11538   if(!GetStack()->GetItems())
11539     Flags &= ~TAKE_ITEMS;
11540 
11541   if(!WillCarryItems())
11542     Flags &= ~GIVE_ITEMS;
11543 
11544   if(!GetPossibleCommandFlags())
11545     Flags &= ~ISSUE_COMMANDS;
11546 
11547   return Flags;
11548 }
11549 
11550 cchar* VerbalBurdenState[] = { "overloaded", "stressed", "burdened", "unburdened" };
11551 col16 VerbalBurdenStateColor[] = { RED, BLUE, BLUE, WHITE };
11552 
GetVerbalBurdenState() const11553 cchar* character::GetVerbalBurdenState() const
11554 {
11555   return VerbalBurdenState[BurdenState];
11556 }
11557 
GetVerbalBurdenStateColor() const11558 col16 character::GetVerbalBurdenStateColor() const
11559 {
11560   return VerbalBurdenStateColor[BurdenState];
11561 }
11562 
GetAttributeAverage() const11563 int character::GetAttributeAverage() const
11564 {
11565   return GetSumOfAttributes() / 7;
11566 }
11567 
GetStandVerb() const11568 cfestring& character::GetStandVerb() const
11569 {
11570   if(ForceCustomStandVerb())
11571     return DataBase->StandVerb;
11572 
11573   static festring Hovering = CONST_S("hovering");
11574   static festring Swimming = CONST_S("swimming");
11575 
11576   if(StateIsActivated(LEVITATION))
11577     return Hovering;
11578 
11579   if(IsSwimming())
11580     return Swimming;
11581 
11582   return DataBase->StandVerb;
11583 }
11584 
CheckApply() const11585 truth character::CheckApply() const
11586 {
11587   if(!CanApply())
11588   {
11589     ADD_MESSAGE("This monster type cannot apply.");
11590     return false;
11591   }
11592 
11593   return true;
11594 }
11595 
EndLevitation()11596 void character::EndLevitation()
11597 {
11598   if(!IsFlying() && GetSquareUnder())
11599   {
11600     if(!game::IsInWilderness())
11601       SignalStepFrom(0);
11602 
11603     if(game::IsInWilderness() || !GetLSquareUnder()->IsFreezed())
11604       TestWalkability();
11605   }
11606 }
11607 
CanMove() const11608 truth character::CanMove() const
11609 {
11610   return !IsRooted() || StateIsActivated(LEVITATION);
11611 }
11612 
CalculateEnchantments()11613 void character::CalculateEnchantments()
11614 {
11615   doforequipments()(this, &item::CalculateEnchantment);
11616   GetStack()->CalculateEnchantments();
11617 }
11618 
GetNewFormForPolymorphWithControl(character * & NewForm)11619 truth character::GetNewFormForPolymorphWithControl(character*& NewForm)
11620 {
11621   festring Topic, Temp;
11622   NewForm = 0;
11623 
11624   if(StateIsActivated(POLYMORPH_LOCK))
11625   {
11626     ADD_MESSAGE("You feel uncertain about your body for a moment.");
11627     return false;
11628   }
11629 
11630   while(!NewForm)
11631   {
11632     festring Temp;
11633     game::RegionListItemEnable(true);
11634     game::RegionSilhouetteEnable(true); //polymorphing may unequip items, so taking a look at them is good
11635     int Return = game::DefaultQuestion(Temp, CONST_S("What do you want to become? [press '?' for a list]"),
11636                                        game::GetDefaultPolymorphTo(), true,
11637                                        &game::PolymorphControlKeyHandler);
11638     game::RegionListItemEnable(false);
11639     game::RegionSilhouetteEnable(false);
11640     if(Return == ABORTED)
11641     {
11642       ADD_MESSAGE("You choose not to polymorph.");
11643       NewForm = this;
11644       return false;
11645     }
11646 
11647     NewForm = protosystem::CreateMonster(Temp);
11648 
11649     if(NewForm)
11650     {
11651       if(NewForm->IsSameAs(this))
11652       {
11653         delete NewForm;
11654         ADD_MESSAGE("You choose not to polymorph.");
11655         NewForm = this;
11656         return false;
11657       }
11658 
11659       if(PolymorphBackup && NewForm->IsSameAs(PolymorphBackup))
11660       {
11661         delete NewForm;
11662         NewForm = ForceEndPolymorph();
11663         return false;
11664       }
11665 
11666       if(NewForm->GetPolymorphIntelligenceRequirement()
11667          > GetAttribute(INTELLIGENCE)
11668          && !game::WizardModeIsActive())
11669       {
11670         ADD_MESSAGE("You feel your mind isn't yet powerful enough to call forth the form of %s.",
11671                     NewForm->CHAR_NAME(INDEFINITE));
11672         delete NewForm;
11673         NewForm = 0;
11674       }
11675       else
11676         NewForm->RemoveAllItems();
11677     }
11678   }
11679 
11680   return true;
11681 }
11682 
CreateSweat(long Volume) const11683 liquid* character::CreateSweat(long Volume) const
11684 {
11685   return liquid::Spawn(GetSweatMaterial(), Volume);
11686 }
11687 
TeleportRandomItem(truth TryToHinderVisibility)11688 truth character::TeleportRandomItem(truth TryToHinderVisibility)
11689 {
11690   if(IsImmuneToItemTeleport() || StateIsActivated(TELEPORT_LOCK))
11691     return false;
11692 
11693   itemvector ItemVector;
11694   std::vector<long> PossibilityVector;
11695   int TotalPossibility = 0;
11696 
11697   for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
11698   {
11699     ItemVector.push_back(*i);
11700     int Possibility = i->GetTeleportPriority();
11701 
11702     if(TryToHinderVisibility)
11703       Possibility += i->GetHinderVisibilityBonus(this);
11704 
11705     PossibilityVector.push_back(Possibility);
11706     TotalPossibility += Possibility;
11707   }
11708 
11709   for(int c = 0; c < GetEquipments(); ++c)
11710   {
11711     item* Equipment = GetEquipment(c);
11712 
11713     if(Equipment)
11714     {
11715       ItemVector.push_back(Equipment);
11716       int Possibility = Equipment->GetTeleportPriority();
11717 
11718       if(TryToHinderVisibility)
11719         Possibility += Equipment->GetHinderVisibilityBonus(this);
11720 
11721       PossibilityVector.push_back(Possibility <<= 1);
11722       TotalPossibility += Possibility;
11723     }
11724   }
11725 
11726   if(!TotalPossibility)
11727     return false;
11728 
11729   int Chosen = femath::WeightedRand(PossibilityVector, TotalPossibility);
11730   item* Item = ItemVector[Chosen];
11731   truth Equipped = PLAYER->Equips(Item);
11732   truth Seen = Item->CanBeSeenByPlayer();
11733   Item->RemoveFromSlot();
11734 
11735   if(Seen)
11736     ADD_MESSAGE("%s disappears.", Item->CHAR_NAME(DEFINITE));
11737 
11738   if(Equipped)
11739     game::AskForKeyPress(CONST_S("Equipment lost! [press any key to continue]"));
11740 
11741   v2 Pos = GetPos();
11742   int Range = Item->GetEmitation() && TryToHinderVisibility ? 25 : 5;
11743   rect Border(Pos + v2(-Range, -Range), Pos + v2(Range, Range));
11744   Pos = GetLevel()->GetRandomSquare(this, 0, &Border);
11745 
11746   if(Pos == ERROR_V2)
11747     Pos = GetLevel()->GetRandomSquare();
11748 
11749   GetNearLSquare(Pos)->GetStack()->AddItem(Item);
11750 
11751   if(Item->CanBeSeenByPlayer())
11752     ADD_MESSAGE("%s appears.", Item->CHAR_NAME(INDEFINITE));
11753 
11754   return true;
11755 }
11756 
HasClearRouteTo(v2 Pos) const11757 truth character::HasClearRouteTo(v2 Pos) const
11758 {
11759   pathcontroller::Map = GetLevel()->GetMap();
11760   pathcontroller::Character = this;
11761   v2 ThisPos = GetPos();
11762   return mapmath<pathcontroller>::DoLine(ThisPos.X, ThisPos.Y, Pos.X, Pos.Y, SKIP_FIRST);
11763 }
11764 
IsTransparent() const11765 truth character::IsTransparent() const
11766 {
11767   return !IsEnormous() || GetTorso()->GetMainMaterial()->IsTransparent() || StateIsActivated(INVISIBLE);
11768 }
11769 
SignalPossibleTransparencyChange()11770 void character::SignalPossibleTransparencyChange()
11771 {
11772   if(!game::IsInWilderness())
11773     for(int c = 0; c < SquaresUnder; ++c)
11774     {
11775       lsquare* Square = GetLSquareUnder(c);
11776 
11777       if(Square)
11778         Square->SignalPossibleTransparencyChange();
11779     }
11780 }
11781 
GetCursorData() const11782 int character::GetCursorData() const
11783 {
11784   int Bad = 0;
11785   int Color = game::PlayerIsRunning() ? BLUE_CURSOR : DARK_CURSOR;
11786 
11787   for(int c = 0; c < BodyParts; ++c)
11788   {
11789     bodypart* BodyPart = GetBodyPart(c);
11790 
11791     if(BodyPart && BodyPart->IsUsable())
11792     {
11793       int ConditionColorIndex = BodyPart->GetConditionColorIndex();
11794 
11795       if((BodyPartIsVital(c) && !ConditionColorIndex)
11796          || (ConditionColorIndex <= 1 && ++Bad == 2))
11797         return Color|CURSOR_FLASH;
11798     }
11799     else if(++Bad == 2)
11800       return Color|CURSOR_FLASH;
11801   }
11802 
11803   Color = game::PlayerIsRunning() ? YELLOW_CURSOR : RED_CURSOR;
11804   return Bad ? Color|CURSOR_FLASH : Color;
11805 }
11806 
TryToName()11807 void character::TryToName()
11808 {
11809   if(!IsPet())
11810     ADD_MESSAGE("%s refuses to let YOU decide what %s's called.", CHAR_NAME(DEFINITE), CHAR_PERSONAL_PRONOUN);
11811   else if(IsPlayer())
11812     ADD_MESSAGE("You can't rename yourself.");
11813   else if(!IsNameable())
11814     ADD_MESSAGE("%s refuses to be called anything else but %s.", CHAR_NAME(DEFINITE), CHAR_NAME(DEFINITE));
11815   else
11816   {
11817     festring Topic = CONST_S("What name will you give to ") + GetName(DEFINITE) + '?';
11818     festring Name;
11819 
11820     if(game::StringQuestion(Name, Topic, WHITE, 0, 80, true) == NORMAL_EXIT && Name.GetSize())
11821       SetAssignedName(Name);
11822   }
11823 }
11824 
GetSituationDanger(ccharacter * Enemy,v2 ThisPos,v2 EnemyPos,truth SeesEnemy) const11825 double character::GetSituationDanger(ccharacter* Enemy, v2 ThisPos, v2 EnemyPos, truth SeesEnemy) const
11826 {
11827   double Danger;
11828 
11829   if(IgnoreDanger() && !IsPlayer())
11830   {
11831     if(Enemy->IgnoreDanger() && !Enemy->IsPlayer())
11832     {
11833       Danger = double(GetHP()) * GetHPRequirementForGeneration()
11834                / (Enemy->GetHP() * Enemy->GetHPRequirementForGeneration());
11835     }
11836     else
11837       Danger = .25 * GetHPRequirementForGeneration() / Enemy->GetHP();
11838   }
11839   else if(Enemy->IgnoreDanger() && !Enemy->IsPlayer())
11840     Danger = 4. * GetHP() / Enemy->GetHPRequirementForGeneration();
11841   else
11842     Danger = GetRelativeDanger(Enemy);
11843 
11844   Danger *= 3. / ((EnemyPos - ThisPos).GetManhattanLength() + 2);
11845 
11846   if(!SeesEnemy)
11847     Danger *= .2;
11848 
11849   if(StateIsActivated(PANIC))
11850     Danger *= .2;
11851 
11852   Danger *= double(GetHP()) * Enemy->GetMaxHP() / (Enemy->GetHP() * GetMaxHP());
11853   return Danger;
11854 }
11855 
ModifySituationDanger(double & Danger) const11856 void character::ModifySituationDanger(double& Danger) const
11857 {
11858   switch(GetTirednessState())
11859   {
11860    case FAINTING:
11861     Danger *= 1.5;
11862    case EXHAUSTED:
11863     Danger *= 1.25;
11864   }
11865 
11866   for(int c = 0; c < STATES; ++c)
11867     if(StateIsActivated(1 << c) && StateData[c].SituationDangerModifier != 0)
11868       (this->*StateData[c].SituationDangerModifier)(Danger);
11869 }
11870 
LycanthropySituationDangerModifier(double & Danger) const11871 void character::LycanthropySituationDangerModifier(double& Danger) const
11872 {
11873   character* Wolf = werewolfwolf::Spawn();
11874   double DangerToWolf = GetRelativeDanger(Wolf);
11875   Danger *= pow(DangerToWolf, 0.1);
11876   delete Wolf;
11877 }
11878 
PoisonedSituationDangerModifier(double & Danger) const11879 void character::PoisonedSituationDangerModifier(double& Danger) const
11880 {
11881   int C = GetTemporaryStateCounter(POISONED);
11882   Danger *= (1 + (C * C) / (GetHP() * 10000. * (GetGlobalResistance(POISON) + 1)));
11883 }
11884 
PolymorphingSituationDangerModifier(double & Danger) const11885 void character::PolymorphingSituationDangerModifier(double& Danger) const
11886 {
11887   if((!StateIsActivated(POLYMORPH_CONTROL)) && (!StateIsActivated(POLYMORPH_LOCK)))
11888     Danger *= 1.5;
11889 }
11890 
PanicSituationDangerModifier(double & Danger) const11891 void character::PanicSituationDangerModifier(double& Danger) const
11892 {
11893   Danger *= 1.5;
11894 }
11895 
ConfusedSituationDangerModifier(double & Danger) const11896 void character::ConfusedSituationDangerModifier(double& Danger) const
11897 {
11898   Danger *= 1.5;
11899 }
11900 
ParasitizedSituationDangerModifier(double & Danger) const11901 void character::ParasitizedSituationDangerModifier(double& Danger) const
11902 {
11903   Danger *= 1.25;
11904 }
11905 
LeprosySituationDangerModifier(double & Danger) const11906 void character::LeprosySituationDangerModifier(double& Danger) const
11907 {
11908   Danger *= 1.5;
11909 }
11910 
AddRandomScienceName(festring & String) const11911 void character::AddRandomScienceName(festring& String) const
11912 {
11913   festring Science = GetScienceTalkName().GetRandomElement().CStr();
11914 
11915   if(Science[0] == '!')
11916   {
11917     String << Science.CStr() + 1;
11918     return;
11919   }
11920 
11921   festring Attribute = GetScienceTalkAdjectiveAttribute().GetRandomElement();
11922   festring Prefix;
11923   truth NoAttrib = Attribute.IsEmpty(), NoSecondAdjective = false;
11924 
11925   if(!Attribute.IsEmpty() && Attribute[0] == '!')
11926   {
11927     NoSecondAdjective = true;
11928     Attribute.Erase(0, 1);
11929   }
11930 
11931   if(!Science.Find("the "))
11932   {
11933     Science.Erase(0, 4);
11934 
11935     if(!Attribute.Find("the ", 0, 4))
11936       Attribute << " the";
11937     else
11938       Attribute.Insert(0, "the ", 4);
11939   }
11940 
11941   if(islower(Science[0])
11942      && Science.Find(' ') == festring::NPos
11943      && Science.Find('-') == festring::NPos
11944      && Science.Find("phobia") == festring::NPos)
11945   {
11946     Prefix = GetScienceTalkPrefix().GetRandomElement();
11947 
11948     if(!Prefix.IsEmpty() && Science.Find(Prefix) != festring::NPos)
11949       Prefix.Empty();
11950   }
11951 
11952   if(!NoAttrib && !NoSecondAdjective == !RAND_GOOD(3))
11953   {
11954     int S1 = NoSecondAdjective ? 0 : GetScienceTalkAdjectiveAttribute().Size;
11955     int S2 = GetScienceTalkSubstantiveAttribute().Size;
11956     festring OtherAttribute;
11957     int Chosen = RAND_GOOD(S1 + S2);
11958 
11959     if(Chosen < S1)
11960       OtherAttribute = GetScienceTalkAdjectiveAttribute()[Chosen];
11961     else
11962       OtherAttribute = GetScienceTalkSubstantiveAttribute()[Chosen - S1];
11963 
11964     if(!OtherAttribute.IsEmpty() && OtherAttribute.Find("the ", 0, 4)
11965        && Attribute.Find(OtherAttribute) == festring::NPos)
11966     {
11967       String << Attribute << ' ' << OtherAttribute << ' ' << Prefix << Science;
11968       return;
11969     }
11970   }
11971 
11972   String << Attribute;
11973 
11974   if(!NoAttrib)
11975     String << ' ';
11976 
11977   String << Prefix << Science;
11978 }
11979 
TryToTalkAboutScience()11980 truth character::TryToTalkAboutScience()
11981 {
11982   if(GetRelation(PLAYER) == HOSTILE
11983      || GetScienceTalkPossibility() <= RAND_GOOD(100)
11984      || PLAYER->GetAttribute(INTELLIGENCE) < GetScienceTalkIntelligenceRequirement()
11985      || PLAYER->GetAttribute(WISDOM) < GetScienceTalkWisdomRequirement()
11986      || PLAYER->GetAttribute(CHARISMA) < GetScienceTalkCharismaRequirement())
11987     return false;
11988 
11989   festring Science;
11990 
11991   if(RAND_GOOD(3))
11992     AddRandomScienceName(Science);
11993   else
11994   {
11995     festring S1, S2;
11996     AddRandomScienceName(S1);
11997     AddRandomScienceName(S2);
11998 
11999     if(S1.Find(S2) == festring::NPos && S2.Find(S1) == festring::NPos)
12000     {
12001       switch(RAND_GOOD(3))
12002       {
12003        case 0: Science = "the relation of "; break;
12004        case 1: Science = "the differences of "; break;
12005        case 2: Science = "the similarities of "; break;
12006       }
12007 
12008       Science << S1 << " and " << S2;
12009     }
12010     else
12011       AddRandomScienceName(Science);
12012   }
12013 
12014   switch((RAND() + GET_TICK()) % 10)
12015   {
12016    case 0:
12017     ADD_MESSAGE("You have a rather pleasant chat about %s with %s.",
12018                 Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
12019     break;
12020    case 1:
12021     ADD_MESSAGE("%s explains a few of %s opinions regarding %s to you.",
12022                 CHAR_DESCRIPTION(DEFINITE), CHAR_POSSESSIVE_PRONOUN, Science.CStr());
12023     break;
12024    case 2:
12025     ADD_MESSAGE("%s reveals a number of %s insightful views of %s to you.",
12026                 CHAR_DESCRIPTION(DEFINITE), CHAR_POSSESSIVE_PRONOUN, Science.CStr());
12027     break;
12028    case 3:
12029     ADD_MESSAGE("You exchange some information pertaining to %s with %s.",
12030                 Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
12031     break;
12032    case 4:
12033     ADD_MESSAGE("You engage in a pretty intriguing conversation about %s with %s.",
12034                 Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
12035     break;
12036    case 5:
12037     ADD_MESSAGE("You discuss at length about %s with %s.",
12038                 Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
12039     break;
12040    case 6:
12041     ADD_MESSAGE("You have a somewhat boring talk concerning %s with %s.",
12042                 Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
12043     break;
12044    case 7:
12045     ADD_MESSAGE("You are drawn into a heated argument regarding %s with %s.",
12046                 Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
12047     break;
12048    case 8:
12049     ADD_MESSAGE("%s delivers a long monologue concerning eg. %s.",
12050                 CHAR_DESCRIPTION(DEFINITE), Science.CStr());
12051     break;
12052    case 9:
12053     ADD_MESSAGE("You dive into a brief but thought-provoking debate over %s with %s",
12054                 Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
12055     break;
12056   }
12057 
12058   PLAYER->EditExperience(INTELLIGENCE, 1000, 50. * GetScienceTalkIntelligenceModifier() / ++ScienceTalks);
12059   PLAYER->EditExperience(WISDOM, 1000, 50. * GetScienceTalkWisdomModifier() / ++ScienceTalks);
12060   PLAYER->EditExperience(CHARISMA, 1000, 50. * GetScienceTalkCharismaModifier() / ++ScienceTalks);
12061   return true;
12062 }
12063 
IsUsingWeaponOfCategory(int Category) const12064 truth character::IsUsingWeaponOfCategory(int Category) const
12065 {
12066   return ((GetMainWielded() && GetMainWielded()->GetWeaponCategory() == Category)
12067           || (GetSecondaryWielded() && GetSecondaryWielded()->GetWeaponCategory() == Category));
12068 }
12069 
TryToUnStickTraps(v2 Dir)12070 truth character::TryToUnStickTraps(v2 Dir)
12071 {
12072   if(!TrapData)
12073     return true;
12074 
12075   std::vector<trapdata> TrapVector;
12076 
12077   for(const trapdata* T = TrapData; T; T = T->Next)
12078     TrapVector.push_back(*TrapData);
12079 
12080   for(uint c = 0; c < TrapVector.size(); ++c)
12081     if(IsEnabled())
12082     {
12083       entity* Trap = game::SearchTrap(TrapVector[c].TrapID);
12084 
12085       if(Trap->GetVictimID() == GetID() && Trap->TryToUnStick(this, Dir))
12086         break;
12087     }
12088 
12089   return !TrapData && IsEnabled();
12090 }
12091 
12092 struct trapidcomparer
12093 {
trapidcomparertrapidcomparer12094   trapidcomparer(ulong ID) : ID(ID) { }
operator ()trapidcomparer12095   truth operator()(const trapdata* T) const { return T->TrapID == ID; }
12096   ulong ID;
12097 };
12098 
RemoveTrap(ulong ID)12099 void character::RemoveTrap(ulong ID)
12100 {
12101   trapdata*& T = ListFind(TrapData, trapidcomparer(ID));
12102   trapdata* ToDel = T;
12103   T = T->Next;
12104   delete ToDel;
12105   doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange);
12106 }
12107 
AddTrap(ulong ID,ulong BodyParts)12108 void character::AddTrap(ulong ID, ulong BodyParts)
12109 {
12110   trapdata*& T = ListFind(TrapData, trapidcomparer(ID));
12111 
12112   if(T)
12113     T->BodyParts |= BodyParts;
12114   else
12115     T = new trapdata(ID, GetID(), BodyParts);
12116 
12117   doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange);
12118 }
12119 
IsStuckToTrap(ulong ID) const12120 truth character::IsStuckToTrap(ulong ID) const
12121 {
12122   for(const trapdata* T = TrapData; T; T = T->Next)
12123     if(T->TrapID == ID)
12124       return true;
12125 
12126   return false;
12127 }
12128 
RemoveTraps()12129 void character::RemoveTraps()
12130 {
12131   for(trapdata* T = TrapData; T;)
12132   {
12133     entity* Trap = game::SearchTrap(T->TrapID);
12134 
12135     if(Trap)
12136       Trap->UnStick();
12137 
12138     trapdata* ToDel = T;
12139     T = T->Next;
12140     delete ToDel;
12141   }
12142 
12143   TrapData = 0;
12144   doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange);
12145 }
12146 
RemoveTraps(int BodyPartIndex)12147 void character::RemoveTraps(int BodyPartIndex)
12148 {
12149   ulong Flag = 1 << BodyPartIndex;
12150 
12151   for(trapdata** T = &TrapData; *T;)
12152     if((*T)->BodyParts & Flag)
12153     {
12154       entity* Trap = game::SearchTrap((*T)->TrapID);
12155 
12156       if(!((*T)->BodyParts &= ~Flag))
12157       {
12158         if(Trap)
12159           Trap->UnStick();
12160 
12161         trapdata* ToDel = *T;
12162         *T = (*T)->Next;
12163         delete ToDel;
12164       }
12165       else
12166       {
12167         if(Trap)
12168           Trap->UnStick(BodyPartIndex);
12169 
12170         T = &(*T)->Next;
12171       }
12172     }
12173     else
12174       T = &(*T)->Next;
12175 
12176   if(GetBodyPart(BodyPartIndex))
12177     GetBodyPart(BodyPartIndex)->SignalPossibleUsabilityChange();
12178 }
12179 
GetTrapDescription() const12180 festring character::GetTrapDescription() const
12181 {
12182   festring Desc;
12183   std::pair<entity*, int> TrapStack[3];
12184   int Index = 0;
12185 
12186   for(const trapdata* T = TrapData; T; T = T->Next)
12187   {
12188     if(Index < 3)
12189     {
12190       entity* Trap = game::SearchTrap(T->TrapID);
12191 
12192       if(Trap)
12193       {
12194         int c;
12195 
12196         for(c = 0; c < Index; ++c)
12197           if(TrapStack[c].first->GetTrapType() == Trap->GetTrapType())
12198             ++TrapStack[c].second;
12199 
12200         if(c == Index)
12201           TrapStack[Index++] = std::make_pair(Trap, 1);
12202       }
12203     }
12204     else
12205     {
12206       ++Index;
12207       break;
12208     }
12209   }
12210 
12211   if(Index <= 3)
12212   {
12213     TrapStack[0].first->AddTrapName(Desc, TrapStack[0].second);
12214 
12215     if(Index == 2)
12216     {
12217       Desc << " and ";
12218       TrapStack[1].first->AddTrapName(Desc, TrapStack[1].second);
12219     }
12220     else if(Index == 3)
12221     {
12222       Desc << ", ";
12223       TrapStack[1].first->AddTrapName(Desc, TrapStack[1].second);
12224       Desc << " and ";
12225       TrapStack[2].first->AddTrapName(Desc, TrapStack[2].second);
12226     }
12227   }
12228   else
12229     Desc << "lots of traps";
12230 
12231   return Desc;
12232 }
12233 
RandomizeHurtBodyPart(ulong BodyParts) const12234 int character::RandomizeHurtBodyPart(ulong BodyParts) const
12235 {
12236   int BodyPartIndex[MAX_BODYPARTS];
12237   int Index = 0;
12238 
12239   for(int c = 0; c < GetBodyParts(); ++c)
12240     if(1 << c & BodyParts)
12241       BodyPartIndex[Index++] = c;
12242 
12243   return BodyPartIndex[RAND_N(Index)];
12244 }
12245 
BodyPartIsStuck(int I) const12246 truth character::BodyPartIsStuck(int I) const
12247 {
12248   for(const trapdata* T = TrapData; T; T = T->Next)
12249     if(1 << I & T->BodyParts)
12250       return true;
12251 
12252   return false;
12253 }
12254 
PrintAttribute(cchar * Desc,int I,int PanelPosX,int PanelPosY) const12255 void character::PrintAttribute(cchar* Desc, int I, int PanelPosX, int PanelPosY) const
12256 {
12257   int Attribute = GetAttribute(I);
12258   int NoBonusAttribute = GetAttribute(I, false);
12259   col16 C = game::GetAttributeColor(I);
12260   festring String = Desc;
12261   String.Resize(5);
12262   String << Attribute;
12263   String.Resize(8);
12264   FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY * 10), C, String.CStr());
12265 
12266   if(Attribute != NoBonusAttribute)
12267   {
12268     int Where = PanelPosX + ((String.GetSize() + 1) << 3);
12269     FONT->Printf(DOUBLE_BUFFER, v2(Where, PanelPosY * 10), LIGHT_GRAY,
12270                  "%d", NoBonusAttribute);
12271   }
12272 }
12273 
AllowUnconsciousness() const12274 truth character::AllowUnconsciousness() const
12275 {
12276   return DataBase->AllowUnconsciousness && TorsoIsAlive();
12277 }
12278 
CanPanic() const12279 truth character::CanPanic() const
12280 {
12281   return !Action || !Action->IsUnconsciousness() || !StateIsActivated(FEARLESS);
12282 }
12283 
GetRandomBodyPart(ulong Possible) const12284 int character::GetRandomBodyPart(ulong Possible) const
12285 {
12286   int OKBodyPart[MAX_BODYPARTS];
12287   int OKBodyParts = 0;
12288 
12289   for(int c = 0; c < BodyParts; ++c)
12290     if(1 << c & Possible && GetBodyPart(c))
12291       OKBodyPart[OKBodyParts++] = c;
12292 
12293   return OKBodyParts ? OKBodyPart[RAND_N(OKBodyParts)] : NONE_INDEX;
12294 }
12295 
EditNP(long What)12296 void character::EditNP(long What)
12297 {
12298   int OldState = GetHungerState();
12299   NP += What;
12300   int NewState = GetHungerState();
12301 
12302   if(OldState > VERY_HUNGRY && NewState == VERY_HUNGRY)
12303     DeActivateVoluntaryAction(CONST_S("You are getting really hungry."));
12304 
12305   if(OldState > STARVING && NewState == STARVING)
12306     DeActivateVoluntaryAction(CONST_S("You are getting extremely hungry."));
12307 }
12308 
IsSwimming() const12309 truth character::IsSwimming() const
12310 {
12311   return !IsFlying() && GetSquareUnder()
12312     && GetSquareUnder()->GetSquareWalkability() & SWIM;
12313 }
12314 
AddBlackUnicornConsumeEndMessage() const12315 void character::AddBlackUnicornConsumeEndMessage() const
12316 {
12317   if(IsPlayer())
12318     ADD_MESSAGE("You feel dirty and loathsome.");
12319 }
12320 
AddGrayUnicornConsumeEndMessage() const12321 void character::AddGrayUnicornConsumeEndMessage() const
12322 {
12323   if(IsPlayer())
12324     ADD_MESSAGE("You feel neutralized.");
12325 }
12326 
AddWhiteUnicornConsumeEndMessage() const12327 void character::AddWhiteUnicornConsumeEndMessage() const
12328 {
12329   if(IsPlayer())
12330     ADD_MESSAGE("You feel purified.");
12331 }
12332 
AddOmmelBoneConsumeEndMessage() const12333 void character::AddOmmelBoneConsumeEndMessage() const
12334 {
12335   if(IsPlayer())
12336     ADD_MESSAGE("You feel the power of all your canine ancestors combining in your body.");
12337   else if(CanBeSeenByPlayer())
12338     ADD_MESSAGE("For a moment %s looks extremely ferocious. You shudder.", CHAR_NAME(DEFINITE));
12339 }
12340 
AddLiquidHorrorConsumeEndMessage() const12341 void character::AddLiquidHorrorConsumeEndMessage() const
12342 {
12343   if(IsPlayer())
12344     ADD_MESSAGE("Untold horrors flash before your eyes. The melancholy of the world is on your shoulders!");
12345   else if(CanBeSeenByPlayer())
12346     ADD_MESSAGE("%s looks as if the true terror of existence flashed before %s eyes!.", CHAR_NAME(DEFINITE), GetPossessivePronoun().CStr());
12347 }
12348 
GetBodyPartSparkleFlags(int) const12349 int character::GetBodyPartSparkleFlags(int) const
12350 {
12351   return ((GetNaturalSparkleFlags() & SKIN_COLOR ? SPARKLING_A : 0)
12352           | (GetNaturalSparkleFlags() & TORSO_MAIN_COLOR ? SPARKLING_B : 0)
12353           | (GetNaturalSparkleFlags() & TORSO_SPECIAL_COLOR ? SPARKLING_D : 0));
12354 }
12355 
IsAnimated() const12356 truth character::IsAnimated() const
12357 {
12358   return combinebodypartpredicates()(this, &bodypart::IsAnimated, 1);
12359 }
12360 
GetNaturalExperience(int Identifier) const12361 double character::GetNaturalExperience(int Identifier) const
12362 {
12363   return DataBase->NaturalExperience[Identifier];
12364 }
12365 
HasBodyPart(sorter Sorter) const12366 truth character::HasBodyPart(sorter Sorter) const
12367 {
12368   if(Sorter == 0)
12369     return true;
12370 
12371   return combinebodypartpredicateswithparam<ccharacter*>()(this, Sorter, this, 1);
12372 }
12373 
PossessesItem(sorter Sorter) const12374 truth character::PossessesItem(sorter Sorter) const
12375 {
12376   if(Sorter == 0)
12377     return true;
12378 
12379   return (GetStack()->SortedItems(this, Sorter)
12380           || combinebodypartpredicateswithparam<ccharacter*>()(this, Sorter, this, 1)
12381           || combineequipmentpredicateswithparam<ccharacter*>()(this, Sorter, this, 1));
12382 }
12383 
12384 /* 0 <= I <= 1 */
12385 
GetRunDescriptionLine(int I) const12386 cchar* character::GetRunDescriptionLine(int I) const
12387 {
12388   if(!GetRunDescriptionLineOne().IsEmpty())
12389     return !I ? GetRunDescriptionLineOne().CStr() : GetRunDescriptionLineTwo().CStr();
12390 
12391   if(IsFlying())
12392     return !I ? "Flying" : " very fast";
12393 
12394   if(IsSwimming())
12395   {
12396     if(IsPlayer() && game::IsInWilderness() && game::PlayerHasBoat())
12397       return !I ? "Sailing" : " very fast";
12398     else
12399       return !I ? "Swimming" : " very fast";
12400   }
12401 
12402   return !I ? "Running" : "";
12403 }
12404 
VomitAtRandomDirection(int Amount)12405 void character::VomitAtRandomDirection(int Amount)
12406 {
12407   /* Lacks support of multitile monsters */
12408   if(game::IsInWilderness() || IsLarge() || Amount <= 0)
12409     return;
12410 
12411   v2 Possible[9] = { GetPos(), GetPos(), GetPos(),
12412                      GetPos(), GetPos(), GetPos(),
12413                      GetPos(), GetPos(), GetPos() };
12414   int Index = 0;
12415 
12416   for(int d = 0; d < 9; ++d)
12417   {
12418     lsquare* Square = GetLSquareUnder()->GetNeighbourLSquare(d);
12419 
12420     if(Square && !Square->VomitingIsDangerous(this))
12421       Possible[Index++] = Square->GetPos();
12422   }
12423 
12424   if(Index)
12425     Vomit(Possible[RAND_N(Index)], Amount);
12426   else
12427     Vomit(GetPos(), Amount);
12428 }
12429 
RemoveLifeSavers()12430 void character::RemoveLifeSavers()
12431 {
12432   for(int c = 0; c < GetEquipments(); ++c)
12433   {
12434     item* Equipment = GetEquipment(c);
12435 
12436     if(Equipment && Equipment->IsInCorrectSlot(c) && Equipment->GetGearStates() & LIFE_SAVED)
12437     {
12438       Equipment->SendToHell();
12439       Equipment->RemoveFromSlot();
12440     }
12441   }
12442 }
12443 
FindCarrier() const12444 ccharacter* character::FindCarrier() const
12445 {
12446   return this; //check
12447 }
12448 
PrintBeginHiccupsMessage() const12449 void character::PrintBeginHiccupsMessage() const
12450 {
12451   if(IsPlayer())
12452     ADD_MESSAGE("Your diaphragm is spasming vehemently.");
12453 }
12454 
PrintEndHiccupsMessage() const12455 void character::PrintEndHiccupsMessage() const
12456 {
12457   if(IsPlayer())
12458     ADD_MESSAGE("You feel your annoying hiccoughs have finally subsided.");
12459 }
12460 
HiccupsHandler()12461 void character::HiccupsHandler()
12462 {
12463   /*if(!(RAND() % 2000))
12464   {
12465     if(IsPlayer())
12466       ADD_MESSAGE("");
12467     else if(CanBeSeenByPlayer())
12468       ADD_MESSAGE("");
12469     else if((PLAYER->GetPos() - GetPos()).GetLengthSquare() <= 400)
12470       ADD_MESSAGE("");
12471 
12472     game::CallForAttention(GetPos(), 400);
12473   }*/
12474 }
12475 
HiccupsSituationDangerModifier(double & Danger) const12476 void character::HiccupsSituationDangerModifier(double& Danger) const
12477 {
12478   Danger *= 1.25;
12479 }
12480 
IsConscious() const12481 bool character::IsConscious() const
12482 {
12483   return !Action || !Action->IsUnconsciousness();
12484 }
12485 
GetNearWSquare(v2 Pos) const12486 wsquare* character::GetNearWSquare(v2 Pos) const
12487 {
12488   return static_cast<wsquare*>(GetSquareUnder()->GetArea()->GetSquare(Pos));
12489 }
12490 
GetNearWSquare(int x,int y) const12491 wsquare* character::GetNearWSquare(int x, int y) const
12492 {
12493   return static_cast<wsquare*>(GetSquareUnder()->GetArea()->GetSquare(x, y));
12494 }
12495 
ForcePutNear(v2 Pos)12496 void character::ForcePutNear(v2 Pos)
12497 {
12498   /* GUM SOLUTION!!! */
12499 
12500   v2 NewPos = game::GetCurrentLevel()->GetNearestFreeSquare(PLAYER, Pos, false);
12501 
12502   if(NewPos == ERROR_V2)
12503     do
12504     {
12505       NewPos = game::GetCurrentLevel()->GetRandomSquare(this);
12506     }
12507     while(NewPos == Pos);
12508 
12509   PutTo(NewPos);
12510 }
12511 
ReceiveMustardGas(int BodyPart,long Volume)12512 void character::ReceiveMustardGas(int BodyPart, long Volume)
12513 {
12514   if(Volume)
12515     GetBodyPart(BodyPart)->AddFluid(liquid::Spawn(MUSTARD_GAS_LIQUID, Volume), CONST_S("skin"), 0, true);
12516 }
12517 
ReceiveMustardGasLiquid(int BodyPartIndex,long Modifier)12518 void character::ReceiveMustardGasLiquid(int BodyPartIndex, long Modifier)
12519 {
12520   bodypart* BodyPart = GetBodyPart(BodyPartIndex);
12521 
12522   if(BodyPart->GetMainMaterial()->GetInteractionFlags() & IS_AFFECTED_BY_MUSTARD_GAS)
12523   {
12524     long Tries = Modifier;
12525     Modifier -= Tries; //opt%?
12526     int Damage = 0;
12527 
12528     for(long c = 0; c < Tries; ++c)
12529       if(!(RAND() % 100))
12530         ++Damage;
12531 
12532     if(Modifier && !(RAND() % 1000 / Modifier))
12533       ++Damage;
12534 
12535     if(Damage)
12536     {
12537       ulong Minute = game::GetTotalMinutes();
12538 
12539       if(GetLastAcidMsgMin() != Minute && (CanBeSeenByPlayer() || IsPlayer()))
12540       {
12541         SetLastAcidMsgMin(Minute);
12542 
12543         if(IsPlayer())
12544           ADD_MESSAGE("Mustard gas dissolves the skin of your %s.",
12545                       BodyPart->GetBodyPartName().CStr());
12546         else
12547           ADD_MESSAGE("Mustard gas dissolves %s.", CHAR_NAME(DEFINITE));
12548       }
12549 
12550       ReceiveBodyPartDamage(0, Damage, MUSTARD_GAS_DAMAGE,
12551                             BodyPartIndex, YOURSELF, false, false, false);
12552       CheckDeath(CONST_S("killed by a fatal exposure to mustard gas"));
12553     }
12554 
12555     if(IsPlayer())
12556     {
12557       action* Action = GetAction();
12558 
12559       if(Action && Action->IsRest() && !Action->InDNDMode()
12560          && BodyPartIsVital(BodyPartIndex) && BodyPart->IsBadlyHurt())
12561       {
12562         ADD_MESSAGE("You're about to die from mustard gas.");
12563 
12564         if(game::TruthQuestion(CONST_S("Mustard gas is dissolving your flesh."
12565                                        " Continue resting? [y/N]"),
12566                                NO))
12567         {
12568           Action->Terminate(false);
12569         }
12570         else
12571         {
12572           Action->ActivateInDNDMode();
12573         }
12574       }
12575     }
12576   }
12577 }
12578 
ReceiveFlames(long Volume)12579 void character::ReceiveFlames(long Volume)
12580 {
12581   if(!Volume)
12582     return;
12583 
12584   for(int c = 0; c < BodyParts; ++c)
12585   {
12586     bodypart* BodyPart = GetBodyPart(c);
12587 
12588     if(BodyPart && BodyPart->GetMainMaterial())
12589     {
12590       if(BodyPart->CanBeBurned()
12591          && (BodyPart->GetMainMaterial()->GetInteractionFlags() & CAN_BURN)
12592          && !BodyPart->IsBurning())
12593       {
12594         BodyPart->TestActivationEnergy(Volume);
12595       }
12596       else if(BodyPart->IsBurning())
12597         BodyPart->GetMainMaterial()->AddToThermalEnergy(Volume);
12598     }
12599   }
12600 
12601   for(int c = 0; c < GetEquipments(); ++c)
12602   {
12603     item* Equipment = GetEquipment(c);
12604 
12605     if(Equipment)
12606     {
12607       if(Equipment->CanBeBurned()
12608          && (Equipment->GetMainMaterial()->GetInteractionFlags() & CAN_BURN)
12609          && !Equipment->IsBurning())
12610       {
12611         Equipment->TestActivationEnergy(Volume);
12612       }
12613       else if(Equipment->IsBurning())
12614         Equipment->GetMainMaterial()->AddToThermalEnergy(Volume);
12615     }
12616   }
12617 }
12618 
IsBadPath(v2 Pos) const12619 truth character::IsBadPath(v2 Pos) const
12620 {
12621   if(!IsGoingSomeWhere())
12622     return false;
12623 
12624   v2 TPos = !Route.empty() ? Route.back() : GoingTo;
12625 
12626   return ((TPos - Pos).GetManhattanLength()
12627           > (TPos - GetPos()).GetManhattanLength());
12628 }
12629 
GetExpModifierRef(expid E)12630 double& character::GetExpModifierRef(expid E)
12631 {
12632   return ExpModifierMap.insert(std::make_pair(E, 1.)).first->second;
12633 }
12634 
12635 /* Should probably do more. Now only makes Player forget gods */
ForgetRandomThing()12636 truth character::ForgetRandomThing()
12637 {
12638   if(IsPlayer())
12639   {
12640     /* hopefully this code isn't some where else */
12641     std::vector<god*> Known;
12642 
12643     for(int c = 1; c <= GODS; ++c)
12644       if(game::GetGod(c)->IsKnown())
12645         Known.push_back(game::GetGod(c));
12646 
12647     if(Known.empty())
12648       return false;
12649 
12650     int RandomGod = RAND_N(Known.size());
12651     Known.at(RandomGod)->SetIsKnown(false);
12652     ADD_MESSAGE("You forget how to pray to %s.",
12653                 Known.at(RandomGod)->GetName());
12654     return true;
12655   }
12656   return false;
12657 }
12658 
CheckForBlock(character * Enemy,item * Weapon,double ToHitValue,int Damage,int Success,int Type)12659 int character::CheckForBlock(character* Enemy, item* Weapon,
12660                              double ToHitValue, int Damage,
12661                              int Success, int Type)
12662 {
12663   return Damage;
12664 }
12665 
ApplyAllGodsKnownBonus()12666 void character::ApplyAllGodsKnownBonus()
12667 {
12668   stack* AddPlace = GetStackUnder();
12669 
12670   if(game::IsInWilderness())
12671     AddPlace = GetStack();
12672   else
12673     AddPlace = GetStackUnder();
12674 
12675   pantheonbook* NewBook = pantheonbook::Spawn();
12676   AddPlace->AddItem(NewBook);
12677 
12678   ADD_MESSAGE("\"MORTAL! BEHOLD THE HOLY SAGA!\"");
12679   ADD_MESSAGE("%s materializes near your feet.",
12680               NewBook->CHAR_NAME(INDEFINITE));
12681 }
12682 
ReceiveSirenSong(character * Siren)12683 truth character::ReceiveSirenSong(character* Siren)
12684 {
12685   if(Siren->GetRelation(this) != HOSTILE)
12686     return false;
12687 
12688   if(RAND_N(GetAttribute(WILL_POWER)) > RAND_N(Siren->GetAttribute(CHARISMA)))
12689   {
12690     if(IsPlayer())
12691       ADD_MESSAGE("The beautiful song of %s makes you feel a little sad.", Siren->CHAR_NAME(DEFINITE));
12692     else if(CanBeSeenByPlayer())
12693       ADD_MESSAGE("%s sings a beautiful melody.", Siren->CHAR_NAME(DEFINITE));
12694     else
12695       ADD_MESSAGE("You hear a beautiful song.");
12696 
12697     EditExperience(WILL_POWER, 100, 1 << 12);
12698     return false;
12699   }
12700 
12701   if(!RAND_N(4))
12702   {
12703     if(IsPlayer())
12704       ADD_MESSAGE("The beautiful melody of %s makes you feel sleepy.", Siren->CHAR_NAME(DEFINITE));
12705     else if(CanBeSeenByPlayer())
12706       ADD_MESSAGE("The beautiful melody of %s makes %s look sleepy.", Siren->CHAR_NAME(DEFINITE), CHAR_NAME(DEFINITE));
12707     else
12708       ADD_MESSAGE("You hear a beautiful song.");
12709 
12710     EditStamina(-((1 + RAND_N(4)) * 10000), true);
12711     return true;
12712   }
12713 
12714   if(!IsPlayer() && IsCharmable() && CanTameWithDulcis(Siren) && !RAND_N(5))
12715   {
12716     ChangeTeam(Siren->GetTeam());
12717 
12718     if(CanBeSeenByPlayer())
12719       ADD_MESSAGE("%s seems to be totally brainwashed by %s's melodies.", CHAR_NAME(DEFINITE), Siren->CHAR_NAME(DEFINITE));
12720     else
12721       ADD_MESSAGE("You hear a beautiful song.");
12722 
12723     return true;
12724   }
12725 
12726   if(!IsImmuneToWhipOfThievery() && !RAND_N(4))
12727   {
12728     item* What = GiveMostExpensiveItem(Siren);
12729 
12730     if(What)
12731     {
12732       if(IsPlayer())
12733         ADD_MESSAGE("%s song persuades you to give %s to %s as a present.",
12734                     Siren->CHAR_NAME(DEFINITE), What->CHAR_NAME(DEFINITE), Siren->CHAR_OBJECT_PRONOUN);
12735       else if(CanBeSeenByPlayer())
12736         ADD_MESSAGE("%s is persuaded to give %s to %s because of %s beautiful singing.",
12737                     CHAR_NAME(DEFINITE), What->CHAR_NAME(INDEFINITE), Siren->CHAR_NAME(DEFINITE), Siren->CHAR_OBJECT_PRONOUN);
12738       else
12739         ADD_MESSAGE("You hear a beautiful song.");
12740 
12741       if(Siren->GetConfig() == AMBASSADOR_SIREN)
12742         Siren->TeleportRandomly(true);
12743     }
12744     else
12745     {
12746       if(IsPlayer())
12747         ADD_MESSAGE("You would like to give something to %s.", Siren->CHAR_NAME(DEFINITE));
12748       else if(CanBeSeenByPlayer())
12749         ADD_MESSAGE("%s looks longingly at %s.", CHAR_NAME(DEFINITE), Siren->CHAR_NAME(DEFINITE));
12750       else
12751         ADD_MESSAGE("You hear a beautiful song.");
12752     }
12753     return true;
12754   }
12755   return false;
12756 }
12757 
12758 // return 0, if no item found
12759 
FindMostExpensiveItem() const12760 item* character::FindMostExpensiveItem() const
12761 {
12762   int MaxPrice = -1;
12763   item* MostExpensive = 0;
12764 
12765   for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
12766     if((*i)->GetPrice() > MaxPrice)
12767     {
12768       MaxPrice = (*i)->GetPrice();
12769       MostExpensive = (*i);
12770     }
12771 
12772   for(int c = 0; c < GetEquipments(); ++c)
12773   {
12774     item* Equipment = GetEquipment(c);
12775 
12776     if(Equipment && Equipment->GetPrice() > MaxPrice)
12777     {
12778       MaxPrice = Equipment->GetPrice();
12779       MostExpensive = Equipment;
12780     }
12781   }
12782 
12783   return MostExpensive;
12784 }
12785 
12786 // returns 0 if no items available
12787 
GiveMostExpensiveItem(character * ToWhom)12788 item* character::GiveMostExpensiveItem(character* ToWhom)
12789 {
12790   item* ToGive = FindMostExpensiveItem();
12791 
12792   if(!ToGive)
12793     return 0;
12794 
12795   truth Equipped = PLAYER->Equips(ToGive);
12796   ToGive->RemoveFromSlot();
12797 
12798   if(Equipped)
12799     game::AskForKeyPress(CONST_S("Equipment lost! [press any key to continue]"));
12800 
12801   ToWhom->ReceiveItemAsPresent(ToGive);
12802   EditAP(-1000);
12803   return ToGive;
12804 }
12805 
ReceiveItemAsPresent(item * Present)12806 void character::ReceiveItemAsPresent(item* Present)
12807 {
12808   if(TestForPickup(Present))
12809     GetStack()->AddItem(Present);
12810   else
12811     GetStackUnder()->AddItem(Present);
12812 }
12813 
12814 /* returns 0 if no enemies in sight */
12815 
GetNearestEnemy() const12816 character* character::GetNearestEnemy() const
12817 {
12818   character* NearestEnemy = 0;
12819   long NearestEnemyDistance = 0x7FFFFFFF;
12820   v2 Pos = GetPos();
12821 
12822   for(int c = 0; c < game::GetTeams(); ++c)
12823     if(GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE)
12824     {
12825       for(character* p : game::GetTeam(c)->GetMember())
12826         if(p->IsEnabled())
12827         {
12828           long ThisDistance = Max<long>(abs(p->GetPos().X - Pos.X),
12829                                         abs(p->GetPos().Y - Pos.Y));
12830 
12831           if((ThisDistance < NearestEnemyDistance
12832               || (ThisDistance == NearestEnemyDistance && !(RAND() % 3)))
12833              && p->CanBeSeenBy(this))
12834           {
12835             NearestEnemy = p;
12836             NearestEnemyDistance = ThisDistance;
12837           }
12838         }
12839     }
12840 
12841   return NearestEnemy;
12842 }
12843 
PrintBeginMindwormedMessage() const12844 void character::PrintBeginMindwormedMessage() const
12845 {
12846   if(IsPlayer())
12847     ADD_MESSAGE("You have a killer headache.");
12848 }
12849 
PrintEndMindwormedMessage() const12850 void character::PrintEndMindwormedMessage() const
12851 {
12852   if(IsPlayer())
12853     ADD_MESSAGE("Your headache finally subsides.");
12854 }
12855 
MindWormCanPenetrateSkull(mindworm *) const12856 truth character::MindWormCanPenetrateSkull(mindworm*) const
12857 {
12858   if(!(RAND() % 2))
12859     return true;
12860   else
12861     return false;
12862 }
12863 
MindwormedHandler()12864 void character::MindwormedHandler()
12865 {
12866   if(!(RAND() % 200))
12867   {
12868     if(IsPlayer())
12869       ADD_MESSAGE("Your brain hurts!");
12870 
12871     BeginTemporaryState(CONFUSED, 100 + RAND_N(100));
12872     return;
12873   }
12874 
12875   // Multiple mind worm hatchlings can hatch, because multiple eggs could have been implanted.
12876   if(!game::IsInWilderness() && !(RAND() % 500))
12877   {
12878     character* Spawned = mindworm::Spawn(HATCHLING);
12879     v2 Pos = game::GetCurrentLevel()->GetNearestFreeSquare(Spawned, GetPos());
12880 
12881     if(Pos == ERROR_V2)
12882     {
12883       delete Spawned;
12884       return;
12885     }
12886 
12887     Spawned->SetTeam(game::GetTeam(MONSTER_TEAM));
12888     Spawned->PutTo(Pos);
12889 
12890     if(IsPlayer())
12891     {
12892       ADD_MESSAGE("%s suddenly digs out of your skull.", Spawned->CHAR_NAME(INDEFINITE));
12893     }
12894     else if(CanBeSeenByPlayer())
12895     {
12896       ADD_MESSAGE("%s suddenly digs out of %s's skull.", Spawned->CHAR_NAME(INDEFINITE), CHAR_NAME(DEFINITE));
12897     }
12898 
12899     ReceiveDamage(0, 1, DRAIN, HEAD, 8, false, false, false, false); // Use DRAIN here so that AV does not apply.
12900     CheckDeath(CONST_S("killed by giving birth to ") + Spawned->GetName(INDEFINITE));
12901   }
12902 }
12903 
CanTameWithDulcis(const character * Tamer) const12904 truth character::CanTameWithDulcis(const character* Tamer) const
12905 {
12906   int TamingDifficulty = GetTamingDifficulty();
12907 
12908   if(TamingDifficulty == NO_TAMING)
12909     return false;
12910 
12911   if(GetAttachedGod() == DULCIS)
12912     return true;
12913 
12914   if(!IgnoreDanger())
12915     TamingDifficulty = Max(TamingDifficulty, int(10 * GetRelativeDanger(Tamer)));
12916   else
12917     TamingDifficulty = Max(TamingDifficulty, (10 * GetHPRequirementForGeneration() / Max(Tamer->GetHP(), 1)));
12918 
12919   TamingDifficulty = Max(TamingDifficulty, GetAttribute(WILL_POWER));
12920 
12921   int Modifier = Tamer->GetAttribute(WISDOM) + Tamer->GetAttribute(CHARISMA);
12922 
12923   if(Tamer->IsPlayer())
12924     Modifier += game::GetGod(DULCIS)->GetRelation() / 20;
12925   else if(Tamer->GetAttachedGod() == DULCIS)
12926     Modifier += 50;
12927 
12928   return Modifier >= TamingDifficulty * 3;
12929 }
12930 
CanTameWithLyre(const character * Tamer) const12931 truth character::CanTameWithLyre(const character* Tamer) const
12932 {
12933   int TamingDifficulty = GetTamingDifficulty();
12934 
12935   if(TamingDifficulty == NO_TAMING)
12936     return false;
12937 
12938   if(!IgnoreDanger())
12939     TamingDifficulty = Max(TamingDifficulty, int(10 * GetRelativeDanger(Tamer)));
12940   else
12941     TamingDifficulty = Max(TamingDifficulty, (10 * GetHPRequirementForGeneration() / Max(Tamer->GetHP(), 1)));
12942 
12943   TamingDifficulty = Max(TamingDifficulty, GetAttribute(WILL_POWER));
12944 
12945   int Modifier = Tamer->GetAttribute(MANA) + Tamer->GetAttribute(CHARISMA);
12946 
12947   return Modifier >= TamingDifficulty * 2;
12948 }
12949 
CanTameWithScroll(const character * Tamer) const12950 truth character::CanTameWithScroll(const character* Tamer) const
12951 {
12952   int TamingDifficulty = GetTamingDifficulty();
12953 
12954   if(TamingDifficulty == NO_TAMING)
12955     return false;
12956 
12957   /*
12958   if(!IgnoreDanger())
12959     TamingDifficulty = Max(TamingDifficulty, int(10 * GetRelativeDanger(Tamer)));
12960   else
12961     TamingDifficulty = Max(TamingDifficulty, (10 * GetHPRequirementForGeneration() / Max(Tamer->GetHP(), 1)));
12962   */
12963 
12964   TamingDifficulty = Max(TamingDifficulty, GetAttribute(WILL_POWER));
12965 
12966   int Modifier = Tamer->GetAttribute(INTELLIGENCE) * 4 + Tamer->GetAttribute(CHARISMA);
12967 
12968   return Modifier >= TamingDifficulty * 5;
12969 }
12970 
CanTameWithResurrection(const character * Tamer) const12971 truth character::CanTameWithResurrection(const character* Tamer) const
12972 {
12973   int TamingDifficulty = GetTamingDifficulty();
12974 
12975   if(TamingDifficulty == NO_TAMING)
12976     return false;
12977 
12978   TamingDifficulty = Max(TamingDifficulty, GetAttribute(WILL_POWER));
12979 
12980   return Tamer->GetAttribute(CHARISMA) >= TamingDifficulty / 2;
12981   // Alternate formula 2/3 * TamingDifficulty <= CHA + (WIS + INT) / 2
12982 }
12983 
CheckSadism()12984 truth character::CheckSadism()
12985 {
12986   if(!IsSadist() || !HasSadistAttackMode() || !IsSmall()) // gum
12987     return false;
12988 
12989   if(!RAND_N(10))
12990   {
12991     for(int d = 0; d < 8; ++d)
12992     {
12993       square* Square = GetNeighbourSquare(d);
12994 
12995       if(Square)
12996       {
12997         character* Char = Square->GetCharacter();
12998 
12999         if(Char && Char->IsMasochist() && GetRelation(Char) == FRIEND
13000            && Char->GetHP() * 3 >= Char->GetMaxHP() * 2
13001            && Hit(Char, Square->GetPos(), d, SADIST_HIT))
13002         {
13003           TerminateGoingTo();
13004           return true;
13005         }
13006       }
13007     }
13008   }
13009 
13010   return false;
13011 }
13012 
CheckForBeverage()13013 truth character::CheckForBeverage()
13014 {
13015   if(StateIsActivated(PANIC) || !IsEnabled() || !UsesNutrition() || CheckIfSatiated())
13016     return false;
13017 
13018   itemvector ItemVector;
13019   GetStack()->FillItemVector(ItemVector);
13020 
13021   for(uint c = 0; c < ItemVector.size(); ++c)
13022     if(ItemVector[c]->IsBeverage(this) && TryToConsume(ItemVector[c]))
13023       return true;
13024 
13025   return false;
13026 }
13027 
Haste()13028 void character::Haste()
13029 {
13030   doforbodyparts()(this, &bodypart::Haste);
13031   doforequipments()(this, &item::Haste);
13032   BeginTemporaryState(HASTE, 500 + RAND() % 1000);
13033 }
13034 
Slow()13035 void character::Slow()
13036 {
13037   doforbodyparts()(this, &bodypart::Slow);
13038   doforequipments()(this, &item::Slow);
13039   BeginTemporaryState(SLOW, 500 + RAND() % 1000);
13040 }
13041 
IsESPBlockedByEquipment() const13042 truth character::IsESPBlockedByEquipment() const
13043 {
13044   for(int c = 0; c < GetEquipments(); ++c)
13045   {
13046     item *Item = GetEquipment(c);
13047 
13048     if (Item && Item->IsHelmet(this) &&
13049         ((Item->GetMainMaterial() && Item->GetMainMaterial()->BlockESP()) ||
13050          (Item->GetSecondaryMaterial() && Item->GetSecondaryMaterial()->BlockESP())))
13051       return !(Item->IsBroken());
13052   }
13053   return false;
13054 }
13055 
TemporaryStateIsActivated(long What) const13056 truth character::TemporaryStateIsActivated (long What) const
13057 {DBG7(this,GetNameSingular().CStr(),TemporaryState&What,TemporaryState,std::bitset<32>(TemporaryState),What,std::bitset<32>(What));
13058   if((What&PANIC) && (TemporaryState&PANIC) && StateIsActivated(FEARLESS))
13059   {DBGLN;
13060     return ((TemporaryState&What) & (~PANIC));
13061   }DBGLN;
13062   if((What&ESP) && (TemporaryState&ESP) && IsESPBlockedByEquipment())
13063   {DBGLN;
13064     return ((TemporaryState&What) & (~ESP));
13065   }DBGLN;
13066   bool b = (TemporaryState & What); DBGLN;
13067   return b;
13068 }
13069 
StateIsActivated(long What) const13070 truth character::StateIsActivated (long What) const
13071 {
13072   if ((What & PANIC) && ((TemporaryState|EquipmentState) & PANIC) && StateIsActivated(FEARLESS))
13073   {
13074     return ((TemporaryState & What) & (~PANIC)) || ((EquipmentState & What) & (~PANIC));
13075   }
13076   if ((What & ESP) && ((TemporaryState|EquipmentState) & ESP) && IsESPBlockedByEquipment())
13077   {
13078     return ((TemporaryState & What) & (~ESP)) || ((EquipmentState & What) & (~ESP));
13079   }
13080   return (TemporaryState & What) || (EquipmentState & What);
13081 }
13082 
CheckAIZapOpportunity()13083 truth character::CheckAIZapOpportunity()
13084 {
13085   if(!CanZap() || !IsHumanoid() || !IsEnabled() || GetAttribute(INTELLIGENCE) < 5)
13086     return false;
13087 
13088   // Check visible area for hostiles:
13089   v2 Pos = GetPos();
13090   v2 TestPos;
13091   int SensibleRange = 5;
13092   int RangeMax = GetLOSRange();
13093 
13094   if(RangeMax < SensibleRange)
13095     SensibleRange = RangeMax;
13096 
13097   int CandidateDirections[8] = {0, 0, 0, 0, 0, 0, 0, 0};
13098   int HostileFound = 0;
13099 
13100   for(int r = 2; r <= SensibleRange; ++r)
13101   {
13102     for(int dir = 0; dir < 8; ++dir)
13103     {
13104       switch(dir)
13105       {
13106        case 0:
13107         TestPos = v2(Pos.X - r, Pos.Y - r);
13108         break;
13109        case 1:
13110         TestPos = v2(Pos.X, Pos.Y - r);
13111         break;
13112        case 2:
13113         TestPos = v2(Pos.X + r, Pos.Y - r);
13114         break;
13115        case 3:
13116         TestPos = v2(Pos.X - r, Pos.Y);
13117         break;
13118        case 4:
13119         TestPos = v2(Pos.X + r, Pos.Y);
13120         break;
13121        case 5:
13122         TestPos = v2(Pos.X - r, Pos.Y + r);
13123         break;
13124        case 6:
13125         TestPos = v2(Pos.X, Pos.Y + r);
13126         break;
13127        case 7:
13128         TestPos = v2(Pos.X + r, Pos.Y + r);
13129         break;
13130       }
13131 
13132       level* Level = GetLevel();
13133 
13134       if(Level->IsValidPos(TestPos))
13135       {
13136         square* TestSquare = GetNearSquare(TestPos);
13137         character* Dude = TestSquare->GetCharacter();
13138 
13139         if((Dude && Dude->IsEnabled() && (GetRelation(Dude) != HOSTILE)
13140            && Dude->SquareUnderCanBeSeenBy(this, false)))
13141         {
13142           CandidateDirections[dir] = BLOCKED;
13143         }
13144 
13145         if(Dude && Dude->IsEnabled() && (GetRelation(Dude) == HOSTILE)
13146            && Dude->SquareUnderCanBeSeenBy(this, false)
13147            && (CandidateDirections[dir] != BLOCKED))
13148         {
13149           CandidateDirections[dir] = SUCCESS;
13150           HostileFound = 1;
13151         }
13152       }
13153     }
13154   }
13155 
13156   int ZapDirection = 0;
13157   int TargetFound = 0;
13158 
13159   if(HostileFound)
13160   {
13161     for(uint dir = 0; dir < 8; ++dir)
13162     {
13163       if((CandidateDirections[dir] == SUCCESS) && !TargetFound)
13164       {
13165         ZapDirection = dir;
13166         TargetFound = 1;
13167       }
13168     }
13169     if(!TargetFound)
13170       return false;
13171   }
13172   else
13173     return false;
13174 
13175 
13176   // Check inventory and equipment for zappable items.
13177   itemvector ItemVector;
13178   GetStack()->FillItemVector(ItemVector);
13179 
13180   for(int c = 0; c < GetEquipments(); ++c)
13181   {
13182     item* Equipment = GetEquipment(c);
13183 
13184     if(Equipment)
13185       ItemVector.push_back(Equipment);
13186   }
13187   std::random_shuffle(ItemVector.begin(), ItemVector.end());
13188 
13189   item* ToBeZapped = 0;
13190   for(uint c = 0; c < ItemVector.size(); ++c)
13191     if(ItemVector[c]->IsZappable(this) && ItemVector[c]->IsZapWorthy(this))
13192     {
13193       ToBeZapped = ItemVector[c];
13194       break;
13195     }
13196 
13197   if(!ToBeZapped)
13198     return false;
13199 
13200   if(CanBeSeenByPlayer())
13201     ADD_MESSAGE("%s zaps %s.", CHAR_NAME(DEFINITE), ToBeZapped->CHAR_NAME(INDEFINITE));
13202 
13203   if(ToBeZapped->Zap(this, GetPos(), ZapDirection))
13204   {
13205     EditAP(-100000 / APBonus(GetAttribute(PERCEPTION)));
13206     return true;
13207   }
13208 
13209   return false;
13210 
13211   // Steps:
13212   // (1) - Acquire target as nearest enemy.
13213   // (2) - Check that this enemy is in range, and is in appropriate direction.
13214   //       No friendly fire!
13215   // (3) - Check inventory for zappable item.
13216   // (4) - Zap item in direction where the enemy is.
13217 }
13218 
GetAdjustedStaminaCost(int BaseCost,int Attribute)13219 int character::GetAdjustedStaminaCost(int BaseCost, int Attribute)
13220 {
13221   if(Attribute > 1)
13222   {
13223     return BaseCost / log10(Attribute);
13224   }
13225 
13226   return BaseCost / 0.20;
13227 }
13228 
TryToStealFromShop(character * Shopkeeper,item * ToSteal)13229 truth character::TryToStealFromShop(character* Shopkeeper, item* ToSteal)
13230 {
13231   if(!IsPlayer())
13232     return RAND_2; // Plain 50% chance for monsters.
13233 
13234   double perception_check;
13235   if(Shopkeeper)
13236     perception_check = 100 - (1000 / (10 + Shopkeeper->GetAttribute(PERCEPTION)));
13237   else
13238     perception_check = 0;
13239 
13240   double base_chance = 100 - (100000 / (2000 + game::GetGod(CLEPTIA)->GetRelation()));
13241   double size_mod = std::pow(0.99999, ((ToSteal->GetWeight() * ToSteal->GetSize()) / GetAttribute(ARM_STRENGTH)));
13242   double stat_mod = std::pow(1.01, ((100 - (1000 / (10 + GetAttribute(DEXTERITY)))) - perception_check));
13243   int normalized_chance = Max(5, Min(95, int(base_chance * size_mod * stat_mod)));
13244 
13245   game::DoEvilDeed(25);
13246   game::GetGod(CLEPTIA)->AdjustRelation(50);
13247 
13248   return (1 + RAND() % 100 < normalized_chance);
13249 }
13250 
IsInBadCondition() const13251 truth character::IsInBadCondition() const
13252 {
13253   for(int c = 0; c < BodyParts; ++c)
13254   {
13255     bodypart* BodyPart = GetBodyPart(c);
13256     if(BodyPart && BodyPartIsVital(c) &&
13257       ((BodyPart->GetHP() * 3 < BodyPart->GetMaxHP()) ||
13258       ((BodyPart->GetHP() == 1) && (BodyPart->GetMaxHP() > 1))))
13259       return true;
13260   }
13261 
13262   return HP * 3 < MaxHP;
13263 }
13264 
GetHitPointDescription() const13265 festring character::GetHitPointDescription() const
13266 {
13267   float Health = (float)GetHP() / (float)GetMaxHP();
13268   festring Desc = "bugged";
13269 
13270   // Do not describe humanoids with missing limbs as "unharmed".
13271   if(!HasAllBodyParts() || IsInBadCondition())
13272     Health *= 0.75;
13273 
13274   if(TorsoIsAlive())
13275   {
13276     if(Health == 1.0)
13277       Desc = IsPlayer() ? "unharmed" : "not hurt"; // Reads better in look message.
13278     else if(Health >= 0.95)
13279       Desc = "bruised";
13280     else if(Health >= 0.8)
13281       Desc = "lightly wounded";
13282     else if(Health >= 0.7)
13283       Desc = "wounded";
13284     else if(Health >= 0.5)
13285       Desc = "heavily wounded";
13286     else if(Health >= 0.3)
13287       Desc = "severely wounded";
13288     else if(Health >= 0.1)
13289       Desc = "mortally wounded";
13290     else if(Health > 0.0)
13291       Desc = "almost dead";
13292     else if(Health <= 0.0)
13293       Desc = "dead";
13294   }
13295   else // Unliving creatures
13296   {
13297     if(Health == 1.0)
13298       Desc = "undamaged";
13299     else if(Health >= 0.95)
13300       Desc = "scratched";
13301     else if(Health >= 0.8)
13302       Desc = "lightly damaged";
13303     else if(Health >= 0.7)
13304       Desc = "damaged";
13305     else if(Health >= 0.5)
13306       Desc = "heavily damaged";
13307     else if(Health >= 0.3)
13308       Desc = "severely damaged";
13309     else if(Health >= 0.1)
13310       Desc = "extremely damaged";
13311     else if(Health > 0.0)
13312       Desc = "almost destroyed";
13313     else if(Health <= 0.0)
13314       Desc = "destroyed";
13315   }
13316 
13317   if(IsUndead())
13318     Desc = "dead";
13319 
13320   return Desc;
13321 }
13322 
WillGetTurnSoon() const13323 truth character::WillGetTurnSoon() const
13324 {
13325   return GetAP() >= 900;
13326 }
13327