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