'g'o should not miss items in corners anymore
[k8-i-v-a-n.git] / src / game / char.cpp
blob5099244447289c8939dd36ca58b965db4d5e5e5a
1 /*
3 * Iter Vehemens ad Necem (IVAN)
4 * Copyright (C) Timo Kiviluoto
5 * Released under the GNU General
6 * Public License
8 * See LICENSING which should be included
9 * along with this file for more details
12 /* Compiled through charset.cpp */
13 #include <time.h>
14 #include <sys/stat.h>
15 #include <sys/types.h>
16 #include <unistd.h>
20 * 0: up-left
21 * 1: up
22 * 2: up-right
23 * 3: left
24 * 4: right
25 * 5: down-left
26 * 6: down
27 * 7: down-right
28 * 8: stand still
30 enum {
31 MDIR_UP_LEFT,
32 MDIR_UP,
33 MDIR_UP_RIGHT,
34 MDIR_LEFT,
35 MDIR_RIGHT,
36 MDIR_DOWN_LEFT,
37 MDIR_DOWN,
38 MDIR_DOWN_RIGHT,
39 MDIR_STAND,
43 /* These statedata structs contain functions and values used for handling
44 * states. Remember to update them. All normal states must have
45 * PrintBeginMessage and PrintEndMessage functions and a Description string.
46 * BeginHandler, EndHandler, Handler (called each tick) and IsAllowed are
47 * optional, enter zero if the state doesn't need one. If the SECRET flag
48 * is set, Description is not shown in the panel without magical means.
49 * You can also set some source (SRC_*) and duration (DUR_*) flags, which
50 * control whether the state can be randomly activated in certain situations.
51 * These flags can be found in ivandef.h. RANDOMIZABLE sets all source
52 * & duration flags at once. */
53 struct statedata {
54 const char *Description;
55 int Flags;
56 void (character::*PrintBeginMessage) () const;
57 void (character::*PrintEndMessage) () const;
58 void (character::*BeginHandler) ();
59 void (character::*EndHandler) ();
60 void (character::*Handler) ();
61 truth (character::*IsAllowed) () const;
62 void (character::*SituationDangerModifier) (double &) const;
66 //WARNING! state count and state order MUST be synced with "define.dat"
67 const statedata StateData[STATES] =
70 "Polymorphed",
71 NO_FLAGS,
75 &character::EndPolymorph,
79 }, {
80 "Hasted",
81 RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL),
82 &character::PrintBeginHasteMessage,
83 &character::PrintEndHasteMessage,
89 }, {
90 "Slowed",
91 RANDOMIZABLE&~SRC_GOOD,
92 &character::PrintBeginSlowMessage,
93 &character::PrintEndSlowMessage,
99 }, {
100 "PolyControl",
101 RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL|SRC_GOOD),
102 &character::PrintBeginPolymorphControlMessage,
103 &character::PrintEndPolymorphControlMessage,
109 }, {
110 "LifeSaved",
111 SECRET,
112 &character::PrintBeginLifeSaveMessage,
113 &character::PrintEndLifeSaveMessage,
119 }, {
120 "Lycanthropy",
121 SECRET|SRC_FOUNTAIN|SRC_CONFUSE_READ|DUR_FLAGS,
122 &character::PrintBeginLycanthropyMessage,
123 &character::PrintEndLycanthropyMessage,
126 &character::LycanthropyHandler,
128 &character::LycanthropySituationDangerModifier
129 }, {
130 "Invisible",
131 RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL),
132 &character::PrintBeginInvisibilityMessage,
133 &character::PrintEndInvisibilityMessage,
134 &character::BeginInvisibility,
135 &character::EndInvisibility,
139 }, {
140 "Infravision",
141 RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL),
142 &character::PrintBeginInfraVisionMessage,
143 &character::PrintEndInfraVisionMessage,
144 &character::BeginInfraVision,
145 &character::EndInfraVision,
149 }, {
150 "ESP",
151 RANDOMIZABLE&~SRC_EVIL,
152 &character::PrintBeginESPMessage,
153 &character::PrintEndESPMessage,
154 &character::BeginESP,
155 &character::EndESP,
159 }, {
160 "Poisoned",
161 DUR_TEMPORARY,
162 &character::PrintBeginPoisonedMessage,
163 &character::PrintEndPoisonedMessage,
166 &character::PoisonedHandler,
167 &character::CanBePoisoned,
168 &character::PoisonedSituationDangerModifier
169 }, {
170 "Teleporting",
171 SECRET|(RANDOMIZABLE&~(SRC_MUSHROOM|SRC_GOOD)),
172 &character::PrintBeginTeleportMessage,
173 &character::PrintEndTeleportMessage,
176 &character::TeleportHandler,
179 }, {
180 "Polymorphing",
181 SECRET|(RANDOMIZABLE&~(SRC_MUSHROOM|SRC_GOOD)),
182 &character::PrintBeginPolymorphMessage,
183 &character::PrintEndPolymorphMessage,
186 &character::PolymorphHandler,
188 &character::PolymorphingSituationDangerModifier
189 }, {
190 "TeleControl",
191 RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL),
192 &character::PrintBeginTeleportControlMessage,
193 &character::PrintEndTeleportControlMessage,
199 }, {
200 "Panicked",
201 NO_FLAGS,
202 &character::PrintBeginPanicMessage,
203 &character::PrintEndPanicMessage,
204 &character::BeginPanic,
205 &character::EndPanic,
207 &character::CanPanic,
208 &character::PanicSituationDangerModifier
209 }, {
210 "Confused",
211 SECRET|(RANDOMIZABLE&~(DUR_PERMANENT|SRC_GOOD)),
212 &character::PrintBeginConfuseMessage,
213 &character::PrintEndConfuseMessage,
217 &character::CanBeConfused,
218 &character::ConfusedSituationDangerModifier
219 }, {
220 "Parasitized",
221 SECRET|(RANDOMIZABLE&~DUR_TEMPORARY),
222 &character::PrintBeginParasitizedMessage,
223 &character::PrintEndParasitizedMessage,
226 &character::ParasitizedHandler,
227 &character::CanBeParasitized,
228 &character::ParasitizedSituationDangerModifier
229 }, {
230 "Searching",
231 NO_FLAGS,
232 &character::PrintBeginSearchingMessage,
233 &character::PrintEndSearchingMessage,
236 &character::SearchingHandler,
239 }, {
240 "GasImmunity",
241 SECRET|(RANDOMIZABLE&~(SRC_GOOD|SRC_EVIL)),
242 &character::PrintBeginGasImmunityMessage,
243 &character::PrintEndGasImmunityMessage,
249 }, {
250 "Levitating",
251 RANDOMIZABLE&~SRC_EVIL,
252 &character::PrintBeginLevitationMessage,
253 &character::PrintEndLevitationMessage,
255 &character::EndLevitation,
259 }, {
260 "Leprosy",
261 SECRET|(RANDOMIZABLE&~DUR_TEMPORARY),
262 &character::PrintBeginLeprosyMessage,
263 &character::PrintEndLeprosyMessage,
264 &character::BeginLeprosy,
265 &character::EndLeprosy,
266 &character::LeprosyHandler,
268 &character::LeprosySituationDangerModifier
269 }, {
270 "Hiccups",
271 SRC_FOUNTAIN|SRC_CONFUSE_READ|DUR_FLAGS,
272 &character::PrintBeginHiccupsMessage,
273 &character::PrintEndHiccupsMessage,
276 &character::HiccupsHandler,
278 &character::HiccupsSituationDangerModifier
279 }, {
280 "Vampirism",
281 DUR_FLAGS, //perhaps no fountain, no secret and no confuse read either: SECRET|SRC_FOUNTAIN|SRC_CONFUSE_READ|
282 &character::PrintBeginVampirismMessage,
283 &character::PrintEndVampirismMessage,
286 &character::VampirismHandler,
288 &character::VampirismSituationDangerModifier
289 }, {
290 "Swimming",
291 SECRET,
292 &character::PrintBeginSwimmingMessage,
293 &character::PrintEndSwimmingMessage,
294 &character::BeginSwimming,
295 &character::EndSwimming,
299 }, {
300 "Detecting",
301 SECRET|(RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL)),
302 &character::PrintBeginDetectMessage,
303 &character::PrintEndDetectMessage,
306 &character::DetectHandler,
309 }, {
310 "Ethereal",
311 NO_FLAGS,
312 &character::PrintBeginEtherealityMessage,
313 &character::PrintEndEtherealityMessage,
314 &character::BeginEthereality,
315 &character::EndEthereality,
319 }, {
320 "Fearless",
321 RANDOMIZABLE&~SRC_EVIL,
322 &character::PrintBeginFearlessMessage,
323 &character::PrintEndFearlessMessage,
324 &character::BeginFearless,
325 &character::EndFearless,
329 }, {
330 "PolymorphLocked",
331 SECRET,
332 &character::PrintBeginPolymorphLockMessage,
333 &character::PrintEndPolymorphLockMessage,
336 &character::PolymorphLockHandler,
339 }, {
340 "Regenerating",
341 SECRET|(RANDOMIZABLE&~SRC_EVIL),
342 &character::PrintBeginRegenerationMessage,
343 &character::PrintEndRegenerationMessage,
349 }, {
350 "DiseaseImmunity",
351 SECRET|(RANDOMIZABLE&~SRC_EVIL),
352 &character::PrintBeginDiseaseImmunityMessage,
353 &character::PrintEndDiseaseImmunityMessage,
359 }, {
360 "TeleportLocked",
361 SECRET,
362 &character::PrintBeginTeleportLockMessage,
363 &character::PrintEndTeleportLockMessage,
366 &character::TeleportLockHandler,
373 characterprototype::characterprototype (const characterprototype *Base, characterspawner Spawner,
374 charactercloner Cloner, cchar *ClassID)
375 : Base(Base),
376 Spawner(Spawner),
377 Cloner(Cloner),
378 ClassID(ClassID)
380 Index = protocontainer<character>::Add(this);
384 void character::CreateInitialEquipment (int SpecialFlags) { AddToInventory(DataBase->Inventory, SpecialFlags); }
385 void character::EditAP (sLong What) { AP = Limit<sLong>(AP+What, -12000, 1200); }
386 int character::GetRandomStepperBodyPart () const { return TORSO_INDEX; }
387 void character::GainIntrinsic (sLong What) { BeginTemporaryState(What, PERMANENT); }
388 truth character::IsUsingArms () const { return GetAttackStyle() & USE_ARMS; }
389 truth character::IsUsingLegs () const { return GetAttackStyle() & USE_LEGS; }
390 truth character::IsUsingHead () const { return GetAttackStyle() & USE_HEAD; }
391 void character::CalculateAllowedWeaponSkillCategories () { AllowedWeaponSkillCategories = MARTIAL_SKILL_CATEGORIES; }
392 festring character::GetBeVerb () const { return IsPlayer() ? CONST_S("are") : CONST_S("is"); }
393 void character::SetEndurance (int What) { BaseExperience[ENDURANCE] = What * EXP_MULTIPLIER; }
394 void character::SetPerception (int What) { BaseExperience[PERCEPTION] = What * EXP_MULTIPLIER; }
395 void character::SetIntelligence (int What) { BaseExperience[INTELLIGENCE] = What * EXP_MULTIPLIER; }
396 void character::SetWisdom (int What) { BaseExperience[WISDOM] = What * EXP_MULTIPLIER; }
397 void character::SetWillPower (int What) { BaseExperience[WILL_POWER] = What * EXP_MULTIPLIER; }
398 void character::SetCharisma (int What) { BaseExperience[CHARISMA] = What * EXP_MULTIPLIER; }
399 void character::SetMana (int What) { BaseExperience[MANA] = What * EXP_MULTIPLIER; }
400 truth character::IsOnGround () const { return MotherEntity && MotherEntity->IsOnGround(); }
401 truth character::LeftOversAreUnique () const { return GetArticleMode() || AssignedName.GetSize(); }
402 truth character::HomeDataIsValid () const { return (HomeData && HomeData->Level == GetLSquareUnder()->GetLevelIndex() && HomeData->Dungeon == GetLSquareUnder()->GetDungeonIndex()); }
403 void character::SetHomePos (v2 Pos) { HomeData->Pos = Pos; }
404 cchar *character::FirstPersonUnarmedHitVerb () const { return "hit"; }
405 cchar *character::FirstPersonCriticalUnarmedHitVerb () const { return "critically hit"; }
406 cchar *character::ThirdPersonUnarmedHitVerb () const { return "hits"; }
407 cchar *character::ThirdPersonCriticalUnarmedHitVerb () const { return "critically hits"; }
408 cchar *character::FirstPersonKickVerb () const { return "kick"; }
409 cchar *character::FirstPersonCriticalKickVerb () const { return "critically kick"; }
410 cchar *character::ThirdPersonKickVerb () const { return "kicks"; }
411 cchar *character::ThirdPersonCriticalKickVerb () const { return "critically kicks"; }
412 cchar *character::FirstPersonBiteVerb () const { return "bite"; }
413 cchar *character::FirstPersonCriticalBiteVerb () const { return "critically bite"; }
414 cchar *character::ThirdPersonBiteVerb () const { return "bites"; }
415 cchar *character::ThirdPersonCriticalBiteVerb () const { return "critically bites"; }
416 cchar *character::UnarmedHitNoun () const { return "attack"; }
417 cchar *character::KickNoun () const { return "kick"; }
418 cchar *character::BiteNoun () const { return "attack"; }
419 cchar *character::GetEquipmentName (int) const { return ""; }
420 const std::list<feuLong> &character::GetOriginalBodyPartID (int I) const { return OriginalBodyPartID[I]; }
421 square *character::GetNeighbourSquare (int I) const { return GetSquareUnder()->GetNeighbourSquare(I); }
422 lsquare *character::GetNeighbourLSquare (int I) const { return static_cast<lsquare *>(GetSquareUnder())->GetNeighbourLSquare(I); }
423 //wsquare *character::GetNeighbourWSquare (int I) const { return static_cast<wsquare *>(GetSquareUnder())->GetNeighbourWSquare(I); }
424 god *character::GetMasterGod () const { return game::GetGod(GetConfig()); }
425 col16 character::GetBodyPartColorA (int, truth) const { return GetSkinColor(); }
426 col16 character::GetBodyPartColorB (int, truth) const { return GetTorsoMainColor(); }
427 col16 character::GetBodyPartColorC (int, truth) const { return GetBeltColor(); } // sorry...
428 col16 character::GetBodyPartColorD (int, truth) const { return GetTorsoSpecialColor(); }
429 int character::GetRandomApplyBodyPart () const { return TORSO_INDEX; }
430 truth character::IsPet () const { return GetTeam()->GetID() == PLAYER_TEAM; }
431 character* character::GetLeader () const { return GetTeam()->GetLeader(); }
432 festring character::GetZombieDescription () const { return " of "+GetName(INDEFINITE); }
433 truth character::BodyPartCanBeSevered (int I) const { return I; }
434 truth character::HasBeenSeen () const { return DataBase->Flags & HAS_BEEN_SEEN; }
435 truth character::IsTemporary () const { return GetTorso()->GetLifeExpectancy(); }
436 cchar *character::GetNormalDeathMessage () const { return "killed @k"; }
439 truth character::IsHomeLevel (level *lvl) const {
440 if (!lvl) return false;
441 // check level homes
442 const fearray<festring> &hlist = DataBase->HomeLevel;
443 if (hlist.Size == 0) return false;
444 // has "*"?
445 for (uInt f = 0; f < hlist.Size; ++f) { if (hlist[f] == "*") return true; }
446 // taken from unique characters code
447 cfestring *tag = lvl->GetLevelScript()->GetTag();
448 if (!tag) return false; // not a possible home level
449 // check for home level
450 for (uInt f = 0; f < hlist.Size; ++f) { if (hlist[f] == *tag) return true; }
451 return false;
455 truth character::MustBeRemovedFromBone () const {
456 if (IsUnique() && !CanBeGenerated()) return true;
457 // check level homes
458 const fearray<festring> &hlist = DataBase->HomeLevel;
459 if (hlist.Size == 0) return false;
460 // has "*"?
461 for (uInt f = 0; f < hlist.Size; ++f) { if (hlist[f] == "*") return true; }
462 // taken from unique characters code
463 if (!IsEnabled() || GetTeam()->GetID() != DataBase->NaturalTeam) return true;
464 return IsHomeLevel(GetLevel());
468 int character::GetMoveType () const {// return (!StateIsActivated(LEVITATION) ? DataBase->MoveType : DataBase->MoveType | FLY); }
469 return
470 (!StateIsActivated(LEVITATION) ? DataBase->MoveType : DataBase->MoveType|FLY)|
471 (!StateIsActivated(ETHEREAL_MOVING) ? DataBase->MoveType : DataBase->MoveType|ETHEREAL)|
472 (!StateIsActivated(SWIMMING) ? DataBase->MoveType : DataBase->MoveType|WALK|SWIM)|
478 int characterdatabase::*ExpPtr[ATTRIBUTES] = {
479 &characterdatabase::DefaultEndurance,
480 &characterdatabase::DefaultPerception,
481 &characterdatabase::DefaultIntelligence,
482 &characterdatabase::DefaultWisdom,
483 &characterdatabase::DefaultWillPower,
484 &characterdatabase::DefaultCharisma,
485 &characterdatabase::DefaultMana,
486 &characterdatabase::DefaultArmStrength,
487 &characterdatabase::DefaultLegStrength,
488 &characterdatabase::DefaultDexterity,
489 &characterdatabase::DefaultAgility
493 contentscript<item> characterdatabase::*EquipmentDataPtr[EQUIPMENT_DATAS] = {
494 &characterdatabase::Helmet,
495 &characterdatabase::Amulet,
496 &characterdatabase::Cloak,
497 &characterdatabase::BodyArmor,
498 &characterdatabase::Belt,
499 &characterdatabase::RightWielded,
500 &characterdatabase::LeftWielded,
501 &characterdatabase::RightRing,
502 &characterdatabase::LeftRing,
503 &characterdatabase::RightGauntlet,
504 &characterdatabase::LeftGauntlet,
505 &characterdatabase::RightBoot,
506 &characterdatabase::LeftBoot
510 character::character (ccharacter &Char) :
511 entity(Char), id(Char), NP(Char.NP), AP(Char.AP),
512 TemporaryState(Char.TemporaryState&~POLYMORPHED),
513 Team(Char.Team), GoingTo(ERROR_V2), Money(0),
514 AssignedName(Char.AssignedName), Action(0),
515 DataBase(Char.DataBase), MotherEntity(0),
516 PolymorphBackup(0), EquipmentState(0), SquareUnder(0),
517 AllowedWeaponSkillCategories(Char.AllowedWeaponSkillCategories),
518 BodyParts(Char.BodyParts),
519 RegenerationCounter(Char.RegenerationCounter),
520 SquaresUnder(Char.SquaresUnder), LastAcidMsgMin(0),
521 Stamina(Char.Stamina), MaxStamina(Char.MaxStamina),
522 BlocksSinceLastTurn(0), GenerationDanger(Char.GenerationDanger),
523 CommandFlags(Char.CommandFlags), WarnFlags(0),
524 ScienceTalks(Char.ScienceTalks), TrapData(0), CounterToMindWormHatch(0)
526 int c;
527 Flags &= ~C_PLAYER;
528 Flags |= C_INITIALIZING|C_IN_NO_MSG_MODE;
529 Stack = new stack(0, this, HIDDEN);
530 for (c = 0; c < STATES; ++c) TemporaryStateCounter[c] = Char.TemporaryStateCounter[c];
531 if (Team) Team->Add(this);
532 for (c = 0; c < BASE_ATTRIBUTES; ++c) BaseExperience[c] = Char.BaseExperience[c];
533 BodyPartSlot = new bodypartslot[BodyParts];
534 OriginalBodyPartID = new std::list<feuLong>[BodyParts];
535 CWeaponSkill = new cweaponskill[AllowedWeaponSkillCategories];
536 SquareUnder = new square *[SquaresUnder];
537 if (SquaresUnder == 1) *SquareUnder = 0; else memset(SquareUnder, 0, SquaresUnder*sizeof(square *));
538 for (c = 0; c < BodyParts; ++c) {
539 BodyPartSlot[c].SetMaster(this);
540 bodypart *CharBodyPart = Char.GetBodyPart(c);
541 OriginalBodyPartID[c] = Char.OriginalBodyPartID[c];
542 if (CharBodyPart) {
543 bodypart *BodyPart = static_cast<bodypart *>(CharBodyPart->Duplicate());
544 SetBodyPart(c, BodyPart);
545 BodyPart->CalculateEmitation();
548 for (c = 0; c < AllowedWeaponSkillCategories; ++c) CWeaponSkill[c] = Char.CWeaponSkill[c];
549 HomeData = Char.HomeData ? new homedata(*Char.HomeData) : 0;
550 ID = game::CreateNewCharacterID(this);
554 character::character () :
555 entity(HAS_BE), NP(50000), AP(0), TemporaryState(0), Team(0),
556 GoingTo(ERROR_V2), Money(0), Action(0), MotherEntity(0),
557 PolymorphBackup(0), EquipmentState(0), SquareUnder(0),
558 RegenerationCounter(0), HomeData(0), LastAcidMsgMin(0),
559 BlocksSinceLastTurn(0), GenerationDanger(DEFAULT_GENERATION_DANGER),
560 WarnFlags(0), ScienceTalks(0), TrapData(0), CounterToMindWormHatch(0)
562 Stack = new stack(0, this, HIDDEN);
566 character::~character () {
567 if (Action) delete Action;
568 if (Team) Team->Remove(this);
569 delete Stack;
570 for (int c = 0; c < BodyParts; ++c) delete GetBodyPart(c);
571 delete [] BodyPartSlot;
572 delete [] OriginalBodyPartID;
573 delete PolymorphBackup;
574 delete [] SquareUnder;
575 delete [] CWeaponSkill;
576 delete HomeData;
577 deleteList(TrapData);
578 game::RemoveCharacterID(ID);
582 void character::Hunger () {
583 auto bst = GetBurdenState();
584 if (bst == OVER_LOADED || bst == STRESSED) {
585 EditNP(-8);
586 EditExperience(LEG_STRENGTH, 150, 1 << 2);
587 EditExperience(AGILITY, -50, 1 << 2);
588 } else if (bst == BURDENED) {
589 EditNP(-2);
590 EditExperience(LEG_STRENGTH, 75, 1 << 1);
591 EditExperience(AGILITY, -25, 1 << 1);
592 } else if (bst == UNBURDENED) {
593 EditNP(-1);
596 auto hst = GetHungerState();
597 if (hst == STARVING) {
598 EditExperience(ARM_STRENGTH, -75, 1 << 3);
599 EditExperience(LEG_STRENGTH, -75, 1 << 3);
600 } else if (hst == VERY_HUNGRY) {
601 EditExperience(ARM_STRENGTH, -50, 1 << 2);
602 EditExperience(LEG_STRENGTH, -50, 1 << 2);
603 } else if (hst == HUNGRY) {
604 EditExperience(ARM_STRENGTH, -25, 1 << 1);
605 EditExperience(LEG_STRENGTH, -25, 1 << 1);
606 } else if (hst == SATIATED) {
607 EditExperience(AGILITY, -25, 1 << 1);
608 } else if (hst == BLOATED) {
609 EditExperience(AGILITY, -50, 1 << 2);
610 } else if (hst == OVER_FED) {
611 EditExperience(AGILITY, -75, 1 << 3);
614 CheckStarvationDeath(CONST_S("starved to death"));
618 int character::TakeHit (character *Enemy, item *Weapon, bodypart *EnemyBodyPart, v2 HitPos, double Damage,
619 double ToHitValue, int Success, int Type, int GivenDir, truth Critical, truth ForceHit)
621 //FIXME: args
622 if (!game::RunCharEvent("before_take_hit", this, Enemy, Weapon)) return DID_NO_DAMAGE;
624 int Dir = (Type == BITE_ATTACK ? YOURSELF : GivenDir);
625 double DodgeValue = GetDodgeValue();
626 if (!Enemy->IsPlayer() && GetAttackWisdomLimit() != NO_LIMIT) Enemy->EditExperience(WISDOM, 75, 1 << 13);
627 if (!Enemy->CanBeSeenBy(this)) ToHitValue *= 2;
628 if (!CanBeSeenBy(Enemy)) DodgeValue *= 2;
629 if (Enemy->StateIsActivated(CONFUSED)) ToHitValue *= 0.75;
631 switch (Enemy->GetTirednessState()) {
632 case FAINTING:
633 ToHitValue *= 0.50;
634 case EXHAUSTED:
635 ToHitValue *= 0.75;
637 switch (GetTirednessState()) {
638 case FAINTING:
639 DodgeValue *= 0.50;
640 case EXHAUSTED:
641 DodgeValue *= 0.75;
644 if (!ForceHit) {
645 if (!IsRetreating()) SetGoingTo(Enemy->GetPos());
646 else SetGoingTo(GetPos()-((Enemy->GetPos()-GetPos())<<4));
647 if (!Enemy->IsRetreating()) Enemy->SetGoingTo(GetPos());
648 else Enemy->SetGoingTo(Enemy->GetPos()-((GetPos()-Enemy->GetPos())<<4));
651 /* Effectively, the average chance to hit is 100% / (DV/THV + 1). */
652 if (RAND() % int(100+ToHitValue/DodgeValue*(100+Success)) < 100 && !Critical && !ForceHit) {
653 Enemy->AddMissMessage(this);
654 EditExperience(AGILITY, 150, 1 << 7);
655 EditExperience(PERCEPTION, 75, 1 << 7);
656 if (Enemy->CanBeSeenByPlayer())
657 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy->GetName(DEFINITE)+CONST_S(" interrupts you."));
658 else
659 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
660 return HAS_DODGED;
663 int TrueDamage = int(Damage*(100+Success)/100)+(RAND()%3 ? 1 : 0);
664 if (Critical) {
665 TrueDamage += TrueDamage >> 1;
666 ++TrueDamage;
669 int BodyPart = ChooseBodyPartToReceiveHit(ToHitValue, DodgeValue);
670 if (Critical) {
671 if (Type == UNARMED_ATTACK) Enemy->AddPrimitiveHitMessage(this, Enemy->FirstPersonCriticalUnarmedHitVerb(), Enemy->ThirdPersonCriticalUnarmedHitVerb(), BodyPart);
672 else if (Type == WEAPON_ATTACK) Enemy->AddWeaponHitMessage(this, Weapon, BodyPart, true);
673 else if (Type == KICK_ATTACK) Enemy->AddPrimitiveHitMessage(this, Enemy->FirstPersonCriticalKickVerb(), Enemy->ThirdPersonCriticalKickVerb(), BodyPart);
674 else if (Type == BITE_ATTACK) Enemy->AddPrimitiveHitMessage(this, Enemy->FirstPersonCriticalBiteVerb(), Enemy->ThirdPersonCriticalBiteVerb(), BodyPart);
675 } else {
676 if (Type == UNARMED_ATTACK) Enemy->AddPrimitiveHitMessage(this, Enemy->FirstPersonUnarmedHitVerb(), Enemy->ThirdPersonUnarmedHitVerb(), BodyPart);
677 else if (Type == WEAPON_ATTACK) Enemy->AddWeaponHitMessage(this, Weapon, BodyPart, false);
678 else if (Type == KICK_ATTACK) Enemy->AddPrimitiveHitMessage(this, Enemy->FirstPersonKickVerb(), Enemy->ThirdPersonKickVerb(), BodyPart);
679 else if (Type == BITE_ATTACK) Enemy->AddPrimitiveHitMessage(this, Enemy->FirstPersonBiteVerb(), Enemy->ThirdPersonBiteVerb(), BodyPart);
682 if (!Critical && TrueDamage && Enemy->AttackIsBlockable(Type)) {
683 TrueDamage = CheckForBlock(Enemy, Weapon, ToHitValue, TrueDamage, Success, Type);
684 if (!TrueDamage || (Weapon && !Weapon->Exists())) {
685 if (Enemy->CanBeSeenByPlayer())
686 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy->GetName(DEFINITE)+CONST_S(" interrupts you."));
687 else
688 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
689 return HAS_BLOCKED;
693 int WeaponSkillHits = CalculateWeaponSkillHits(Enemy);
694 int DoneDamage = ReceiveBodyPartDamage(Enemy, TrueDamage, PHYSICAL_DAMAGE, BodyPart, Dir, false, Critical, true, Type == BITE_ATTACK && Enemy->BiteCapturesBodyPart());
695 truth Succeeded = (GetBodyPart(BodyPart) && HitEffect(Enemy, Weapon, HitPos, Type, BodyPart, Dir, !DoneDamage, Critical, DoneDamage)) || DoneDamage;
696 if (Succeeded) Enemy->WeaponSkillHit(Weapon, Type, WeaponSkillHits);
698 if (Weapon) {
699 if (Weapon->Exists() && DoneDamage < TrueDamage) Weapon->ReceiveDamage(Enemy, TrueDamage-DoneDamage, PHYSICAL_DAMAGE);
700 if (Weapon->Exists() && DoneDamage && SpillsBlood() && GetBodyPart(BodyPart) &&
701 (GetBodyPart(BodyPart)->IsAlive() || GetBodyPart(BodyPart)->GetMainMaterial()->IsLiquid()))
702 Weapon->SpillFluid(0, CreateBlood(15+RAND()%15));
705 if (Enemy->AttackIsBlockable(Type)) SpecialBodyDefenceEffect(Enemy, EnemyBodyPart, Type);
707 if (!Succeeded) {
708 if (Enemy->CanBeSeenByPlayer())
709 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy->GetName(DEFINITE)+CONST_S(" interrupts you."));
710 else
711 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
713 return DID_NO_DAMAGE;
716 if (CheckDeath(GetNormalDeathMessage(), Enemy, Enemy->IsPlayer() ? FORCE_MSG : 0)) return HAS_DIED;
718 if (Enemy->CanBeSeenByPlayer())
719 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy->GetName(DEFINITE)+CONST_S(" interrupts you."));
720 else
721 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
723 return HAS_HIT;
727 struct svpriorityelement {
728 svpriorityelement (int BodyPart, int StrengthValue) : BodyPart(BodyPart), StrengthValue(StrengthValue) {}
729 bool operator < (const svpriorityelement &AnotherPair) const { return StrengthValue > AnotherPair.StrengthValue; }
730 int BodyPart;
731 int StrengthValue;
735 int character::ChooseBodyPartToReceiveHit (double ToHitValue, double DodgeValue) {
736 if (BodyParts == 1) return 0;
737 std::priority_queue<svpriorityelement> SVQueue;
738 for (int c = 0; c < BodyParts; ++c) {
739 bodypart *BodyPart = GetBodyPart(c);
740 if (BodyPart && (BodyPart->GetHP() != 1 || BodyPart->CanBeSevered(PHYSICAL_DAMAGE)))
741 SVQueue.push(svpriorityelement(c, ModifyBodyPartHitPreference(c, BodyPart->GetStrengthValue()+BodyPart->GetHP())));
743 while (SVQueue.size()) {
744 svpriorityelement E = SVQueue.top();
745 int ToHitPercentage = int(GLOBAL_WEAK_BODYPART_HIT_MODIFIER*ToHitValue*GetBodyPart(E.BodyPart)->GetBodyPartVolume()/(DodgeValue*GetBodyVolume()));
746 ToHitPercentage = ModifyBodyPartToHitChance(E.BodyPart, ToHitPercentage);
747 if (ToHitPercentage < 1) ToHitPercentage = 1;
748 else if (ToHitPercentage > 95) ToHitPercentage = 95;
749 if (ToHitPercentage > RAND()%100) return E.BodyPart;
750 SVQueue.pop();
752 return 0;
756 void character::Be () {
757 if (game::ForceJumpToPlayerBe()) {
758 if (!IsPlayer()) return;
759 game::SetForceJumpToPlayerBe(false);
760 } else {
761 truth ForceBe = HP != MaxHP || AllowSpoil();
762 for (int c = 0; c < BodyParts; ++c) {
763 bodypart *BodyPart = GetBodyPart(c);
764 if (BodyPart && (ForceBe || BodyPart->NeedsBe())) BodyPart->Be();
766 HandleStates();
767 if (!IsEnabled()) return;
768 if (GetTeam() == PLAYER->GetTeam()) {
769 for (int c = 0; c < AllowedWeaponSkillCategories; ++c) {
770 if (CWeaponSkill[c].Tick() && IsPlayer()) CWeaponSkill[c].AddLevelDownMessage(c);
772 SWeaponSkillTick();
774 if (IsPlayer()) {
775 if (GetHungerState() == STARVING && !(RAND()%50)) LoseConsciousness(250+RAND_N(250), true);
776 if (!Action || Action->AllowFoodConsumption()) Hunger();
778 if (Stamina != MaxStamina) RegenerateStamina();
779 if (HP != MaxHP || StateIsActivated(REGENERATION)) Regenerate();
780 if (Action && AP >= 1000) ActionAutoTermination();
781 if (Action && AP >= 1000) {
782 Action->Handle();
783 if (!IsEnabled()) return;
784 } else {
785 EditAP(GetStateAPGain(100));
788 if (AP >= 1000) {
789 SpecialTurnHandler();
790 BlocksSinceLastTurn = 0;
791 if (IsPlayer()) {
792 static int Timer = 0;
794 if (ivanconfig::GetAutoSaveInterval() && !GetAction() && ++Timer >= ivanconfig::GetAutoSaveInterval()) {
795 //fprintf(stderr, "autosaving..."); fflush(stderr);
796 game::Save(game::GetAutoSaveFileName());
797 //fprintf(stderr, "done\n"); fflush(stderr);
798 Timer = 0;
800 game::CalculateNextDanger();
801 if (!StateIsActivated(POLYMORPHED)) game::UpdatePlayerAttributeAverage();
802 if (!game::IsInWilderness()) Search(GetAttribute(PERCEPTION));
803 if (!Action) {
804 GetPlayerCommand();
805 } else {
806 if (Action->ShowEnvironment()) {
807 static int Counter = 0;
808 if (++Counter == 10) {
809 game::DrawEverything();
810 Counter = 0;
813 msgsystem::ThyMessagesAreNowOld();
814 if (Action->IsVoluntary() && READ_KEY()) Action->Terminate(false);
816 } else {
817 if (!Action && !game::IsInWilderness()) GetAICommand();
823 void character::Move (v2 MoveTo, truth TeleportMove, truth Run) {
824 if (!IsEnabled()) return;
825 /* Test whether the player is stuck to something */
826 if (!TeleportMove && !TryToUnStickTraps(MoveTo-GetPos())) return;
827 if (Run && !IsPlayer() && TorsoIsAlive() &&
828 (Stamina <= 10000/Max(GetAttribute(LEG_STRENGTH), 1) || (!StateIsActivated(PANIC) && Stamina < MaxStamina>>2))) {
829 Run = false;
831 RemoveTraps();
832 if (GetBurdenState() != OVER_LOADED || TeleportMove) {
833 lsquare *OldSquareUnder[MAX_SQUARES_UNDER];
835 if (!game::IsInWilderness()) {
836 if (IsPlayer()) {
837 // idiotic code!
838 area *ca = GetSquareUnder()->GetArea();
840 for (int f = 0; f < MDIR_STAND; ++f) {
841 v2 np = GetPos()+game::GetMoveVector(f);
843 if (np.X >= 0 && np.Y >= 0 && np.X < ca->GetXSize() && np.Y < ca->GetYSize()) {
844 lsquare *sq = static_cast<lsquare *>(ca->GetSquare(np.X, np.Y));
846 sq->SetGoSeen(true);
851 for (int c = 0; c < GetSquaresUnder(); ++c) OldSquareUnder[c] = GetLSquareUnder(c);
853 Remove();
854 PutTo(MoveTo);
855 if (!TeleportMove) {
856 /* Multitiled creatures should behave differently, maybe? */
857 if (Run) {
858 int ED = GetSquareUnder()->GetEntryDifficulty(), Base = 10000;
860 EditAP(-GetMoveAPRequirement(ED)>>1);
861 EditNP(-24*ED);
862 EditExperience(AGILITY, 125, ED<<7);
863 if (IsPlayer()) {
864 auto hst = GetHungerState();
865 if (hst == SATIATED) Base = 11000;
866 else if (hst == BLOATED) Base = 12500;
867 else if (hst == OVER_FED) Base = 15000;
869 EditStamina(-Base/Max(GetAttribute(LEG_STRENGTH), 1), true);
870 } else {
871 int ED = GetSquareUnder()->GetEntryDifficulty();
873 EditAP(-GetMoveAPRequirement(ED));
874 EditNP(-12*ED);
875 EditExperience(AGILITY, 75, ED<<7);
878 if (IsPlayer()) ShowNewPosInfo();
879 if (IsPlayer() && !game::IsInWilderness()) GetStackUnder()->SetSteppedOn(true);
880 if (!game::IsInWilderness()) SignalStepFrom(OldSquareUnder);
881 } else {
882 if (IsPlayer()) {
883 cchar *CrawlVerb = StateIsActivated(LEVITATION) ? "float" : "crawl";
885 ADD_MESSAGE("You try very hard to %s forward. But your load is too heavy.", CrawlVerb);
887 EditAP(-1000);
892 void character::GetAICommand () {
893 //if (strcmp(GetTypeID(), "siren") == 0) fprintf(stderr, "siren::GetAICommand; enabled=%d\n", IsEnabled());
894 //fprintf(stderr, "char::GetAICommand; class=<%s>\n", GetTypeID());
895 SeekLeader(GetLeader());
896 if (FollowLeader(GetLeader())) return;
897 if (CheckForEnemies(true, true, true)) return;
898 if (CheckForUsefulItemsOnGround()) return;
899 if (CheckForDoors()) return;
900 if (CheckSadism()) return;
901 if (MoveRandomly()) return;
902 EditAP(-1000);
906 truth character::MoveTowardsTarget (truth Run) {
907 v2 MoveTo[3];
908 v2 TPos;
909 v2 Pos = GetPos();
911 if (!Route.empty()) {
912 TPos = Route.back();
913 Route.pop_back();
914 } else TPos = GoingTo;
916 MoveTo[0] = v2(0, 0);
917 MoveTo[1] = v2(0, 0);
918 MoveTo[2] = v2(0, 0);
920 if (TPos.X < Pos.X) {
921 if (TPos.Y < Pos.Y) {
922 MoveTo[0] = v2(-1, -1);
923 MoveTo[1] = v2(-1, 0);
924 MoveTo[2] = v2( 0, -1);
925 } else if (TPos.Y == Pos.Y) {
926 MoveTo[0] = v2(-1, 0);
927 MoveTo[1] = v2(-1, -1);
928 MoveTo[2] = v2(-1, 1);
929 } else if (TPos.Y > Pos.Y) {
930 MoveTo[0] = v2(-1, 1);
931 MoveTo[1] = v2(-1, 0);
932 MoveTo[2] = v2( 0, 1);
934 } else if (TPos.X == Pos.X) {
935 if (TPos.Y < Pos.Y) {
936 MoveTo[0] = v2( 0, -1);
937 MoveTo[1] = v2(-1, -1);
938 MoveTo[2] = v2( 1, -1);
939 } else if (TPos.Y == Pos.Y) {
940 TerminateGoingTo();
941 return false;
942 } else if (TPos.Y > Pos.Y) {
943 MoveTo[0] = v2( 0, 1);
944 MoveTo[1] = v2(-1, 1);
945 MoveTo[2] = v2( 1, 1);
947 } else if (TPos.X > Pos.X) {
948 if (TPos.Y < Pos.Y) {
949 MoveTo[0] = v2(1, -1);
950 MoveTo[1] = v2(1, 0);
951 MoveTo[2] = v2(0, -1);
952 } else if (TPos.Y == Pos.Y) {
953 MoveTo[0] = v2(1, 0);
954 MoveTo[1] = v2(1, -1);
955 MoveTo[2] = v2(1, 1);
956 } else if (TPos.Y > Pos.Y) {
957 MoveTo[0] = v2(1, 1);
958 MoveTo[1] = v2(1, 0);
959 MoveTo[2] = v2(0, 1);
963 v2 ModifiedMoveTo = ApplyStateModification(MoveTo[0]);
965 if (TryMove(ModifiedMoveTo, true, Run)) return true;
967 int L = (Pos-TPos).GetManhattanLength();
969 if (RAND()&1) Swap(MoveTo[1], MoveTo[2]);
971 if (Pos.IsAdjacent(TPos)) {
972 TerminateGoingTo();
973 return false;
976 if ((Pos+MoveTo[1]-TPos).GetManhattanLength() <= L && TryMove(ApplyStateModification(MoveTo[1]), true, Run)) return true;
977 if ((Pos+MoveTo[2]-TPos).GetManhattanLength() <= L && TryMove(ApplyStateModification(MoveTo[2]), true, Run)) return true;
978 Illegal.insert(Pos+ModifiedMoveTo);
979 if (CreateRoute()) return true;
980 return false;
984 int character::CalculateNewSquaresUnder (lsquare **NewSquare, v2 Pos) const {
985 if (GetLevel()->IsValidPos(Pos)) {
986 *NewSquare = GetNearLSquare(Pos);
987 return 1;
989 return 0;
993 truth character::TryMove (v2 MoveVector, truth Important, truth Run) {
994 lsquare *MoveToSquare[MAX_SQUARES_UNDER];
995 character *Pet[MAX_SQUARES_UNDER];
996 character *Neutral[MAX_SQUARES_UNDER];
997 character *Hostile[MAX_SQUARES_UNDER];
998 v2 PetPos[MAX_SQUARES_UNDER];
999 v2 NeutralPos[MAX_SQUARES_UNDER];
1000 v2 HostilePos[MAX_SQUARES_UNDER];
1001 v2 MoveTo = GetPos()+MoveVector;
1002 int Direction = game::GetDirectionForVector(MoveVector);
1003 if (Direction == DIR_ERROR) ABORT("Direction fault.");
1004 if (!game::IsInWilderness()) {
1005 int Squares = CalculateNewSquaresUnder(MoveToSquare, MoveTo);
1006 if (Squares) {
1007 int Pets = 0;
1008 int Neutrals = 0;
1009 int Hostiles = 0;
1010 for (int c = 0; c < Squares; ++c) {
1011 character* Char = MoveToSquare[c]->GetCharacter();
1012 if (Char && Char != this) {
1013 v2 Pos = MoveToSquare[c]->GetPos();
1014 if (IsAlly(Char)) {
1015 Pet[Pets] = Char;
1016 PetPos[Pets++] = Pos;
1017 } else if (Char->GetRelation(this) != HOSTILE) {
1018 Neutral[Neutrals] = Char;
1019 NeutralPos[Neutrals++] = Pos;
1020 } else {
1021 Hostile[Hostiles] = Char;
1022 HostilePos[Hostiles++] = Pos;
1027 if (Hostiles == 1) return Hit(Hostile[0], HostilePos[0], Direction);
1028 if (Hostiles) {
1029 int Index = RAND() % Hostiles;
1030 return Hit(Hostile[Index], HostilePos[Index], Direction);
1033 if (Neutrals == 1) {
1034 if (!IsPlayer() && !Pets && Important && CanMoveOn(MoveToSquare[0]))
1035 return HandleCharacterBlockingTheWay(Neutral[0], NeutralPos[0], Direction);
1036 else
1037 return IsPlayer() && Hit(Neutral[0], NeutralPos[0], Direction);
1038 } else if (Neutrals) {
1039 if (IsPlayer()) {
1040 int Index = RAND() % Neutrals;
1041 return Hit(Neutral[Index], NeutralPos[Index], Direction);
1043 return false;
1046 if (!IsPlayer()) {
1047 for (int c = 0; c < Squares; ++c) if (MoveToSquare[c]->IsScary(this)) return false;
1050 if (Pets == 1) {
1051 if (IsPlayer() && !ivanconfig::GetBeNice() && Pet[0]->IsMasochist() && HasSadistAttackMode() &&
1052 game::TruthQuestion("Do you want to punish " + Pet[0]->GetObjectPronoun() + "?"))
1053 return Hit(Pet[0], PetPos[0], Direction, SADIST_HIT);
1054 else
1055 return (Important && (CanMoveOn(MoveToSquare[0]) ||
1056 (IsPlayer() && game::GoThroughWallsCheatIsActive())) && Displace(Pet[0]));
1057 } else if (Pets) return false;
1059 if ((CanMove() && CanMoveOn(MoveToSquare[0])) || ((game::GoThroughWallsCheatIsActive() && IsPlayer()))) {
1060 Move(MoveTo, false, Run);
1061 if (IsEnabled() && GetPos() == GoingTo) TerminateGoingTo();
1062 return true;
1063 } else {
1064 for (int c = 0; c < Squares; ++c) {
1065 olterrain *Terrain = MoveToSquare[c]->GetOLTerrain();
1066 if (Terrain && Terrain->CanBeOpened()) {
1067 if (CanOpen()) {
1068 if (Terrain->IsLocked()) {
1069 if (IsPlayer()) {
1070 /*k8*/
1071 if (ivanconfig::GetKickDownDoors()) {
1072 if (game::TruthQuestion(CONST_S("Locked! Do you want to kick ")+Terrain->GetName(DEFINITE)+"?", true, game::GetMoveCommandKeyBetweenPoints(PLAYER->GetPos(), MoveToSquare[0]->GetPos()))) {
1073 Kick(MoveToSquare[c], Direction);
1074 return true;
1075 } else {
1076 return false;
1079 /*k8*/
1080 ADD_MESSAGE("The %s is locked.", Terrain->GetNameSingular().CStr()); /* not sure if this is better than "the door is locked", but I guess it _might_ be slighltly better */
1081 return false;
1082 } else if (Important && CheckKick()) {
1083 room *Room = MoveToSquare[c]->GetRoom();
1084 if (!Room || Room->AllowKick(this, MoveToSquare[c])) {
1085 int HP = Terrain->GetHP();
1086 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s kicks %s.", CHAR_NAME(DEFINITE), Terrain->CHAR_NAME(DEFINITE));
1087 Kick(MoveToSquare[c], Direction);
1088 olterrain *NewTerrain = MoveToSquare[c]->GetOLTerrain();
1089 if (NewTerrain == Terrain && Terrain->GetHP() == HP) { // BUG!
1090 Illegal.insert(MoveTo);
1091 CreateRoute();
1093 return true;
1096 } else { /* if (Terrain->IsLocked()) */
1097 /*if(!IsPlayer() || game::TruthQuestion(CONST_S("Do you want to open ")+Terrain->GetName(DEFINITE)+"?", false, game::GetMoveCommandKeyBetweenPoints(PLAYER->GetPos(), MoveToSquare[0]->GetPos()))) return MoveToSquare[c]->Open(this);*/
1098 /* Non-players always try to open it */
1099 if (!IsPlayer()) return MoveToSquare[c]->Open(this);
1100 if (game::TruthQuestion(CONST_S("Do you want to open ")+Terrain->GetName(DEFINITE)+"?", false, game::GetMoveCommandKeyBetweenPoints(PLAYER->GetPos(), MoveToSquare[0]->GetPos()))) {
1101 return MoveToSquare[c]->Open(this);
1103 return false;
1104 } /* if (Terrain->IsLocked()) */
1105 } else { /* if (CanOpen()) */
1106 if (IsPlayer()) {
1107 ADD_MESSAGE("This monster type cannot open doors.");
1108 return false;
1110 if (Important) {
1111 Illegal.insert(MoveTo);
1112 return CreateRoute();
1114 } /* if (CanOpen()) */
1115 } /* if (Terrain && Terrain->CanBeOpened()) */
1116 } /* for */
1117 } /* if */
1118 return false;
1119 } else {
1120 if (IsPlayer() && !IsStuck() && GetLevel()->IsOnGround() && game::TruthQuestion(CONST_S("Do you want to leave ")+game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex())+"?")) {
1121 if (HasPetrussNut() && !HasGoldenEagleShirt()) {
1122 game::TextScreen(CONST_S("An undead and sinister voice greets you as you leave the city behind:\n\n\"MoRtAl! ThOu HaSt SlAuGtHeReD pEtRuS aNd PlEaSeD mE!\nfRoM tHiS dAy On, ThOu ArT tHe DeArEsT sErVaNt Of AlL eViL!\"\n\nYou are victorious!"));
1123 game::GetCurrentArea()->SendNewDrawRequest();
1124 game::DrawEverything();
1125 ShowAdventureInfo();
1126 festring Msg = CONST_S("killed Petrus and became the Avatar of Chaos");
1127 PLAYER->AddScoreEntry(Msg, 3, false);
1128 game::End(Msg);
1129 return true;
1131 if (game::TryTravel(WORLD_MAP, WORLD_MAP, game::GetCurrentDungeonIndex())) return true;
1133 return false;
1135 } else {
1136 /** No multitile support */
1137 if (CanMove() && GetArea()->IsValidPos(MoveTo) && (CanMoveOn(GetNearWSquare(MoveTo)) || game::GoThroughWallsCheatIsActive())) {
1138 if (!game::GoThroughWallsCheatIsActive()) {
1139 charactervector &V = game::GetWorldMap()->GetPlayerGroup();
1140 truth Discard = false;
1141 for (uInt c = 0; c < V.size(); ++c) {
1142 if (!V[c]->CanMoveOn(GetNearWSquare(MoveTo))) {
1143 if (!Discard) {
1144 ADD_MESSAGE("One or more of your team members cannot cross this terrain.");
1145 if (!game::TruthQuestion("Discard them?")) return false;
1146 Discard = true;
1148 if (Discard) delete V[c];
1149 V.erase(V.begin() + c--);
1153 Move(MoveTo, false);
1154 return true;
1155 } else {
1156 return false;
1162 void character::CreateCorpse (lsquare *Square) {
1163 if (!BodyPartsDisappearWhenSevered() && !game::AllBodyPartsVanish()) {
1164 corpse *Corpse = corpse::Spawn(0, NO_MATERIALS);
1165 Corpse->SetDeceased(this);
1166 Square->AddItem(Corpse);
1167 Disable();
1168 } else {
1169 SendToHell();
1174 void character::Die (character *Killer, cfestring &Msg, feuLong DeathFlags) {
1175 /* Note: This function musn't delete any objects, since one of these may be
1176 the one currently processed by pool::Be()! */
1177 if (!IsEnabled()) return;
1179 if (!game::RunCharEvent("before_die", this, Killer)) return;
1181 RemoveTraps();
1182 if (IsPlayer()) {
1183 ADD_MESSAGE("You die.");
1184 game::DrawEverything();
1185 if (game::TruthQuestion(CONST_S("Do you want to save screenshot?"), REQUIRES_ANSWER)) {
1186 festring dir;
1187 #ifdef LOCAL_SAVES
1188 dir << ivanconfig::GetMyDir() << "/save";
1189 outputfile::makeDir(dir.CStr());
1190 #else
1191 dir << getenv("HOME") << "/.ivan-save";
1192 outputfile::makeDir(dir.CStr());
1193 #endif
1194 dir << "/deathshots";
1195 outputfile::makeDir(dir.CStr());
1196 festring timestr;
1197 time_t t = time(NULL);
1198 struct tm *ts = localtime(&t);
1199 if (ts) {
1200 timestr << (int)(ts->tm_year%100);
1201 int t = ts->tm_mon+1;
1202 if (t < 10) timestr << '0'; timestr << t;
1203 t = ts->tm_mday; if (t < 10) timestr << '0'; timestr << t;
1204 timestr << '_';
1205 t = ts->tm_hour; if (t < 10) timestr << '0'; timestr << t;
1206 t = ts->tm_min; if (t < 10) timestr << '0'; timestr << t;
1207 t = ts->tm_sec; if (t < 10) timestr << '0'; timestr << t;
1208 } else {
1209 timestr = "heh";
1211 festring ext = ".png";
1212 festring fname = dir+"/deathshot_"+timestr;
1213 if (inputfile::fileExists(fname+ext)) {
1214 for (int f = 0; f < 1000; f++) {
1215 char buf[16];
1216 sprintf(buf, "%03d", f);
1217 festring fn = fname+buf;
1218 if (!inputfile::fileExists(fn+ext)) {
1219 fname = fn;
1220 break;
1224 fname << ext;
1225 //fprintf(stderr, "deathshot: %s\n", fname.CStr());
1226 DOUBLE_BUFFER->SavePNG(fname);
1228 if (game::WizardModeIsActive()) {
1229 game::DrawEverything();
1230 if (!game::TruthQuestion(CONST_S("Do you want to do this, cheater?"), REQUIRES_ANSWER)) {
1231 RestoreBodyParts();
1232 ResetSpoiling();
1233 RestoreHP();
1234 RestoreStamina();
1235 ResetStates();
1236 SetNP(SATIATED_LEVEL);
1237 SendNewDrawRequest();
1238 return;
1241 } else if (CanBeSeenByPlayer() && !(DeathFlags&DISALLOW_MSG)) {
1242 ProcessAndAddMessage(GetDeathMessage());
1243 } else if (DeathFlags&FORCE_MSG) {
1244 ADD_MESSAGE("You sense the death of something.");
1247 if (!(DeathFlags&FORBID_REINCARNATION)) {
1248 if (StateIsActivated(LIFE_SAVED) && CanMoveOn(!game::IsInWilderness() ? GetSquareUnder() : PLAYER->GetSquareUnder())) {
1249 SaveLife();
1250 return;
1252 if (SpecialSaveLife()) return;
1253 } else if (StateIsActivated(LIFE_SAVED)) {
1254 RemoveLifeSavers();
1257 Flags |= C_IN_NO_MSG_MODE;
1258 character *Ghost = 0;
1259 if (IsPlayer()) {
1260 game::RemoveSaves();
1261 if (!game::IsInWilderness()) {
1262 Ghost = game::CreateGhost();
1263 Ghost->Disable();
1267 square *SquareUnder[MAX_SQUARES_UNDER];
1268 memset(SquareUnder, 0, sizeof(SquareUnder));
1269 Disable();
1270 if (IsPlayer() || !game::IsInWilderness()) {
1271 for (int c = 0; c < SquaresUnder; ++c) SquareUnder[c] = GetSquareUnder(c);
1272 Remove();
1273 } else {
1274 charactervector& V = game::GetWorldMap()->GetPlayerGroup();
1275 V.erase(std::find(V.begin(), V.end(), this));
1277 //lsquare **LSquareUnder = reinterpret_cast<lsquare **>(SquareUnder); /* warning; wtf? */
1278 lsquare *LSquareUnder[MAX_SQUARES_UNDER];
1279 memmove(LSquareUnder, SquareUnder, sizeof(SquareUnder));
1281 if (!game::IsInWilderness()) {
1282 if (!StateIsActivated(POLYMORPHED)) {
1283 if (!IsPlayer() && !IsTemporary() && !Msg.IsEmpty()) game::SignalDeath(this, Killer, Msg);
1284 if (!(DeathFlags&DISALLOW_CORPSE)) CreateCorpse(LSquareUnder[0]); else SendToHell();
1285 } else {
1286 if (!IsPlayer() && !IsTemporary() && !Msg.IsEmpty()) game::SignalDeath(GetPolymorphBackup(), Killer, Msg);
1287 GetPolymorphBackup()->CreateCorpse(LSquareUnder[0]);
1288 GetPolymorphBackup()->Flags &= ~C_POLYMORPHED;
1289 SetPolymorphBackup(0);
1290 SendToHell();
1292 } else {
1293 if (!IsPlayer() && !IsTemporary() && !Msg.IsEmpty()) game::SignalDeath(this, Killer, Msg);
1294 SendToHell();
1297 if (IsPlayer()) {
1298 if (!game::IsInWilderness()) {
1299 for (int c = 0; c < GetSquaresUnder(); ++c) LSquareUnder[c]->SetTemporaryEmitation(GetEmitation());
1301 ShowAdventureInfo();
1302 if (!game::IsInWilderness()) {
1303 for(int c = 0; c < GetSquaresUnder(); ++c) LSquareUnder[c]->SetTemporaryEmitation(0);
1307 if (!game::IsInWilderness()) {
1308 if (GetSquaresUnder() == 1) {
1309 stack *StackUnder = LSquareUnder[0]->GetStack();
1310 GetStack()->MoveItemsTo(StackUnder);
1311 doforbodypartswithparam<stack*>()(this, &bodypart::DropEquipment, StackUnder);
1312 } else {
1313 while (GetStack()->GetItems()) GetStack()->GetBottom()->MoveTo(LSquareUnder[RAND_N(GetSquaresUnder())]->GetStack());
1314 for (int c = 0; c < BodyParts; ++c) {
1315 bodypart *BodyPart = GetBodyPart(c);
1316 if (BodyPart) BodyPart->DropEquipment(LSquareUnder[RAND_N(GetSquaresUnder())]->GetStack());
1321 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(0);
1323 Flags &= ~C_IN_NO_MSG_MODE;
1325 if (IsPlayer()) {
1326 AddScoreEntry(Msg);
1327 if (!game::IsInWilderness()) {
1328 Ghost->PutTo(LSquareUnder[0]->GetPos());
1329 Ghost->Enable();
1330 game::CreateBone();
1332 game::TextScreen(CONST_S("Unfortunately you died."), ZERO_V2, WHITE, true, true, &game::ShowDeathSmiley);
1333 game::End(Msg);
1338 void character::AddMissMessage (ccharacter *Enemy) const {
1339 festring Msg;
1340 if (Enemy->IsPlayer()) Msg = GetDescription(DEFINITE)+" misses you!";
1341 else if (IsPlayer()) Msg = CONST_S("You miss ")+Enemy->GetDescription(DEFINITE)+'!';
1342 else if (CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer()) Msg = GetDescription(DEFINITE)+" misses "+Enemy->GetDescription(DEFINITE)+'!';
1343 else return;
1344 ADD_MESSAGE("%s", Msg.CStr());
1348 void character::AddBlockMessage (ccharacter *Enemy, citem *Blocker, cfestring &HitNoun, truth Partial) const {
1349 festring Msg;
1350 festring BlockVerb = (Partial ? " to partially block the " : " to block the ")+HitNoun;
1351 if (IsPlayer()) {
1352 Msg << "You manage" << BlockVerb << " with your " << Blocker->GetName(UNARTICLED) << '!';
1353 } else if (Enemy->IsPlayer() || Enemy->CanBeSeenByPlayer()) {
1354 if (CanBeSeenByPlayer())
1355 Msg << GetName(DEFINITE) << " manages" << BlockVerb << " with " << GetPossessivePronoun() << ' ' << Blocker->GetName(UNARTICLED) << '!';
1356 else
1357 Msg << "Something manages" << BlockVerb << " with something!";
1358 } else {
1359 return;
1361 ADD_MESSAGE("%s", Msg.CStr());
1365 void character::AddPrimitiveHitMessage (ccharacter *Enemy, cfestring &FirstPersonHitVerb,
1366 cfestring &ThirdPersonHitVerb, int BodyPart) const
1368 festring Msg;
1369 festring BodyPartDescription;
1370 if (BodyPart && (Enemy->CanBeSeenByPlayer() || Enemy->IsPlayer()))
1371 BodyPartDescription << " in the " << Enemy->GetBodyPartName(BodyPart);
1372 if (Enemy->IsPlayer())
1373 Msg << GetDescription(DEFINITE) << ' ' << ThirdPersonHitVerb << " you" << BodyPartDescription << '!';
1374 else if (IsPlayer())
1375 Msg << "You " << FirstPersonHitVerb << ' ' << Enemy->GetDescription(DEFINITE) << BodyPartDescription << '!';
1376 else if (CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer())
1377 Msg << GetDescription(DEFINITE) << ' ' << ThirdPersonHitVerb << ' ' << Enemy->GetDescription(DEFINITE) + BodyPartDescription << '!';
1378 else
1379 return;
1380 ADD_MESSAGE("%s", Msg.CStr());
1384 cchar *const HitVerb[] = { "strike", "slash", "stab" };
1385 cchar *const HitVerb3rdPersonEnd[] = { "s", "es", "s" };
1388 void character::AddWeaponHitMessage (ccharacter *Enemy, citem *Weapon, int BodyPart, truth Critical) const {
1389 festring Msg;
1390 festring BodyPartDescription;
1392 if (BodyPart && (Enemy->CanBeSeenByPlayer() || Enemy->IsPlayer()))
1393 BodyPartDescription << " in the " << Enemy->GetBodyPartName(BodyPart);
1395 int FittingTypes = 0;
1396 int DamageFlags = Weapon->GetDamageFlags();
1397 int DamageType = 0;
1399 for (int c = 0; c < DAMAGE_TYPES; ++c) {
1400 if (1 << c & DamageFlags) {
1401 if (!FittingTypes || !RAND_N(FittingTypes+1)) DamageType = c;
1402 ++FittingTypes;
1406 if (!FittingTypes) ABORT("No damage flags specified for %s!", Weapon->CHAR_NAME(UNARTICLED));
1408 festring NewHitVerb = Critical ? " critically " : " ";
1409 NewHitVerb << HitVerb[DamageType];
1410 cchar *const E = HitVerb3rdPersonEnd[DamageType];
1412 if (Enemy->IsPlayer()) {
1413 Msg << GetDescription(DEFINITE) << NewHitVerb << E << " you" << BodyPartDescription;
1414 if (CanBeSeenByPlayer()) Msg << " with " << GetPossessivePronoun() << ' ' << Weapon->GetName(UNARTICLED);
1415 Msg << '!';
1416 } else if (IsPlayer()) {
1417 Msg << "You" << NewHitVerb << ' ' << Enemy->GetDescription(DEFINITE) << BodyPartDescription << '!';
1418 } else if(CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer()) {
1419 Msg << GetDescription(DEFINITE) << NewHitVerb << E << ' ' << Enemy->GetDescription(DEFINITE) << BodyPartDescription;
1420 if (CanBeSeenByPlayer()) Msg << " with " << GetPossessivePronoun() << ' ' << Weapon->GetName(UNARTICLED);
1421 Msg << '!';
1422 } else {
1423 return;
1425 ADD_MESSAGE("%s", Msg.CStr());
1429 truth character::GeneralHasItem (ItemCheckerCB chk, truth checkContainers) const {
1430 // inventory
1431 if (GetStack()->GeneralHasItem(this, chk, checkContainers)) return true;
1432 // equipments
1433 for (int c = 0; c < GetEquipments(); ++c) {
1434 item *it = GetEquipment(c);
1435 if (!it) continue;
1436 if (chk(it)) return true;
1437 if (it->IsOpenable(this)) {
1438 stack *cst = it->GetContained();
1439 if (cst) {
1440 if (cst->GeneralHasItem(this, chk, checkContainers)) return true;
1444 return false;
1448 static truth isEncryptedScroll (item *i) { return i->IsEncryptedScroll(); }
1449 truth character::HasEncryptedScroll () const {
1450 return GeneralHasItem(::isEncryptedScroll, false); // not in containers
1451 //return combineequipmentpredicates()(this, &item::IsEncryptedScroll, 1);
1455 static truth isElpuriHead (item *i) { return i->IsHeadOfElpuri(); }
1456 truth character::HasHeadOfElpuri () const {
1457 return GeneralHasItem(::isElpuriHead, true); // Petrus can see into containers
1458 //return combineequipmentpredicates()(this, &item::IsHeadOfElpuri, 1);
1462 static truth isPetrussNut (item *i) { return i->IsPetrussNut(); }
1463 truth character::HasPetrussNut () const {
1464 return GeneralHasItem(::isPetrussNut, true); // gods can see into containers
1465 //return combineequipmentpredicates()(this, &item::IsPetrussNut, 1);
1469 static truth isGoldenEagleShirt (item *i) { return i->IsGoldenEagleShirt(); }
1470 truth character::HasGoldenEagleShirt () const {
1471 return GeneralHasItem(::isGoldenEagleShirt, true); // Petrus can see into containers
1472 //return combineequipmentpredicates()(this, &item::IsGoldenEagleShirt, 1);
1476 static truth isShadowVeil (item *i) { return i->IsShadowVeil(); }
1477 truth character::HasShadowVeil () const {
1478 return GeneralHasItem(::isShadowVeil, false); // not in containers
1479 //return combineequipmentpredicates()(this, &item::IsShadowVeil, 1);
1483 static truth isLostRubyFlamingSword (item *i) { return i->IsLostRubyFlamingSword(); }
1484 truth character::HasLostRubyFlamingSword () const {
1485 return GeneralHasItem(::isLostRubyFlamingSword, true); // it shines even through a container
1486 //return combineequipmentpredicates()(this, &item::IsLostRubyFlamingSword, 1);
1490 truth character::HasOmmelBlood () const {
1491 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) {
1492 if (i->IsKleinBottle() && i->GetSecondaryMaterial() && i->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD) return true;
1494 for (int c = 0; c < GetEquipments(); ++c) {
1495 item *Item = GetEquipment(c);
1496 if (Item && Item->IsKleinBottle() && Item->GetSecondaryMaterial() && Item->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD) return true;
1498 return false; //combineequipmentpredicates()(this, &item::IsKleinBottle, 1);
1502 truth character::HasCurdledBlood () const {
1503 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) {
1504 if (i->IsKleinBottle() && i->GetSecondaryMaterial() && i->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD) return true;
1506 for (int c = 0; c < GetEquipments(); ++c) {
1507 item *Item = GetEquipment(c);
1508 if (Item && Item->IsKleinBottle() && Item->GetSecondaryMaterial() && Item->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD) return true;
1510 return false; //combineequipmentpredicates()(this, &item::IsKleinBottle, 1);
1514 truth character::CurdleOmmelBlood () const {
1515 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) {
1516 if (i->IsKleinBottle() && i->GetSecondaryMaterial() && i->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD) {
1517 i->ChangeSecondaryMaterial(MAKE_MATERIAL(CURDLED_OMMEL_BLOOD));
1518 return true;
1522 for (int c = 0; c < GetEquipments(); ++c) {
1523 item *Item = GetEquipment(c);
1525 if (Item && Item->IsKleinBottle() && Item->GetSecondaryMaterial() && Item->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD) {
1526 Item->ChangeSecondaryMaterial(MAKE_MATERIAL(CURDLED_OMMEL_BLOOD));
1527 return true;
1530 return false; //combineequipmentpredicates()(this, &item::IsKleinBottle, 1);
1534 truth character::RemoveCurdledOmmelBlood () {
1535 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) {
1536 if (i->IsKleinBottle() && i->GetSecondaryMaterial() && i->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD) {
1537 (*i)->RemoveFromSlot();
1538 (*i)->SendToHell();
1539 return true;
1542 for (int c = 0; c < GetEquipments(); ++c) {
1543 item *Item = GetEquipment(c);
1544 if (Item && Item->IsKleinBottle() && Item->GetSecondaryMaterial() && Item->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD) {
1545 Item->RemoveFromSlot();
1546 Item->SendToHell();
1547 return true;
1550 return false;
1554 int character::GeneralRemoveItem (ItemCheckerCB chk, truth allItems, truth checkContainers) {
1555 // inventory
1556 int count = GetStack()->GeneralRemoveItem(this, chk, allItems, checkContainers);
1557 // equipments
1558 if (count == 0 || allItems) {
1559 for (int c = 0; c < GetEquipments(); ++c) {
1560 item *it = GetEquipment(c);
1561 if (!it) continue;
1562 if (chk(it)) {
1563 it->RemoveFromSlot();
1564 it->SendToHell();
1565 ++count;
1566 if (!allItems) break;
1567 } else if (it->IsOpenable(this)) {
1568 stack *cst = it->GetContained();
1569 if (cst) {
1570 int rcc = cst->GeneralRemoveItem(this, chk, allItems, checkContainers);
1571 if (rcc) {
1572 count += rcc;
1573 if (!allItems) break;
1579 return count;
1583 //static truth isEncryptedScroll (item *i) { return i->IsEncryptedScroll(); }
1584 truth character::RemoveEncryptedScroll () { return GeneralRemoveItem(::isEncryptedScroll, false, true) != 0; }
1587 static truth isMondedrPass (item *i) { return i->IsMondedrPass(); }
1588 truth character::RemoveMondedrPass () { return GeneralRemoveItem(::isMondedrPass, false, false) != 0; }
1591 static truth isRingOfThieves (item *i) { return i->IsRingOfThieves(); }
1592 truth character::RemoveRingOfThieves () { return GeneralRemoveItem(::isRingOfThieves, false, true) != 0; }
1595 truth character::RemoveShadowVeil () { return (GeneralRemoveItem(::isShadowVeil, false, false) != 0); }
1597 truth character::RemoveHeadOfElpuri () { return (GeneralRemoveItem(::isElpuriHead, false, false) != 0); }
1600 truth character::ReadItem (item *ToBeRead) {
1601 if (!ToBeRead->CanBeRead(this)) {
1602 if (IsPlayer()) ADD_MESSAGE("You can't read this.");
1603 return false;
1605 if (!GetLSquareUnder()->IsDark() || game::GetSeeWholeMapCheatMode()) {
1606 if (StateIsActivated(CONFUSED) && !(RAND()&7)) {
1607 if (!ToBeRead->IsDestroyable(this)) {
1608 ADD_MESSAGE("You read some words of %s and understand exactly nothing.", ToBeRead->CHAR_NAME(DEFINITE));
1609 } else {
1610 ADD_MESSAGE("%s is very confusing. Or perhaps you are just too confused?", ToBeRead->CHAR_NAME(DEFINITE));
1611 ActivateRandomState(SRC_CONFUSE_READ, 1000+RAND()%1500);
1612 ToBeRead->RemoveFromSlot();
1613 ToBeRead->SendToHell();
1615 EditAP(-1000);
1616 return true;
1618 if (ToBeRead->Read(this)) {
1619 if (!game::WizardModeIsActive()) {
1620 /* This AP is used to take the stuff out of backpack */
1621 DexterityAction(5);
1623 return true;
1625 return false;
1627 if (IsPlayer()) ADD_MESSAGE("It's too dark here to read.");
1628 return false;
1632 void character::CalculateBurdenState () {
1633 int OldBurdenState = BurdenState;
1634 sLong SumOfMasses = GetCarriedWeight();
1635 sLong CarryingStrengthUnits = sLong(GetCarryingStrength())*2500;
1636 if (SumOfMasses > (CarryingStrengthUnits << 1) + CarryingStrengthUnits) BurdenState = OVER_LOADED;
1637 else if (SumOfMasses > CarryingStrengthUnits << 1) BurdenState = STRESSED;
1638 else if (SumOfMasses > CarryingStrengthUnits) BurdenState = BURDENED;
1639 else BurdenState = UNBURDENED;
1640 if (!IsInitializing() && BurdenState != OldBurdenState) CalculateBattleInfo();
1644 void character::Save (outputfile &SaveFile) const {
1645 #ifdef SAVE_TEMP_GUARDS
1646 SaveFile << (uShort)GetConfig();
1647 #endif
1648 SaveFile << DataBase->CfgStrName;
1650 Stack->Save(SaveFile);
1651 SaveFile << ID;
1652 for (int c = 0; c < BASE_ATTRIBUTES; ++c) SaveFile << BaseExperience[c];
1654 SaveFile << ExpModifierMap;
1655 SaveFile << NP << AP << Stamina << GenerationDanger << ScienceTalks << CounterToMindWormHatch;
1656 SaveFile << TemporaryState << EquipmentState << Money << GoingTo << RegenerationCounter << Route << Illegal;
1657 SaveFile << CurrentSweatMaterial;
1658 SaveFile.Put(!!IsEnabled());
1659 SaveFile << HomeData << BlocksSinceLastTurn << CommandFlags;
1660 SaveFile << WarnFlags << (uShort)Flags;
1662 for (int c = 0; c < BodyParts; ++c) SaveFile << BodyPartSlot[c] << OriginalBodyPartID[c];
1664 SaveLinkedList(SaveFile, TrapData);
1665 SaveFile << Action;
1667 for (int c = 0; c < STATES; ++c) SaveFile << TemporaryStateCounter[c];
1669 if (GetTeam()) {
1670 SaveFile.Put(true);
1671 SaveFile << (feuLong)Team->GetID(); // feuLong
1672 } else {
1673 SaveFile.Put(false);
1676 if (GetTeam() && GetTeam()->GetLeader() == this) SaveFile.Put(true); else SaveFile.Put(false);
1678 SaveFile << AssignedName << PolymorphBackup;
1680 for (int c = 0; c < AllowedWeaponSkillCategories; ++c) SaveFile << CWeaponSkill[c];
1685 void character::Load (inputfile &SaveFile) {
1686 LoadSquaresUnder();
1688 #ifdef SAVE_TEMP_GUARDS
1689 uShort checkcfgid = ReadType(uShort, SaveFile);
1690 #endif
1691 festring acfgname;
1692 SaveFile >> acfgname;
1693 int acfgid = databasecreator<character>::FindConfigByName(FindProtoType(), acfgname);
1694 if (acfgid == -1) ABORT("Cannot find '%s' config '%s'!", FindProtoType()->GetClassID(), acfgname.CStr());
1696 Stack->Load(SaveFile);
1697 SaveFile >> ID;
1698 game::AddCharacterID(this, ID);
1700 for (int c = 0; c < BASE_ATTRIBUTES; ++c) SaveFile >> BaseExperience[c];
1702 SaveFile >> ExpModifierMap;
1703 SaveFile >> NP >> AP >> Stamina >> GenerationDanger >> ScienceTalks >> CounterToMindWormHatch;
1704 SaveFile >> TemporaryState >> EquipmentState >> Money >> GoingTo >> RegenerationCounter >> Route >> Illegal;
1705 SaveFile >> CurrentSweatMaterial;
1707 if (!SaveFile.Get()) Disable();
1709 SaveFile >> HomeData >> BlocksSinceLastTurn >> CommandFlags;
1710 SaveFile >> WarnFlags;
1711 WarnFlags &= ~WARNED;
1712 Flags |= ReadType(uShort, SaveFile) & ~ENTITY_FLAGS;
1714 for (int c = 0; c < BodyParts; ++c) {
1715 SaveFile >> BodyPartSlot[c] >> OriginalBodyPartID[c];
1716 item *BodyPart = *BodyPartSlot[c];
1717 if (BodyPart) BodyPart->Disable();
1720 LoadLinkedList(SaveFile, TrapData);
1721 SaveFile >> Action;
1723 if (Action) Action->SetActor(this);
1725 for (int c = 0; c < STATES; ++c) SaveFile >> TemporaryStateCounter[c];
1727 if (SaveFile.Get()) SetTeam(game::GetTeam(ReadType(feuLong, SaveFile)));
1729 if (SaveFile.Get()) GetTeam()->SetLeader(this);
1731 SaveFile >> AssignedName >> PolymorphBackup;
1733 for (int c = 0; c < AllowedWeaponSkillCategories; ++c) SaveFile >> CWeaponSkill[c];
1735 //databasecreator<character>::InstallDataBase(this, ReadType(uShort, SaveFile));
1736 databasecreator<character>::InstallDataBase(this, acfgid);
1738 if (IsEnabled() && !game::IsInWilderness()) {
1739 for (int c = 1; c < GetSquaresUnder(); ++c) GetSquareUnder(c)->SetCharacter(this);
1742 #ifdef SAVE_TEMP_GUARDS
1743 if (acfgid != (int)checkcfgid) {
1744 fprintf(stderr, "character::Load(0): typeid=<%s>; config=%d; configname=<%s>; dbcfg=%d (%s) [%u]\n", GetTypeID(), acfgid, acfgname.CStr(), DataBase->Config, DataBase->CfgStrName.CStr(), (unsigned)checkcfgid);
1746 #endif
1748 const fearray<festring> &lt = GetLevelTags();
1749 if (lt.Size > 1) {
1750 fprintf(stderr, "====\n");
1751 for (uInt f = 0; f < lt.Size; ++f) fprintf(stderr, " %u: [%s]\n", f, lt[f].CStr());
1757 truth character::Engrave (cfestring &What) {
1758 GetLSquareUnder()->Engrave(What);
1759 return true;
1762 truth character::MoveRandomly () {
1763 //if (strcmp(GetTypeID(), "siren") == 0) fprintf(stderr, "siren::MoveRandomly; enabled=%d\n", IsEnabled());
1764 if (!IsEnabled()) return false;
1765 for (int c = 0; c < 10; ++c) {
1766 v2 ToTry = game::GetMoveVector(RAND()&7);
1767 if (GetLevel()->IsValidPos(GetPos()+ToTry)) {
1768 lsquare *Square = GetNearLSquare(GetPos()+ToTry);
1770 if (strcmp(GetTypeID(), "siren") == 0) {
1771 fprintf(stderr, "siren::MoveRandomly; c=%d; IsDangerous=%d; IsScary=d\n", Square->IsDangerous(this), Square->IsScary(this));
1774 if (!Square->IsDangerous(this) && !Square->IsScary(this) && TryMove(ToTry, false, false)) return true;
1777 return false;
1781 truth character::TestForPickup (item *ToBeTested) const {
1782 if (MakesBurdened(ToBeTested->GetWeight()+GetCarriedWeight())) return false;
1783 return true;
1787 void character::AddScoreEntry (cfestring &Description, double Multiplier, truth AddEndLevel) const {
1788 if (!game::WizardModeIsReallyActive()) {
1789 highscore HScore;
1790 if (!HScore.CheckVersion()) {
1791 if (game::Menu(0, v2(RES.X >> 1, RES.Y >> 1), CONST_S("The highscore version doesn't match.\rDo you want to erase previous records and start a new file?\rNote, if you answer no, the score of your current game will be lost!\r"), CONST_S("Yes\rNo\r"), LIGHT_GRAY)) return;
1792 HScore.Clear();
1794 festring Desc = game::GetPlayerName();
1795 Desc << ", " << Description;
1796 if (AddEndLevel) Desc << " in "+(game::IsInWilderness() ? "the world map" : game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex()));
1797 HScore.Add(sLong(game::GetScore()*Multiplier), Desc);
1798 HScore.Save();
1803 truth character::CheckDeath (cfestring &Msg, character *Murderer, feuLong DeathFlags) {
1804 if (!IsEnabled()) return true;
1805 if (game::IsSumoWrestling() && IsDead()) {
1806 game::EndSumoWrestling(!!IsPlayer());
1807 return true;
1809 if ((DeathFlags&FORCE_DEATH) || IsDead()) {
1810 if (Murderer && Murderer->IsPlayer() && GetTeam()->GetKillEvilness()) game::DoEvilDeed(GetTeam()->GetKillEvilness());
1811 festring SpecifierMsg;
1812 int SpecifierParts = 0;
1813 if (GetPolymorphBackup()) {
1814 SpecifierMsg << " polymorphed into ";
1815 id::AddName(SpecifierMsg, INDEFINITE);
1816 ++SpecifierParts;
1818 if (!(DeathFlags&IGNORE_TRAPS) && IsStuck()) {
1819 if (SpecifierParts++) SpecifierMsg << " and";
1820 SpecifierMsg << " caught in " << GetTrapDescription();
1822 if (GetAction() && !((DeathFlags&IGNORE_UNCONSCIOUSNESS) && GetAction()->IsUnconsciousness())) {
1823 festring ActionMsg = GetAction()->GetDeathExplanation();
1824 if (!ActionMsg.IsEmpty()) {
1825 if (SpecifierParts > 1) {
1826 SpecifierMsg = ActionMsg << ',' << SpecifierMsg;
1827 } else {
1828 if (SpecifierParts) SpecifierMsg << " and";
1829 SpecifierMsg << ActionMsg;
1831 ++SpecifierParts;
1834 festring NewMsg = Msg;
1835 if (Murderer == this) {
1836 SEARCH_N_REPLACE(NewMsg, "@bkp", CONST_S("by ") + GetPossessivePronoun(false) + " own");
1837 SEARCH_N_REPLACE(NewMsg, "@bk", CONST_S("by ") + GetObjectPronoun(false) + "self");
1838 SEARCH_N_REPLACE(NewMsg, "@k", GetObjectPronoun(false) + "self");
1839 } else if (Murderer) {
1840 SEARCH_N_REPLACE(NewMsg, "@bkp", CONST_S("by ") + Murderer->GetName(INDEFINITE) + "'s");
1841 SEARCH_N_REPLACE(NewMsg, "@bk", CONST_S("by ") + Murderer->GetName(INDEFINITE));
1842 SEARCH_N_REPLACE(NewMsg, "@k", CONST_S("by ") + Murderer->GetName(INDEFINITE));
1844 if (SpecifierParts) NewMsg << " while" << SpecifierMsg;
1845 if (IsPlayer() && game::WizardModeIsActive()) ADD_MESSAGE("Death message: %s. Score: %d.", NewMsg.CStr(), game::GetScore());
1846 Die(Murderer, NewMsg, DeathFlags);
1847 return true;
1849 return false;
1853 truth character::CheckStarvationDeath (cfestring &Msg) {
1854 if (GetNP() < 1 && UsesNutrition()) return CheckDeath(Msg, 0, FORCE_DEATH);
1855 return false;
1859 void character::ThrowItem (int Direction, item *ToBeThrown) {
1860 if (Direction > 7) ABORT("Throw in TOO odd direction...");
1861 ToBeThrown->Fly(this, Direction, GetAttribute(ARM_STRENGTH));
1865 void character::HasBeenHitByItem (character *Thrower, item *Thingy, int Damage, double ToHitValue, int Direction) {
1866 if (IsPlayer()) ADD_MESSAGE("%s hits you.", Thingy->CHAR_NAME(DEFINITE));
1867 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s hits %s.", Thingy->CHAR_NAME(DEFINITE), CHAR_NAME(DEFINITE));
1868 int BodyPart = ChooseBodyPartToReceiveHit(ToHitValue, DodgeValue);
1869 int WeaponSkillHits = Thrower ? CalculateWeaponSkillHits(Thrower) : 0;
1870 int DoneDamage = ReceiveBodyPartDamage(Thrower, Damage, PHYSICAL_DAMAGE, BodyPart, Direction);
1871 truth Succeeded = (GetBodyPart(BodyPart) && HitEffect(Thrower, Thingy, Thingy->GetPos(), THROW_ATTACK, BodyPart, Direction, !DoneDamage, false, DoneDamage)) || DoneDamage;
1872 if (Succeeded && Thrower) Thrower->WeaponSkillHit(Thingy, THROW_ATTACK, WeaponSkillHits);
1873 festring DeathMsg = CONST_S("killed by a flying ")+Thingy->GetName(UNARTICLED);
1874 if (CheckDeath(DeathMsg, Thrower)) return;
1875 if (Thrower) {
1876 if (Thrower->CanBeSeenByPlayer())
1877 DeActivateVoluntaryAction(CONST_S("The attack of ")+Thrower->GetName(DEFINITE)+CONST_S(" interrupts you."));
1878 else
1879 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
1880 } else {
1881 DeActivateVoluntaryAction(CONST_S("The hit interrupts you."));
1886 truth character::DodgesFlyingItem (item *Item, double ToHitValue) {
1887 return !Item->EffectIsGood() && RAND() % int(100+ToHitValue/DodgeValue*100) < 100;
1891 void character::GetPlayerCommand () {
1892 command *cmd;
1893 truth HasActed = false;
1894 while (!HasActed) {
1895 game::DrawEverything();
1896 if (game::GetDangerFound() && !StateIsActivated(FEARLESS)) {
1897 if (game::GetDangerFound() > 500.) {
1898 if (game::GetCausePanicFlag()) {
1899 game::SetCausePanicFlag(false);
1900 BeginTemporaryState(PANIC, 500+RAND_N(500));
1902 game::AskForEscPress(CONST_S("You are horrified by your situation!"));
1903 } else if (ivanconfig::GetWarnAboutDanger()) {
1904 if (game::GetDangerFound() > 50.) game::AskForEscPress(CONST_S("You sense great danger!"));
1905 else game::AskForEscPress(CONST_S("You sense danger!"));
1907 game::SetDangerFound(0);
1909 game::SetIsInGetCommand(true);
1910 int Key = GET_KEY();
1911 game::SetIsInGetCommand(false);
1912 if (Key != '+' && Key != '-' && Key != 'M') msgsystem::ThyMessagesAreNowOld(); // gum
1913 truth ValidKeyPressed = false;
1915 for (int c = 0; c < DIRECTION_COMMAND_KEYS; ++c) {
1916 if (Key == game::GetMoveCommandKey(c)) {
1917 if (c != MDIR_STAND && globalwindowhandler::lastCtrl && !globalwindowhandler::lastAlt && !globalwindowhandler::lastShift && !game::IsInWilderness()) {
1918 // C-dir: Go
1919 int Dir = c;
1920 go *Go = go::Spawn(this);
1921 Go->SetDirection(Dir);
1922 Go->SetPrevWasTurn(false);
1923 SetAction(Go);
1924 EditAP(GetStateAPGain(100)); // gum solution
1925 GoOn(Go, true);
1926 HasActed = true;
1927 } else {
1928 HasActed = TryMove(ApplyStateModification(game::GetMoveVector(c)), true, game::PlayerIsRunning());
1930 ValidKeyPressed = true;
1931 if (HasActed) break;
1935 if (!ValidKeyPressed) {
1936 for (int c = 0; (cmd = commandsystem::GetCommand(c)); ++c) {
1937 /* k8 */
1938 /* Numpad aliases for most commonly used commands */
1939 if (Key == KEY_DEL && cmd->GetName() == "Eat") Key = cmd->GetKey();
1940 if (Key == KEY_INS && cmd->GetName() == "PickUp") Key = cmd->GetKey();
1941 if (Key == KEY_PLUS && cmd->GetName() == "EquipmentScreen") Key = cmd->GetKey();
1942 /* k8 */
1943 if (Key == cmd->GetKey()) {
1944 if (game::IsInWilderness() && !commandsystem::GetCommand(c)->IsUsableInWilderness()) {
1945 ADD_MESSAGE("This function cannot be used while in wilderness.");
1946 } else if (!game::WizardModeIsActive() && commandsystem::GetCommand(c)->IsWizardModeFunction()) {
1947 ADD_MESSAGE("Activate wizardmode to use this function.");
1948 } else {
1949 HasActed = commandsystem::GetCommand(c)->GetLinkedFunction()(this);
1951 ValidKeyPressed = true;
1952 break;
1956 if (!ValidKeyPressed) ADD_MESSAGE("Unknown key. Press '?' for a list of commands.");
1958 game::IncreaseTurn();
1962 void character::Vomit (v2 Pos, int Amount, truth ShowMsg) {
1963 if (!CanVomit()) return;
1964 if (ShowMsg) {
1965 if (IsPlayer()) ADD_MESSAGE("You vomit.");
1966 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s vomits.", CHAR_NAME(DEFINITE));
1968 if (VomittingIsUnhealthy()) {
1969 EditExperience(ARM_STRENGTH, -75, 1 << 9);
1970 EditExperience(LEG_STRENGTH, -75, 1 << 9);
1972 if (IsPlayer()) {
1973 EditNP(-2500-RAND()%2501);
1974 CheckStarvationDeath(CONST_S("vomited himself to death"));
1976 if (StateIsActivated(PARASITIZED) && !(RAND() & 7)) {
1977 if (IsPlayer()) ADD_MESSAGE("You notice a dead broad tapeworm among your former stomach contents.");
1978 DeActivateTemporaryState(PARASITIZED);
1980 if (!game::IsInWilderness()) {
1981 GetNearLSquare(Pos)->ReceiveVomit(this, liquid::Spawn(GetVomitMaterial(), sLong(sqrt(GetBodyVolume())*Amount/1000)));
1986 truth character::Polymorph (character *NewForm, int Counter) {
1987 if (!IsPolymorphable() || (!IsPlayer() && game::IsInWilderness())) {
1988 delete NewForm;
1989 return false;
1991 RemoveTraps();
1992 if (GetAction()) GetAction()->Terminate(false);
1993 NewForm->SetAssignedName("");
1994 if (IsPlayer())
1995 ADD_MESSAGE("Your body glows in a crimson light. You transform into %s!", NewForm->CHAR_NAME(INDEFINITE));
1996 else if (CanBeSeenByPlayer())
1997 ADD_MESSAGE("%s glows in a crimson light and %s transforms into %s!", CHAR_NAME(DEFINITE), GetPersonalPronoun().CStr(), NewForm->CHAR_NAME(INDEFINITE));
1999 Flags |= C_IN_NO_MSG_MODE;
2000 NewForm->Flags |= C_IN_NO_MSG_MODE;
2001 NewForm->ChangeTeam(GetTeam());
2002 NewForm->GenerationDanger = GenerationDanger;
2003 NewForm->mOnEvents = this->mOnEvents;
2005 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(NewForm);
2007 v2 Pos = GetPos();
2008 Remove();
2009 NewForm->PutToOrNear(Pos);
2010 NewForm->SetAssignedName(GetAssignedName());
2011 NewForm->ActivateTemporaryState(POLYMORPHED);
2012 NewForm->SetTemporaryStateCounter(POLYMORPHED, Counter);
2014 if (TemporaryStateIsActivated(POLYMORPHED)) {
2015 NewForm->SetPolymorphBackup(GetPolymorphBackup());
2016 SetPolymorphBackup(0);
2017 SendToHell();
2018 } else {
2019 NewForm->SetPolymorphBackup(this);
2020 Flags |= C_POLYMORPHED;
2021 Disable();
2024 GetStack()->MoveItemsTo(NewForm->GetStack());
2025 NewForm->SetMoney(GetMoney());
2026 DonateEquipmentTo(NewForm);
2027 Flags &= ~C_IN_NO_MSG_MODE;
2028 NewForm->Flags &= ~C_IN_NO_MSG_MODE;
2029 NewForm->CalculateAll();
2031 if (IsPlayer()) {
2032 Flags &= ~C_PLAYER;
2033 game::SetPlayer(NewForm);
2034 game::SendLOSUpdateRequest();
2035 UpdateESPLOS();
2038 NewForm->TestWalkability();
2039 return true;
2043 void character::BeKicked (character *Kicker, item *Boot, bodypart *Leg, v2 HitPos, double KickDamage,
2044 double ToHitValue, int Success, int Direction, truth Critical, truth ForceHit)
2046 //FIXME: other args
2047 if (!game::RunCharEvent("before_kicked_by", this, Kicker, Boot)) return;
2049 auto hitres = (TakeHit(Kicker, Boot, Leg, HitPos, KickDamage, ToHitValue, Success, KICK_ATTACK, Direction, Critical, ForceHit));
2050 if (hitres == HAS_HIT || hitres == HAS_BLOCKED || hitres == DID_NO_DAMAGE) {
2051 if (IsEnabled() && !CheckBalance(KickDamage)) {
2052 if (IsPlayer()) ADD_MESSAGE("The kick throws you off balance.");
2053 else if (Kicker->IsPlayer()) ADD_MESSAGE("The kick throws %s off balance.", CHAR_DESCRIPTION(DEFINITE));
2054 v2 FallToPos = GetPos()+game::GetMoveVector(Direction);
2055 FallTo(Kicker, FallToPos);
2061 /* Return true if still in balance */
2062 truth character::CheckBalance (double KickDamage) {
2063 return !CanMove() || IsStuck() || !KickDamage || (!IsFlying() && KickDamage*5 < RAND()%GetSize());
2067 void character::FallTo (character *GuiltyGuy, v2 Where) {
2068 EditAP(-500);
2069 lsquare *MoveToSquare[MAX_SQUARES_UNDER];
2070 int Squares = CalculateNewSquaresUnder(MoveToSquare, Where);
2071 if (Squares) {
2072 truth NoRoom = false;
2073 for (int c = 0; c < Squares; ++c) {
2074 olterrain *Terrain = MoveToSquare[c]->GetOLTerrain();
2075 if (Terrain && !CanMoveOn(Terrain)) { NoRoom = true; break; }
2077 if (NoRoom) {
2078 if (HasHead()) {
2079 if (IsPlayer()) ADD_MESSAGE("You hit your head on the wall.");
2080 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s hits %s head on the wall.", CHAR_NAME(DEFINITE), GetPossessivePronoun().CStr());
2082 ReceiveDamage(GuiltyGuy, 1+RAND()%5, PHYSICAL_DAMAGE, HEAD);
2083 CheckDeath(CONST_S("killed by hitting a wall due to being kicked @bk"), GuiltyGuy);
2084 } else {
2085 if (IsFreeForMe(MoveToSquare[0])) Move(Where, true);
2086 // Place code that handles characters bouncing to each other here
2092 truth character::CheckCannibalism (cmaterial *What) const {
2093 return GetTorso()->GetMainMaterial()->IsSameAs(What);
2097 void character::StandIdleAI () {
2098 SeekLeader(GetLeader());
2099 if (CheckForEnemies(true, true, true)) return;
2100 if (CheckForUsefulItemsOnGround()) return;
2101 if (FollowLeader(GetLeader())) return;
2102 if (CheckForDoors()) return;
2103 if (MoveTowardsHomePos()) return;
2104 if (CheckSadism()) return;
2105 EditAP(-1000);
2109 truth character::LoseConsciousness (int Counter, truth HungerFaint) {
2110 if (!AllowUnconsciousness()) return false;
2111 action *Action = GetAction();
2112 if (Action) {
2113 if (HungerFaint && !Action->AllowUnconsciousness()) return false;
2114 if (Action->IsUnconsciousness()) {
2115 static_cast<unconsciousness *>(Action)->RaiseCounterTo(Counter);
2116 return true;
2118 Action->Terminate(false);
2120 if (IsPlayer()) ADD_MESSAGE("You lose consciousness.");
2121 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s loses consciousness.", CHAR_NAME(DEFINITE));
2122 unconsciousness *Unconsciousness = unconsciousness::Spawn(this);
2123 Unconsciousness->SetCounter(Counter);
2124 SetAction(Unconsciousness);
2125 return true;
2129 void character::DeActivateVoluntaryAction (cfestring &Reason) {
2130 if (GetAction() && GetAction()->IsVoluntary()) {
2131 if (IsPlayer()) {
2132 if (Reason.GetSize()) ADD_MESSAGE("%s", Reason.CStr());
2133 if (game::TruthQuestion(CONST_S("Continue ")+GetAction()->GetDescription()+"?")) GetAction()->ActivateInDNDMode();
2134 else GetAction()->Terminate(false);
2135 } else {
2136 GetAction()->Terminate(false);
2142 void character::ActionAutoTermination () {
2143 if (!GetAction() || !GetAction()->IsVoluntary() || GetAction()->InDNDMode()) return;
2144 v2 Pos = GetPos();
2145 for (int c = 0; c < game::GetTeams(); ++c) {
2146 if (GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE) {
2147 for (std::list<character *>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i) {
2148 character *ch = *i;
2149 if (ch->IsEnabled() && ch->CanBeSeenBy(this, false, true) && (ch->CanMove() || ch->GetPos().IsAdjacent(Pos)) && ch->CanAttack()) {
2150 if (IsPlayer()) {
2151 ADD_MESSAGE("%s seems to be hostile.", ch->CHAR_NAME(DEFINITE));
2152 if (game::TruthQuestion(CONST_S("Continue ")+GetAction()->GetDescription()+"?")) GetAction()->ActivateInDNDMode();
2153 else GetAction()->Terminate(false);
2154 } else {
2155 GetAction()->Terminate(false);
2157 return;
2165 truth character::CheckForEnemies (truth CheckDoors, truth CheckGround, truth MayMoveRandomly, truth RunTowardsTarget) {
2166 if (!IsEnabled()) return false;
2167 truth HostileCharsNear = false;
2168 character *NearestChar = 0;
2169 sLong NearestDistance = 0x7FFFFFFF;
2170 v2 Pos = GetPos();
2171 for (int c = 0; c < game::GetTeams(); ++c) {
2172 if (GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE) {
2173 for (std::list<character*>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i) {
2174 character *ch = *i;
2175 if (ch->IsEnabled() && GetAttribute(WISDOM) < ch->GetAttackWisdomLimit()) {
2176 sLong ThisDistance = Max<sLong>(abs(ch->GetPos().X - Pos.X), abs(ch->GetPos().Y - Pos.Y));
2177 if (ThisDistance <= GetLOSRangeSquare()) HostileCharsNear = true;
2178 if ((ThisDistance < NearestDistance || (ThisDistance == NearestDistance && !(RAND() % 3))) &&
2179 ch->CanBeSeenBy(this, false, IsGoingSomeWhere()) &&
2180 (!IsGoingSomeWhere() || HasClearRouteTo(ch->GetPos()))) {
2181 NearestChar = ch;
2182 NearestDistance = ThisDistance;
2189 if (NearestChar) {
2190 if (GetAttribute(INTELLIGENCE) >= 10 || IsSpy()) game::CallForAttention(GetPos(), 100);
2191 if (SpecialEnemySightedReaction(NearestChar)) return true;
2192 if (IsExtraCoward() && !StateIsActivated(PANIC) && NearestChar->GetRelativeDanger(this) >= 0.5 && !StateIsActivated(FEARLESS)) {
2193 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s sees %s.", CHAR_NAME(DEFINITE), NearestChar->CHAR_DESCRIPTION(DEFINITE));
2194 BeginTemporaryState(PANIC, 500+RAND()%500);
2196 if (!IsRetreating()) {
2197 if (CheckGround && NearestDistance > 2 && CheckForUsefulItemsOnGround(false)) return true;
2198 SetGoingTo(NearestChar->GetPos());
2199 } else {
2200 SetGoingTo(Pos-((NearestChar->GetPos()-Pos)<<4));
2202 return MoveTowardsTarget(true);
2203 } else {
2204 character *Leader = GetLeader();
2205 if (Leader == this) Leader = 0;
2206 if (!Leader && IsGoingSomeWhere()) {
2207 if (!MoveTowardsTarget(RunTowardsTarget)) {
2208 TerminateGoingTo();
2209 return false;
2210 } else {
2211 if (!IsEnabled()) return true;
2212 if (GetPos() == GoingTo) TerminateGoingTo();
2213 return true;
2215 } else {
2216 if ((!Leader || (Leader && !IsGoingSomeWhere())) && HostileCharsNear) {
2217 if (CheckDoors && CheckForDoors()) return true;
2218 if (CheckGround && CheckForUsefulItemsOnGround()) return true;
2219 if (MayMoveRandomly && MoveRandomly()) return true; // one has heard that an enemy is near but doesn't know where
2221 return false;
2227 truth character::CheckForDoors () {
2228 if (!CanOpen() || !IsEnabled()) return false;
2229 for (int d = 0; d < GetNeighbourSquares(); ++d) {
2230 lsquare *Square = GetNeighbourLSquare(d);
2231 if (Square && Square->GetOLTerrain() && Square->GetOLTerrain()->Open(this)) return true;
2233 return false;
2237 truth character::CheckForUsefulItemsOnGround (truth CheckFood) {
2238 if (StateIsActivated(PANIC) || !IsEnabled()) return false;
2239 itemvector ItemVector;
2240 GetStackUnder()->FillItemVector(ItemVector);
2241 for (uInt c = 0; c < ItemVector.size(); ++c) {
2242 if (ItemVector[c]->CanBeSeenBy(this) && ItemVector[c]->IsPickable(this)) {
2243 if (!(CommandFlags & DONT_CHANGE_EQUIPMENT) && TryToEquip(ItemVector[c])) return true;
2244 if (CheckFood && UsesNutrition() && !CheckIfSatiated() && TryToConsume(ItemVector[c])) return true;
2245 if (IsRangedAttacker() && (ItemVector[c])->GetThrowItemTypes() && TryToAddToInventory(ItemVector[c])) return true;
2248 return false;
2252 truth character::TryToAddToInventory (item *Item) {
2253 if (!(GetBurdenState() > STRESSED) || !CanUseEquipment() || Item->GetSquaresUnder() != 1) return false;
2254 room *Room = GetRoom();
2255 if (!Room || Room->PickupItem(this, Item, 1)) {
2256 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s picks up %s from the ground.", CHAR_NAME(DEFINITE), Item->CHAR_NAME(INDEFINITE));
2257 Item->MoveTo(GetStack());
2258 DexterityAction(5);
2259 return true;
2261 return false;
2265 truth character::CheckInventoryForItemToThrow (item *ToBeChecked) {
2266 return (ToBeChecked->GetThrowItemTypes() & GetWhatThrowItemTypesToThrow()) ? true : false; //hehe
2270 truth character::CheckThrowItemOpportunity () {
2271 if (!IsRangedAttacker() || !CanThrow() || !IsHumanoid() || !IsSmall() || !IsEnabled()) return false; // total gum
2272 //fprintf(stderr, "character::CheckThrowItemOpportunity...\n");
2273 // Steps:
2274 // (1) - Acquire target as nearest enemy
2275 // (2) - Check that this enemy is in range, and is in appropriate direction; no friendly fire!
2276 // (3) - check inventory for throwing weapon, select this weapon
2277 // (4) - throw item in direction where the enemy is
2279 //Check the visible area for hostiles
2280 int ThrowDirection = 0;
2281 int TargetFound = 0;
2282 v2 Pos = GetPos();
2283 v2 TestPos;
2284 int RangeMax = GetLOSRange();
2285 int CandidateDirections[7] = {0, 0, 0, 0, 0, 0, 0};
2286 int HostileFound = 0;
2287 item *ToBeThrown = 0;
2288 level *Level = GetLevel();
2290 for (int r = 1; r <= RangeMax; ++r) {
2291 for (int dir = 0; dir < MDIR_STAND; ++dir) {
2293 switch (dir) {
2294 case 0: TestPos = v2(Pos.X-r, Pos.Y-r); break;
2295 case 1: TestPos = v2(Pos.X, Pos.Y-r); break;
2296 case 2: TestPos = v2(Pos.X+r, Pos.Y-r); break;
2297 case 3: TestPos = v2(Pos.X-r, Pos.Y); break;
2298 case 4: TestPos = v2(Pos.X+r, Pos.Y); break;
2299 case 5: TestPos = v2(Pos.X-r, Pos.Y+r); break;
2300 case 6: TestPos = v2(Pos.X, Pos.Y+r); break;
2301 case 7: TestPos = v2(Pos.X+r, Pos.Y+r); break;
2303 if (Level->IsValidPos(TestPos)) {
2304 square *TestSquare = GetNearSquare(TestPos);
2305 character *Dude = TestSquare->GetCharacter();
2307 if (Dude && Dude->IsEnabled() && Dude->CanBeSeenBy(this, false, true)) {
2308 if (GetRelation(Dude) != HOSTILE) CandidateDirections[dir] = BLOCKED;
2309 else if (GetRelation(Dude) == HOSTILE && CandidateDirections[dir] != BLOCKED) {
2310 //then load this candidate position direction into the vector of possible throw directions
2311 CandidateDirections[dir] = SUCCESS;
2312 HostileFound = 1;
2319 if (HostileFound) {
2320 for (int dir = 0; dir < MDIR_STAND; ++dir) {
2321 if (CandidateDirections[dir] == SUCCESS && !TargetFound) {
2322 ThrowDirection = dir;
2323 TargetFound = 1;
2324 break;
2327 if (!TargetFound) return false;
2328 } else {
2329 return false;
2331 //fprintf(stderr, "throw: has target.\n");
2332 // check inventory for throwing weapon
2333 itemvector ItemVector;
2334 GetStack()->FillItemVector(ItemVector);
2335 for (uInt c = 0; c < ItemVector.size(); ++c) {
2336 if (ItemVector[c]->IsThrowingWeapon()) {
2337 ToBeThrown = ItemVector[c];
2338 break;
2341 if (!ToBeThrown) return false;
2342 //fprintf(stderr, "throw: has throwing weapon.\n");
2343 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s throws %s.", CHAR_NAME(DEFINITE), ToBeThrown->CHAR_NAME(INDEFINITE));
2344 ThrowItem(ThrowDirection, ToBeThrown);
2345 EditExperience(ARM_STRENGTH, 75, 1<<8);
2346 EditExperience(DEXTERITY, 75, 1<<8);
2347 EditExperience(PERCEPTION, 75, 1<<8);
2348 EditNP(-50);
2349 DexterityAction(5);
2350 TerminateGoingTo();
2351 return true;
2355 truth character::CheckAIZapOpportunity () {
2356 if (/*!IsRangedAttacker() || */ !CanZap() || !IsHumanoid() || !IsSmall() || !IsEnabled()) return false; // total gum
2357 // Steps:
2358 // (1) - Acquire target as nearest enemy
2359 // (2) - Check that this enemy is in range, and is in appropriate direction; no friendly fire!
2360 // (3) - check inventory for zappable item
2361 // (4) - zap item in direction where the enemy is
2362 //Check the rest of the visible area for hostiles
2363 v2 Pos = GetPos();
2364 v2 TestPos;
2365 int SensibleRange = 5;
2366 int RangeMax = GetLOSRange();
2367 if (RangeMax < SensibleRange) SensibleRange = RangeMax;
2368 int CandidateDirections[7] = {0, 0, 0, 0, 0, 0, 0};
2369 int HostileFound = 0;
2370 int ZapDirection = 0;
2371 int TargetFound = 0;
2372 item *ToBeZapped = 0;
2373 level *Level = GetLevel();
2375 for (int r = 2; r <= SensibleRange; ++r) {
2376 for (int dir = 0; dir < MDIR_STAND; ++dir) {
2377 switch (dir) {
2378 case 0: TestPos = v2(Pos.X-r, Pos.Y-r); break;
2379 case 1: TestPos = v2(Pos.X, Pos.Y-r); break;
2380 case 2: TestPos = v2(Pos.X+r, Pos.Y-r); break;
2381 case 3: TestPos = v2(Pos.X-r, Pos.Y); break;
2382 case 4: TestPos = v2(Pos.X+r, Pos.Y); break;
2383 case 5: TestPos = v2(Pos.X-r, Pos.Y+r); break;
2384 case 6: TestPos = v2(Pos.X, Pos.Y+r); break;
2385 case 7: TestPos = v2(Pos.X+r, Pos.Y+r); break;
2387 if (Level->IsValidPos(TestPos)) {
2388 square *TestSquare = GetNearSquare(TestPos);
2389 character *Dude = TestSquare->GetCharacter();
2391 if (Dude && Dude->IsEnabled() && Dude->CanBeSeenBy(this, false, true)) {
2392 if (GetRelation(Dude) != HOSTILE) CandidateDirections[dir] = BLOCKED;
2393 else if (GetRelation(Dude) == HOSTILE && CandidateDirections[dir] != BLOCKED) {
2394 //then load this candidate position direction into the vector of possible zap directions
2395 CandidateDirections[dir] = SUCCESS;
2396 HostileFound = 1;
2403 if (HostileFound) {
2404 for (int dir = 0; dir < MDIR_STAND; ++dir) {
2405 if (CandidateDirections[dir] == SUCCESS && !TargetFound) {
2406 ZapDirection = dir;
2407 TargetFound = 1;
2408 break;
2411 if (!TargetFound) return false;
2412 } else {
2413 return false;
2415 // check inventory for zappable item
2416 itemvector ItemVector;
2417 GetStack()->FillItemVector(ItemVector);
2418 for (unsigned int c = 0; c < ItemVector.size(); ++c) {
2419 if (ItemVector[c]->GetMinCharges() > 0 && ItemVector[c]->GetPrice()) {
2420 // bald-faced gum solution for choosing zappables that have shots left.
2421 // MinCharges needs to be replaced. Empty wands have zero price!
2422 ToBeZapped = ItemVector[c];
2423 break;
2426 if (!ToBeZapped) return false;
2427 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s zaps %s.", CHAR_NAME(DEFINITE), ToBeZapped->CHAR_NAME(INDEFINITE));
2428 if (ToBeZapped->Zap(this, GetPos(), ZapDirection)) {
2429 EditAP(-100000/APBonus(GetAttribute(PERCEPTION)));
2430 return true;
2431 } else {
2432 return false;
2434 TerminateGoingTo();
2435 return true;
2439 truth character::FollowLeader (character *Leader) {
2440 if (!Leader || Leader == this || !IsEnabled()) return false;
2441 if ((CommandFlags&FOLLOW_LEADER) && Leader->CanBeSeenBy(this) && Leader->SquareUnderCanBeSeenBy(this, true)) {
2442 v2 Distance = GetPos()-GoingTo;
2443 if (abs(Distance.X) <= 2 && abs(Distance.Y) <= 2) return false;
2444 return MoveTowardsTarget(false);
2446 if (IsGoingSomeWhere()) {
2447 if (!MoveTowardsTarget(true)) {
2448 TerminateGoingTo();
2449 return false;
2451 return true;
2453 return false;
2457 void character::SeekLeader (ccharacter *Leader) {
2458 if (Leader && Leader != this) {
2459 if (Leader->CanBeSeenBy(this) && (Leader->SquareUnderCanBeSeenBy(this, true) || !IsGoingSomeWhere())) {
2460 if (CommandFlags&FOLLOW_LEADER) SetGoingTo(Leader->GetPos());
2461 } else if (!IsGoingSomeWhere()) {
2462 team *Team = GetTeam();
2463 for (std::list<character *>::const_iterator i = Team->GetMember().begin(); i != Team->GetMember().end(); ++i) {
2464 character *ch = *i;
2465 if (ch->IsEnabled() && ch->GetID() != GetID() &&
2466 (CommandFlags & FOLLOW_LEADER) == (ch->CommandFlags & FOLLOW_LEADER) && ch->CanBeSeenBy(this)) {
2467 v2 Pos = ch->GetPos();
2468 v2 Distance = GetPos()-Pos;
2469 if (abs(Distance.X) > 2 && abs(Distance.Y) > 2) {
2470 SetGoingTo(Pos);
2471 break;
2480 int character::GetMoveEase () const {
2481 if (BurdenState == OVER_LOADED || BurdenState == STRESSED) return 50;
2482 if (BurdenState == BURDENED) return 75;
2483 if (BurdenState == UNBURDENED) return 100;
2484 return 666;
2488 int character::GetLOSRange () const {
2489 if (!game::IsInWilderness()) return GetAttribute(PERCEPTION)*GetLevel()->GetLOSModifier()/48;
2490 return 3;
2494 truth character::Displace (character *Who, truth Forced) {
2495 if (GetBurdenState() == OVER_LOADED) {
2496 if (IsPlayer()) {
2497 cchar *CrawlVerb = StateIsActivated(LEVITATION) ? "float" : "crawl";
2498 ADD_MESSAGE("You try very hard to %s forward. But your load is too heavy.", CrawlVerb);
2499 EditAP(-1000);
2500 return true;
2502 return false;
2505 double Danger = GetRelativeDanger(Who);
2506 int PriorityDifference = Limit(GetDisplacePriority()-Who->GetDisplacePriority(), -31, 31);
2508 if (IsPlayer()) ++PriorityDifference;
2509 else if (Who->IsPlayer()) --PriorityDifference;
2511 if (PriorityDifference >= 0) Danger *= 1 << PriorityDifference;
2512 else Danger /= 1 << -PriorityDifference;
2514 if (IsSmall() && Who->IsSmall() &&
2515 (Forced || Danger > 1.0 || !(Who->IsPlayer() || Who->IsBadPath(GetPos()))) &&
2516 !IsStuck() && !Who->IsStuck() && (!Who->GetAction() || Who->GetAction()->TryDisplace()) &&
2517 CanMove() && Who->CanMove() && Who->CanMoveOn(GetLSquareUnder())) {
2518 if (IsPlayer()) ADD_MESSAGE("You displace %s!", Who->CHAR_DESCRIPTION(DEFINITE));
2519 else if (Who->IsPlayer()) ADD_MESSAGE("%s displaces you!", CHAR_DESCRIPTION(DEFINITE));
2520 else if (CanBeSeenByPlayer() || Who->CanBeSeenByPlayer()) ADD_MESSAGE("%s displaces %s!", CHAR_DESCRIPTION(DEFINITE), Who->CHAR_DESCRIPTION(DEFINITE));
2521 lsquare *OldSquareUnder1[MAX_SQUARES_UNDER];
2522 lsquare *OldSquareUnder2[MAX_SQUARES_UNDER];
2523 for (int c = 0; c < GetSquaresUnder(); ++c) OldSquareUnder1[c] = GetLSquareUnder(c);
2524 for (int c = 0; c < Who->GetSquaresUnder(); ++c) OldSquareUnder2[c] = Who->GetLSquareUnder(c);
2525 v2 Pos = GetPos();
2526 v2 WhoPos = Who->GetPos();
2527 Remove();
2528 Who->Remove();
2529 PutTo(WhoPos);
2530 Who->PutTo(Pos);
2531 EditAP(-GetMoveAPRequirement(GetSquareUnder()->GetEntryDifficulty()) - 500);
2532 EditNP(-12*GetSquareUnder()->GetEntryDifficulty());
2533 EditExperience(AGILITY, 75, GetSquareUnder()->GetEntryDifficulty() << 7);
2534 if (IsPlayer()) ShowNewPosInfo();
2535 if (Who->IsPlayer()) Who->ShowNewPosInfo();
2536 SignalStepFrom(OldSquareUnder1);
2537 Who->SignalStepFrom(OldSquareUnder2);
2538 return true;
2539 } else {
2540 if (IsPlayer()) {
2541 ADD_MESSAGE("%s resists!", Who->CHAR_DESCRIPTION(DEFINITE));
2542 EditAP(-1000);
2543 return true;
2545 return false;
2550 void character::SetNP (sLong What) {
2551 int OldState = GetHungerState();
2552 NP = What;
2553 if (IsPlayer()) {
2554 int NewState = GetHungerState();
2555 if (NewState == STARVING && OldState > STARVING) DeActivateVoluntaryAction(CONST_S("You are getting really hungry."));
2556 else if (NewState == VERY_HUNGRY && OldState > VERY_HUNGRY) DeActivateVoluntaryAction(CONST_S("You are getting very hungry."));
2557 else if (NewState == HUNGRY && OldState > HUNGRY) DeActivateVoluntaryAction(CONST_S("You are getting hungry."));
2562 void character::ShowNewPosInfo () const {
2563 msgsystem::EnterBigMessageMode();
2564 v2 Pos = GetPos();
2566 if (ivanconfig::GetAutoCenterMap()) {
2567 game::UpdateCameraX();
2568 game::UpdateCameraY();
2569 } else {
2570 if (Pos.X < game::GetCamera().X+3 || Pos.X >= game::GetCamera().X+game::GetScreenXSize()-3) game::UpdateCameraX();
2571 if (Pos.Y < game::GetCamera().Y+3 || Pos.Y >= game::GetCamera().Y+game::GetScreenYSize()-3) game::UpdateCameraY();
2574 game::SendLOSUpdateRequest();
2575 game::DrawEverythingNoBlit();
2576 UpdateESPLOS();
2578 if (!game::IsInWilderness()) {
2579 if (GetLSquareUnder()->IsDark() && !game::GetSeeWholeMapCheatMode()) ADD_MESSAGE("It's dark in here!");
2581 GetLSquareUnder()->ShowSmokeMessage();
2582 itemvectorvector PileVector;
2583 GetStackUnder()->Pile(PileVector, this, CENTER);
2585 if (PileVector.size()) {
2586 truth Feel = !GetLSquareUnder()->IsTransparent() || GetLSquareUnder()->IsDark();
2588 if (PileVector.size() == 1) {
2589 if (Feel) {
2590 ADD_MESSAGE("You feel %s lying here.", PileVector[0][0]->GetName(INDEFINITE, PileVector[0].size()).CStr());
2591 } else {
2592 if (ivanconfig::GetShowFullItemDesc() && PileVector[0][0]->AllowDetailedDescription()) {
2593 festring text;
2595 PileVector[0][0]->AddInventoryEntry(PLAYER, text, PileVector[0].size(), true);
2596 //fprintf(stderr, "invdsc : [%s]\n", text.CStr());
2597 ADD_MESSAGE("%s %s lying here.", text.CStr(), PileVector[0].size() == 1 ? "is" : "are");
2598 } else {
2599 ADD_MESSAGE("%s %s lying here.", PileVector[0][0]->GetName(INDEFINITE, PileVector[0].size()).CStr(), PileVector[0].size() == 1 ? "is" : "are");
2602 fprintf(stderr, "description: [%s]\n", PileVector[0][0]->GetDescription(INDEFINITE).CStr());
2603 fprintf(stderr, "strength : [%s]\n", PileVector[0][0]->GetStrengthValueDescription());
2604 fprintf(stderr, "basetohit : [%s]\n", PileVector[0][0]->GetBaseToHitValueDescription());
2605 fprintf(stderr, "baseblock : [%s]\n", PileVector[0][0]->GetBaseBlockValueDescription());
2606 fprintf(stderr, "extdsc : [%s]\n", PileVector[0][0]->GetExtendedDescription().CStr());
2609 } else {
2610 int Items = 0;
2611 for (uInt c = 0; c < PileVector.size(); ++c) {
2612 if ((Items += PileVector[c].size()) > 3) break;
2614 if (Items > 3) {
2615 if (Feel) ADD_MESSAGE("You feel several items lying here.");
2616 else ADD_MESSAGE("Several items are lying here.");
2617 } else if (Items) {
2618 if (Feel) ADD_MESSAGE("You feel a few items lying here.");
2619 else ADD_MESSAGE("A few items are lying here.");
2624 festring SideItems;
2625 GetLSquareUnder()->GetSideItemDescription(SideItems);
2627 if (!SideItems.IsEmpty()) ADD_MESSAGE("There is %s.", SideItems.CStr());
2629 if (GetLSquareUnder()->HasEngravings()) {
2630 if (CanRead()) ADD_MESSAGE("Something has been engraved here: \"%s\"", GetLSquareUnder()->GetEngraved());
2631 else ADD_MESSAGE("Something has been engraved here.");
2635 msgsystem::LeaveBigMessageMode();
2639 void character::Hostility (character *Enemy) {
2640 if (Enemy == this || !Enemy || !Team || !Enemy->Team) return;
2641 if (Enemy->IsMasochist() && GetRelation(Enemy) == FRIEND) return;
2642 if (!IsAlly(Enemy)) {
2643 GetTeam()->Hostility(Enemy->GetTeam());
2644 } else if (IsPlayer() && !Enemy->IsPlayer()) {
2645 // I believe both may be players due to polymorph feature...
2646 if (Enemy->CanBeSeenByPlayer()) ADD_MESSAGE("%s becomes enraged.", Enemy->CHAR_NAME(DEFINITE));
2647 Enemy->ChangeTeam(game::GetTeam(BETRAYED_TEAM));
2652 stack *character::GetGiftStack () const {
2653 if (GetLSquareUnder()->GetRoomIndex() && !GetLSquareUnder()->GetRoom()->AllowDropGifts()) return GetStack();
2654 return GetStackUnder();
2658 truth character::MoveRandomlyInRoom () {
2659 for (int c = 0; c < 10; ++c) {
2660 v2 ToTry = game::GetMoveVector(RAND()&7);
2661 if (GetLevel()->IsValidPos(GetPos()+ToTry)) {
2662 lsquare *Square = GetNearLSquare(GetPos()+ToTry);
2663 if (!Square->IsDangerous(this) && !Square->IsScary(this) &&
2664 (!Square->GetOLTerrain() || !Square->GetOLTerrain()->IsDoor()) &&
2665 TryMove(ToTry, false, false)) return true;
2668 return false;
2672 //#define dirlogf(...) do { fprintf(stderr, __VA_ARGS__); } while (0)
2673 #define dirlogf(...) ((void)0)
2676 static const int revDir[MDIR_STAND] = { MDIR_DOWN_RIGHT, MDIR_DOWN, MDIR_DOWN_LEFT, MDIR_RIGHT, MDIR_LEFT, MDIR_UP_RIGHT, MDIR_UP, MDIR_UP_LEFT };
2677 static const bool orthoDir[MDIR_STAND] = { false, true, false, true, true, false, true, false };
2680 // only for ortho moveDir
2681 static inline truth IsDirExcluded (int moveDir, int dir) {
2682 if (moveDir == dir) return true;
2683 switch (moveDir) {
2684 case MDIR_UP: return (dir == MDIR_UP_LEFT || dir == MDIR_UP_RIGHT);
2685 case MDIR_LEFT: return (dir == MDIR_UP_LEFT || dir == MDIR_DOWN_LEFT);
2686 case MDIR_RIGHT: return (dir == MDIR_UP_RIGHT || dir == MDIR_DOWN_RIGHT);
2687 case MDIR_DOWN: return (dir == MDIR_DOWN_LEFT || dir == MDIR_DOWN_RIGHT);
2689 return false;
2693 truth character::IsPassableSquare (int x, int y) const {
2694 if (x >= 0 && y >= 0) {
2695 area *ca = GetSquareUnder()->GetArea();
2696 lsquare *sq;
2698 if (x >= ca->GetXSize() || y >= ca->GetYSize()) return false;
2699 sq = static_cast<lsquare *>(ca->GetSquare(x, y));
2700 return sq && CanMoveOn(sq);
2702 return false;
2706 void character::CountPossibleMoveDirs (cv2 pos, int *odirs, int *ndirs, int exclideDir) const {
2707 if (odirs) *odirs = 0;
2708 if (ndirs) *ndirs = 0;
2709 for (int f = 0; f < MDIR_STAND; ++f) {
2710 if (!IsDirExcluded(exclideDir, f)) {
2711 if (IsPassableSquare(pos+game::GetMoveVector(f))) {
2712 if (orthoDir[f]) {
2713 if (odirs) ++(*odirs);
2714 } else {
2715 if (ndirs) ++(*ndirs);
2724 * in corridor (for orto-dirs):
2725 * count dirs excluding ortho-dir we going:
2726 * if there is one or less ortho-dirs and one or less non-ortho-dirs, we are in corridor
2728 // only for ortho-dirs
2729 truth character::IsInCorridor (int x, int y, int moveDir) const {
2730 int od = 0, nd = 0;
2732 dirlogf("IsInCorridor(%d,%d,%d)\n", x, y, moveDir);
2733 // reverse moveDir
2734 moveDir = (moveDir >= 0 && moveDir < MDIR_STAND ? revDir[moveDir] : -1);
2735 dirlogf(" reversedDir: %d\n", moveDir);
2736 CountPossibleMoveDirs(v2(x, y), &od, &nd, moveDir);
2737 dirlogf(" possibleDirs: (%d:%d)\n", od, nd);
2738 dirlogf(" IsInCorridor: %s\n", ((od <= 1 && nd <= 1) ? "yes" : "no"));
2739 return (od <= 1 && nd <= 1);
2743 cv2 character::GetDiagonalForDirs (int moveDir, int newDir) const {
2744 switch (moveDir) {
2745 case MDIR_UP:
2746 switch (newDir) {
2747 case MDIR_LEFT: return game::GetMoveVector(MDIR_UP_LEFT);
2748 case MDIR_RIGHT: return game::GetMoveVector(MDIR_UP_RIGHT);
2750 break;
2751 case MDIR_DOWN:
2752 switch (newDir) {
2753 case MDIR_LEFT: return game::GetMoveVector(MDIR_DOWN_LEFT);
2754 case MDIR_RIGHT: return game::GetMoveVector(MDIR_DOWN_RIGHT);
2756 break;
2757 case MDIR_LEFT:
2758 switch (newDir) {
2759 case MDIR_UP: return game::GetMoveVector(MDIR_UP_LEFT);
2760 case MDIR_DOWN: return game::GetMoveVector(MDIR_DOWN_LEFT);
2762 break;
2763 case MDIR_RIGHT:
2764 switch (newDir) {
2765 case MDIR_UP: return game::GetMoveVector(MDIR_UP_RIGHT);
2766 case MDIR_DOWN: return game::GetMoveVector(MDIR_DOWN_RIGHT);
2768 break;
2770 ABORT("wtf in character::GetDiagonalForDirs()");
2774 truth character::IsInTunnelDeadEnd () const {
2775 int od, nd;
2777 CountPossibleMoveDirs(GetPos(), &od, &nd, -1);
2778 return (od <= 1 && nd == 0);
2783 * try to walk in the given dir
2784 * can do two steps without a turn and still in corridor?
2785 * yes:
2786 * just go
2787 * no:
2788 * go in non-ortho dir, set prevdir to last ortho-dir from corridor tracing
2790 // only for ortho-dirs; assume that the char is in corridor
2791 int character::CheckCorridorMove (v2 &moveVector, cv2 pos, int moveDir, truth *markAsTurn) const {
2792 moveVector = game::GetMoveVector(moveDir);
2793 v2 ps1(pos+moveVector);
2795 if (markAsTurn) *markAsTurn = true;
2797 if (IsPassableSquare(ps1)) {
2798 // we can do first step in the given dir
2799 // check if we will be in corridor after it
2800 dirlogf("CheckCorridorMove: can do first step\n");
2801 if (IsInCorridor(ps1, moveDir)) {
2802 // check second step
2803 v2 ps2(ps1+moveVector);
2804 dirlogf("CheckCorridorMove: still in corridor after the first step\n");
2805 if (IsPassableSquare(ps2)) {
2806 // can do second step
2807 dirlogf("CheckCorridorMove: can do second step\n");
2808 return moveDir;
2809 } else {
2810 // can't do second step; but we still in corridor, so we should make a turn
2811 int newDir = -1; // direction to turn
2812 for (int f = 0; f < MDIR_STAND; ++f) {
2813 if (f != moveDir && orthoDir[f] && f != revDir[moveDir] && IsPassableSquare(ps1+game::GetMoveVector(f))) {
2814 newDir = f;
2815 break;
2818 dirlogf("CheckCorridorMove: can't do second step; moveDir=%d; newDir=%d\n", moveDir, newDir);
2819 if (newDir < 0) {
2820 // dead end, will stop
2821 return moveDir;
2823 // we should do diagonal move
2824 moveVector = GetDiagonalForDirs(moveDir, newDir);
2825 // if this is 'one-tile-turn', we should not change the direction to newDir
2826 if (IsPassableSquare(ps1+game::GetMoveVector(newDir)+game::GetMoveVector(moveDir))) {
2827 // yes, this is 'one-tile-turn'
2828 dirlogf("CheckCorridorMove: one-tile-turn, don't change dir\n");
2829 /* 'g'o bug:
2831 * ####.######
2832 * ####*......
2833 * ..@..######
2834 * ######
2836 * 'g'o right: should stop at '*', but it just goes right
2838 if (markAsTurn) *markAsTurn = IsInCorridor(ps1+game::GetMoveVector(newDir), newDir);
2839 newDir = moveDir;
2841 return newDir;
2844 dirlogf("CheckCorridorMove: can do one or two steps; move forward\n");
2845 // can do one or two steps: check for T-junction
2846 // we should stop if we have more than two open dirs, or one of open dirs is not moveDir
2847 int dcount = 0;
2848 for (int f = 0; f < MDIR_STAND; ++f) {
2849 if (f == revDir[moveDir]) continue; // skip "reverse dir" check
2850 v2 ps2(pos+game::GetMoveVector(f));
2851 if (IsPassableSquare(ps2)) {
2852 ++dcount;
2853 if (dcount > 2) return -1; // more than two open dirs, stop
2854 if (f != moveDir) return -1; // one of open dirs is not moveDir
2857 // just move forward
2858 return moveDir;
2860 dirlogf("CheckCorridorMove: dead end\n");
2861 // can't go, assume invalid direction
2862 return -1;
2866 truth character::IsDangerousSquare (v2 pos) const {
2867 if (!IsPassableSquare(pos)) return false;
2868 lsquare *MoveToSquare[MAX_SQUARES_UNDER];
2869 auto Squares = CalculateNewSquaresUnder(MoveToSquare, pos);
2870 for (decltype(Squares) c = 0; c < Squares; ++c) {
2871 lsquare *Square = MoveToSquare[c];
2872 if (IsPlayer()) {
2873 if (!Square->HasBeenSeen()) continue;
2874 } else {
2875 if (!Square->CanBeSeenBy(this)) continue;
2877 // check if someone is standing at the square
2878 if (Square->GetCharacter() && GetTeam() != Square->GetCharacter()->GetTeam() && Square->GetCharacter()->CanBeSeenBy(this)) return true;
2879 if (Square->IsDangerous(this)) {
2880 if (IsPlayer() && Square->HasBeenSeen()) return true;
2881 if (Square->CanBeSeenBy(this)) return true;
2884 return false;
2888 void character::MarkAdjacentItemsAsSeen (v2 pos) {
2889 lsquare *sqlist[MAX_SQUARES_UNDER];
2890 for (int d = 0; d < MDIR_STAND; ++d) {
2891 auto np = pos+game::GetMoveVector(d);
2892 if (!IsPassableSquare(np)) continue;
2893 auto sqcount = CalculateNewSquaresUnder(sqlist, np);
2894 for (int n = 0; n < sqcount; ++n) {
2895 lsquare *sq = sqlist[n];
2896 if ((IsPlayer() && sq->HasBeenSeen()) || sq->CanBeSeenBy(this)) {
2897 sq->GetStack()->SetSteppedOn(true);
2904 truth character::HasInterestingItemsAt (v2 pos) {
2905 if (!IsPassableSquare(pos)) return false;
2906 lsquare *MoveToSquare[MAX_SQUARES_UNDER];
2907 int sq2 = CalculateNewSquaresUnder(MoveToSquare, pos);
2908 for (int c = 0; c < sq2; ++c) {
2909 lsquare *sq = MoveToSquare[c];
2910 if (IsPlayer()) {
2911 if (!sq->HasBeenSeen()) continue;
2912 } else {
2913 if (!sq->CanBeSeenBy(this)) continue;
2915 if (sq->GetStack()->HasSomethingFunny(this, ivanconfig::GetStopOnCorpses(), ivanconfig::GetStopOnSeenItems())) {
2916 return true;
2919 return false;
2923 void character::GoOn (go *Go, truth FirstStep) {
2924 dirlogf("=== character::GoOn; dir=%d; pos=(%d,%d) ===\n", Go->GetDirection(), GetPos().X, GetPos().Y);
2925 if (FirstStep) {
2926 dirlogf("FirstStep\n");
2927 mPrevMoveDir = Go->GetDirection();
2928 Go->SetIsWalkingInOpen(!IsInCorridor(Go->GetDirection()));
2931 v2 MoveVector = ApplyStateModification(game::GetMoveVector(Go->GetDirection()));
2932 lsquare *MoveToSquare[MAX_SQUARES_UNDER];
2933 int Squares = CalculateNewSquaresUnder(MoveToSquare, GetPos()+MoveVector);
2934 int moveDir = game::MoveVectorToDirection(MoveVector);
2936 if (Squares == 0 || !CanMoveOn(MoveToSquare[0])) {
2937 dirlogf("just can't move\n");
2938 Go->Terminate(false);
2939 return;
2942 if (FirstStep) {
2943 // first step: mark all adjacent items as seen
2944 MarkAdjacentItemsAsSeen(GetPos());
2945 } else {
2946 // not a first step
2948 // check for corridor<->open place
2949 if (!Go->GetPrevWasTurn() && Go->IsWalkingInOpen() != !IsInCorridor(GetPos(), moveDir)) {
2950 dirlogf("moved to/from open place\n");
2951 Go->Terminate(false);
2952 return;
2955 // check for room change
2956 uInt OldRoomIndex = GetLSquareUnder()->GetRoomIndex();
2957 uInt CurrentRoomIndex = MoveToSquare[0]->GetRoomIndex();
2958 if (OldRoomIndex && (CurrentRoomIndex != OldRoomIndex)) {
2959 // room is about to be changed, stop here
2960 dirlogf("room about to be changed\n");
2961 Go->Terminate(false);
2962 return;
2965 // stop near a dangerous square
2966 if (IsDangerousSquare(GetPos()+MoveVector)) {
2967 dirlogf("sense the danger\n");
2968 Go->Terminate(false);
2969 return;
2973 // if the state modified the direction, move and stop
2974 if (moveDir != Go->GetDirection()) {
2975 dirlogf("move affected by state\n");
2976 if (TryMove(MoveVector, true, game::PlayerIsRunning())) {
2977 game::DrawEverything();
2978 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
2980 Go->Terminate(false);
2981 return;
2984 truth doStop = false, markAsTurn = false;
2985 if (!FirstStep) {
2986 // continuous walking
2987 if (Go->IsWalkingInOpen() || !orthoDir[moveDir]) {
2988 // walking in open space or diagonal walking
2989 v2 newPos(GetPos()+MoveVector);
2990 int ood, ond, nod, nnd;
2992 * open: stop if # of possible dirs in next step != # of possible dirs in current step
2993 * (or next step is in corridor)
2995 dirlogf("open walking\n");
2996 if (IsInCorridor(newPos, moveDir)) {
2997 // trying to enter the corridor, stop right here
2998 dirlogf("entering the corridor\n");
2999 Go->Terminate(false);
3000 return;
3002 CountPossibleMoveDirs(GetPos(), &ood, &ond);
3003 CountPossibleMoveDirs(newPos, &nod, &nnd);
3004 if (ood != nod || ond != nnd) {
3005 // # of directions to walk to changed, stop right here
3006 dirlogf("# of directions changed from (%d:%d) to (%d:%d)\n", ood, ond, nod, nnd);
3007 //Go->Terminate(false);
3008 //return;
3009 doStop = true;
3011 // ok, we can do this move
3012 } else {
3013 // ortho-walking thru the corridor
3014 int newDir = CheckCorridorMove(MoveVector, GetPos(), moveDir, &markAsTurn);
3015 if (newDir < 0) {
3016 // ah, something weird; stop right here
3017 Go->Terminate(false);
3018 return;
3020 // if this is diagonal move: check if we have something interesting in previous dir
3021 if (MoveVector.X && MoveVector.Y) {
3022 if (HasInterestingItemsAt(GetPos()+game::GetMoveVector(moveDir))) {
3023 // move to item and stop
3024 newDir = moveDir;
3025 MoveVector = game::GetMoveVector(moveDir);
3026 doStop = true;
3029 // as `MoveVector` can change here, recalc squares
3030 Squares = CalculateNewSquaresUnder(MoveToSquare, GetPos()+MoveVector);
3031 // perform possible turn
3032 Go->SetDirection(newDir);
3035 // stop near the dangerous square
3036 for (int mdv = 0; mdv < MDIR_STAND; ++mdv) {
3037 if (IsDangerousSquare(GetPos()+MoveVector+game::GetMoveVector(mdv))) {
3038 dirlogf(" danger!\n");
3039 Go->Terminate(false);
3040 return;
3045 // now try to perform the move
3046 dirlogf("trying to make the move\n");
3048 square *BeginSquare = GetSquareUnder();
3049 uInt OldRoomIndex = GetLSquareUnder()->GetRoomIndex();
3050 uInt CurrentRoomIndex = MoveToSquare[0]->GetRoomIndex();
3052 // stop on the square with something interesting
3053 if (!doStop) {
3054 // idiotic code!
3055 area *ca = GetSquareUnder()->GetArea();
3056 v2 npos = GetPos()+MoveVector;
3057 for (int f = 0; f < MDIR_STAND; ++f) {
3058 v2 np = npos+game::GetMoveVector(f);
3059 if (np.X >= 0 && np.Y >= 0 && np.X < ca->GetXSize() && np.Y < ca->GetYSize()) {
3060 lsquare *sq = static_cast<lsquare *>(ca->GetSquare(np.X, np.Y));
3061 if (IsPlayer()) {
3062 if (!sq->HasBeenSeen()) continue;
3063 } else {
3064 if (!sq->CanBeSeenBy(this)) continue;
3066 //if (!sq->CanBeSeenBy(this)) continue;
3067 olterrain *terra = sq->GetOLTerrain();
3068 if (terra) {
3069 dirlogf("** OK terra at %d; door: %s; seen: %s\n", f, (terra->IsDoor() ? "yes" : "no"), (sq->IsGoSeen() ? "yes" : "no"));
3070 if (terra->IsDoor()) {
3071 if (ivanconfig::GetStopOnSeenDoors() || !sq->IsGoSeen()) {
3072 dirlogf(" *** stop near the door\n");
3073 doStop = true;
3074 break;
3081 // check items
3082 if (!doStop) {
3083 for (int c = 0; c < Squares; ++c) {
3084 lsquare *Square = MoveToSquare[c];
3085 if (IsPlayer()) {
3086 if (!Square->HasBeenSeen()) continue;
3087 } else {
3088 if (!Square->CanBeSeenBy(this)) continue;
3090 if (Square->GetStack()->HasSomethingFunny(this, ivanconfig::GetStopOnCorpses(), ivanconfig::GetStopOnSeenItems())) {
3091 dirlogf(" stepped near something interesting\n");
3092 doStop = true;
3093 break;
3098 // check items in adjacent squares too, so diagonal move won't miss any
3099 if (!doStop) {
3100 for (int f = 0; f < MDIR_STAND && !doStop; ++f) {
3101 v2 np = game::GetMoveVector(f);
3102 if (np == MoveVector) continue; // this will be checked on the next move
3103 if (HasInterestingItemsAt(GetPos()+np)) {
3104 dirlogf(" stepped near something interesting\n");
3105 // stop
3106 Go->Terminate(false);
3107 return;
3113 Go->SetPrevWasTurn(markAsTurn && MoveVector.X && MoveVector.Y); // diagonal move?
3115 truth moveOk = TryMove(MoveVector, true, game::PlayerIsRunning());
3117 if (!moveOk || BeginSquare == GetSquareUnder() || (CurrentRoomIndex && (OldRoomIndex != CurrentRoomIndex))) {
3118 dirlogf(" stopped\n");
3119 if (moveOk) {
3120 game::DrawEverything();
3121 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
3123 Go->Terminate(false);
3124 return;
3127 if (FirstStep) {
3128 mPrevMoveDir = Go->GetDirection();
3129 Go->SetIsWalkingInOpen(!IsInCorridor(moveDir));
3132 game::DrawEverything();
3133 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
3134 if (doStop) Go->Terminate(false);
3138 void character::SetTeam (team *What) {
3139 Team = What;
3140 What->Add(this);
3144 void character::ChangeTeam (team *What) {
3145 if (Team) Team->Remove(this);
3146 Team = What;
3147 SendNewDrawRequest();
3148 if (Team) Team->Add(this);
3152 truth character::ChangeRandomAttribute (int HowMuch) {
3153 for (int c = 0; c < 50; ++c) {
3154 int AttribID = RAND()%ATTRIBUTES;
3155 if (EditAttribute(AttribID, HowMuch)) return true;
3157 return false;
3161 int character::RandomizeReply (sLong &Said, int Replies) {
3162 truth NotSaid = false;
3163 for (int c = 0; c < Replies; ++c) {
3164 if (!(Said & (1 << c))) {
3165 NotSaid = true;
3166 break;
3169 if (!NotSaid) Said = 0;
3170 sLong ToSay;
3171 while (Said & 1 << (ToSay = RAND() % Replies));
3172 Said |= 1 << ToSay;
3173 return ToSay;
3177 void character::DisplayInfo (festring &Msg) {
3178 if (IsPlayer()) {
3179 Msg << " You are " << GetStandVerb() << " here.";
3180 } else {
3181 Msg << ' ' << GetName(INDEFINITE).CapitalizeCopy() << " is " << GetStandVerb() << " here. " << GetPersonalPronoun().CapitalizeCopy();
3182 cchar *Separator1 = GetAction() ? "," : " and";
3183 cchar *Separator2 = " and";
3184 if (GetTeam() == PLAYER->GetTeam()) {
3185 Msg << " is tame";
3186 } else {
3187 int Relation = GetRelation(PLAYER);
3188 if (Relation == HOSTILE) Msg << " is hostile";
3189 else if (Relation == UNCARING) {
3190 Msg << " does not care about you";
3191 Separator1 = Separator2 = " and is";
3192 } else {
3193 Msg << " is friendly";
3196 if (StateIsActivated(PANIC)) {
3197 Msg << Separator1 << " panicked";
3198 Separator2 = " and";
3200 if (GetAction()) Msg << Separator2 << ' ' << GetAction()->GetDescription();
3201 Msg << '.';
3206 void character::TestWalkability () {
3207 if (!IsEnabled()) return;
3208 square *SquareUnder = !game::IsInWilderness() ? GetSquareUnder() : PLAYER->GetSquareUnder();
3209 if (SquareUnder->IsFatalToStay() && !CanMoveOn(SquareUnder)) {
3210 truth Alive = false;
3211 if (!game::IsInWilderness() || IsPlayer()) {
3212 for (int d = 0; d < GetNeighbourSquares(); ++d) {
3213 square *Square = GetNeighbourSquare(d);
3214 if (Square && CanMoveOn(Square) && IsFreeForMe(Square)) {
3215 if (IsPlayer()) ADD_MESSAGE("%s.", SquareUnder->SurviveMessage(this));
3216 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE), SquareUnder->MonsterSurviveMessage(this));
3217 Move(Square->GetPos(), true); // actually, this shouldn't be a teleport move
3218 SquareUnder->SurviveEffect(this);
3219 Alive = true;
3220 break;
3224 if (!Alive) {
3225 if (IsPlayer()) {
3226 Remove();
3227 SendToHell();
3228 festring DeathMsg = festring(SquareUnder->DeathMessage(this));
3229 game::AskForEscPress(DeathMsg+".");
3230 festring Msg = SquareUnder->ScoreEntry(this);
3231 PLAYER->AddScoreEntry(Msg);
3232 game::End(Msg);
3233 } else {
3234 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE), SquareUnder->MonsterDeathVerb(this));
3235 Die(0, SquareUnder->ScoreEntry(this), DISALLOW_MSG);
3242 int character::GetSize () const {
3243 if (GetTorso()->GetSize() < 1) {
3244 fprintf(stderr, "WARNING: character::GetSize() is %d for %s!\n", GetTorso()->GetSize(), GetNameSingular().CStr());
3245 return 1;
3247 return GetTorso()->GetSize();
3251 void character::SetMainMaterial (material *NewMaterial, int SpecialFlags) {
3252 NewMaterial->SetVolume(GetBodyPart(0)->GetMainMaterial()->GetVolume());
3253 GetBodyPart(0)->SetMainMaterial(NewMaterial, SpecialFlags);
3254 for (int c = 1; c < BodyParts; ++c) {
3255 NewMaterial = NewMaterial->SpawnMore(GetBodyPart(c)->GetMainMaterial()->GetVolume());
3256 GetBodyPart(c)->SetMainMaterial(NewMaterial, SpecialFlags);
3261 void character::ChangeMainMaterial (material *NewMaterial, int SpecialFlags) {
3262 NewMaterial->SetVolume(GetBodyPart(0)->GetMainMaterial()->GetVolume());
3263 GetBodyPart(0)->ChangeMainMaterial(NewMaterial, SpecialFlags);
3264 for (int c = 1; c < BodyParts; ++c) {
3265 NewMaterial = NewMaterial->SpawnMore(GetBodyPart(c)->GetMainMaterial()->GetVolume());
3266 GetBodyPart(c)->ChangeMainMaterial(NewMaterial, SpecialFlags);
3271 void character::SetSecondaryMaterial (material *, int) {
3272 ABORT("Illegal character::SetSecondaryMaterial call!");
3276 void character::ChangeSecondaryMaterial (material *, int) {
3277 ABORT("Illegal character::ChangeSecondaryMaterial call!");
3281 void character::TeleportRandomly (truth Intentional) {
3282 v2 TelePos = ERROR_V2;
3283 if (StateIsActivated(TELEPORT_LOCK)) { ADD_MESSAGE("You flicker for a second."); return; }
3284 if (StateIsActivated(TELEPORT_CONTROL)) {
3285 if (IsPlayer()) {
3286 v2 Input = game::PositionQuestion(CONST_S("Where do you wish to teleport? [direction keys move cursor, space accepts]"), GetPos(), &game::TeleportHandler, 0, false);
3287 if (Input == ERROR_V2) Input = GetPos(); // esc pressed
3288 lsquare *Square = GetNearLSquare(Input);
3289 if (CanMoveOn(Square) || game::GoThroughWallsCheatIsActive()) {
3290 if (Square->GetPos() == GetPos()) {
3291 ADD_MESSAGE("You disappear and reappear.");
3292 return;
3294 if (IsFreeForMe(Square)) {
3295 if ((Input-GetPos()).GetLengthSquare() <= GetTeleportRangeSquare()) {
3296 EditExperience(INTELLIGENCE, 100, 1 << 10);
3297 TelePos = Input;
3298 } else {
3299 ADD_MESSAGE("You cannot concentrate yourself enough to control a teleport that far.");
3301 } else {
3302 character *C = Square->GetCharacter();
3303 if (C) ADD_MESSAGE("For a moment you feel very much like %s.", C->CHAR_NAME(INDEFINITE));
3304 else ADD_MESSAGE("You feel that something weird has happened, but can't really tell what exactly.");
3306 } else {
3307 ADD_MESSAGE("You feel like having been hit by something really hard from the inside.");
3309 } else if (!Intentional) {
3310 if (IsGoingSomeWhere() && GetLevel()->IsValidPos(GoingTo)) {
3311 v2 Where = GetLevel()->GetNearestFreeSquare(this, GoingTo);
3312 if (Where != ERROR_V2 && (Where-GetPos()).GetLengthSquare() <= GetTeleportRangeSquare()) {
3313 EditExperience(INTELLIGENCE, 100, 1 << 10);
3314 Where = TelePos;
3320 if (TelePos == ERROR_V2) {
3321 TelePos = GetLevel()->GetRandomSquare(this);
3322 if (TelePos == ERROR_V2) return;
3325 if (IsPlayer()) {
3326 ADD_MESSAGE("A rainbow-colored whirlpool twists the existence around you. You are sucked through a tunnel piercing a myriad of surreal universes. Luckily you return to this dimension in one piece.");
3329 room *PossibleRoom = game::GetCurrentLevel()->GetLSquare(TelePos)->GetRoom();
3330 if (!PossibleRoom) {
3331 //if it's outside of a room
3332 Move(TelePos, true);
3333 if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE));
3334 if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
3335 } else if (PossibleRoom && PossibleRoom->IsOKToTeleportInto()) {
3336 // If it's inside of a room, check whether a ward is active that might impede the player
3337 Move(TelePos, true);
3338 if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE));
3339 if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
3340 } else {
3341 if (IsPlayer()) {
3342 ADD_MESSAGE("A mighty force blasts you back to where you were standing. A ward prevents you from teleporting.");
3344 game::GetCurrentLevel()->Explosion(this, CONST_S("killed by an explosion triggered when attempting to teleport into room protected by a ward"), PLAYER->GetPos(), 300 >> 3, false);
3346 beamdata Beam
3348 this,
3349 CONST_S("killed by an explosion triggered when attempting to teleport into room protected by a ward"),
3350 YOURSELF,
3351 3 // or 0 ?
3353 lsquare* Square = GetNearLSquare(GetPos());
3354 Square->DrawParticles(RED);
3355 Square->FireBall(Beam);*/
3360 void character::DoDetecting () {
3361 material *TempMaterial;
3363 for (;;) {
3364 festring Temp = game::DefaultQuestion(CONST_S("What material do you want to detect?"), game::GetDefaultDetectMaterial());
3365 TempMaterial = protosystem::CreateMaterial(Temp);
3366 if (TempMaterial) break;
3367 game::DrawEverythingNoBlit();
3370 level *Level = GetLevel();
3371 int Squares = Level->DetectMaterial(TempMaterial);
3373 if (Squares > GetAttribute(INTELLIGENCE) * (25+RAND()%51)) {
3374 ADD_MESSAGE("An enormous burst of geographical information overwhelms your consciousness. Your mind cannot cope with it and your memories blur.");
3375 Level->BlurMemory();
3376 BeginTemporaryState(CONFUSED, 1000 + RAND() % 1000);
3377 EditExperience(INTELLIGENCE, -100, 1 << 12);
3378 } else if (!Squares) {
3379 ADD_MESSAGE("You feel a sudden urge to imagine the dark void of a starless night sky.");
3380 EditExperience(INTELLIGENCE, 200, 1 << 12);
3381 } else {
3382 ADD_MESSAGE("You feel attracted to all things made of %s.", TempMaterial->GetName(false, false).CStr());
3383 game::PositionQuestion(CONST_S("Detecting material [direction keys move cursor, space exits]"), GetPos(), 0, 0, false);
3384 EditExperience(INTELLIGENCE, 300, 1 << 12);
3387 delete TempMaterial;
3388 Level->CalculateLuminances();
3389 game::SendLOSUpdateRequest();
3393 void character::RestoreHP () {
3394 doforbodyparts()(this, &bodypart::FastRestoreHP);
3395 HP = MaxHP;
3399 void character::RestoreLivingHP () {
3400 HP = 0;
3401 for (int c = 0; c < BodyParts; ++c) {
3402 bodypart *BodyPart = GetBodyPart(c);
3403 if (BodyPart && BodyPart->CanRegenerate()) {
3404 BodyPart->FastRestoreHP();
3405 HP += BodyPart->GetHP();
3411 truth character::AllowDamageTypeBloodSpill (int Type) {
3412 if ((Type&0xFFF) == PHYSICAL_DAMAGE) return true;
3413 if ((Type&0xFFF) == SOUND) return true;
3414 if ((Type&0xFFF) == ENERGY) return true;
3416 if ((Type&0xFFF) == ACID) return false;
3417 if ((Type&0xFFF) == FIRE) return false;
3418 if ((Type&0xFFF) == DRAIN) return false;
3419 if ((Type&0xFFF) == POISON) return false;
3420 if ((Type&0xFFF) == ELECTRICITY) return false;
3421 if ((Type&0xFFF) == MUSTARD_GAS_DAMAGE) return false;
3422 if ((Type&0xFFF) == PSI) return false;
3424 ABORT("Unknown blood effect destroyed the dungeon!");
3425 return false;
3429 /* Returns truly done damage */
3430 int character::ReceiveBodyPartDamage (character *Damager, int Damage, int Type, int BodyPartIndex,
3431 int Direction, truth PenetrateResistance, truth Critical, truth ShowNoDamageMsg, truth CaptureBodyPart)
3433 bodypart *BodyPart = GetBodyPart(BodyPartIndex);
3434 if (!Damager || Damager->AttackMayDamageArmor()) BodyPart->DamageArmor(Damager, Damage, Type);
3435 if (!PenetrateResistance) {
3436 Damage -= (BodyPart->GetTotalResistance(Type)>>1)+RAND()%((BodyPart->GetTotalResistance(Type)>>1)+1);
3438 if (int(Damage) < 1) {
3439 if (Critical) {
3440 Damage = 1;
3441 } else {
3442 if (ShowNoDamageMsg) {
3443 if (IsPlayer()) ADD_MESSAGE("You are not hurt.");
3444 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s is not hurt.", GetPersonalPronoun().CStr());
3446 return 0;
3450 if (Critical && AllowDamageTypeBloodSpill(Type) && !game::IsInWilderness()) {
3451 BodyPart->SpillBlood(2+(RAND()&1));
3452 for (int d = 0; d < GetNeighbourSquares(); ++d) {
3453 lsquare *Square = GetNeighbourLSquare(d);
3454 if (Square && Square->IsFlyable()) BodyPart->SpillBlood(1, Square->GetPos());
3458 if (BodyPart->ReceiveDamage(Damager, Damage, Type, Direction) && BodyPartCanBeSevered(BodyPartIndex)) {
3459 if (DamageTypeDestroysBodyPart(Type)) {
3460 if (IsPlayer()) ADD_MESSAGE("Your %s is destroyed!", BodyPart->GetBodyPartName().CStr());
3461 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s is destroyed!", GetPossessivePronoun().CStr(), BodyPart->GetBodyPartName().CStr());
3462 GetBodyPart(BodyPartIndex)->DropEquipment();
3463 item *Severed = SevereBodyPart(BodyPartIndex);
3464 if (Severed) Severed->DestroyBodyPart(!game::IsInWilderness() ? GetStackUnder() : GetStack());
3465 SendNewDrawRequest();
3466 if (IsPlayer()) game::AskForEscPress(CONST_S("Bodypart destroyed!"));
3467 } else {
3468 if (IsPlayer()) ADD_MESSAGE("Your %s is severed off!", BodyPart->GetBodyPartName().CStr());
3469 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s is severed off!", GetPossessivePronoun().CStr(), BodyPart->GetBodyPartName().CStr());
3470 item *Severed = SevereBodyPart(BodyPartIndex);
3471 SendNewDrawRequest();
3472 if (Severed) {
3473 if (CaptureBodyPart) {
3474 Damager->GetLSquareUnder()->AddItem(Severed);
3475 } else if (!game::IsInWilderness()) {
3476 /** No multi-tile humanoid support! */
3477 GetStackUnder()->AddItem(Severed);
3478 if (Direction != YOURSELF) Severed->Fly(0, Direction, Damage);
3479 } else {
3480 GetStack()->AddItem(Severed);
3482 Severed->DropEquipment();
3483 } else if (IsPlayer() || CanBeSeenByPlayer()) {
3484 ADD_MESSAGE("It vanishes.");
3486 if (IsPlayer()) game::AskForEscPress(CONST_S("Bodypart severed!"));
3488 if (CanPanicFromSeveredBodyPart() && RAND()%100 < GetPanicLevel() && !StateIsActivated(PANIC) && !IsDead() && !StateIsActivated(FEARLESS)) {
3489 BeginTemporaryState(PANIC, 1000+RAND()%1001);
3491 SpecialBodyPartSeverReaction();
3494 if (!IsDead()) CheckPanic(500);
3496 return Damage;
3500 /* Returns 0 if bodypart disappears */
3501 item *character::SevereBodyPart (int BodyPartIndex, truth ForceDisappearance, stack *EquipmentDropStack) {
3502 bodypart *BodyPart = GetBodyPart(BodyPartIndex);
3503 if (StateIsActivated(LEPROSY)) BodyPart->GetMainMaterial()->SetIsInfectedByLeprosy(true);
3504 if (ForceDisappearance || BodyPartsDisappearWhenSevered() || StateIsActivated(POLYMORPHED) || game::AllBodyPartsVanish()) {
3505 BodyPart->DropEquipment(EquipmentDropStack);
3506 BodyPart->RemoveFromSlot();
3507 CalculateAttributeBonuses();
3508 CalculateBattleInfo();
3509 BodyPart->SendToHell();
3510 SignalPossibleTransparencyChange();
3511 RemoveTraps(BodyPartIndex);
3512 return 0;
3514 BodyPart->SetOwnerDescription("of " + GetName(INDEFINITE));
3515 BodyPart->SetIsUnique(LeftOversAreUnique());
3516 UpdateBodyPartPicture(BodyPartIndex, true);
3517 BodyPart->RemoveFromSlot();
3518 BodyPart->RandomizePosition();
3519 CalculateAttributeBonuses();
3520 CalculateBattleInfo();
3521 BodyPart->Enable();
3522 SignalPossibleTransparencyChange();
3523 RemoveTraps(BodyPartIndex);
3524 return BodyPart;
3528 /* The second int is actually TargetFlags, which is not used here, but seems to be used in humanoid::ReceiveDamage.
3529 * Returns true if the character really receives damage */
3530 truth character::ReceiveDamage (character *Damager, int Damage, int Type, int, int Direction,
3531 truth, truth PenetrateArmor, truth Critical, truth ShowMsg)
3533 truth Affected = ReceiveBodyPartDamage(Damager, Damage, Type, 0, Direction, PenetrateArmor, Critical, ShowMsg);
3534 if (DamageTypeAffectsInventory(Type)) {
3535 for (int c = 0; c < GetEquipments(); ++c) {
3536 item *Equipment = GetEquipment(c);
3537 if (Equipment) Equipment->ReceiveDamage(Damager, Damage, Type);
3539 GetStack()->ReceiveDamage(Damager, Damage, Type);
3541 return Affected;
3545 festring character::GetDescription (int Case) const {
3546 if (IsPlayer()) return CONST_S("you");
3547 if (CanBeSeenByPlayer()) return GetName(Case);
3548 return CONST_S("something");
3552 festring character::GetPersonalPronoun (truth PlayersView) const {
3553 if (IsPlayer() && PlayersView) return CONST_S("you");
3554 if (GetSex() == UNDEFINED || (PlayersView && !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("it");
3555 if (GetSex() == MALE) return CONST_S("he");
3556 return CONST_S("she");
3560 festring character::GetPossessivePronoun (truth PlayersView) const {
3561 if (IsPlayer() && PlayersView) return CONST_S("your");
3562 if (GetSex() == UNDEFINED || (PlayersView && !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("its");
3563 if (GetSex() == MALE) return CONST_S("his");
3564 return CONST_S("her");
3568 festring character::GetObjectPronoun (truth PlayersView) const {
3569 if (IsPlayer() && PlayersView) return CONST_S("you");
3570 if (GetSex() == UNDEFINED || (PlayersView && !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("it");
3571 if (GetSex() == MALE) return CONST_S("him");
3572 return CONST_S("her");
3576 void character::AddName (festring &String, int Case) const {
3577 if (AssignedName.IsEmpty()) {
3578 id::AddName(String, Case);
3579 } else if (!(Case & PLURAL)) {
3580 if (!ShowClassDescription()) {
3581 String << AssignedName;
3582 } else {
3583 String << AssignedName << ' ';
3584 id::AddName(String, (Case|ARTICLE_BIT)&~INDEFINE_BIT);
3586 } else {
3587 id::AddName(String, Case);
3588 String << " named " << AssignedName;
3593 int character::GetHungerState () const {
3594 if (!UsesNutrition()) return NOT_HUNGRY;
3595 if (GetNP() > OVER_FED_LEVEL) return OVER_FED;
3596 if (GetNP() > BLOATED_LEVEL) return BLOATED;
3597 if (GetNP() > SATIATED_LEVEL) return SATIATED;
3598 if (GetNP() > NOT_HUNGER_LEVEL) return NOT_HUNGRY;
3599 if (GetNP() > HUNGER_LEVEL) return HUNGRY;
3600 if (GetNP() > VERY_HUNGER_LEVEL) return VERY_HUNGRY;
3601 return STARVING;
3605 truth character::CanConsume (material *Material) const {
3606 return GetConsumeFlags() & Material->GetConsumeType();
3610 void character::SetTemporaryStateCounter (sLong State, int What) {
3611 for (int c = 0; c < STATES; ++c) {
3612 if ((1 << c) & State) TemporaryStateCounter[c] = What;
3617 void character::EditTemporaryStateCounter (sLong State, int What) {
3618 for (int c = 0; c < STATES; ++c) {
3619 if ((1 << c) & State) TemporaryStateCounter[c] += What;
3624 int character::GetTemporaryStateCounter (sLong State) const {
3625 for (int c = 0; c < STATES; ++c) {
3626 if ((1 << c) & State) return TemporaryStateCounter[c];
3628 ABORT("Illegal GetTemporaryStateCounter request!");
3629 return 0;
3633 truth character::CheckKick () const {
3634 if (!CanKick()) {
3635 if (IsPlayer()) ADD_MESSAGE("This race can't kick.");
3636 return false;
3638 return true;
3642 int character::GetResistance (int Type) const {
3643 if ((Type&0xFFF) == PHYSICAL_DAMAGE) return 0;
3644 if ((Type&0xFFF) == DRAIN) return 0;
3645 if ((Type&0xFFF) == MUSTARD_GAS_DAMAGE) return 0;
3646 if ((Type&0xFFF) == PSI) return 0;
3648 if ((Type&0xFFF) == ENERGY) return GetEnergyResistance();
3649 if ((Type&0xFFF) == FIRE) return GetFireResistance();
3650 if ((Type&0xFFF) == POISON) return GetPoisonResistance();
3651 if ((Type&0xFFF) == ELECTRICITY) return GetElectricityResistance();
3652 if ((Type&0xFFF) == ACID) return GetAcidResistance();
3653 if ((Type&0xFFF) == SOUND) return GetSoundResistance();
3655 ABORT("Resistance lack detected!");
3656 return 0;
3660 void character::Regenerate () {
3661 if (HP == MaxHP) {
3662 if (StateIsActivated(REGENERATION) && !(RAND()%3000)) {
3663 bodypart *NewBodyPart = GenerateRandomBodyPart();
3664 if (!NewBodyPart) return;
3665 NewBodyPart->SetHP(1);
3666 if (IsPlayer()) ADD_MESSAGE("You grow a new %s.", NewBodyPart->GetBodyPartName().CStr());
3667 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE), NewBodyPart->GetBodyPartName().CStr());
3669 return;
3671 sLong RegenerationBonus = 0;
3672 truth NoHealableBodyParts = true;
3673 for (int c = 0; c < BodyParts; ++c) {
3674 bodypart *BodyPart = GetBodyPart(c);
3675 if (BodyPart && BodyPart->CanRegenerate()) {
3676 RegenerationBonus += BodyPart->GetMaxHP();
3677 if (NoHealableBodyParts && BodyPart->GetHP() < BodyPart->GetMaxHP()) NoHealableBodyParts = false;
3680 if (!RegenerationBonus || NoHealableBodyParts) return;
3681 RegenerationBonus *= (50+GetAttribute(ENDURANCE));
3683 if (Action && Action->IsRest()) {
3684 if (SquaresUnder == 1) RegenerationBonus *= GetSquareUnder()->GetRestModifier() << 1;
3685 else {
3686 int Lowest = GetSquareUnder(0)->GetRestModifier();
3687 for (int c = 1; c < GetSquaresUnder(); ++c) {
3688 int Mod = GetSquareUnder(c)->GetRestModifier();
3689 if (Mod < Lowest) Lowest = Mod;
3691 RegenerationBonus *= Lowest << 1;
3695 RegenerationCounter += RegenerationBonus;
3697 while (RegenerationCounter > 1250000) {
3698 bodypart *BodyPart = HealHitPoint();
3699 if (!BodyPart) break;
3700 EditNP(-Max(7500/MaxHP, 1));
3701 RegenerationCounter -= 1250000;
3702 int HP = BodyPart->GetHP();
3703 EditExperience(ENDURANCE, Min(1000*BodyPart->GetMaxHP()/(HP*HP), 300), 1000);
3708 void character::PrintInfo () const {
3709 felist Info(CONST_S("Information about ")+GetName(DEFINITE));
3710 for (int c = 0; c < GetEquipments(); ++c) {
3711 item *Equipment = GetEquipment(c);
3712 if ((EquipmentEasilyRecognized(c) || game::WizardModeIsActive()) && Equipment) {
3713 int ImageKey = game::AddToItemDrawVector(itemvector(1, Equipment));
3714 Info.AddEntry(festring(GetEquipmentName(c))+": "+Equipment->GetName(INDEFINITE), LIGHT_GRAY, 0, ImageKey, true);
3717 if (Info.IsEmpty()) {
3718 ADD_MESSAGE("There's nothing special to tell about %s.", CHAR_NAME(DEFINITE));
3719 } else {
3720 game::SetStandardListAttributes(Info);
3721 Info.SetEntryDrawer(game::ItemEntryDrawer);
3722 Info.Draw();
3724 game::ClearItemDrawVector();
3728 truth character::TryToRiseFromTheDead () {
3729 for (int c = 0; c < BodyParts; ++c) {
3730 bodypart *BodyPart = GetBodyPart(c);
3731 if (BodyPart) {
3732 BodyPart->ResetSpoiling();
3733 if (BodyPart->CanRegenerate() || BodyPart->GetHP() < 1) BodyPart->SetHP(1);
3736 ResetStates();
3737 return true;
3741 truth character::RaiseTheDead (character *) {
3742 truth Useful = false;
3743 for (int c = 0; c < BodyParts; ++c) {
3744 bodypart *BodyPart = GetBodyPart(c);
3745 if (!BodyPart && CanCreateBodyPart(c)) {
3746 CreateBodyPart(c)->SetHP(1);
3747 if (IsPlayer()) ADD_MESSAGE("Suddenly you grow a new %s.", GetBodyPartName(c).CStr());
3748 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE), GetBodyPartName(c).CStr());
3749 Useful = true;
3750 } else if (BodyPart && BodyPart->CanRegenerate() && BodyPart->GetHP() < 1) {
3751 BodyPart->SetHP(1);
3754 if (!Useful) {
3755 if (IsPlayer()) ADD_MESSAGE("You shudder.");
3756 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s shudders.", CHAR_NAME(DEFINITE));
3758 return Useful;
3762 void character::SetSize (int Size) {
3763 for (int c = 0; c < BodyParts; ++c) {
3764 bodypart *BodyPart = GetBodyPart(c);
3765 if (BodyPart) BodyPart->SetSize(GetBodyPartSize(c, Size));
3770 sLong character::GetBodyPartSize (int I, int TotalSize) const {
3771 if (I == TORSO_INDEX) return TotalSize;
3772 ABORT("Weird bodypart size request for a character!");
3773 return 0;
3777 sLong character::GetBodyPartVolume (int I) const {
3778 if (I == TORSO_INDEX) return GetTotalVolume();
3779 ABORT("Weird bodypart volume request for a character!");
3780 return 0;
3784 void character::CreateBodyParts (int SpecialFlags) {
3785 for (int c = 0; c < BodyParts; ++c) if (CanCreateBodyPart(c)) CreateBodyPart(c, SpecialFlags);
3789 void character::RestoreBodyParts () {
3790 for (int c = 0; c < BodyParts; ++c) if (!GetBodyPart(c) && CanCreateBodyPart(c)) CreateBodyPart(c);
3794 void character::UpdatePictures () {
3795 if (!PictureUpdatesAreForbidden()) for (int c = 0; c < BodyParts; ++c) UpdateBodyPartPicture(c, false);
3799 bodypart *character::MakeBodyPart (int I) const {
3800 if (I == TORSO_INDEX) return normaltorso::Spawn(0, NO_MATERIALS);
3801 ABORT("Weird bodypart to make for a character!");
3802 return 0;
3806 bodypart *character::CreateBodyPart (int I, int SpecialFlags) {
3807 bodypart *BodyPart = MakeBodyPart(I);
3808 material *Material = CreateBodyPartMaterial(I, GetBodyPartVolume(I));
3809 BodyPart->InitMaterials(Material, false);
3810 BodyPart->SetSize(GetBodyPartSize(I, GetTotalSize()));
3811 BodyPart->SetBloodMaterial(GetBloodMaterial());
3812 BodyPart->SetNormalMaterial(Material->GetConfig());
3813 BodyPart->SetHP(1);
3814 SetBodyPart(I, BodyPart);
3815 BodyPart->InitSpecialAttributes();
3816 if (!(SpecialFlags & NO_PIC_UPDATE)) UpdateBodyPartPicture(I, false);
3817 if (!IsInitializing()) {
3818 CalculateBattleInfo();
3819 SendNewDrawRequest();
3820 SignalPossibleTransparencyChange();
3822 return BodyPart;
3826 v2 character::GetBodyPartBitmapPos (int I, truth) const {
3827 if (I == TORSO_INDEX) return GetTorsoBitmapPos();
3828 ABORT("Weird bodypart BitmapPos request for a character!");
3829 return v2();
3833 void character::UpdateBodyPartPicture (int I, truth Severed) {
3834 bodypart *BP = GetBodyPart(I);
3835 if (BP) {
3836 BP->SetBitmapPos(GetBodyPartBitmapPos(I, Severed));
3837 BP->GetMainMaterial()->SetSkinColor(GetBodyPartColorA(I, Severed));
3838 BP->GetMainMaterial()->SetSkinColorIsSparkling(GetBodyPartSparkleFlags(I) & SPARKLING_A);
3839 BP->SetMaterialColorB(GetBodyPartColorB(I, Severed));
3840 BP->SetMaterialColorC(GetBodyPartColorC(I, Severed));
3841 BP->SetMaterialColorD(GetBodyPartColorD(I, Severed));
3842 BP->SetSparkleFlags(GetBodyPartSparkleFlags(I));
3843 BP->SetSpecialFlags(GetSpecialBodyPartFlags(I));
3844 BP->SetWobbleData(GetBodyPartWobbleData(I));
3845 BP->UpdatePictures();
3850 void character::LoadDataBaseStats () {
3851 for (int c = 0; c < BASE_ATTRIBUTES; ++c) {
3852 BaseExperience[c] = DataBase->NaturalExperience[c];
3853 if (BaseExperience[c]) LimitRef(BaseExperience[c], MIN_EXP, MAX_EXP);
3855 SetMoney(GetDefaultMoney());
3856 SetInitialSweatMaterial(GetSweatMaterial());
3857 const fearray<sLong> &Skills = GetKnownCWeaponSkills();
3858 if (Skills.Size) {
3859 const fearray<sLong> &Hits = GetCWeaponSkillHits();
3860 if (Hits.Size == 1) {
3861 for (uInt c = 0; c < Skills.Size; ++c) {
3862 if (Skills[c] < AllowedWeaponSkillCategories) CWeaponSkill[Skills[c]].AddHit(Hits[0]*100);
3864 } else if (Hits.Size == Skills.Size) {
3865 for (uInt c = 0; c < Skills.Size; ++c) {
3866 if (Skills[c] < AllowedWeaponSkillCategories) CWeaponSkill[Skills[c]].AddHit(Hits[c]*100);
3868 } else {
3869 ABORT("Illegal weapon skill hit array size detected!");
3875 character *characterprototype::SpawnAndLoad (inputfile &SaveFile) const {
3876 character *Char = Spawner(0, LOAD);
3877 Char->Load(SaveFile);
3878 Char->CalculateAll();
3879 return Char;
3883 void character::Initialize (int NewConfig, int SpecialFlags) {
3884 Flags |= C_INITIALIZING|C_IN_NO_MSG_MODE;
3885 CalculateBodyParts();
3886 CalculateAllowedWeaponSkillCategories();
3887 CalculateSquaresUnder();
3888 BodyPartSlot = new bodypartslot[BodyParts];
3889 OriginalBodyPartID = new std::list<feuLong>[BodyParts];
3890 CWeaponSkill = new cweaponskill[AllowedWeaponSkillCategories];
3891 SquareUnder = new square*[SquaresUnder];
3893 if (SquaresUnder == 1) *SquareUnder = 0; else memset(SquareUnder, 0, SquaresUnder*sizeof(square *));
3895 for (int c = 0; c < BodyParts; ++c) BodyPartSlot[c].SetMaster(this);
3897 if (!(SpecialFlags & LOAD)) {
3898 ID = game::CreateNewCharacterID(this);
3899 databasecreator<character>::InstallDataBase(this, NewConfig);
3900 LoadDataBaseStats();
3901 TemporaryState |= GetClassStates();
3902 if (TemporaryState) {
3903 for (int c = 0; c < STATES; ++c) if (TemporaryState & (1 << c)) TemporaryStateCounter[c] = PERMANENT;
3906 CreateBodyParts(SpecialFlags | NO_PIC_UPDATE);
3907 InitSpecialAttributes();
3908 CommandFlags = GetDefaultCommandFlags();
3910 if (GetAttribute(INTELLIGENCE, false) < 8) CommandFlags &= ~DONT_CONSUME_ANYTHING_VALUABLE; // gum
3911 if (!GetDefaultName().IsEmpty()) SetAssignedName(GetDefaultName());
3914 if (!(SpecialFlags & LOAD)) PostConstruct();
3916 if (!(SpecialFlags & LOAD)) {
3917 if (!(SpecialFlags & NO_EQUIPMENT)) CreateInitialEquipment((SpecialFlags & NO_EQUIPMENT_PIC_UPDATE) >> 1);
3918 if (!(SpecialFlags & NO_PIC_UPDATE)) UpdatePictures();
3919 CalculateAll();
3920 RestoreHP();
3921 RestoreStamina();
3924 Flags &= ~(C_INITIALIZING|C_IN_NO_MSG_MODE);
3928 truth character::TeleportNear (character *Caller) {
3929 v2 Where = GetLevel()->GetNearestFreeSquare(this, Caller->GetPos());
3930 if (Where == ERROR_V2) return false;
3931 Move(Where, true);
3932 return true;
3936 void character::ReceiveHeal (sLong Amount) {
3937 int c;
3938 for (c = 0; c < Amount / 10; ++c) if (!HealHitPoint()) break;
3939 Amount -= c*10;
3940 if (RAND()%10 < Amount) HealHitPoint();
3941 if (Amount >= 250 || RAND()%250 < Amount) {
3942 bodypart *NewBodyPart = GenerateRandomBodyPart();
3943 if (!NewBodyPart) return;
3944 NewBodyPart->SetHP(1);
3945 if (IsPlayer()) ADD_MESSAGE("You grow a new %s.", NewBodyPart->GetBodyPartName().CStr());
3946 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE), NewBodyPart->GetBodyPartName().CStr());
3951 void character::AddHealingLiquidConsumeEndMessage () const {
3952 if (IsPlayer()) ADD_MESSAGE("You feel better.");
3953 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks healthier.", CHAR_NAME(DEFINITE));
3957 void character::ReceiveSchoolFood (sLong SizeOfEffect) {
3958 SizeOfEffect += RAND()%SizeOfEffect;
3959 if (SizeOfEffect >= 250) VomitAtRandomDirection(SizeOfEffect);
3960 if (!(RAND() % 3) && SizeOfEffect >= 500 && EditAttribute(ENDURANCE, SizeOfEffect/500)) {
3961 if (IsPlayer()) ADD_MESSAGE("You gain a little bit of toughness for surviving this stuff.");
3962 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks tougher.", CHAR_NAME(DEFINITE));
3964 BeginTemporaryState(POISONED, (SizeOfEffect>>1));
3968 void character::AddSchoolFoodConsumeEndMessage () const {
3969 if (IsPlayer()) ADD_MESSAGE("Yuck! This stuff tasted like vomit and old mousepads.");
3973 void character::AddSchoolFoodHitMessage () const {
3974 if (IsPlayer()) ADD_MESSAGE("Yuck! This stuff feels like vomit and old mousepads.");
3978 void character::ReceiveNutrition (sLong SizeOfEffect) {
3979 EditNP(SizeOfEffect);
3983 void character::ReceiveOmmelUrine (sLong Amount) {
3984 EditExperience(ARM_STRENGTH, 500, Amount<<4);
3985 EditExperience(LEG_STRENGTH, 500, Amount<<4);
3986 if (IsPlayer()) game::DoEvilDeed(Amount/25);
3990 void character::ReceiveOmmelCerumen (sLong Amount) {
3991 EditExperience(INTELLIGENCE, 500, Amount << 5);
3992 EditExperience(WISDOM, 500, Amount << 5);
3993 if (IsPlayer()) game::DoEvilDeed(Amount / 25);
3997 void character::ReceiveOmmelSweat (sLong Amount) {
3998 EditExperience(AGILITY, 500, Amount << 4);
3999 EditExperience(DEXTERITY, 500, Amount << 4);
4000 RestoreStamina();
4001 if (IsPlayer()) game::DoEvilDeed(Amount / 25);
4005 void character::ReceiveOmmelTears (sLong Amount) {
4006 EditExperience(PERCEPTION, 500, Amount << 4);
4007 EditExperience(CHARISMA, 500, Amount << 4);
4008 if (IsPlayer()) game::DoEvilDeed(Amount / 25);
4012 void character::ReceiveOmmelSnot (sLong Amount) {
4013 EditExperience(ENDURANCE, 500, Amount << 5);
4014 RestoreLivingHP();
4015 if (IsPlayer()) game::DoEvilDeed(Amount / 25);
4019 void character::ReceiveOmmelBone (sLong Amount) {
4020 EditExperience(ARM_STRENGTH, 500, Amount << 6);
4021 EditExperience(LEG_STRENGTH, 500, Amount << 6);
4022 EditExperience(DEXTERITY, 500, Amount << 6);
4023 EditExperience(AGILITY, 500, Amount << 6);
4024 EditExperience(ENDURANCE, 500, Amount << 6);
4025 EditExperience(PERCEPTION, 500, Amount << 6);
4026 EditExperience(INTELLIGENCE, 500, Amount << 6);
4027 EditExperience(WISDOM, 500, Amount << 6);
4028 EditExperience(CHARISMA, 500, Amount << 6);
4029 RestoreLivingHP();
4030 RestoreStamina();
4031 if (IsPlayer()) game::DoEvilDeed(Amount / 25);
4035 void character::AddOmmelConsumeEndMessage () const {
4036 if (IsPlayer()) ADD_MESSAGE("You feel a primitive force coursing through your veins.");
4037 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks more powerful.", CHAR_NAME(DEFINITE));
4041 void character::ReceivePepsi (sLong Amount) {
4042 ReceiveDamage(0, Amount / 100, POISON, TORSO);
4043 EditExperience(PERCEPTION, Amount, 1 << 14);
4044 if (CheckDeath(CONST_S("was poisoned by pepsi"), 0)) return;
4045 if (IsPlayer()) game::DoEvilDeed(Amount / 10);
4049 void character::AddPepsiConsumeEndMessage () const {
4050 if (IsPlayer()) ADD_MESSAGE("Urgh. You feel your guruism fading away.");
4051 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks very lame.", CHAR_NAME(DEFINITE));
4055 void character::ReceiveDarkness (sLong Amount) {
4056 EditExperience(INTELLIGENCE, -Amount / 5, 1 << 13);
4057 EditExperience(WISDOM, -Amount / 5, 1 << 13);
4058 EditExperience(CHARISMA, -Amount / 5, 1 << 13);
4059 if (IsPlayer()) game::DoEvilDeed(int(Amount / 50));
4063 void character::AddFrogFleshConsumeEndMessage () const {
4064 if (IsPlayer()) ADD_MESSAGE("Arg. You feel the fate of a navastater placed upon you...");
4065 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks like a navastater.", CHAR_NAME(DEFINITE));
4069 void character::ReceiveKoboldFlesh (sLong) {
4070 /* As it is commonly known, the possibility of fainting per 500 cubic
4071 centimeters of kobold flesh is exactly 5%. */
4072 if (!(RAND() % 20)) {
4073 if (IsPlayer()) ADD_MESSAGE("You lose control of your legs and fall down.");
4074 LoseConsciousness(250 + RAND_N(250));
4079 void character::AddKoboldFleshConsumeEndMessage () const {
4080 if (IsPlayer()) ADD_MESSAGE("This stuff tasted really funny.");
4084 void character::AddKoboldFleshHitMessage () const {
4085 if (IsPlayer()) ADD_MESSAGE("You feel very funny.");
4089 void character::AddBoneConsumeEndMessage () const {
4090 if (IsPlayer()) ADD_MESSAGE("You feel like a hippie.");
4091 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s barks happily.", CHAR_NAME(DEFINITE)); // this suspects that nobody except dogs can eat bones
4094 truth character::RawEditAttribute (double &Experience, int Amount) const {
4095 /* Check if the attribute is disabled for creature */
4096 if (!Experience) return false;
4097 if ((Amount < 0 && Experience < 2 * EXP_MULTIPLIER) || (Amount > 0 && Experience > 999 * EXP_MULTIPLIER)) return false;
4098 Experience += Amount * EXP_MULTIPLIER;
4099 LimitRef<double>(Experience, MIN_EXP, MAX_EXP);
4100 return true;
4104 void character::DrawPanel (truth AnimationDraw) const {
4105 if (AnimationDraw) { DrawStats(true); return; }
4106 igraph::BlitBackGround(v2(19 + (game::GetScreenXSize() << 4), 0), v2(RES.X - 19 - (game::GetScreenXSize() << 4), RES.Y));
4107 igraph::BlitBackGround(v2(16, 45 + (game::GetScreenYSize() << 4)), v2(game::GetScreenXSize() << 4, 9));
4108 FONT->Printf(DOUBLE_BUFFER, v2(16, 45 + (game::GetScreenYSize() << 4)), WHITE, "%s", GetPanelName().CStr());
4109 game::UpdateAttributeMemory();
4110 int PanelPosX = RES.X - 96;
4111 int PanelPosY = DrawStats(false);
4112 PrintAttribute("End", ENDURANCE, PanelPosX, PanelPosY++);
4113 PrintAttribute("Per", PERCEPTION, PanelPosX, PanelPosY++);
4114 PrintAttribute("Int", INTELLIGENCE, PanelPosX, PanelPosY++);
4115 PrintAttribute("Wis", WISDOM, PanelPosX, PanelPosY++);
4116 PrintAttribute("Wil", WILL_POWER, PanelPosX, PanelPosY++);
4117 PrintAttribute("Cha", CHARISMA, PanelPosX, PanelPosY++);
4118 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Siz %d", GetSize());
4119 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), IsInBadCondition() ? RED : WHITE, "HP %d/%d", GetHP(), GetMaxHP());
4120 ++PanelPosY;
4121 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Gold: %d", GetMoney());
4122 ++PanelPosY;
4124 if (game::IsInWilderness())
4125 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Worldmap");
4126 else
4127 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "%s", game::GetCurrentDungeon()->GetShortLevelDescription(game::GetCurrentLevelIndex()).CapitalizeCopy().CStr());
4129 ivantime Time;
4130 game::GetTime(Time);
4131 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Day %d", Time.Day);
4132 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Time %d:%s%d", Time.Hour, Time.Min < 10 ? "0" : "", Time.Min);
4133 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Turn %d", game::GetTurn());
4135 ++PanelPosY;
4137 if (GetAction()) {
4138 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "%s", festring(GetAction()->GetDescription()).CapitalizeCopy().CStr());
4141 //printf("========= STATES =========\n");
4142 for (int c = 0; c < STATES; ++c) {
4143 //printf(" %d: %s (%s)\n", c, StateData[c].Description, (StateIsActivated(1<<c) ? "TAN" : "ona"));
4144 if (!(StateData[c].Flags & SECRET) && StateIsActivated(1 << c) && (1 << c != HASTE || !StateIsActivated(SLOW)) && (1 << c != SLOW || !StateIsActivated(HASTE))) {
4145 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), (1 << c) & EquipmentState || TemporaryStateCounter[c] >= PERMANENT ? BLUE : WHITE, "%s", StateData[c].Description);
4149 auto hst = GetHungerState();
4150 if (hst == STARVING) FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), RED, "Starving");
4151 else if (hst == VERY_HUNGRY) FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), RED, "Very hungry");
4152 else if (hst == HUNGRY) FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), ORANGE, "Hungry");
4153 else if (hst == SATIATED) FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Satiated");
4154 else if (hst == BLOATED) FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Bloated");
4155 else if (hst == OVER_FED) FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Overfed!");
4157 auto bst = GetBurdenState();
4158 if (bst == OVER_LOADED) FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), RED, "Overload!");
4159 else if (bst == STRESSED) FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), ORANGE, "Stressed");
4160 else if (bst == BURDENED) FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), BLUE, "Burdened");
4162 auto trst = GetTirednessState();
4163 if (trst == FAINTING) FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), RED, "Fainting");
4164 else if (trst == EXHAUSTED) FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), ORANGE, "Exhausted");
4166 if (game::PlayerIsRunning()) {
4167 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "%s", GetRunDescriptionLine(0));
4168 cchar *SecondLine = GetRunDescriptionLine(1);
4169 if (strlen(SecondLine)) FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "%s", SecondLine);
4174 void character::CalculateDodgeValue () {
4175 DodgeValue = 0.05 * GetMoveEase() * GetAttribute(AGILITY) / sqrt(GetSize());
4176 if (IsFlying()) DodgeValue *= 2;
4177 if (DodgeValue < 1) DodgeValue = 1;
4181 truth character::DamageTypeAffectsInventory (int Type) {
4182 if ((Type&0xFFF) == SOUND) return true;
4183 if ((Type&0xFFF) == ENERGY) return true;
4184 if ((Type&0xFFF) == ACID) return true;
4185 if ((Type&0xFFF) == FIRE) return true;
4186 if ((Type&0xFFF) == ELECTRICITY) return true;
4188 if ((Type&0xFFF) == PHYSICAL_DAMAGE) return false;
4189 if ((Type&0xFFF) == POISON) return false;
4190 if ((Type&0xFFF) == DRAIN) return false;
4191 if ((Type&0xFFF) == MUSTARD_GAS_DAMAGE) return false;
4192 if ((Type&0xFFF) == PSI) return false;
4194 ABORT("Unknown reaping effect destroyed dungeon!");
4195 return false;
4199 int character::CheckForBlockWithArm (character *Enemy, item *Weapon, arm *Arm,
4200 double WeaponToHitValue, int Damage, int Success, int Type)
4202 int BlockStrength = Arm->GetBlockCapability();
4203 double BlockValue = Arm->GetBlockValue();
4204 if (BlockStrength && BlockValue) {
4205 item *Blocker = Arm->GetWielded();
4206 if (RAND() % int(100+WeaponToHitValue/BlockValue/(1<<BlocksSinceLastTurn)*(100+Success)) < 100) {
4207 int NewDamage = BlockStrength < Damage ? Damage-BlockStrength : 0;
4208 if (Type == UNARMED_ATTACK) AddBlockMessage(Enemy, Blocker, Enemy->UnarmedHitNoun(), NewDamage);
4209 else if (Type == WEAPON_ATTACK) AddBlockMessage(Enemy, Blocker, "attack", NewDamage);
4210 else if (Type == KICK_ATTACK) AddBlockMessage(Enemy, Blocker, Enemy->KickNoun(), NewDamage);
4211 else if (Type == BITE_ATTACK) AddBlockMessage(Enemy, Blocker, Enemy->BiteNoun(), NewDamage);
4212 sLong Weight = Blocker->GetWeight();
4213 sLong StrExp = Limit(15 * Weight / 200, 75, 300);
4214 sLong DexExp = Weight ? Limit(75000 / Weight, 75, 300) : 300;
4215 Arm->EditExperience(ARM_STRENGTH, StrExp, 1 << 8);
4216 Arm->EditExperience(DEXTERITY, DexExp, 1 << 8);
4217 EditStamina(-10000 / GetAttribute(ARM_STRENGTH), false);
4218 if (Arm->TwoHandWieldIsActive()) {
4219 arm *PairArm = Arm->GetPairArm();
4220 PairArm->EditExperience(ARM_STRENGTH, StrExp, 1 << 8);
4221 PairArm->EditExperience(DEXTERITY, DexExp, 1 << 8);
4223 Blocker->WeaponSkillHit(Enemy->CalculateWeaponSkillHits(this));
4224 Blocker->ReceiveDamage(this, Damage, PHYSICAL_DAMAGE);
4225 Blocker->BlockEffect(this, Enemy, Weapon, Type);
4226 if (Weapon) Weapon->ReceiveDamage(Enemy, Damage - NewDamage, PHYSICAL_DAMAGE);
4227 if (BlocksSinceLastTurn < 16) ++BlocksSinceLastTurn;
4228 return NewDamage;
4231 return Damage;
4235 sLong character::GetStateAPGain (sLong BaseAPGain) const {
4236 if (!StateIsActivated(HASTE) == !StateIsActivated(SLOW)) return BaseAPGain;
4237 if (StateIsActivated(HASTE)) return (BaseAPGain * 5) >> 2;
4238 return (BaseAPGain << 2) / 5;
4242 void character::SignalEquipmentAdd (int EquipmentIndex) {
4243 item *Equipment = GetEquipment(EquipmentIndex);
4244 if (Equipment->IsInCorrectSlot(EquipmentIndex)) {
4245 sLong AddedStates = Equipment->GetGearStates();
4246 if (AddedStates) {
4247 for (int c = 0; c < STATES; ++c) {
4248 if (AddedStates & (1 << c)) {
4249 if (!StateIsActivated(1 << c)) {
4250 if (!IsInNoMsgMode()) (this->*StateData[c].PrintBeginMessage)();
4251 EquipmentState |= 1 << c;
4252 if (StateData[c].BeginHandler) (this->*StateData[c].BeginHandler)();
4253 } else {
4254 EquipmentState |= 1 << c;
4260 if (!IsInitializing() && Equipment->IsInCorrectSlot(EquipmentIndex)) ApplyEquipmentAttributeBonuses(Equipment);
4264 void character::SignalEquipmentRemoval (int, citem *Item) {
4265 CalculateEquipmentState();
4266 if (CalculateAttributeBonuses()) CheckDeath(festring("lost ")+GetPossessivePronoun(false)+" vital "+Item->GetName(INDEFINITE));
4270 void character::CalculateEquipmentState () {
4271 sLong Back = EquipmentState;
4272 EquipmentState = 0;
4273 for (int c = 0; c < GetEquipments(); ++c) {
4274 item *Equipment = GetEquipment(c);
4275 if (Equipment && Equipment->IsInCorrectSlot(c)) EquipmentState |= Equipment->GetGearStates();
4277 for (int c = 0; c < STATES; ++c) {
4278 if (Back & (1 << c) && !StateIsActivated(1 << c)) {
4279 if (StateData[c].EndHandler) {
4280 (this->*StateData[c].EndHandler)();
4281 if (!IsEnabled()) return;
4283 if (!IsInNoMsgMode()) (this->*StateData[c].PrintEndMessage)();
4289 /* Counter = duration in ticks */
4290 void character::BeginTemporaryState (sLong State, int Counter) {
4291 if (!Counter) return;
4292 if (State&POLYMORPHED) ABORT("No Polymorphing with BeginTemporaryState!");
4293 while (State != 0) {
4294 sLong st = 0, sidx;
4295 for (sidx = 0; sidx < STATES; ++sidx) if (State&(1<<sidx)) { st = (1<<sidx); break; }
4296 if (!st) { break; /*ABORT("BeginTemporaryState works only when State == 2^n!");*/ }
4297 State &= ~st;
4298 if (TemporaryStateIsActivated(st)) {
4299 int OldCounter = GetTemporaryStateCounter(st);
4300 if (OldCounter != PERMANENT) EditTemporaryStateCounter(st, Max(Counter, 50-OldCounter));
4301 } else if (StateData[sidx].IsAllowed == 0 || (this->*StateData[sidx].IsAllowed)()) {
4302 SetTemporaryStateCounter(st, Max(Counter, 50));
4303 if (!EquipmentStateIsActivated(st)) {
4304 if (!IsInNoMsgMode()) (this->*StateData[sidx].PrintBeginMessage)();
4305 ActivateTemporaryState(st);
4306 if (StateData[sidx].BeginHandler) (this->*StateData[sidx].BeginHandler)();
4307 } else {
4308 ActivateTemporaryState(st);
4315 void character::HandleStates () {
4316 if (!TemporaryState && !EquipmentState) return;
4317 for (int c = 0; c < STATES; ++c) {
4318 if (TemporaryState & (1 << c) && TemporaryStateCounter[c] != PERMANENT) {
4319 if (!--TemporaryStateCounter[c]) {
4320 TemporaryState &= ~(1 << c);
4321 if (!(EquipmentState & (1 << c))) {
4322 if (StateData[c].EndHandler) {
4323 (this->*StateData[c].EndHandler)();
4324 if (!IsEnabled()) return;
4326 if (!TemporaryStateCounter[c]) (this->*StateData[c].PrintEndMessage)();
4330 if (StateIsActivated(1 << c)) {
4331 if (StateData[c].Handler) (this->*StateData[c].Handler)();
4333 if (!IsEnabled()) return;
4338 void character::PrintBeginPolymorphControlMessage () const {
4339 if (IsPlayer()) ADD_MESSAGE("You feel your mind has total control over your body.");
4343 void character::PrintEndPolymorphControlMessage () const {
4344 if (IsPlayer()) ADD_MESSAGE("You are somehow uncertain of your willpower.");
4348 void character::PrintBeginLifeSaveMessage () const {
4349 if (IsPlayer()) ADD_MESSAGE("You hear Hell's gates being locked just now.");
4353 void character::PrintEndLifeSaveMessage () const {
4354 if (IsPlayer()) ADD_MESSAGE("You feel the Afterlife is welcoming you once again.");
4358 void character::PrintBeginLycanthropyMessage () const {
4359 if (IsPlayer()) ADD_MESSAGE("You suddenly notice you've always loved full moons.");
4363 void character::PrintEndLycanthropyMessage () const {
4364 if (IsPlayer()) ADD_MESSAGE("You feel the wolf inside you has had enough of your bad habits.");
4368 void character::PrintBeginVampirismMessage () const {
4369 if (IsPlayer()) ADD_MESSAGE("You suddenly decide you have always hated garlic.");
4373 void character::PrintEndVampirismMessage () const {
4374 if (IsPlayer()) ADD_MESSAGE("You recall your delight of the morning sunshine back in New Attnam. You are a vampire no longer.");
4378 void character::PrintBeginInvisibilityMessage () const {
4379 if ((PLAYER->StateIsActivated(INFRA_VISION) && IsWarm()) || (PLAYER->StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5)) {
4380 if (IsPlayer()) ADD_MESSAGE("You seem somehow transparent.");
4381 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s seems somehow transparent.", CHAR_NAME(DEFINITE));
4382 } else {
4383 if (IsPlayer()) ADD_MESSAGE("You fade away.");
4384 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s disappears!", CHAR_NAME(DEFINITE));
4389 void character::PrintEndInvisibilityMessage () const {
4390 if ((PLAYER->StateIsActivated(INFRA_VISION) && IsWarm()) || (PLAYER->StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5)) {
4391 if (IsPlayer()) ADD_MESSAGE("Your notice your transparency has ended.");
4392 else if (CanBeSeenByPlayer()) ADD_MESSAGE("The appearance of %s seems far more solid now.", CHAR_NAME(INDEFINITE));
4393 } else {
4394 if (IsPlayer()) ADD_MESSAGE("You reappear.");
4395 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s appears from nowhere!", CHAR_NAME(INDEFINITE));
4400 void character::PrintBeginInfraVisionMessage () const {
4401 if (IsPlayer()) {
4402 if (StateIsActivated(INVISIBLE) && IsWarm() && !(StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5))
4403 ADD_MESSAGE("You reappear.");
4404 else
4405 ADD_MESSAGE("You feel your perception being magically altered.");
4410 void character::PrintEndInfraVisionMessage () const {
4411 if (IsPlayer()) {
4412 if (StateIsActivated(INVISIBLE) && IsWarm() && !(StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5))
4413 ADD_MESSAGE("You disappear.");
4414 else
4415 ADD_MESSAGE("You feel your perception returning to normal.");
4420 void character::PrintBeginESPMessage () const {
4421 if (IsPlayer()) ADD_MESSAGE("You suddenly feel like being only a tiny part of a great network of intelligent minds.");
4425 void character::PrintEndESPMessage () const {
4426 if (IsPlayer()) ADD_MESSAGE("You are filled with desire to be just yourself from now on.");
4430 void character::PrintBeginHasteMessage () const {
4431 if (IsPlayer()) ADD_MESSAGE("Time slows down to a crawl.");
4432 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks faster!", CHAR_NAME(DEFINITE));
4436 void character::PrintEndHasteMessage () const {
4437 if (IsPlayer()) ADD_MESSAGE("Everything seems to move much faster now.");
4438 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks slower!", CHAR_NAME(DEFINITE));
4442 void character::PrintBeginSlowMessage () const {
4443 if (IsPlayer()) ADD_MESSAGE("Everything seems to move much faster now.");
4444 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks slower!", CHAR_NAME(DEFINITE));
4448 void character::PrintEndSlowMessage () const {
4449 if (IsPlayer()) ADD_MESSAGE("Time slows down to a crawl.");
4450 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks faster!", CHAR_NAME(DEFINITE));
4454 void character::EndPolymorph () {
4455 ForceEndPolymorph();
4459 character *character::ForceEndPolymorph () {
4460 if (IsPlayer()) {
4461 ADD_MESSAGE("You return to your true form.");
4462 } else if (game::IsInWilderness()) {
4463 ActivateTemporaryState(POLYMORPHED);
4464 SetTemporaryStateCounter(POLYMORPHED, 10);
4465 return this; // fast gum solution, state ends when the player enters a dungeon
4467 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s returns to %s true form.", CHAR_NAME(DEFINITE), GetPossessivePronoun().CStr());
4468 RemoveTraps();
4469 if (GetAction()) GetAction()->Terminate(false);
4470 v2 Pos = GetPos();
4471 SendToHell();
4472 Remove();
4473 character *Char = GetPolymorphBackup();
4474 Flags |= C_IN_NO_MSG_MODE;
4475 Char->Flags |= C_IN_NO_MSG_MODE;
4476 Char->PutToOrNear(Pos);
4477 Char->ChangeTeam(GetTeam());
4478 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(Char);
4479 SetPolymorphBackup(0);
4480 Char->Enable();
4481 Char->Flags &= ~C_POLYMORPHED;
4482 GetStack()->MoveItemsTo(Char->GetStack());
4483 DonateEquipmentTo(Char);
4484 Char->SetMoney(GetMoney());
4485 Flags &= ~C_IN_NO_MSG_MODE;
4486 Char->Flags &= ~C_IN_NO_MSG_MODE;
4487 Char->CalculateAll();
4488 Char->SetAssignedName(GetAssignedName());
4489 if (IsPlayer()) {
4490 Flags &= ~C_PLAYER;
4491 game::SetPlayer(Char);
4492 game::SendLOSUpdateRequest();
4493 UpdateESPLOS();
4495 Char->TestWalkability();
4496 return Char;
4500 void character::LycanthropyHandler () {
4501 if (StateIsActivated(POLYMORPH_LOCK)) return;
4502 if (IsOfType("werewolfwolf")) return;
4503 if (!(RAND() % 2000)) {
4504 if (StateIsActivated(POLYMORPH_CONTROL) && (IsPlayer() ? !game::TruthQuestion(CONST_S("Do you wish to change into a werewolf?")) : false)) return;
4505 Polymorph(werewolfwolf::Spawn(), 1000 + RAND() % 2000);
4510 void character::SaveLife () {
4511 if (TemporaryStateIsActivated(LIFE_SAVED)) {
4512 if (IsPlayer())
4513 ADD_MESSAGE("But wait! You glow briefly red and seem to be in a better shape!");
4514 else if (CanBeSeenByPlayer())
4515 ADD_MESSAGE("But wait, suddenly %s glows briefly red and seems to be in a better shape!", GetPersonalPronoun().CStr());
4516 DeActivateTemporaryState(LIFE_SAVED);
4517 } else {
4518 item *LifeSaver = 0;
4519 for (int c = 0; c < GetEquipments(); ++c) {
4520 item *Equipment = GetEquipment(c);
4521 if (Equipment && Equipment->IsInCorrectSlot(c) && Equipment->GetGearStates() & LIFE_SAVED) LifeSaver = Equipment;
4523 if (!LifeSaver) ABORT("The Universe can only kill you once!");
4524 if (IsPlayer())
4525 ADD_MESSAGE("But wait! Your %s glows briefly red and disappears and you seem to be in a better shape!", LifeSaver->CHAR_NAME(UNARTICLED));
4526 else if (CanBeSeenByPlayer())
4527 ADD_MESSAGE("But wait, suddenly %s %s glows briefly red and disappears and %s seems to be in a better shape!", GetPossessivePronoun().CStr(), LifeSaver->CHAR_NAME(UNARTICLED), GetPersonalPronoun().CStr());
4528 LifeSaver->RemoveFromSlot();
4529 LifeSaver->SendToHell();
4532 if (IsPlayer()) game::AskForEscPress(CONST_S("Life saved!"));
4534 RestoreBodyParts();
4535 ResetSpoiling();
4536 RestoreHP();
4537 RestoreStamina();
4538 ResetStates();
4540 if (GetNP() < SATIATED_LEVEL) SetNP(SATIATED_LEVEL);
4542 SendNewDrawRequest();
4544 if (GetAction()) GetAction()->Terminate(false);
4548 character *character::PolymorphRandomly (int MinDanger, int MaxDanger, int Time) {
4549 character *NewForm = 0;
4550 if (StateIsActivated(POLYMORPH_LOCK)) { ADD_MESSAGE("You feel uncertain about your body for a moment."); return NewForm; }
4551 if (StateIsActivated(POLYMORPH_CONTROL)) {
4552 if (IsPlayer()) {
4553 if (!GetNewFormForPolymorphWithControl(NewForm)) return NewForm;
4554 } else {
4555 NewForm = protosystem::CreateMonster(MinDanger*10, MaxDanger*10, NO_EQUIPMENT);
4557 } else {
4558 NewForm = protosystem::CreateMonster(MinDanger, MaxDanger, NO_EQUIPMENT);
4560 Polymorph(NewForm, Time);
4561 return NewForm;
4565 /* In reality, the reading takes Time / (Intelligence * 10) turns */
4566 void character::StartReading (item *Item, sLong Time) {
4567 study *Read = study::Spawn(this);
4568 Read->SetLiteratureID(Item->GetID());
4569 if (game::WizardModeIsActive()) Time = 1;
4570 Read->SetCounter(Time);
4571 SetAction(Read);
4572 if (IsPlayer()) ADD_MESSAGE("You start reading %s.", Item->CHAR_NAME(DEFINITE));
4573 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s starts reading %s.", CHAR_NAME(DEFINITE), Item->CHAR_NAME(DEFINITE));
4577 /* Call when one makes something with his/her/its hands.
4578 * Difficulty of 5 takes about one turn, so it's the most common to use. */
4579 void character::DexterityAction (int Difficulty) {
4580 EditAP(-20000 * Difficulty / APBonus(GetAttribute(DEXTERITY)));
4581 EditExperience(DEXTERITY, Difficulty * 15, 1 << 7);
4585 /* If Theoretically != false, range is not a factor. */
4586 truth character::CanBeSeenByPlayer (truth Theoretically, truth IgnoreESP) const {
4587 if (IsEnabled() && !game::IsGenerating() && (Theoretically || GetSquareUnder())) {
4588 truth MayBeESPSeen = PLAYER->IsEnabled() && !IgnoreESP && PLAYER->StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5;
4589 truth MayBeInfraSeen = PLAYER->IsEnabled() && PLAYER->StateIsActivated(INFRA_VISION) && IsWarm();
4590 truth Visible = !StateIsActivated(INVISIBLE) || MayBeESPSeen || MayBeInfraSeen;
4591 if (game::IsInWilderness()) return Visible;
4592 if (MayBeESPSeen && (Theoretically || GetDistanceSquareFrom(PLAYER) <= PLAYER->GetESPRangeSquare())) return true;
4593 if (!Visible) return false;
4594 return (Theoretically || SquareUnderCanBeSeenByPlayer(MayBeInfraSeen));
4596 return false;
4600 truth character::CanBeSeenBy (ccharacter *Who, truth Theoretically, truth IgnoreESP) const {
4601 if (Who->IsPlayer()) return CanBeSeenByPlayer(Theoretically, IgnoreESP);
4602 if (IsEnabled() && !game::IsGenerating() && (Theoretically || GetSquareUnder())) {
4603 truth MayBeESPSeen = Who->IsEnabled() && !IgnoreESP && Who->StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5;
4604 truth MayBeInfraSeen = Who->IsEnabled() && Who->StateIsActivated(INFRA_VISION) && IsWarm();
4605 truth Visible = !StateIsActivated(INVISIBLE) || MayBeESPSeen || MayBeInfraSeen;
4606 if (game::IsInWilderness()) return Visible;
4607 if (MayBeESPSeen && (Theoretically || GetDistanceSquareFrom(Who) <= Who->GetESPRangeSquare())) return true;
4608 if (!Visible) return false;
4609 return (Theoretically || SquareUnderCanBeSeenBy(Who, MayBeInfraSeen));
4611 return false;
4615 truth character::SquareUnderCanBeSeenByPlayer (truth IgnoreDarkness) const {
4616 if (!GetSquareUnder()) return false;
4617 int S1 = SquaresUnder, S2 = PLAYER->SquaresUnder;
4618 if (S1 == 1 && S2 == 1) {
4619 if (GetSquareUnder()->CanBeSeenByPlayer(IgnoreDarkness)) return true;
4620 if (IgnoreDarkness) {
4621 int LOSRangeSquare = PLAYER->GetLOSRangeSquare();
4622 if ((GetPos() - PLAYER->GetPos()).GetLengthSquare() <= LOSRangeSquare) {
4623 eyecontroller::Map = GetLevel()->GetMap();
4624 return mapmath<eyecontroller>::DoLine(PLAYER->GetPos().X, PLAYER->GetPos().Y, GetPos().X, GetPos().Y, SKIP_FIRST|LINE_BOTH_DIRS);
4627 return false;
4628 } else {
4629 for (int c1 = 0; c1 < S1; ++c1) {
4630 lsquare *Square = GetLSquareUnder(c1);
4631 if (Square->CanBeSeenByPlayer(IgnoreDarkness)) return true;
4632 else if (IgnoreDarkness) {
4633 v2 Pos = Square->GetPos();
4634 int LOSRangeSquare = PLAYER->GetLOSRangeSquare();
4635 for (int c2 = 0; c2 < S2; ++c2) {
4636 v2 PlayerPos = PLAYER->GetPos(c2);
4637 if ((Pos-PlayerPos).GetLengthSquare() <= LOSRangeSquare) {
4638 eyecontroller::Map = GetLevel()->GetMap();
4639 if (mapmath<eyecontroller>::DoLine(PlayerPos.X, PlayerPos.Y, Pos.X, Pos.Y, SKIP_FIRST|LINE_BOTH_DIRS)) return true;
4644 return false;
4649 truth character::SquareUnderCanBeSeenBy (ccharacter *Who, truth IgnoreDarkness) const {
4650 int S1 = SquaresUnder, S2 = Who->SquaresUnder;
4651 int LOSRangeSquare = Who->GetLOSRangeSquare();
4652 if (S1 == 1 && S2 == 1) return GetSquareUnder()->CanBeSeenFrom(Who->GetPos(), LOSRangeSquare, IgnoreDarkness);
4653 for (int c1 = 0; c1 < S1; ++c1) {
4654 lsquare *Square = GetLSquareUnder(c1);
4655 for (int c2 = 0; c2 < S2; ++c2) if (Square->CanBeSeenFrom(Who->GetPos(c2), LOSRangeSquare, IgnoreDarkness)) return true;
4657 return false;
4661 int character::GetDistanceSquareFrom (ccharacter *Who) const {
4662 int S1 = SquaresUnder, S2 = Who->SquaresUnder;
4663 if (S1 == 1 && S2 == 1) return (GetPos() - Who->GetPos()).GetLengthSquare();
4664 v2 MinDist(0x7FFF, 0x7FFF);
4665 int MinLength = 0xFFFF;
4666 for (int c1 = 0; c1 < S1; ++c1) {
4667 for (int c2 = 0; c2 < S2; ++c2) {
4668 v2 Dist = GetPos(c1)-Who->GetPos(c2);
4669 if (Dist.X < 0) Dist.X = -Dist.X;
4670 if (Dist.Y < 0) Dist.Y = -Dist.Y;
4671 if (Dist.X <= MinDist.X && Dist.Y <= MinDist.Y) {
4672 MinDist = Dist;
4673 MinLength = Dist.GetLengthSquare();
4674 } else if (Dist.X < MinDist.X || Dist.Y < MinDist.Y) {
4675 int Length = Dist.GetLengthSquare();
4676 if (Length < MinLength) {
4677 MinDist = Dist;
4678 MinLength = Length;
4683 return MinLength;
4687 void character::AttachBodyPart (bodypart *BodyPart) {
4688 SetBodyPart(BodyPart->GetBodyPartIndex(), BodyPart);
4689 if (!AllowSpoil()) BodyPart->ResetSpoiling();
4690 BodyPart->ResetPosition();
4691 BodyPart->UpdatePictures();
4692 CalculateAttributeBonuses();
4693 CalculateBattleInfo();
4694 SendNewDrawRequest();
4695 SignalPossibleTransparencyChange();
4699 /* Returns true if the character has all bodyparts, false if not. */
4700 truth character::HasAllBodyParts () const {
4701 for (int c = 0; c < BodyParts; ++c) if (!GetBodyPart(c) && CanCreateBodyPart(c)) return false;
4702 return true;
4706 bodypart *character::GenerateRandomBodyPart () {
4707 int NeededBodyPart[MAX_BODYPARTS];
4708 int Index = 0;
4709 for (int c = 0; c < BodyParts; ++c) if (!GetBodyPart(c) && CanCreateBodyPart(c)) NeededBodyPart[Index++] = c;
4710 return Index ? CreateBodyPart(NeededBodyPart[RAND() % Index]) : 0;
4714 /* Searches the character's Stack and if it find some bodyparts there that are the character's
4715 * old bodyparts returns a stackiterator to one of them (choosen in random).
4716 * If no fitting bodyparts are found the function returns 0 */
4717 bodypart *character::FindRandomOwnBodyPart (truth AllowNonLiving) const {
4718 itemvector LostAndFound;
4719 for (int c = 0; c < BodyParts; ++c) {
4720 if (!GetBodyPart(c)) {
4721 for (std::list<feuLong>::iterator i = OriginalBodyPartID[c].begin(); i != OriginalBodyPartID[c].end(); ++i) {
4722 bodypart *Found = static_cast<bodypart *>(SearchForItem(*i));
4723 if (Found && (AllowNonLiving || Found->CanRegenerate())) LostAndFound.push_back(Found);
4727 if (LostAndFound.empty()) return 0;
4728 return static_cast<bodypart *>(LostAndFound[RAND() % LostAndFound.size()]);
4732 void character::PrintBeginPoisonedMessage () const {
4733 if (IsPlayer()) ADD_MESSAGE("You seem to be very ill.");
4734 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks very ill.", CHAR_NAME(DEFINITE));
4738 void character::PrintEndPoisonedMessage () const {
4739 if (IsPlayer()) ADD_MESSAGE("You feel better again.");
4740 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks better.", CHAR_NAME(DEFINITE));
4744 void character::PoisonedHandler () {
4745 if (!(RAND() % 100)) VomitAtRandomDirection(500 + RAND_N(250));
4746 int Damage = 0;
4747 for (int Used = 0; Used < GetTemporaryStateCounter(POISONED); Used += 100) if (!(RAND() % 100)) ++Damage;
4748 if (Damage) {
4749 ReceiveDamage(0, Damage, POISON, ALL, 8, false, false, false, false);
4750 CheckDeath(CONST_S("died of acute poisoning"), 0);
4755 truth character::IsWarm () const {
4756 return combinebodypartpredicates()(this, &bodypart::IsWarm, 1);
4760 truth character::IsWarmBlooded() const
4762 return combinebodypartpredicates()(this, &bodypart::IsWarmBlooded, 1);
4766 void character::BeginInvisibility () {
4767 UpdatePictures();
4768 SendNewDrawRequest();
4769 SignalPossibleTransparencyChange();
4773 void character::BeginInfraVision () {
4774 if (IsPlayer()) GetArea()->SendNewDrawRequest();
4778 void character::BeginESP () {
4779 if (IsPlayer()) GetArea()->SendNewDrawRequest();
4783 void character::EndInvisibility () {
4784 UpdatePictures();
4785 SendNewDrawRequest();
4786 SignalPossibleTransparencyChange();
4790 void character::EndInfraVision () {
4791 if (IsPlayer() && IsEnabled()) GetArea()->SendNewDrawRequest();
4795 void character::EndESP () {
4796 if (IsPlayer() && IsEnabled()) GetArea()->SendNewDrawRequest();
4800 void character::Draw (blitdata &BlitData) const {
4801 col24 L = BlitData.Luminance;
4802 if (PLAYER->IsEnabled() &&
4803 ((PLAYER->StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5 &&
4804 (PLAYER->GetPos() - GetPos()).GetLengthSquare() <= PLAYER->GetESPRangeSquare()) ||
4805 (PLAYER->StateIsActivated(INFRA_VISION) && IsWarm())))
4806 BlitData.Luminance = ivanconfig::GetContrastLuminance();
4808 DrawBodyParts(BlitData);
4809 BlitData.Luminance = ivanconfig::GetContrastLuminance();
4810 BlitData.Src.Y = 16;
4811 cint SquareIndex = BlitData.CustomData & SQUARE_INDEX_MASK;
4813 if (GetTeam() == PLAYER->GetTeam() && !IsPlayer() && SquareIndex == GetTameSymbolSquareIndex()) {
4814 BlitData.Src.X = 32;
4815 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData);
4818 if (IsFlying() && SquareIndex == GetFlySymbolSquareIndex()) {
4819 BlitData.Src.X = 128;
4820 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData);
4823 if (IsSwimming() && SquareIndex == GetSwimmingSymbolSquareIndex()) {
4824 BlitData.Src.X = 240;
4825 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData);
4828 if (GetAction() && GetAction()->IsUnconsciousness() && SquareIndex == GetUnconsciousSymbolSquareIndex()) {
4829 BlitData.Src.X = 224;
4830 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData);
4833 BlitData.Src.X = BlitData.Src.Y = 0;
4834 BlitData.Luminance = L;
4838 void character::DrawBodyParts (blitdata &BlitData) const {
4839 GetTorso()->Draw(BlitData);
4843 void character::PrintBeginTeleportMessage () const {
4844 if (IsPlayer()) ADD_MESSAGE("You feel jumpy.");
4848 void character::PrintEndTeleportMessage () const {
4849 if (IsPlayer()) ADD_MESSAGE("You suddenly realize you've always preferred walking to jumping.");
4853 void character::PrintBeginDetectMessage () const {
4854 if (IsPlayer()) ADD_MESSAGE("You feel curious about your surroundings.");
4858 void character::PrintEndDetectMessage () const {
4859 if (IsPlayer()) ADD_MESSAGE("You decide to rely on your intuition from now on.");
4863 void character::TeleportHandler () {
4864 if (!(RAND() % 1500) && !game::IsInWilderness()) {
4865 if (IsPlayer()) ADD_MESSAGE("You feel an urgent spatial relocation is now appropriate.");
4866 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s disappears.", CHAR_NAME(DEFINITE));
4867 TeleportRandomly();
4872 void character::DetectHandler () {
4873 if (IsPlayer()) {
4874 //the AI can't be asked position questions! So only the player can hav this state really :/ a bit daft of me
4875 if (!(RAND()%3000) && !game::IsInWilderness()) {
4876 ADD_MESSAGE("Your mind wanders in search of something.");
4877 DoDetecting(); //in fact, who knows what would happen if a dark frog had the detecting state?
4883 void character::PrintBeginPolymorphMessage () const {
4884 if (IsPlayer()) ADD_MESSAGE("An uncomfortable uncertainty of who you really are overwhelms you.");
4888 void character::PrintEndPolymorphMessage () const {
4889 if (IsPlayer()) ADD_MESSAGE("You feel you are you and no one else.");
4893 void character::PolymorphHandler () {
4894 if (!(RAND() % 1500)) PolymorphRandomly(1, 999999, 200 + RAND() % 800);
4897 void character::PrintBeginTeleportControlMessage () const {
4898 if (IsPlayer()) ADD_MESSAGE("You feel very controlled.");
4902 void character::PrintEndTeleportControlMessage () const {
4903 if (IsPlayer()) ADD_MESSAGE("You feel your control slipping.");
4907 void character::DisplayStethoscopeInfo (character *) const {
4908 felist Info(CONST_S("Information about ") + GetDescription(DEFINITE));
4909 AddSpecialStethoscopeInfo(Info);
4910 Info.AddEntry(CONST_S("Endurance: ") + GetAttribute(ENDURANCE), LIGHT_GRAY);
4911 Info.AddEntry(CONST_S("Perception: ") + GetAttribute(PERCEPTION), LIGHT_GRAY);
4912 Info.AddEntry(CONST_S("Intelligence: ") + GetAttribute(INTELLIGENCE), LIGHT_GRAY);
4913 Info.AddEntry(CONST_S("Wisdom: ") + GetAttribute(WISDOM), LIGHT_GRAY);
4914 //Info.AddEntry(CONST_S("Willpower: ") + GetAttribute(WILL_POWER), LIGHT_GRAY);
4915 Info.AddEntry(CONST_S("Charisma: ") + GetAttribute(CHARISMA), LIGHT_GRAY);
4916 Info.AddEntry(CONST_S("HP: ") + GetHP() + "/" + GetMaxHP(), IsInBadCondition() ? RED : LIGHT_GRAY);
4917 if (GetAction()) Info.AddEntry(festring(GetAction()->GetDescription()).CapitalizeCopy(), LIGHT_GRAY);
4918 for (int c = 0; c < STATES; ++c) {
4919 if (StateIsActivated(1 << c) && (1 << c != HASTE || !StateIsActivated(SLOW)) && (1 << c != SLOW || !StateIsActivated(HASTE)))
4920 Info.AddEntry(StateData[c].Description, LIGHT_GRAY);
4922 switch (GetTirednessState()) {
4923 case FAINTING: Info.AddEntry("Fainting", RED); break;
4924 case EXHAUSTED: Info.AddEntry("Exhausted", LIGHT_GRAY); break;
4926 game::SetStandardListAttributes(Info);
4927 Info.Draw();
4931 truth character::CanUseStethoscope (truth PrintReason) const {
4932 if (PrintReason) ADD_MESSAGE("This type of monster can't use a stethoscope.");
4933 return false;
4937 /* Effect used by at least Sophos.
4938 * NOTICE: Doesn't check for death! */
4939 void character::TeleportSomePartsAway (int NumberToTeleport) {
4940 if (StateIsActivated(TELEPORT_LOCK)) {
4941 if (IsPlayer()) ADD_MESSAGE("You feel very itchy for a moment.");
4942 return;
4944 for (int c = 0; c < NumberToTeleport; ++c) {
4945 int RandomBodyPart = GetRandomNonVitalBodyPart();
4946 if (RandomBodyPart == NONE_INDEX) {
4947 for (; c < NumberToTeleport; ++c) {
4948 GetTorso()->SetHP((GetTorso()->GetHP() << 2) / 5);
4949 sLong TorsosVolume = GetTorso()->GetMainMaterial()->GetVolume() / 10;
4950 if (!TorsosVolume) break;
4951 sLong Amount = (RAND() % TorsosVolume)+1;
4952 item *Lump = GetTorso()->GetMainMaterial()->CreateNaturalForm(Amount);
4953 GetTorso()->GetMainMaterial()->EditVolume(-Amount);
4954 auto pos = GetLevel()->GetRandomSquare();
4955 if (pos == ERROR_V2) {
4956 fprintf(stderr, "oops. `character::TeleportSomePartsAway()` failed.\n");
4957 delete Lump;
4958 } else {
4959 Lump->MoveTo(GetNearLSquare(pos)->GetStack());
4961 if (IsPlayer()) ADD_MESSAGE("Parts of you teleport away.");
4962 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Parts of %s teleport away.", CHAR_NAME(DEFINITE));
4964 } else {
4965 item *SeveredBodyPart = SevereBodyPart(RandomBodyPart);
4966 if (SeveredBodyPart) {
4967 auto pos = GetLevel()->GetRandomSquare();
4968 if (pos == ERROR_V2) {
4969 fprintf(stderr, "oops. `character::TeleportSomePartsAway()` failed.\n");
4970 //SeveredBodyPart->SendToHell(); //???
4971 delete SeveredBodyPart;
4972 if (IsPlayer()) ADD_MESSAGE("Your %s disappears.", GetBodyPartName(RandomBodyPart).CStr());
4973 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s disappears.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart).CStr());
4974 return;
4976 GetNearLSquare(pos)->AddItem(SeveredBodyPart);
4977 SeveredBodyPart->DropEquipment();
4978 if (IsPlayer()) ADD_MESSAGE("Your %s teleports away.", GetBodyPartName(RandomBodyPart).CStr());
4979 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s teleports away.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart).CStr());
4980 } else {
4981 if (IsPlayer()) ADD_MESSAGE("Your %s disappears.", GetBodyPartName(RandomBodyPart).CStr());
4982 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s disappears.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart).CStr());
4989 /* Returns an index of a random bodypart that is not vital. If no non-vital bodypart is found returns NONE_INDEX */
4990 int character::GetRandomNonVitalBodyPart () const {
4991 int OKBodyPart[MAX_BODYPARTS];
4992 int OKBodyParts = 0;
4993 for (int c = 0; c < BodyParts; ++c) if (GetBodyPart(c) && !BodyPartIsVital(c)) OKBodyPart[OKBodyParts++] = c;
4994 return OKBodyParts ? OKBodyPart[RAND() % OKBodyParts] : NONE_INDEX;
4998 void character::CalculateVolumeAndWeight () {
4999 Volume = Stack->GetVolume();
5000 Weight = Stack->GetWeight();
5001 BodyVolume = 0;
5002 CarriedWeight = Weight;
5003 for (int c = 0; c < BodyParts; ++c) {
5004 bodypart *BodyPart = GetBodyPart(c);
5005 if (BodyPart) {
5006 BodyVolume += BodyPart->GetBodyPartVolume();
5007 Volume += BodyPart->GetVolume();
5008 CarriedWeight += BodyPart->GetCarriedWeight();
5009 Weight += BodyPart->GetWeight();
5015 void character::SignalVolumeAndWeightChange () {
5016 if (!IsInitializing()) {
5017 CalculateVolumeAndWeight();
5018 if (IsEnabled()) CalculateBurdenState();
5019 if (MotherEntity) MotherEntity->SignalVolumeAndWeightChange();
5024 void character::SignalEmitationIncrease (col24 EmitationUpdate) {
5025 if (game::CompareLights(EmitationUpdate, Emitation) > 0) {
5026 game::CombineLights(Emitation, EmitationUpdate);
5027 if (MotherEntity) MotherEntity->SignalEmitationIncrease(EmitationUpdate);
5028 else if (SquareUnder[0] && !game::IsInWilderness()) {
5029 for(int c = 0; c < GetSquaresUnder(); ++c) GetLSquareUnder()->SignalEmitationIncrease(EmitationUpdate);
5035 void character::SignalEmitationDecrease (col24 EmitationUpdate) {
5036 if (game::CompareLights(EmitationUpdate, Emitation) >= 0 && Emitation) {
5037 col24 Backup = Emitation;
5038 CalculateEmitation();
5039 if (Backup != Emitation) {
5040 if (MotherEntity) MotherEntity->SignalEmitationDecrease(EmitationUpdate);
5041 else if (SquareUnder[0] && !game::IsInWilderness()) {
5042 for (int c = 0; c < GetSquaresUnder(); ++c) GetLSquareUnder(c)->SignalEmitationDecrease(EmitationUpdate);
5049 void character::CalculateEmitation () {
5050 Emitation = GetBaseEmitation();
5051 for (int c = 0; c < BodyParts; ++c) {
5052 bodypart *BodyPart = GetBodyPart(c);
5053 if (BodyPart) game::CombineLights(Emitation, BodyPart->GetEmitation());
5055 game::CombineLights(Emitation, Stack->GetEmitation());
5059 void character::CalculateAll () {
5060 Flags |= C_INITIALIZING;
5061 CalculateAttributeBonuses();
5062 CalculateVolumeAndWeight();
5063 CalculateEmitation();
5064 CalculateBodyPartMaxHPs(0);
5065 CalculateMaxStamina();
5066 CalculateBurdenState();
5067 CalculateBattleInfo();
5068 Flags &= ~C_INITIALIZING;
5072 void character::CalculateHP () {
5073 HP = sumbodypartproperties()(this, &bodypart::GetHP);
5077 void character::CalculateMaxHP () {
5078 MaxHP = sumbodypartproperties()(this, &bodypart::GetMaxHP);
5082 void character::CalculateBodyPartMaxHPs (feuLong Flags) {
5083 doforbodypartswithparam<feuLong>()(this, &bodypart::CalculateMaxHP, Flags);
5084 CalculateMaxHP();
5085 CalculateHP();
5089 truth character::EditAttribute (int Identifier, int Value) {
5090 if (Identifier == ENDURANCE && UseMaterialAttributes()) return false;
5091 if (RawEditAttribute(BaseExperience[Identifier], Value)) {
5092 if (!IsInitializing()) {
5093 if (Identifier == LEG_STRENGTH) CalculateBurdenState();
5094 else if (Identifier == ENDURANCE) CalculateBodyPartMaxHPs();
5095 else if (IsPlayer() && Identifier == PERCEPTION) game::SendLOSUpdateRequest();
5096 else if (IsPlayerKind() && (Identifier == INTELLIGENCE || Identifier == WISDOM || Identifier == CHARISMA)) UpdatePictures();
5097 CalculateBattleInfo();
5099 return true;
5101 return false;
5105 truth character::ActivateRandomState (int Flags, int Time, sLong Seed) {
5106 femath::SaveSeed();
5107 if (Seed) femath::SetSeed(Seed);
5108 sLong ToBeActivated = GetRandomState(Flags|DUR_TEMPORARY);
5109 femath::LoadSeed();
5110 if (!ToBeActivated) return false;
5111 BeginTemporaryState(ToBeActivated, Time);
5112 return true;
5116 truth character::GainRandomIntrinsic (int Flags) {
5117 sLong ToBeActivated = GetRandomState(Flags|DUR_PERMANENT);
5118 if (!ToBeActivated) return false;
5119 GainIntrinsic(ToBeActivated);
5120 return true;
5124 /* Returns 0 if state not found */
5125 sLong character::GetRandomState (int Flags) const {
5126 sLong OKStates[STATES];
5127 int NumberOfOKStates = 0;
5128 for (int c = 0; c < STATES; ++c) {
5129 if (StateData[c].Flags & Flags & DUR_FLAGS && StateData[c].Flags & Flags & SRC_FLAGS) OKStates[NumberOfOKStates++] = 1 << c;
5131 return NumberOfOKStates ? OKStates[RAND() % NumberOfOKStates] : 0;
5135 int characterprototype::CreateSpecialConfigurations (characterdatabase **TempConfig, int Configs, int Level) {
5136 if (Level == 0 && TempConfig[0]->CreateDivineConfigurations) {
5137 Configs = databasecreator<character>::CreateDivineConfigurations(this, TempConfig, Configs);
5139 if (Level == 1 && TempConfig[0]->CreateUndeadConfigurations) {
5140 for (int c = 1; c < protocontainer<character>::GetSize(); ++c) {
5141 const character::prototype *Proto = protocontainer<character>::GetProto(c);
5142 if (!Proto) continue; // missing character
5143 const character::database *const *CharacterConfigData = Proto->GetConfigData();
5144 if (!CharacterConfigData) ABORT("No database entry for character <%s>!", Proto->GetClassID());
5145 const character::database*const* End = CharacterConfigData+Proto->GetConfigSize();
5146 for (++CharacterConfigData; CharacterConfigData != End; ++CharacterConfigData) {
5147 const character::database *CharacterDataBase = *CharacterConfigData;
5148 if (CharacterDataBase->UndeadVersions) {
5149 character::database* ConfigDataBase = new character::database(**TempConfig);
5150 festring ucfgname = "undead(";
5151 //ucfgname << (*TempConfig)->CfgStrName;
5152 ucfgname << CharacterDataBase->CfgStrName;
5153 ucfgname << ") ";
5154 ucfgname << Proto->GetClassID();
5155 //fprintf(stderr, "UNDEAD: %s\n", ucfgname.CStr());
5156 ConfigDataBase->InitDefaults(this, (c << 8) | CharacterDataBase->Config, ucfgname);
5157 ConfigDataBase->PostFix << "of ";
5158 if (CharacterDataBase->Adjective.GetSize()) {
5159 if (CharacterDataBase->UsesLongAdjectiveArticle) ConfigDataBase->PostFix << "an ";
5160 else ConfigDataBase->PostFix << "a ";
5161 ConfigDataBase->PostFix << CharacterDataBase->Adjective << ' ';
5162 } else {
5163 if (CharacterDataBase->UsesLongArticle) ConfigDataBase->PostFix << "an ";
5164 else ConfigDataBase->PostFix << "a ";
5166 ConfigDataBase->PostFix << CharacterDataBase->NameSingular;
5167 if (CharacterDataBase->PostFix.GetSize()) ConfigDataBase->PostFix << ' ' << CharacterDataBase->PostFix;
5168 int P1 = TempConfig[0]->UndeadAttributeModifier;
5169 int P2 = TempConfig[0]->UndeadVolumeModifier;
5170 int c2;
5171 for (c2 = 0; c2 < ATTRIBUTES; ++c2) ConfigDataBase->*ExpPtr[c2] = CharacterDataBase->*ExpPtr[c2] * P1 / 100;
5172 for (c2 = 0; c2 < EQUIPMENT_DATAS; ++c2) ConfigDataBase->*EquipmentDataPtr[c2] = contentscript<item>();
5173 ConfigDataBase->DefaultIntelligence = 5;
5174 ConfigDataBase->DefaultWisdom = 5;
5175 ConfigDataBase->DefaultCharisma = 5;
5176 ConfigDataBase->TotalSize = CharacterDataBase->TotalSize;
5177 ConfigDataBase->Sex = CharacterDataBase->Sex;
5178 ConfigDataBase->AttributeBonus = CharacterDataBase->AttributeBonus;
5179 ConfigDataBase->TotalVolume = CharacterDataBase->TotalVolume * P2 / 100;
5180 if (TempConfig[0]->UndeadCopyMaterials) {
5181 ConfigDataBase->HeadBitmapPos = CharacterDataBase->HeadBitmapPos;
5182 ConfigDataBase->HairColor = CharacterDataBase->HairColor;
5183 ConfigDataBase->EyeColor = CharacterDataBase->EyeColor;
5184 ConfigDataBase->CapColor = CharacterDataBase->CapColor;
5185 ConfigDataBase->FleshMaterial = CharacterDataBase->FleshMaterial;
5186 ConfigDataBase->BloodMaterial = CharacterDataBase->BloodMaterial;
5187 ConfigDataBase->VomitMaterial = CharacterDataBase->VomitMaterial;
5188 ConfigDataBase->SweatMaterial = CharacterDataBase->SweatMaterial;
5190 ConfigDataBase->KnownCWeaponSkills = CharacterDataBase->KnownCWeaponSkills;
5191 ConfigDataBase->CWeaponSkillHits = CharacterDataBase->CWeaponSkillHits;
5192 ConfigDataBase->PostProcess();
5193 TempConfig[Configs++] = ConfigDataBase;
5198 if (Level == 0 && TempConfig[0]->CreateGolemMaterialConfigurations) {
5199 for (int c = 1; c < protocontainer<material>::GetSize(); ++c) {
5200 const material::prototype* Proto = protocontainer<material>::GetProto(c);
5201 if (!Proto) continue; // missing matherial
5202 const material::database*const* MaterialConfigData = Proto->GetConfigData();
5203 const material::database*const* End = MaterialConfigData + Proto->GetConfigSize();
5204 for (++MaterialConfigData; MaterialConfigData != End; ++MaterialConfigData) {
5205 const material::database* MaterialDataBase = *MaterialConfigData;
5206 if (MaterialDataBase->CategoryFlags&IS_GOLEM_MATERIAL) {
5207 character::database* ConfigDataBase = new character::database(**TempConfig);
5208 festring gcfgname = "golem ";
5209 gcfgname << MaterialDataBase->CfgStrName;
5210 //fprintf(stderr, "GOLEM %d [%s]\n", MaterialDataBase->Config, gcfgname.CStr());
5211 ConfigDataBase->InitDefaults(this, MaterialDataBase->Config, gcfgname);
5212 ConfigDataBase->Adjective = MaterialDataBase->NameStem;
5213 ConfigDataBase->UsesLongAdjectiveArticle = MaterialDataBase->NameFlags & USE_AN;
5214 ConfigDataBase->AttachedGod = MaterialDataBase->AttachedGod;
5215 TempConfig[Configs++] = ConfigDataBase;
5220 return Configs;
5224 double character::GetTimeToDie (ccharacter *Enemy, int Damage, double ToHitValue, truth AttackIsBlockable, truth UseMaxHP) const {
5225 double DodgeValue = GetDodgeValue();
5226 if (!Enemy->CanBeSeenBy(this, true)) ToHitValue *= 2;
5227 if (!CanBeSeenBy(Enemy, true)) DodgeValue *= 2;
5228 double MinHits = 1000;
5229 truth First = true;
5230 for (int c = 0; c < BodyParts; ++c) {
5231 if (BodyPartIsVital(c) && GetBodyPart(c)) {
5232 double Hits = GetBodyPart(c)->GetTimeToDie(Damage, ToHitValue, DodgeValue, AttackIsBlockable, UseMaxHP);
5233 if (First) { MinHits = Hits; First = false; } else MinHits = 1/(1/MinHits+1/Hits);
5236 return MinHits;
5240 double character::GetRelativeDanger (ccharacter *Enemy, truth UseMaxHP) const {
5241 double Danger = Enemy->GetTimeToKill(this, UseMaxHP)/GetTimeToKill(Enemy, UseMaxHP);
5242 int EnemyAP = Enemy->GetMoveAPRequirement(1);
5243 int ThisAP = GetMoveAPRequirement(1);
5244 if (EnemyAP > ThisAP) Danger *= 1.25; else if (ThisAP > EnemyAP) Danger *= 0.80;
5245 if (!Enemy->CanBeSeenBy(this, true)) Danger *= (Enemy->IsPlayer() ? 0.2 : 0.5);
5246 if (!CanBeSeenBy(Enemy, true)) Danger *= (IsPlayer() ? 5.0 : 2.0);
5247 if (GetAttribute(INTELLIGENCE) < 10 && !IsPlayer()) Danger *= 0.80;
5248 if (Enemy->GetAttribute(INTELLIGENCE) < 10 && !Enemy->IsPlayer()) Danger *= 1.25;
5249 return Limit(Danger, 0.001, 1000.0);
5253 festring character::GetBodyPartName (int I, truth Articled) const {
5254 if (I == TORSO_INDEX) return Articled ? CONST_S("a torso") : CONST_S("torso");
5255 ABORT("Illegal character bodypart name request!");
5256 return "";
5260 item *character::SearchForItem (feuLong ID) const {
5261 item *Equipment = findequipment<feuLong>()(this, &item::HasID, ID);
5262 if (Equipment) return Equipment;
5263 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) if (i->GetID() == ID) return *i;
5264 return 0;
5268 truth character::ContentsCanBeSeenBy (ccharacter *Viewer) const {
5269 return (Viewer == this);
5273 truth character::HitEffect (character *Enemy, item* Weapon, v2 HitPos, int Type, int BodyPartIndex,
5274 int Direction, truth BlockedByArmour, truth Critical, int DoneDamage)
5276 if (Weapon) return Weapon->HitEffect(this, Enemy, HitPos, BodyPartIndex, Direction, BlockedByArmour);
5277 if (Type == UNARMED_ATTACK) return Enemy->SpecialUnarmedEffect(this, HitPos, BodyPartIndex, Direction, BlockedByArmour);
5278 if (Type == KICK_ATTACK) return Enemy->SpecialKickEffect(this, HitPos, BodyPartIndex, Direction, BlockedByArmour);
5279 if (Type == BITE_ATTACK) return Enemy->SpecialBiteEffect(this, HitPos, BodyPartIndex, Direction, BlockedByArmour, Critical, DoneDamage);
5280 return false;
5284 void character::WeaponSkillHit (item *Weapon, int Type, int Hits) {
5285 int Category;
5286 if (Type == UNARMED_ATTACK) Category = UNARMED;
5287 else if (Type == WEAPON_ATTACK) { Weapon->WeaponSkillHit(Hits); return; }
5288 else if (Type == KICK_ATTACK) Category = KICK;
5289 else if (Type == BITE_ATTACK) Category = BITE;
5290 else if (Type == THROW_ATTACK) { if (!IsHumanoid()) return; Category = Weapon->GetWeaponCategory(); }
5291 else { ABORT("Illegal Type %d passed to character::WeaponSkillHit()!", Type); return; }
5292 if (GetCWeaponSkill(Category)->AddHit(Hits)) {
5293 CalculateBattleInfo();
5294 if (IsPlayer()) GetCWeaponSkill(Category)->AddLevelUpMessage(Category);
5299 /* Returns 0 if character cannot be duplicated */
5300 character *character::Duplicate (feuLong Flags) {
5301 if (!(Flags & IGNORE_PROHIBITIONS) && !CanBeCloned()) return 0;
5302 character *Char = GetProtoType()->Clone(this);
5303 if (Flags & MIRROR_IMAGE) {
5304 DuplicateEquipment(Char, Flags & ~IGNORE_PROHIBITIONS);
5305 Char->SetLifeExpectancy(Flags >> LE_BASE_SHIFT & LE_BASE_RANGE, Flags >> LE_RAND_SHIFT & LE_RAND_RANGE);
5307 Char->CalculateAll();
5308 Char->CalculateEmitation();
5309 Char->UpdatePictures();
5310 Char->Flags &= ~(C_INITIALIZING|C_IN_NO_MSG_MODE);
5311 return Char;
5315 truth character::TryToEquip (item *Item) {
5316 if (!Item->AllowEquip() || !CanUseEquipment() || GetAttribute(WISDOM) >= Item->GetWearWisdomLimit() || Item->GetSquaresUnder() != 1) {
5317 return false;
5320 for (int e = 0; e < GetEquipments(); ++e) {
5321 if (GetBodyPartOfEquipment(e) && EquipmentIsAllowed(e)) {
5322 sorter Sorter = EquipmentSorter(e);
5323 if ((Sorter == 0 || (Item->*Sorter)(this)) &&
5324 ((e != RIGHT_WIELDED_INDEX && e != LEFT_WIELDED_INDEX) ||
5325 Item->IsWeapon(this) || Item->IsShield(this)) && AllowEquipment(Item, e))
5327 item *OldEquipment = GetEquipment(e);
5328 if (BoundToUse(OldEquipment, e)) continue;
5329 lsquare *LSquareUnder = GetLSquareUnder();
5330 stack *StackUnder = LSquareUnder->GetStack();
5331 msgsystem::DisableMessages();
5332 Flags |= C_PICTURE_UPDATES_FORBIDDEN;
5333 LSquareUnder->Freeze();
5334 StackUnder->Freeze();
5335 double Danger = GetRelativeDanger(PLAYER);
5336 if (OldEquipment) OldEquipment->RemoveFromSlot();
5337 Item->RemoveFromSlot();
5338 SetEquipment(e, Item);
5339 double NewDanger = GetRelativeDanger(PLAYER);
5340 Item->RemoveFromSlot();
5341 StackUnder->AddItem(Item);
5342 if (OldEquipment) SetEquipment(e, OldEquipment);
5343 msgsystem::EnableMessages();
5344 Flags &= ~C_PICTURE_UPDATES_FORBIDDEN;
5345 LSquareUnder->UnFreeze();
5346 StackUnder->UnFreeze();
5347 if (OldEquipment) {
5348 if (NewDanger > Danger || BoundToUse(Item, e)) {
5349 room *Room = GetRoom();
5350 if (!Room || Room->PickupItem(this, Item, 1)) {
5351 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s drops %s %s and equips %s instead.", CHAR_NAME(DEFINITE), CHAR_POSSESSIVE_PRONOUN, OldEquipment->CHAR_NAME(UNARTICLED), Item->CHAR_NAME(INDEFINITE));
5352 if (Room) Room->DropItem(this, OldEquipment, 1);
5353 OldEquipment->MoveTo(StackUnder);
5354 Item->RemoveFromSlot();
5355 SetEquipment(e, Item);
5356 DexterityAction(5);
5357 return true;
5360 } else {
5361 if (NewDanger > Danger || (NewDanger == Danger && e != RIGHT_WIELDED_INDEX && e != LEFT_WIELDED_INDEX) || BoundToUse(Item, e)) {
5362 room *Room = GetRoom();
5363 if (!Room || Room->PickupItem(this, Item, 1)) {
5364 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s picks up and equips %s.", CHAR_NAME(DEFINITE), Item->CHAR_NAME(INDEFINITE));
5365 Item->RemoveFromSlot();
5366 SetEquipment(e, Item);
5367 DexterityAction(5);
5368 return true;
5375 return false;
5379 truth character::TryToConsume (item *Item) {
5380 return Item->CanBeEatenByAI(this) && ConsumeItem(Item, Item->GetConsumeMaterial(this)->GetConsumeVerb());
5384 void character::UpdateESPLOS () const {
5385 if (StateIsActivated(ESP) && !game::IsInWilderness()) {
5386 for (int c = 0; c < game::GetTeams(); ++c) {
5387 for (std::list<character *>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i) {
5388 const character *ch = *i;
5389 if (ch->IsEnabled()) ch->SendNewDrawRequest();
5396 int character::GetCWeaponSkillLevel (citem *Item) const {
5397 if (Item->GetWeaponCategory() < GetAllowedWeaponSkillCategories()) return GetCWeaponSkill(Item->GetWeaponCategory())->GetLevel();
5398 return 0;
5402 void character::PrintBeginPanicMessage () const {
5403 if (IsPlayer()) ADD_MESSAGE("You panic!");
5404 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s panics.", CHAR_NAME(DEFINITE));
5408 void character::PrintEndPanicMessage () const {
5409 if (IsPlayer()) ADD_MESSAGE("You finally calm down.");
5410 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s calms down.", CHAR_NAME(DEFINITE));
5414 void character::CheckPanic (int Ticks) {
5415 if (GetPanicLevel() > 1 && !StateIsActivated(PANIC) && GetHP()*100 < RAND()%(GetPanicLevel()*GetMaxHP()<<1) && !StateIsActivated(FEARLESS)) {
5416 BeginTemporaryState(PANIC, ((Ticks * 3) >> 2) + RAND() % ((Ticks >> 1) + 1)); // 25% randomness to ticks...
5421 /* returns 0 if fails else the newly created character */
5422 character *character::DuplicateToNearestSquare (character *Cloner, feuLong Flags) {
5423 character *NewlyCreated = Duplicate(Flags);
5424 if (!NewlyCreated) return 0;
5425 if (Flags & CHANGE_TEAM && Cloner) NewlyCreated->ChangeTeam(Cloner->GetTeam());
5426 if (!NewlyCreated->PutNear(GetPos(), true)) {
5427 //NewlyCreated->Disable();
5428 //NewlyCreated->SendToHell();
5429 delete NewlyCreated;
5430 return 0;
5432 return NewlyCreated;
5436 void character::SignalSpoil (material *m) {
5437 if (GetMotherEntity()) GetMotherEntity()->SignalSpoil(m);
5438 else Disappear(0, "spoil", &item::IsVeryCloseToSpoiling);
5442 truth character::CanHeal () const {
5443 for (int c = 0; c < BodyParts; ++c) {
5444 bodypart *BodyPart = GetBodyPart(c);
5445 if (BodyPart && BodyPart->CanRegenerate() && BodyPart->GetHP() < BodyPart->GetMaxHP()) return true;
5447 return false;
5451 int character::GetRelation (ccharacter *Who) const {
5452 return GetTeam()->GetRelation(Who->GetTeam());
5456 truth (item::*AffectTest[BASE_ATTRIBUTES])() const = {
5457 &item::AffectsEndurance,
5458 &item::AffectsPerception,
5459 &item::AffectsIntelligence,
5460 &item::AffectsWisdom,
5461 &item::AffectsWillPower,
5462 &item::AffectsCharisma,
5463 &item::AffectsMana
5467 /* Returns nonzero if endurance has decreased and death may occur */
5468 truth character::CalculateAttributeBonuses () {
5469 doforbodyparts()(this, &bodypart::CalculateAttributeBonuses);
5470 int BackupBonus[BASE_ATTRIBUTES];
5471 int BackupCarryingBonus = CarryingBonus;
5472 CarryingBonus = 0;
5473 int c1;
5474 for (c1 = 0; c1 < BASE_ATTRIBUTES; ++c1) {
5475 BackupBonus[c1] = AttributeBonus[c1];
5476 AttributeBonus[c1] = 0;
5478 for (c1 = 0; c1 < GetEquipments(); ++c1) {
5479 item *Equipment = GetEquipment(c1);
5480 if (!Equipment || !Equipment->IsInCorrectSlot(c1)) continue;
5481 for (int c2 = 0; c2 < BASE_ATTRIBUTES; ++c2) {
5482 if ((Equipment->*AffectTest[c2])()) AttributeBonus[c2] += Equipment->GetEnchantment();
5484 if (Equipment->AffectsCarryingCapacity()) CarryingBonus += Equipment->GetCarryingBonus();
5487 ApplySpecialAttributeBonuses();
5489 if (IsPlayer() && !IsInitializing() && AttributeBonus[PERCEPTION] != BackupBonus[PERCEPTION]) game::SendLOSUpdateRequest();
5490 if (IsPlayer() && !IsInitializing() && AttributeBonus[INTELLIGENCE] != BackupBonus[INTELLIGENCE]) UpdateESPLOS();
5492 if (!IsInitializing() && CarryingBonus != BackupCarryingBonus) CalculateBurdenState();
5494 if (!IsInitializing() && AttributeBonus[ENDURANCE] != BackupBonus[ENDURANCE]) {
5495 CalculateBodyPartMaxHPs();
5496 CalculateMaxStamina();
5497 return AttributeBonus[ENDURANCE] < BackupBonus[ENDURANCE];
5500 return false;
5504 void character::ApplyEquipmentAttributeBonuses (item *Equipment) {
5505 if (Equipment->AffectsEndurance()) {
5506 AttributeBonus[ENDURANCE] += Equipment->GetEnchantment();
5507 CalculateBodyPartMaxHPs();
5508 CalculateMaxStamina();
5510 if (Equipment->AffectsPerception()) {
5511 AttributeBonus[PERCEPTION] += Equipment->GetEnchantment();
5512 if (IsPlayer()) game::SendLOSUpdateRequest();
5514 if (Equipment->AffectsIntelligence()) {
5515 AttributeBonus[INTELLIGENCE] += Equipment->GetEnchantment();
5516 if (IsPlayer()) UpdateESPLOS();
5518 if (Equipment->AffectsWisdom()) AttributeBonus[WISDOM] += Equipment->GetEnchantment();
5519 if (Equipment->AffectsWillPower()) AttributeBonus[WILL_POWER] += Equipment->GetEnchantment();
5520 if (Equipment->AffectsCharisma()) AttributeBonus[CHARISMA] += Equipment->GetEnchantment();
5521 if (Equipment->AffectsMana()) AttributeBonus[MANA] += Equipment->GetEnchantment();
5522 if (Equipment->AffectsCarryingCapacity()) {
5523 CarryingBonus += Equipment->GetCarryingBonus();
5524 CalculateBurdenState();
5529 void character::ReceiveAntidote (sLong Amount) {
5530 if (StateIsActivated(POISONED)) {
5531 if (GetTemporaryStateCounter(POISONED) > Amount) {
5532 EditTemporaryStateCounter(POISONED, -Amount);
5533 Amount = 0;
5534 } else {
5535 if (IsPlayer()) ADD_MESSAGE("Aaaah... You feel much better.");
5536 Amount -= GetTemporaryStateCounter(POISONED);
5537 DeActivateTemporaryState(POISONED);
5540 if ((Amount >= 100 || RAND_N(100) < Amount) && StateIsActivated(PARASITIZED)) {
5541 if (IsPlayer()) ADD_MESSAGE("Something in your belly didn't seem to like this stuff.");
5542 DeActivateTemporaryState(PARASITIZED);
5543 Amount -= Min(100, Amount);
5545 if ((Amount >= 100 || RAND_N(100) < Amount) && StateIsActivated(LEPROSY)) {
5546 if (IsPlayer()) ADD_MESSAGE("You are not falling to pieces anymore.");
5547 DeActivateTemporaryState(LEPROSY);
5548 Amount -= Min(100, Amount);
5553 void character::AddAntidoteConsumeEndMessage () const {
5554 if (StateIsActivated(POISONED)) {
5555 // true only if the antidote didn't cure the poison completely
5556 if (IsPlayer()) ADD_MESSAGE("Your body processes the poison in your veins with rapid speed.");
5561 truth character::IsDead () const {
5562 for (int c = 0; c < BodyParts; ++c) {
5563 bodypart *BodyPart = GetBodyPart(c);
5564 if (BodyPartIsVital(c) && (!BodyPart || BodyPart->GetHP() < 1)) return true;
5566 return false;
5570 void character::SignalSpoilLevelChange (material *m) {
5571 if (GetMotherEntity()) GetMotherEntity()->SignalSpoilLevelChange(m); else UpdatePictures();
5575 void character::AddOriginalBodyPartID (int I, feuLong What) {
5576 if (std::find(OriginalBodyPartID[I].begin(), OriginalBodyPartID[I].end(), What) == OriginalBodyPartID[I].end()) {
5577 OriginalBodyPartID[I].push_back(What);
5578 if (OriginalBodyPartID[I].size() > 100) OriginalBodyPartID[I].erase(OriginalBodyPartID[I].begin());
5583 void character::AddToInventory (const fearray<contentscript<item> > &ItemArray, int SpecialFlags) {
5584 for (uInt c1 = 0; c1 < ItemArray.Size; ++c1) {
5585 if (ItemArray[c1].IsValid()) {
5586 const interval *TimesPtr = ItemArray[c1].GetTimes();
5587 int Times = TimesPtr ? TimesPtr->Randomize() : 1;
5588 for (int c2 = 0; c2 < Times; ++c2) {
5589 item *Item = ItemArray[c1].Instantiate(SpecialFlags);
5590 if (Item) {
5591 Stack->AddItem(Item);
5592 Item->SpecialGenerationHandler();
5600 truth character::HasHadBodyPart (citem *Item) const {
5601 for (int c = 0; c < BodyParts; ++c) {
5602 if (std::find(OriginalBodyPartID[c].begin(), OriginalBodyPartID[c].end(), Item->GetID()) != OriginalBodyPartID[c].end()) {
5603 return true;
5606 return GetPolymorphBackup() && GetPolymorphBackup()->HasHadBodyPart(Item);
5610 festring &character::ProcessMessage (festring &Msg) const {
5611 SEARCH_N_REPLACE(Msg, "@nu", GetName(UNARTICLED));
5612 SEARCH_N_REPLACE(Msg, "@ni", GetName(INDEFINITE));
5613 SEARCH_N_REPLACE(Msg, "@nd", GetName(DEFINITE));
5614 SEARCH_N_REPLACE(Msg, "@du", GetDescription(UNARTICLED));
5615 SEARCH_N_REPLACE(Msg, "@di", GetDescription(INDEFINITE));
5616 SEARCH_N_REPLACE(Msg, "@dd", GetDescription(DEFINITE));
5617 SEARCH_N_REPLACE(Msg, "@pp", GetPersonalPronoun());
5618 SEARCH_N_REPLACE(Msg, "@sp", GetPossessivePronoun());
5619 SEARCH_N_REPLACE(Msg, "@op", GetObjectPronoun());
5620 SEARCH_N_REPLACE(Msg, "@Nu", GetName(UNARTICLED).CapitalizeCopy());
5621 SEARCH_N_REPLACE(Msg, "@Ni", GetName(INDEFINITE).CapitalizeCopy());
5622 SEARCH_N_REPLACE(Msg, "@Nd", GetName(DEFINITE).CapitalizeCopy());
5623 SEARCH_N_REPLACE(Msg, "@Du", GetDescription(UNARTICLED).CapitalizeCopy());
5624 SEARCH_N_REPLACE(Msg, "@Di", GetDescription(INDEFINITE).CapitalizeCopy());
5625 SEARCH_N_REPLACE(Msg, "@Dd", GetDescription(DEFINITE).CapitalizeCopy());
5626 SEARCH_N_REPLACE(Msg, "@Pp", GetPersonalPronoun().CapitalizeCopy());
5627 SEARCH_N_REPLACE(Msg, "@Sp", GetPossessivePronoun().CapitalizeCopy());
5628 SEARCH_N_REPLACE(Msg, "@Op", GetObjectPronoun().CapitalizeCopy());
5629 SEARCH_N_REPLACE(Msg, "@Gd", GetMasterGod()->GetName());
5630 return Msg;
5634 void character::ProcessAndAddMessage (festring Msg) const {
5635 ADD_MESSAGE("%s", ProcessMessage(Msg).CStr());
5639 void character::BeTalkedTo () {
5640 static sLong Said;
5641 if (GetRelation(PLAYER) == HOSTILE)
5642 ProcessAndAddMessage(GetHostileReplies()[RandomizeReply(Said, GetHostileReplies().Size)]);
5643 else
5644 ProcessAndAddMessage(GetFriendlyReplies()[RandomizeReply(Said, GetFriendlyReplies().Size)]);
5648 truth character::CheckZap () {
5649 if (!CanZap()) {
5650 ADD_MESSAGE("This monster type can't zap.");
5651 return false;
5653 return true;
5657 void character::DamageAllItems (character *Damager, int Damage, int Type) {
5658 GetStack()->ReceiveDamage(Damager, Damage, Type);
5659 for (int c = 0; c < GetEquipments(); ++c) {
5660 item *Equipment = GetEquipment(c);
5661 if (Equipment) Equipment->ReceiveDamage(Damager, Damage, Type);
5666 truth character::Equips (citem *Item) const {
5667 return combineequipmentpredicateswithparam<feuLong>()(this, &item::HasID, Item->GetID(), 1);
5671 void character::PrintBeginConfuseMessage () const {
5672 if (IsPlayer()) ADD_MESSAGE("You feel quite happy.");
5676 void character::PrintEndConfuseMessage () const {
5677 if (IsPlayer()) ADD_MESSAGE("The world is boring again.");
5681 v2 character::ApplyStateModification (v2 TryDirection) const {
5682 if (!StateIsActivated(CONFUSED) || RAND() & 15 || game::IsInWilderness()) return TryDirection;
5683 v2 To = GetLevel()->GetFreeAdjacentSquare(this, GetPos(), true);
5684 if (To == ERROR_V2) return TryDirection;
5685 To -= GetPos();
5686 if (To != TryDirection && IsPlayer()) ADD_MESSAGE("Whoa! You somehow don't manage to walk straight.");
5687 return To;
5691 void character::AddConfuseHitMessage () const {
5692 if (IsPlayer()) ADD_MESSAGE("This stuff is confusing.");
5696 item *character::SelectFromPossessions (cfestring &Topic, sorter Sorter) {
5697 itemvector ReturnVector;
5698 SelectFromPossessions(ReturnVector, Topic, NO_MULTI_SELECT, Sorter);
5699 return !ReturnVector.empty() ? ReturnVector[0] : 0;
5703 truth character::SelectFromPossessions (itemvector &ReturnVector, cfestring &Topic, int Flags, sorter Sorter) {
5704 felist List(Topic);
5705 truth InventoryPossible = GetStack()->SortedItems(this, Sorter);
5706 if (InventoryPossible) List.AddEntry(CONST_S("choose from inventory"), LIGHT_GRAY, 20, game::AddToItemDrawVector(itemvector()));
5707 truth Any = false;
5708 itemvector Item;
5709 festring Entry;
5710 int c;
5711 for (c = 0; c < BodyParts; ++c) {
5712 bodypart *BodyPart = GetBodyPart(c);
5713 if (BodyPart && (Sorter == 0 || (BodyPart->*Sorter)(this))) {
5714 Item.push_back(BodyPart);
5715 Entry.Empty();
5716 BodyPart->AddName(Entry, UNARTICLED);
5717 int ImageKey = game::AddToItemDrawVector(itemvector(1, BodyPart));
5718 List.AddEntry(Entry, LIGHT_GRAY, 20, ImageKey, true);
5719 Any = true;
5722 for (c = 0; c < GetEquipments(); ++c) {
5723 item *Equipment = GetEquipment(c);
5724 if (Equipment && (Sorter == 0 || (Equipment->*Sorter)(this))) {
5725 Item.push_back(Equipment);
5726 Entry = GetEquipmentName(c);
5727 Entry << ':';
5728 Entry.Resize(20);
5729 Equipment->AddInventoryEntry(this, Entry, 1, true);
5730 AddSpecialEquipmentInfo(Entry, c);
5731 int ImageKey = game::AddToItemDrawVector(itemvector(1, Equipment));
5732 List.AddEntry(Entry, LIGHT_GRAY, 20, ImageKey, true);
5733 Any = true;
5736 if (Any) {
5737 game::SetStandardListAttributes(List);
5738 List.SetFlags(SELECTABLE|DRAW_BACKGROUND_AFTERWARDS);
5739 List.SetEntryDrawer(game::ItemEntryDrawer);
5740 game::DrawEverythingNoBlit();
5741 int Chosen = List.Draw();
5742 game::ClearItemDrawVector();
5743 if (Chosen != ESCAPED) {
5744 if ((InventoryPossible && !Chosen) || Chosen & FELIST_ERROR_BIT) {
5745 GetStack()->DrawContents(ReturnVector, this, Topic, Flags, Sorter);
5746 } else {
5747 ReturnVector.push_back(Item[InventoryPossible ? Chosen - 1 : Chosen]);
5748 if (Flags & SELECT_PAIR && ReturnVector[0]->HandleInPairs()) {
5749 item *PairEquipment = GetPairEquipment(ReturnVector[0]->GetEquipmentIndex());
5750 if (PairEquipment && PairEquipment->CanBePiledWith(ReturnVector[0], this)) ReturnVector.push_back(PairEquipment);
5754 } else {
5755 if (!GetStack()->SortedItems(this, Sorter)) return false;
5756 game::ClearItemDrawVector();
5757 GetStack()->DrawContents(ReturnVector, this, Topic, Flags, Sorter);
5759 return true;
5763 truth character::EquipsSomething (sorter Sorter) {
5764 for (int c = 0; c < GetEquipments(); ++c) {
5765 item *Equipment = GetEquipment(c);
5766 if (Equipment && (Sorter == 0 || (Equipment->*Sorter)(this))) return true;
5768 return false;
5772 material *character::CreateBodyPartMaterial (int, sLong Volume) const {
5773 return MAKE_MATERIAL(GetFleshMaterial(), Volume);
5777 truth character::CheckTalk () {
5778 if (!CanTalk()) {
5779 ADD_MESSAGE("This monster does not know the art of talking.");
5780 return false;
5782 return true;
5786 truth character::MoveTowardsHomePos () {
5787 if (HomeDataIsValid() && IsEnabled()) {
5788 SetGoingTo(HomeData->Pos);
5789 return MoveTowardsTarget(false) || (!GetPos().IsAdjacent(HomeData->Pos) && MoveRandomly());
5791 return false;
5795 int character::TryToChangeEquipment (stack *MainStack, stack *SecStack, int Chosen) {
5796 if (!GetBodyPartOfEquipment(Chosen)) {
5797 ADD_MESSAGE("Bodypart missing!");
5798 return 0;
5801 item *OldEquipment = GetEquipment(Chosen);
5802 if (!IsPlayer() && BoundToUse(OldEquipment, Chosen)) {
5803 ADD_MESSAGE("%s refuses to unequip %s.", CHAR_DESCRIPTION(DEFINITE), OldEquipment->CHAR_NAME(DEFINITE));
5804 return 0;
5806 if (OldEquipment) OldEquipment->MoveTo(MainStack);
5808 sorter Sorter = EquipmentSorter(Chosen);
5809 if (!MainStack->SortedItems(this, Sorter) && (!SecStack || !SecStack->SortedItems(this, Sorter))) {
5810 ADD_MESSAGE("You haven't got any item that could be used for this purpose.");
5811 return 0;
5814 game::DrawEverythingNoBlit();
5815 itemvector ItemVector;
5816 int Return = MainStack->DrawContents(ItemVector, SecStack, this,
5817 CONST_S("Choose ")+GetEquipmentName(Chosen)+':',
5818 (SecStack ? CONST_S("Items in your inventory") : CONST_S("")),
5819 (SecStack ? festring(CONST_S("Items in ")+GetPossessivePronoun()+" inventory") : CONST_S("")),
5820 (SecStack ? festring(GetDescription(DEFINITE)+" is "+GetVerbalBurdenState()) : CONST_S("")),
5821 GetVerbalBurdenStateColor(),
5822 NONE_AS_CHOICE|NO_MULTI_SELECT|SELECT_PAIR|SKIP_FIRST_IF_NO_OLD|SELECT_MOST_RECENT,
5823 Sorter, OldEquipment);
5824 if (Return == ESCAPED) {
5825 if (OldEquipment) {
5826 OldEquipment->RemoveFromSlot();
5827 SetEquipment(Chosen, OldEquipment);
5829 return 0;
5832 item *Item = (ItemVector.empty() ? 0 : ItemVector[0]);
5833 int otherChosen = -1;
5835 if (Item) {
5836 if (!IsPlayer() && !AllowEquipment(Item, Chosen)) {
5837 ADD_MESSAGE("%s refuses to equip %s.", CHAR_DESCRIPTION(DEFINITE), Item->CHAR_NAME(DEFINITE));
5838 return 0;
5840 if (ItemVector[0]->HandleInPairs() && ItemVector.size() > 1) {
5841 switch (Chosen) {
5842 case RIGHT_GAUNTLET_INDEX: otherChosen = LEFT_GAUNTLET_INDEX; break;
5843 case LEFT_GAUNTLET_INDEX: otherChosen = RIGHT_GAUNTLET_INDEX; break;
5844 case RIGHT_BOOT_INDEX: otherChosen = LEFT_BOOT_INDEX; break;
5845 case LEFT_BOOT_INDEX: otherChosen = RIGHT_BOOT_INDEX; break;
5846 default: break;
5848 if (otherChosen != -1) {
5849 if (GetBodyPartOfEquipment(otherChosen)) {
5850 if (!game::TruthQuestion("Do you want to wear both items?", NO)) otherChosen = -1;
5851 } else {
5852 otherChosen = -1;
5856 // wear/wield first item
5857 Item->RemoveFromSlot();
5858 SetEquipment(Chosen, Item);
5859 if (CheckIfEquipmentIsNotUsable(Chosen)) { Item->MoveTo(MainStack); Item = 0; otherChosen = -1; } // small bug?
5860 // wear/wield possible second item
5861 if (Item && otherChosen != -1 && ItemVector[0]->HandleInPairs() && ItemVector.size() > 1 && GetBodyPartOfEquipment(otherChosen)) {
5862 item *otherOld = GetEquipment(otherChosen);
5863 if (otherOld && !IsPlayer() && BoundToUse(otherOld, otherChosen)) {
5864 ADD_MESSAGE("%s refuses to unequip %s.", CHAR_DESCRIPTION(DEFINITE), otherOld->CHAR_NAME(DEFINITE));
5865 otherChosen = -1;
5866 } else if (otherOld) {
5867 otherOld->MoveTo(MainStack);
5869 if (otherChosen != -1) {
5870 ItemVector[1]->RemoveFromSlot();
5871 SetEquipment(otherChosen, ItemVector[1]);
5872 if (CheckIfEquipmentIsNotUsable(otherChosen)) { ItemVector[1]->MoveTo(MainStack); otherChosen = -1; } // small bug?
5877 return (Item != OldEquipment ? (otherChosen != -1 ? 2 : 1) : 0);
5881 void character::PrintBeginParasitizedMessage () const {
5882 if (IsPlayer()) ADD_MESSAGE("You feel you are no longer alone.");
5886 void character::PrintEndParasitizedMessage () const {
5887 if (IsPlayer()) ADD_MESSAGE("A feeling of long welcome emptiness overwhelms you.");
5891 void character::ParasitizedHandler () {
5892 EditNP(-5);
5893 if (!(RAND() % 250)) {
5894 if (IsPlayer()) ADD_MESSAGE("Ugh. You feel something violently carving its way through your intestines.");
5895 ReceiveDamage(0, 1, POISON, TORSO, 8, false, false, false, false);
5896 CheckDeath(CONST_S("killed by a vile parasite"), 0);
5901 truth character::CanFollow () const {
5902 return CanMove() && !StateIsActivated(PANIC) && !IsStuck();
5906 festring character::GetKillName () const {
5907 if (!GetPolymorphBackup()) return GetName(INDEFINITE);
5908 festring KillName;
5909 GetPolymorphBackup()->AddName(KillName, INDEFINITE);
5910 KillName << " polymorphed into ";
5911 id::AddName(KillName, INDEFINITE);
5912 return KillName;
5916 festring character::GetPanelName () const {
5917 festring Name;
5918 Name << AssignedName << " the " << game::GetVerbalPlayerAlignment() << ' ';
5919 id::AddName(Name, UNARTICLED);
5920 return Name;
5924 sLong character::GetMoveAPRequirement (int Difficulty) const {
5925 return (!StateIsActivated(PANIC) ? 10000000 : 8000000) * Difficulty / (APBonus(GetAttribute(AGILITY)) * GetMoveEase());
5929 bodypart *character::HealHitPoint() {
5930 int NeedHeal = 0, NeedHealIndex[MAX_BODYPARTS];
5931 for (int c = 0; c < BodyParts; ++c) {
5932 bodypart *BodyPart = GetBodyPart(c);
5933 if (BodyPart && BodyPart->CanRegenerate() && BodyPart->GetHP() < BodyPart->GetMaxHP()) NeedHealIndex[NeedHeal++] = c;
5935 if (NeedHeal) {
5936 bodypart *BodyPart = GetBodyPart(NeedHealIndex[RAND() % NeedHeal]);
5937 BodyPart->IncreaseHP();
5938 ++HP;
5939 return BodyPart;
5941 return 0;
5945 void character::CreateHomeData () {
5946 HomeData = new homedata;
5947 lsquare *Square = GetLSquareUnder();
5948 HomeData->Pos = Square->GetPos();
5949 HomeData->Dungeon = Square->GetDungeonIndex();
5950 HomeData->Level = Square->GetLevelIndex();
5951 HomeData->Room = Square->GetRoomIndex();
5955 room *character::GetHomeRoom() const {
5956 if (HomeDataIsValid() && HomeData->Room) return GetLevel()->GetRoom(HomeData->Room);
5957 return 0;
5961 void character::RemoveHomeData () {
5962 delete HomeData;
5963 HomeData = 0;
5967 void character::AddESPConsumeMessage () const {
5968 if (IsPlayer()) ADD_MESSAGE("You feel a strange mental activity.");
5972 void character::SetBodyPart (int I, bodypart *What) {
5973 BodyPartSlot[I].PutInItem(What);
5974 if (What) {
5975 What->SignalPossibleUsabilityChange();
5976 What->Disable();
5977 AddOriginalBodyPartID(I, What->GetID());
5978 if (What->GetMainMaterial()->IsInfectedByLeprosy()) GainIntrinsic(LEPROSY);
5979 else if (StateIsActivated(LEPROSY)) What->GetMainMaterial()->SetIsInfectedByLeprosy(true);
5984 truth character::ConsumeItem (item *Item, cfestring &ConsumeVerb) {
5985 if (IsPlayer() && HasHadBodyPart(Item) && !game::TruthQuestion(CONST_S("Are you sure? You may be able to put it back..."))) {
5986 return false;
5988 if (Item->IsOnGround() && GetRoom() && !GetRoom()->ConsumeItem(this, Item, 1)) {
5989 return false;
5991 if (IsPlayer()) ADD_MESSAGE("You begin %s %s.", ConsumeVerb.CStr(), Item->CHAR_NAME(DEFINITE));
5992 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s begins %s %s.", CHAR_NAME(DEFINITE), ConsumeVerb.CStr(), Item->CHAR_NAME(DEFINITE));
5993 consume *Consume = consume::Spawn(this);
5994 Consume->SetDescription(ConsumeVerb);
5995 Consume->SetConsumingID(Item->GetID());
5996 SetAction(Consume);
5997 DexterityAction(5);
5998 return true;
6002 truth character::CheckThrow () const {
6003 if (!CanThrow()) {
6004 ADD_MESSAGE("This monster type cannot throw.");
6005 return false;
6007 return true;
6011 void character::GetHitByExplosion (const explosion *Explosion, int Damage) {
6012 int DamageDirection = GetPos() == Explosion->Pos ? RANDOM_DIR : game::CalculateRoughDirection(GetPos() - Explosion->Pos);
6013 if (!IsPet() && Explosion->Terrorist && Explosion->Terrorist->IsPet()) Explosion->Terrorist->Hostility(this);
6014 GetTorso()->SpillBlood((8 - Explosion->Size + RAND() % (8 - Explosion->Size)) >> 1);
6015 if (DamageDirection == RANDOM_DIR) DamageDirection = RAND()&7;
6016 v2 SpillPos = GetPos() + game::GetMoveVector(DamageDirection);
6017 if (SquareUnder[0] && GetArea()->IsValidPos(SpillPos)) GetTorso()->SpillBlood((8-Explosion->Size+RAND()%(8-Explosion->Size))>>1, SpillPos);
6018 if (IsPlayer()) ADD_MESSAGE("You are hit by the explosion!");
6019 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s is hit by the explosion.", CHAR_NAME(DEFINITE));
6020 truth WasUnconscious = GetAction() && GetAction()->IsUnconsciousness();
6021 ReceiveDamage(Explosion->Terrorist, Damage >> 1, FIRE, ALL, DamageDirection, true, false, false, false);
6022 if (IsEnabled()) {
6023 ReceiveDamage(Explosion->Terrorist, Damage >> 1, PHYSICAL_DAMAGE, ALL, DamageDirection, true, false, false, false);
6024 CheckDeath(Explosion->DeathMsg, Explosion->Terrorist, !WasUnconscious ? IGNORE_UNCONSCIOUSNESS : 0);
6029 void character::SortAllItems (const sortdata &SortData) {
6030 GetStack()->SortAllItems(SortData);
6031 doforequipmentswithparam<const sortdata&>()(this, &item::SortAllItems, SortData);
6035 void character::PrintBeginSearchingMessage () const {
6036 if (IsPlayer()) ADD_MESSAGE("You feel you can now notice even the very smallest details around you.");
6040 void character::PrintEndSearchingMessage () const {
6041 if (IsPlayer()) ADD_MESSAGE("You feel less perceptive.");
6045 void character::SearchingHandler () {
6046 if (!game::IsInWilderness()) Search(15);
6050 void character::Search (int Perception) {
6051 for (int d = 0; d < GetExtendedNeighbourSquares(); ++d) {
6052 lsquare *LSquare = GetNeighbourLSquare(d);
6053 if (LSquare) LSquare->GetStack()->Search(this, Min(Perception, 200));
6058 // surprisingly returns 0 if fails
6059 character *character::GetRandomNeighbour (int RelationFlags) const {
6060 character *Chars[MAX_NEIGHBOUR_SQUARES];
6061 int Index = 0;
6062 for (int d = 0; d < GetNeighbourSquares(); ++d) {
6063 lsquare *LSquare = GetNeighbourLSquare(d);
6064 if (LSquare) {
6065 character *Char = LSquare->GetCharacter();
6066 if (Char && (GetRelation(Char) & RelationFlags)) Chars[Index++] = Char;
6069 return Index ? Chars[RAND() % Index] : 0;
6073 void character::ResetStates () {
6074 for (int c = 0; c < STATES; ++c) {
6075 if (1 << c != POLYMORPHED && TemporaryStateIsActivated(1 << c) && TemporaryStateCounter[c] != PERMANENT) {
6076 TemporaryState &= ~(1 << c);
6077 if (StateData[c].EndHandler) {
6078 (this->*StateData[c].EndHandler)();
6079 if (!IsEnabled())return;
6086 void characterdatabase::InitDefaults (const characterprototype *NewProtoType, int NewConfig, cfestring &acfgstrname) {
6087 IsAbstract = false;
6088 ProtoType = NewProtoType;
6089 Config = NewConfig;
6090 CfgStrName = acfgstrname;
6091 Alias.Clear();
6095 void character::PrintBeginGasImmunityMessage () const {
6096 if (IsPlayer()) ADD_MESSAGE("All smells fade away.");
6100 void character::PrintEndGasImmunityMessage () const {
6101 if (IsPlayer()) ADD_MESSAGE("Yuck! The world smells bad again.");
6105 void character::ShowAdventureInfo () const {
6106 static const char *lists[4][4] = {
6107 { "Show massacre history",
6108 "Show inventory",
6109 "Show message history",
6110 NULL },
6111 { "Show inventory",
6112 "Show message history",
6113 NULL,
6114 NULL },
6115 { "Show message history",
6116 NULL,
6117 NULL,
6118 NULL },
6119 { "Show massacre history",
6120 "Show message history",
6121 NULL,
6122 NULL }
6124 // massacre, inventory, messages
6125 static const int nums[4][3] = {
6126 { 0, 1, 2},
6127 {-1, 0, 1},
6128 {-1,-1, 0},
6129 { 0,-1, 0}
6131 int idx = 0;
6132 if (GetStack()->GetItems()) {
6133 idx = game::MassacreListsEmpty() ? 1 : 0;
6134 } else {
6135 idx = game::MassacreListsEmpty() ? 2 : 3;
6137 int sel = -1;
6138 for (;;) {
6139 sel = game::ListSelectorArray(sel, CONST_S("Do you want to see some funny history?"), lists[idx]);
6140 if (sel < 0) break;
6141 if (sel == nums[idx][0] && !game::MassacreListsEmpty()) {
6142 game::DisplayMassacreLists();
6144 if (sel == nums[idx][1] && GetStack()->GetItems()) {
6145 GetStack()->DrawContents(this, CONST_S("Your inventory"), NO_SELECT);
6146 for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) i->DrawContents(this);
6147 doforequipmentswithparam<ccharacter *>()(this, &item::DrawContents, this);
6149 if (sel == nums[idx][2]) {
6150 msgsystem::DrawMessageHistory();
6156 truth character::EditAllAttributes (int Amount) {
6157 if (!Amount) return true;
6158 int c;
6159 truth MayEditMore = false;
6160 for (c = 0; c < BodyParts; ++c) {
6161 bodypart *BodyPart = GetBodyPart(c);
6162 if (BodyPart && BodyPart->EditAllAttributes(Amount)) MayEditMore = true;
6164 for (c = 0; c < BASE_ATTRIBUTES; ++c) {
6165 if (BaseExperience[c]) {
6166 BaseExperience[c] += Amount * EXP_MULTIPLIER;
6167 LimitRef(BaseExperience[c], MIN_EXP, MAX_EXP);
6168 if ((Amount < 0 && BaseExperience[c] != MIN_EXP) || (Amount > 0 && BaseExperience[c] != MAX_EXP)) MayEditMore = true;
6171 CalculateAll();
6172 RestoreHP();
6173 RestoreStamina();
6174 if (IsPlayer()) {
6175 game::SendLOSUpdateRequest();
6176 UpdateESPLOS();
6178 if (IsPlayerKind()) UpdatePictures();
6179 return MayEditMore;
6183 #ifdef WIZARD
6184 void character::AddAttributeInfo (festring &Entry) const {
6185 Entry.Resize(57);
6186 Entry << GetAttribute(ENDURANCE);
6187 Entry.Resize(60);
6188 Entry << GetAttribute(PERCEPTION);
6189 Entry.Resize(63);
6190 Entry << GetAttribute(INTELLIGENCE);
6191 Entry.Resize(66);
6192 Entry << GetAttribute(WISDOM);
6193 Entry.Resize(69);
6194 Entry << GetAttribute(CHARISMA);
6195 Entry.Resize(72);
6196 Entry << GetAttribute(MANA);
6200 void character::AddDefenceInfo (felist &List) const {
6201 festring Entry;
6202 for (int c = 0; c < BodyParts; ++c) {
6203 bodypart *BodyPart = GetBodyPart(c);
6204 if (BodyPart) {
6205 Entry = CONST_S(" ");
6206 BodyPart->AddName(Entry, UNARTICLED);
6207 Entry.Resize(60);
6208 Entry << BodyPart->GetMaxHP();
6209 Entry.Resize(70);
6210 Entry << BodyPart->GetTotalResistance(PHYSICAL_DAMAGE);
6211 List.AddEntry(Entry, LIGHT_GRAY);
6217 void character::DetachBodyPart () {
6218 ADD_MESSAGE("You haven't got any extra bodyparts.");
6220 #endif
6223 void character::ReceiveHolyBanana (sLong Amount) {
6224 Amount <<= 1;
6225 EditExperience(ARM_STRENGTH, Amount, 1 << 13);
6226 EditExperience(LEG_STRENGTH, Amount, 1 << 13);
6227 EditExperience(DEXTERITY, Amount, 1 << 13);
6228 EditExperience(AGILITY, Amount, 1 << 13);
6229 EditExperience(ENDURANCE, Amount, 1 << 13);
6230 EditExperience(PERCEPTION, Amount, 1 << 13);
6231 EditExperience(INTELLIGENCE, Amount, 1 << 13);
6232 EditExperience(WISDOM, Amount, 1 << 13);
6233 EditExperience(CHARISMA, Amount, 1 << 13);
6234 RestoreLivingHP();
6238 void character::AddHolyBananaConsumeEndMessage () const {
6239 if (IsPlayer()) ADD_MESSAGE("You feel a mysterious strengthening fire coursing through your body.");
6240 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s is surrounded by a swirling fire aura.", CHAR_NAME(DEFINITE));
6244 void character::ReceiveHolyMango (sLong Amount) {
6245 Amount <<= 1;
6246 EditExperience(ARM_STRENGTH, Amount, 1 << 13);
6247 EditExperience(LEG_STRENGTH, Amount, 1 << 13);
6248 EditExperience(DEXTERITY, Amount, 1 << 13);
6249 EditExperience(AGILITY, Amount, 1 << 13);
6250 EditExperience(ENDURANCE, Amount, 1 << 13);
6251 EditExperience(PERCEPTION, Amount, 1 << 13);
6252 EditExperience(INTELLIGENCE, Amount, 1 << 13);
6253 EditExperience(WISDOM, Amount, 1 << 13);
6254 EditExperience(CHARISMA, Amount, 1 << 13);
6255 RestoreLivingHP();
6259 void character::AddHolyMangoConsumeEndMessage () const {
6260 if (IsPlayer()) ADD_MESSAGE("You feel a mysterious strengthening fire coursing through your body.");
6261 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s is surrounded by a swirling fire aura.", CHAR_NAME(DEFINITE));
6265 truth character::PreProcessForBone () {
6266 if (IsPet() && IsEnabled()) {
6267 Die(0, CONST_S(""), FORBID_REINCARNATION);
6268 return true;
6270 if (GetAction()) GetAction()->Terminate(false);
6271 if (TemporaryStateIsActivated(POLYMORPHED)) {
6272 character *PolymorphBackup = GetPolymorphBackup();
6273 EndPolymorph();
6274 PolymorphBackup->PreProcessForBone();
6275 return true;
6277 if (MustBeRemovedFromBone()) return false;
6278 if (IsUnique() && !CanBeGenerated()) game::SignalQuestMonsterFound();
6279 RestoreLivingHP();
6280 ResetStates();
6281 RemoveTraps();
6282 GetStack()->PreProcessForBone();
6283 doforequipments()(this, &item::PreProcessForBone);
6284 doforbodyparts()(this, &bodypart::PreProcessForBone);
6285 game::RemoveCharacterID(ID);
6286 ID = -ID;
6287 game::AddCharacterID(this, ID);
6288 return true;
6292 truth character::PostProcessForBone (double &DangerSum, int& Enemies) {
6293 if (PostProcessForBone()) {
6294 if (GetRelation(PLAYER) == HOSTILE) {
6295 double Danger = GetRelativeDanger(PLAYER, true);
6296 if (Danger > 99.0) game::SetTooGreatDangerFound(true);
6297 else if (!IsUnique() && !IgnoreDanger()) {
6298 DangerSum += Danger;
6299 ++Enemies;
6302 return true;
6304 return false;
6308 truth character::PostProcessForBone () {
6309 feuLong NewID = game::CreateNewCharacterID(this);
6310 game::GetBoneCharacterIDMap().insert(std::make_pair(-ID, NewID));
6311 game::RemoveCharacterID(ID);
6312 ID = NewID;
6313 if (IsUnique() && CanBeGenerated()) {
6314 if (DataBase->Flags & HAS_BEEN_GENERATED) return false;
6315 SignalGeneration();
6317 GetStack()->PostProcessForBone();
6318 doforequipments()(this, &item::PostProcessForBone);
6319 doforbodyparts()(this, &bodypart::PostProcessForBone);
6320 return true;
6324 void character::FinalProcessForBone () {
6325 Flags &= ~C_PLAYER;
6326 GetStack()->FinalProcessForBone();
6327 doforequipments()(this, &item::FinalProcessForBone);
6328 int c;
6329 for (c = 0; c < BodyParts; ++c) {
6330 for (std::list<feuLong>::iterator i = OriginalBodyPartID[c].begin(); i != OriginalBodyPartID[c].end();) {
6331 boneidmap::iterator BI = game::GetBoneItemIDMap().find(*i);
6332 if (BI == game::GetBoneItemIDMap().end()) {
6333 std::list<feuLong>::iterator Dirt = i++;
6334 OriginalBodyPartID[c].erase(Dirt);
6335 } else {
6336 *i = BI->second;
6337 ++i;
6344 void character::SetSoulID (feuLong What) {
6345 if (GetPolymorphBackup()) GetPolymorphBackup()->SetSoulID(What);
6349 truth character::SearchForItem (citem *Item) const {
6350 if (combineequipmentpredicateswithparam<feuLong>()(this, &item::HasID, Item->GetID(), 1)) return true;
6351 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) if (*i == Item) return true;
6352 return false;
6356 item *character::SearchForItem (const sweaponskill *SWeaponSkill) const {
6357 for (int c = 0; c < GetEquipments(); ++c) {
6358 item *Equipment = GetEquipment(c);
6359 if (Equipment && SWeaponSkill->IsSkillOf(Equipment)) return Equipment;
6361 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) if (SWeaponSkill->IsSkillOf(*i)) return *i;
6362 return 0;
6366 truth character::PutNear (v2 Pos, truth allowFail) {
6367 v2 NewPos = game::GetCurrentLevel()->GetNearestFreeSquare(this, Pos, false);
6368 if (NewPos == ERROR_V2) {
6369 do {
6370 NewPos = game::GetCurrentLevel()->GetRandomSquare(this, 0, nullptr, NewPos);
6371 if (NewPos == ERROR_V2) {
6372 if (allowFail) return false;
6373 ABORT("Oops... Out of free squares in `character::PutNear()`!");
6375 } while (NewPos == Pos);
6377 PutTo(NewPos);
6378 return true;
6382 truth character::PutToOrNearNoTeleport (v2 pos) {
6383 if (!game::IsInWilderness()) {
6384 // get resulting position; starting position is allowed
6385 pos = game::GetCurrentLevel()->GetNearestFreeSquare(this, pos, true);
6387 if (pos == ERROR_V2) return false;
6388 PutTo(pos);
6389 return true;
6393 truth character::PutNearNoTeleport (v2 pos) {
6394 if (!game::IsInWilderness()) {
6395 // get resulting position; starting position is allowed
6396 pos = game::GetCurrentLevel()->GetNearestFreeSquare(this, pos, false);
6398 if (pos == ERROR_V2) return false;
6399 PutTo(pos);
6400 return true;
6404 truth character::PutToOrNear (v2 Pos, truth allowFail) {
6405 if (game::IsInWilderness() || (CanMoveOn(game::GetCurrentLevel()->GetLSquare(Pos)) && IsFreeForMe(game::GetCurrentLevel()->GetLSquare(Pos)))) {
6406 PutTo(Pos);
6407 return true;
6408 } else {
6409 return PutNear(Pos, allowFail);
6414 void character::PutTo (v2 Pos) {
6415 SquareUnder[0] = game::GetCurrentArea()->GetSquare(Pos);
6416 SquareUnder[0]->AddCharacter(this);
6420 void character::Remove () {
6421 SquareUnder[0]->RemoveCharacter();
6422 SquareUnder[0] = 0;
6426 void character::SendNewDrawRequest () const {
6427 for (int c = 0; c < SquaresUnder; ++c) {
6428 square *Square = GetSquareUnder(c);
6429 if (Square) Square->SendNewDrawRequest();
6434 truth character::IsOver (v2 Pos) const {
6435 for (int c = 0; c < SquaresUnder; ++c) {
6436 square *Square = GetSquareUnder(c);
6437 if (Square && Square->GetPos() == Pos) return true;
6439 return false;
6443 truth character::CanTheoreticallyMoveOn (const lsquare *LSquare) const { return GetMoveType() & LSquare->GetTheoreticalWalkability(); }
6444 truth character::CanMoveOn (const lsquare *LSquare) const { return GetMoveType() & LSquare->GetWalkability(); }
6445 truth character::CanMoveOn (const square *Square) const { return GetMoveType() & Square->GetSquareWalkability(); }
6446 truth character::CanMoveOn (const olterrain *OLTerrain) const { return GetMoveType() & OLTerrain->GetWalkability(); }
6447 truth character::CanMoveOn (const oterrain *OTerrain) const { return GetMoveType() & OTerrain->GetWalkability(); }
6448 truth character::IsFreeForMe(square *Square) const { return !Square->GetCharacter() || Square->GetCharacter() == this; }
6449 void character::LoadSquaresUnder () { SquareUnder[0] = game::GetSquareInLoad(); }
6451 truth character::AttackAdjacentEnemyAI () {
6452 if (!IsEnabled()) return false;
6453 character *Char[MAX_NEIGHBOUR_SQUARES];
6454 v2 Pos[MAX_NEIGHBOUR_SQUARES];
6455 int Dir[MAX_NEIGHBOUR_SQUARES];
6456 int Index = 0;
6457 for (int d = 0; d < GetNeighbourSquares(); ++d) {
6458 square *Square = GetNeighbourSquare(d);
6459 if (Square) {
6460 character *Enemy = Square->GetCharacter();
6461 if (Enemy && (GetRelation(Enemy) == HOSTILE || StateIsActivated(CONFUSED))) {
6462 Dir[Index] = d;
6463 Pos[Index] = Square->GetPos();
6464 Char[Index++] = Enemy;
6468 if (Index) {
6469 int ChosenIndex = RAND() % Index;
6470 Hit(Char[ChosenIndex], Pos[ChosenIndex], Dir[ChosenIndex]);
6471 return true;
6473 return false;
6477 void character::SignalStepFrom (lsquare **OldSquareUnder) {
6478 int c;
6479 lsquare *NewSquareUnder[MAX_SQUARES_UNDER];
6480 for (c = 0; c < GetSquaresUnder(); ++c) NewSquareUnder[c] = GetLSquareUnder(c);
6481 for (c = 0; c < GetSquaresUnder(); ++c) {
6482 if (IsEnabled() && GetLSquareUnder(c) == NewSquareUnder[c]) NewSquareUnder[c]->StepOn(this, OldSquareUnder);
6487 int character::GetSumOfAttributes () const {
6488 return GetAttribute(ENDURANCE) + GetAttribute(PERCEPTION) + GetAttribute(INTELLIGENCE) + GetAttribute(WISDOM) + GetAttribute(CHARISMA) + GetAttribute(ARM_STRENGTH) + GetAttribute(AGILITY);
6492 void character::IntelligenceAction (int Difficulty) {
6493 EditAP(-20000 * Difficulty / APBonus(GetAttribute(INTELLIGENCE)));
6494 EditExperience(INTELLIGENCE, Difficulty * 15, 1 << 7);
6498 struct walkabilitycontroller {
6499 static truth Handler (int x, int y) {
6500 return x >= 0 && y >= 0 && x < LevelXSize && y < LevelYSize && Map[x][y]->GetTheoreticalWalkability() & MoveType;
6502 static lsquare ***Map;
6503 static int LevelXSize, LevelYSize;
6504 static int MoveType;
6508 lsquare ***walkabilitycontroller::Map;
6509 int walkabilitycontroller::LevelXSize, walkabilitycontroller::LevelYSize;
6510 int walkabilitycontroller::MoveType;
6513 truth character::CreateRoute () {
6514 Route.clear();
6515 if (GetAttribute(INTELLIGENCE) >= 10 && !StateIsActivated(CONFUSED)) {
6516 v2 Pos = GetPos();
6517 walkabilitycontroller::Map = GetLevel()->GetMap();
6518 walkabilitycontroller::LevelXSize = GetLevel()->GetXSize();
6519 walkabilitycontroller::LevelYSize = GetLevel()->GetYSize();
6520 walkabilitycontroller::MoveType = GetMoveType();
6521 node *Node;
6522 for (int c = 0; c < game::GetTeams(); ++c)
6523 for (std::list<character *>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i) {
6524 character *Char = *i;
6525 if (Char->IsEnabled() && !Char->Route.empty() && (Char->GetMoveType()&GetMoveType()) == Char->GetMoveType()) {
6526 v2 CharGoingTo = Char->Route[0];
6527 v2 iPos = Char->Route.back();
6528 if ((GoingTo-CharGoingTo).GetLengthSquare() <= 100 && (Pos - iPos).GetLengthSquare() <= 100 &&
6529 mapmath<walkabilitycontroller>::DoLine(CharGoingTo.X, CharGoingTo.Y, GoingTo.X, GoingTo.Y, SKIP_FIRST|LINE_BOTH_DIRS) &&
6530 mapmath<walkabilitycontroller>::DoLine(Pos.X, Pos.Y, iPos.X, iPos.Y, SKIP_FIRST|LINE_BOTH_DIRS)) {
6531 if (!Illegal.empty() && Illegal.find(Char->Route.back()) != Illegal.end()) continue;
6532 Node = GetLevel()->FindRoute(CharGoingTo, GoingTo, Illegal, GetMoveType());
6533 if (Node) { while(Node->Last) { Route.push_back(Node->Pos); Node = Node->Last; } }
6534 else { Route.clear(); continue; }
6535 Route.insert(Route.end(), Char->Route.begin(), Char->Route.end());
6536 Node = GetLevel()->FindRoute(Pos, iPos, Illegal, GetMoveType());
6537 if (Node) { while (Node->Last) { Route.push_back(Node->Pos); Node = Node->Last; } }
6538 else { Route.clear(); continue; }
6539 IntelligenceAction(1);
6540 return true;
6544 Node = GetLevel()->FindRoute(Pos, GoingTo, Illegal, GetMoveType());
6545 if (Node) { while(Node->Last) { Route.push_back(Node->Pos); Node = Node->Last; } }
6546 else TerminateGoingTo();
6547 IntelligenceAction(5);
6548 return true;
6550 return false;
6554 void character::SetGoingTo (v2 What) {
6555 if (GoingTo != What) {
6556 GoingTo = What;
6557 Route.clear();
6558 Illegal.clear();
6563 void character::TerminateGoingTo () {
6564 GoingTo = ERROR_V2;
6565 Route.clear();
6566 Illegal.clear();
6570 truth character::CheckForFood (int Radius) {
6571 if (StateIsActivated(PANIC) || !UsesNutrition() || !IsEnabled()) return false;
6572 v2 Pos = GetPos();
6573 int x, y;
6574 for (int r = 1; r <= Radius; ++r) {
6575 x = Pos.X-r;
6576 if (x >= 0) {
6577 for (y = Pos.Y-r; y <= Pos.Y+r; ++y) if (CheckForFoodInSquare(v2(x, y))) return true;
6579 x = Pos.X+r;
6580 if (x < GetLevel()->GetXSize()) {
6581 for (y = Pos.Y-r; y <= Pos.Y+r; ++y) if (CheckForFoodInSquare(v2(x, y))) return true;
6583 y = Pos.Y-r;
6584 if (y >= 0) {
6585 for (x = Pos.X-r; x <= Pos.X+r; ++x) if (CheckForFoodInSquare(v2(x, y))) return true;
6587 y = Pos.Y+r;
6588 if (y < GetLevel()->GetYSize()) {
6589 for (x = Pos.X-r; x <= Pos.X+r; ++x) if (CheckForFoodInSquare(v2(x, y))) return true;
6592 return false;
6596 truth character::CheckForFoodInSquare (v2 Pos) {
6597 level *Level = GetLevel();
6598 if (Level->IsValidPos(Pos)) {
6599 lsquare *Square = Level->GetLSquare(Pos);
6600 stack *Stack = Square->GetStack();
6601 if (Stack->GetItems()) {
6602 for (stackiterator i = Stack->GetBottom(); i.HasItem(); ++i) {
6603 if (i->IsPickable(this) && i->CanBeSeenBy(this) && i->CanBeEatenByAI(this) && (!Square->GetRoomIndex() || Square->GetRoom()->AllowFoodSearch())) {
6604 SetGoingTo(Pos);
6605 return MoveTowardsTarget(false);
6610 return false;
6614 void character::SetConfig (int NewConfig, int SpecialFlags) {
6615 databasecreator<character>::InstallDataBase(this, NewConfig);
6616 CalculateAll();
6617 CheckIfSeen();
6618 if (!(SpecialFlags & NO_PIC_UPDATE)) UpdatePictures();
6622 truth character::IsOver (citem *Item) const {
6623 for (int c1 = 0; c1 < Item->GetSquaresUnder(); ++c1)
6624 for (int c2 = 0; c2 < SquaresUnder; ++c2)
6625 if (Item->GetPos(c1) == GetPos(c2)) return true;
6626 return false;
6630 truth character::CheckConsume (cfestring &Verb) const {
6631 if (!UsesNutrition()) {
6632 if (IsPlayer()) ADD_MESSAGE("In this form you can't and don't need to %s.", Verb.CStr());
6633 return false;
6635 return true;
6639 void character::PutTo (lsquare *To) {
6640 PutTo(To->GetPos());
6644 double character::RandomizeBabyExperience (double SumE) {
6645 if (!SumE) return 0;
6646 double E = (SumE / 4) - (SumE / 32) + (double(RAND()) / MAX_RAND) * (SumE / 16 + 1);
6647 return Limit(E, MIN_EXP, MAX_EXP);
6651 liquid *character::CreateBlood (sLong Volume) const {
6652 return liquid::Spawn(GetBloodMaterial(), Volume);
6656 void character::SpillFluid (character *Spiller, liquid *Liquid, int SquareIndex) {
6657 //fprintf(stderr, "character(%s:%d)::SpillFluid: Liquid->GetName(0):<%s>\n", GetTypeID(), GetConfig(), Liquid->GetName(false, false).CStr());
6658 sLong ReserveVolume = Liquid->GetVolume() >> 1;
6659 Liquid->EditVolume(-ReserveVolume);
6660 //fprintf(stderr, "character::SpillFluid: Liquid->GetName(1):<%s>\n", Liquid->GetName(false, false).CStr());
6661 GetStack()->SpillFluid(Spiller, Liquid, sLong(Liquid->GetVolume() * sqrt(double(GetStack()->GetVolume()) / GetVolume())));
6662 //fprintf(stderr, "character::SpillFluid: Liquid->GetName(2):<%s>\n", Liquid->GetName(false, false).CStr());
6663 Liquid->EditVolume(ReserveVolume);
6664 //fprintf(stderr, "character::SpillFluid: Liquid->GetName(3):<%s>\n", Liquid->GetName(false, false).CStr());
6665 sLong Modifier[MAX_BODYPARTS], ModifierSum = 0;
6666 for (int c = 0; c < BodyParts; ++c) {
6667 if (GetBodyPart(c)) {
6668 Modifier[c] = sLong(sqrt(GetBodyPart(c)->GetVolume()));
6669 if (Modifier[c]) Modifier[c] *= 1 + (RAND() & 3);
6670 ModifierSum += Modifier[c];
6671 } else {
6672 Modifier[c] = 0;
6675 for (int c = 1; c < GetBodyParts(); ++c) {
6676 if (GetBodyPart(c) && IsEnabled()) {
6677 //fprintf(stderr, "character::SpillFluid: Liquid->GetName(4:%d):<%s>\n", c, Liquid->GetName(false, false).CStr());
6678 GetBodyPart(c)->SpillFluid(Spiller, Liquid->SpawnMoreLiquid(Liquid->GetVolume() * Modifier[c] / ModifierSum), SquareIndex);
6679 //fprintf(stderr, "character::SpillFluid: Liquid->GetName(5:%d):<%s>\n", c, Liquid->GetName(false, false).CStr());
6682 if (IsEnabled()) {
6683 Liquid->SetVolume(Liquid->GetVolume() * Modifier[TORSO_INDEX] / ModifierSum);
6684 //fprintf(stderr, "character::SpillFluid: Liquid->GetName(6):<%s>\n", Liquid->GetName(false, false).CStr());
6685 GetTorso()->SpillFluid(Spiller, Liquid, SquareIndex);
6686 //fprintf(stderr, "character::SpillFluid: Liquid->GetName(7):<%s>\n", Liquid->GetName(false, false).CStr());
6688 //fprintf(stderr, "character::SpillFluid: Liquid->GetName(8):<%s>\n", Liquid->GetName(false, false).CStr());
6692 void character::StayOn (liquid *Liquid) {
6693 Liquid->TouchEffect(this, TORSO_INDEX);
6697 truth character::IsAlly (ccharacter *Char) const {
6698 return Char->GetTeam()->GetID() == GetTeam()->GetID();
6702 void character::ResetSpoiling () {
6703 doforbodyparts()(this, &bodypart::ResetSpoiling);
6707 item *character::SearchForItem (ccharacter *Char, sorter Sorter) const {
6708 item *Equipment = findequipment<ccharacter *>()(this, Sorter, Char);
6709 if (Equipment) return Equipment;
6710 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) if (((*i)->*Sorter)(Char)) return *i;
6711 return 0;
6715 truth character::DetectMaterial (cmaterial *Material) const {
6716 return GetStack()->DetectMaterial(Material) ||
6717 combinebodypartpredicateswithparam<cmaterial*>()(this, &bodypart::DetectMaterial, Material, 1) ||
6718 combineequipmentpredicateswithparam<cmaterial*>()(this, &item::DetectMaterial, Material, 1);
6722 truth character::DamageTypeDestroysBodyPart (int Type) {
6723 return (Type&0xFFF) != PHYSICAL_DAMAGE;
6727 truth character::CheckIfTooScaredToHit (ccharacter *Enemy) const {
6728 if (IsPlayer() && StateIsActivated(PANIC)) {
6729 for (int d = 0; d < GetNeighbourSquares(); ++d) {
6730 square *Square = GetNeighbourSquare(d);
6731 if (Square) {
6732 if(CanMoveOn(Square) && (!Square->GetCharacter() || Square->GetCharacter()->IsPet())) {
6733 ADD_MESSAGE("You are too scared to attack %s.", Enemy->CHAR_DESCRIPTION(DEFINITE));
6734 return true;
6739 return false;
6743 void character::PrintBeginLevitationMessage () const {
6744 if (!IsFlying()) {
6745 if (IsPlayer()) ADD_MESSAGE("You rise into the air like a small hot-air balloon.");
6746 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s begins to float.", CHAR_NAME(DEFINITE));
6751 void character::PrintEndLevitationMessage () const {
6752 if (!IsFlying()) {
6753 if (IsPlayer()) ADD_MESSAGE("You descend gently onto the ground.");
6754 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s drops onto the ground.", CHAR_NAME(DEFINITE));
6759 truth character::IsLimbIndex (int I) {
6760 switch (I) {
6761 case RIGHT_ARM_INDEX:
6762 case LEFT_ARM_INDEX:
6763 case RIGHT_LEG_INDEX:
6764 case LEFT_LEG_INDEX:
6765 return true;
6767 return false;
6771 void character::EditExperience (int Identifier, double Value, double Speed) {
6772 if (!AllowExperience() || (Identifier == ENDURANCE && UseMaterialAttributes())) return;
6773 int Change = RawEditExperience(BaseExperience[Identifier], GetNaturalExperience(Identifier), Value, Speed);
6774 if (!Change) return;
6775 cchar *PlayerMsg = 0, *NPCMsg = 0;
6776 switch (Identifier) {
6777 case ENDURANCE:
6778 if (Change > 0) {
6779 PlayerMsg = "You feel tougher than anything!";
6780 if (IsPet()) NPCMsg = "Suddenly %s looks tougher.";
6781 } else {
6782 PlayerMsg = "You feel less healthy.";
6783 if (IsPet()) NPCMsg = "Suddenly %s looks less healthy.";
6785 CalculateBodyPartMaxHPs();
6786 CalculateMaxStamina();
6787 break;
6788 case PERCEPTION:
6789 if (IsPlayer()) {
6790 if (Change > 0) {
6791 PlayerMsg = "You now see the world in much better detail than before.";
6792 } else {
6793 PlayerMsg = "You feel very guru.";
6794 game::GetGod(VALPURUS)->AdjustRelation(100);
6796 game::SendLOSUpdateRequest();
6798 break;
6799 case INTELLIGENCE:
6800 if (IsPlayer()) {
6801 if (Change > 0) PlayerMsg = "Suddenly the inner structure of the Multiverse around you looks quite simple.";
6802 else PlayerMsg = "It surely is hard to think today.";
6803 UpdateESPLOS();
6805 if (IsPlayerKind()) UpdatePictures();
6806 break;
6807 case WISDOM:
6808 if (IsPlayer()) {
6809 if (Change > 0) PlayerMsg = "You feel your life experience increasing all the time.";
6810 else PlayerMsg = "You feel like having done something unwise.";
6812 if (IsPlayerKind()) UpdatePictures();
6813 break;
6814 case CHARISMA:
6815 if (Change > 0) {
6816 PlayerMsg = "You feel very confident of your social skills.";
6817 if (IsPet()) {
6818 if (GetAttribute(CHARISMA) <= 15) NPCMsg = "%s looks less ugly.";
6819 else NPCMsg = "%s looks more attractive.";
6821 } else {
6822 PlayerMsg = "You feel somehow disliked.";
6823 if (IsPet()) {
6824 if (GetAttribute(CHARISMA) < 15) NPCMsg = "%s looks more ugly.";
6825 else NPCMsg = "%s looks less attractive.";
6828 if (IsPlayerKind()) UpdatePictures();
6829 break;
6830 case MANA:
6831 if (Change > 0) {
6832 PlayerMsg = "You feel magical forces coursing through your body!";
6833 NPCMsg = "You notice an odd glow around %s.";
6834 } else {
6835 PlayerMsg = "You feel your magical abilities withering slowly.";
6836 NPCMsg = "You notice strange vibrations in the air around %s. But they disappear rapidly.";
6838 break;
6841 if (IsPlayer()) ADD_MESSAGE("%s", PlayerMsg);
6842 else if (NPCMsg && CanBeSeenByPlayer()) ADD_MESSAGE(NPCMsg, CHAR_NAME(DEFINITE));
6844 CalculateBattleInfo();
6848 int character::RawEditExperience (double &Exp, double NaturalExp, double Value, double Speed) const {
6849 double OldExp = Exp;
6850 if (Speed < 0) {
6851 Speed = -Speed;
6852 Value = -Value;
6854 if(!OldExp || !Value || (Value > 0 && OldExp >= NaturalExp * (100 + Value) / 100) ||
6855 (Value < 0 && OldExp <= NaturalExp * (100 + Value) / 100)) return 0;
6856 if (!IsPlayer()) Speed *= 1.5;
6857 Exp += (NaturalExp * (100 + Value) - 100 * OldExp) * Speed * EXP_DIVISOR;
6858 LimitRef(Exp, MIN_EXP, MAX_EXP);
6859 int NewA = int(Exp * EXP_DIVISOR);
6860 int OldA = int(OldExp * EXP_DIVISOR);
6861 int Delta = NewA - OldA;
6862 if (Delta > 0) Exp = Max(Exp, (NewA + 0.05) * EXP_MULTIPLIER);
6863 else if (Delta < 0) Exp = Min(Exp, (NewA + 0.95) * EXP_MULTIPLIER);
6864 LimitRef(Exp, MIN_EXP, MAX_EXP);
6865 return Delta;
6869 int character::GetAttribute (int Identifier, truth AllowBonus) const {
6870 int A = int(BaseExperience[Identifier] * EXP_DIVISOR);
6871 if (AllowBonus && Identifier == INTELLIGENCE && BrainsHurt()) return Max((A + AttributeBonus[INTELLIGENCE]) / 3, 1);
6872 return A && AllowBonus ? Max(A + AttributeBonus[Identifier], 1) : A;
6876 void characterdatabase::PostProcess () {
6877 double AM = (100 + AttributeBonus) * EXP_MULTIPLIER / 100;
6878 for (int c = 0; c < ATTRIBUTES; ++c) NaturalExperience[c] = this->*ExpPtr[c] * AM;
6882 void character::EditDealExperience (sLong Price) {
6883 EditExperience(CHARISMA, sqrt(Price) / 5, 1 << 9);
6887 void character::PrintBeginLeprosyMessage () const {
6888 if (IsPlayer()) ADD_MESSAGE("You feel you're falling in pieces.");
6892 void character::PrintEndLeprosyMessage () const {
6893 if (IsPlayer()) ADD_MESSAGE("You feel your limbs are stuck in place tightly."); // CHANGE OR DIE
6897 void character::TryToInfectWithLeprosy (ccharacter *Infector) {
6898 if (!IsImmuneToLeprosy() &&
6899 ((GetRelation(Infector) == HOSTILE && !RAND_N(50 * GetAttribute(ENDURANCE))) ||
6900 !RAND_N(500 * GetAttribute(ENDURANCE)))) GainIntrinsic(LEPROSY);
6904 void character::SignalGeneration () {
6905 const_cast<database *>(DataBase)->Flags |= HAS_BEEN_GENERATED;
6909 void character::CheckIfSeen () {
6910 if (IsPlayer() || CanBeSeenByPlayer()) SignalSeen();
6914 void character::SignalSeen () {
6915 if (!(WarnFlags & WARNED) && GetRelation(PLAYER) == HOSTILE && !StateIsActivated(FEARLESS)) {
6916 double Danger = GetRelativeDanger(PLAYER);
6917 if (Danger > 5.0) {
6918 game::SetDangerFound(Max(game::GetDangerFound(), Danger));
6919 if (Danger > 500.0 && !(WarnFlags & HAS_CAUSED_PANIC)) {
6920 WarnFlags |= HAS_CAUSED_PANIC;
6921 game::SetCausePanicFlag(true);
6923 WarnFlags |= WARNED;
6926 const_cast<database *>(DataBase)->Flags |= HAS_BEEN_SEEN;
6930 int character::GetPolymorphIntelligenceRequirement () const {
6931 if (DataBase->PolymorphIntelligenceRequirement == DEPENDS_ON_ATTRIBUTES) return Max(GetAttributeAverage() - 5, 0);
6932 return DataBase->PolymorphIntelligenceRequirement;
6936 void character::RemoveAllItems () {
6937 GetStack()->Clean();
6938 for (int c = 0; c < GetEquipments(); ++c) {
6939 item *Equipment = GetEquipment(c);
6940 if (Equipment) {
6941 Equipment->RemoveFromSlot();
6942 Equipment->SendToHell();
6948 int character::CalculateWeaponSkillHits (ccharacter *Enemy) const {
6949 if (Enemy->IsPlayer()) {
6950 configid ConfigID(GetType(), GetConfig());
6951 const dangerid& DangerID = game::GetDangerMap().find(ConfigID)->second;
6952 return Min(int(DangerID.EquippedDanger * 2000), 1000);
6954 return Min(int(GetRelativeDanger(Enemy, true) * 2000), 1000);
6958 truth character::CanUseEquipment (int I) const {
6959 return CanUseEquipment() && I < GetEquipments() && GetBodyPartOfEquipment(I) && EquipmentIsAllowed(I);
6963 /* Target mustn't have any equipment */
6964 void character::DonateEquipmentTo (character *Character) {
6965 if (IsPlayer()) {
6966 feuLong *EquipmentMemory = game::GetEquipmentMemory();
6967 for (int c = 0; c < MAX_EQUIPMENT_SLOTS; ++c) {
6968 item *Item = GetEquipment(c);
6969 if (Item) {
6970 if (Character->CanUseEquipment(c)) {
6971 Item->RemoveFromSlot();
6972 Character->SetEquipment(c, Item);
6973 } else {
6974 EquipmentMemory[c] = Item->GetID();
6975 Item->MoveTo(Character->GetStack());
6977 } else if (CanUseEquipment(c)) {
6978 EquipmentMemory[c] = 0;
6979 } else if (EquipmentMemory[c] && Character->CanUseEquipment(c)) {
6980 for (stackiterator i = Character->GetStack()->GetBottom(); i.HasItem(); ++i) {
6981 if (i->GetID() == EquipmentMemory[c]) {
6982 item *Item = *i;
6983 Item->RemoveFromSlot();
6984 Character->SetEquipment(c, Item);
6985 break;
6988 EquipmentMemory[c] = 0;
6991 } else {
6992 for (int c = 0; c < GetEquipments(); ++c) {
6993 item *Item = GetEquipment(c);
6994 if (Item) {
6995 if (Character->CanUseEquipment(c)) {
6996 Item->RemoveFromSlot();
6997 Character->SetEquipment(c, Item);
6998 } else {
6999 Item->MoveTo(Character->GetStackUnder());
7007 void character::ReceivePeaSoup (sLong) {
7008 if (!game::IsInWilderness()) {
7009 lsquare *Square = GetLSquareUnder();
7010 if (Square->IsFlyable()) Square->AddSmoke(gas::Spawn(FART, 250));
7015 void character::AddPeaSoupConsumeEndMessage () const {
7016 if (IsPlayer()) {
7017 if (CanHear()) ADD_MESSAGE("Mmmh! The soup is very tasty. You hear a small puff.");
7018 else ADD_MESSAGE("Mmmh! The soup is very tasty.");
7019 } else if (CanBeSeenByPlayer() && PLAYER->CanHear()) {
7020 // change someday
7021 ADD_MESSAGE("You hear a small puff.");
7026 void character::CalculateMaxStamina () {
7027 MaxStamina = TorsoIsAlive() ? GetAttribute(ENDURANCE) * 10000 : 0;
7031 void character::EditStamina (int Amount, truth CanCauseUnconsciousness) {
7032 if (!TorsoIsAlive()) return;
7033 int UnconsciousnessStamina = MaxStamina >> 3;
7034 if (!CanCauseUnconsciousness && Amount < 0) {
7035 if (Stamina > UnconsciousnessStamina) {
7036 Stamina += Amount;
7037 if (Stamina < UnconsciousnessStamina) Stamina = UnconsciousnessStamina;
7039 return;
7041 int OldStamina = Stamina;
7042 Stamina += Amount;
7043 if (Stamina > MaxStamina) {
7044 Stamina = MaxStamina;
7045 } else if (Stamina < 0) {
7046 Stamina = 0;
7047 LoseConsciousness(250 + RAND_N(250));
7048 } else if (IsPlayer()) {
7049 if (OldStamina >= MaxStamina >> 2 && Stamina < MaxStamina >> 2) {
7050 ADD_MESSAGE("You are getting a little tired.");
7051 } else if(OldStamina >= UnconsciousnessStamina && Stamina < UnconsciousnessStamina) {
7052 ADD_MESSAGE("You are seriously out of breath!");
7053 game::SetPlayerIsRunning(false);
7056 if (IsPlayer() && StateIsActivated(PANIC) && GetTirednessState() != FAINTING) game::SetPlayerIsRunning(true);
7060 void character::RegenerateStamina () {
7061 if (GetTirednessState() != UNTIRED) {
7062 EditExperience(ENDURANCE, 50, 1);
7063 if (Sweats() && TorsoIsAlive() && !RAND_N(30) && !game::IsInWilderness()) {
7064 // Sweat amount proportional to endurance also
7065 //sLong Volume = sLong(0.05 * sqrt(GetBodyVolume()));
7066 sLong Volume = long(0.05*sqrt(GetBodyVolume()*GetAttribute(ENDURANCE)/10));
7067 if (GetTirednessState() == FAINTING) Volume <<= 1;
7068 for (int c = 0; c < SquaresUnder; ++c) GetLSquareUnder(c)->SpillFluid(0, CreateSweat(Volume), false, false);
7071 int Bonus = 1;
7072 if (Action) {
7073 if (Action->IsRest()) {
7074 if (SquaresUnder == 1) {
7075 Bonus = GetSquareUnder()->GetRestModifier() << 1;
7076 } else {
7077 int Lowest = GetSquareUnder(0)->GetRestModifier();
7078 for (int c = 1; c < GetSquaresUnder(); ++c) {
7079 int Mod = GetSquareUnder(c)->GetRestModifier();
7080 if (Mod < Lowest) Lowest = Mod;
7082 Bonus = Lowest << 1;
7084 } else if (Action->IsUnconsciousness()) Bonus = 2;
7086 int Plus1 = 100;
7087 auto bst = GetBurdenState();
7088 if (bst == OVER_LOADED) Plus1 = 25;
7089 else if (bst == STRESSED) Plus1 = 50;
7090 else if (bst == BURDENED) Plus1 = 75;
7091 int Plus2 = 100;
7092 if (IsPlayer()) {
7093 auto hst = GetHungerState();
7094 if (hst == STARVING) Plus2 = 25;
7095 else if (hst == VERY_HUNGRY) Plus2 = 50;
7096 else if (hst == HUNGRY) Plus2 = 75;
7098 Stamina += Plus1 * Plus2 * Bonus / 1000;
7099 if (Stamina > MaxStamina) Stamina = MaxStamina;
7100 if (IsPlayer() && StateIsActivated(PANIC) && GetTirednessState() != FAINTING) game::SetPlayerIsRunning(true);
7104 void character::BeginPanic () {
7105 if (IsPlayer() && GetTirednessState() != FAINTING) game::SetPlayerIsRunning(true);
7106 DeActivateVoluntaryAction();
7110 void character::EndPanic () {
7111 if (IsPlayer()) game::SetPlayerIsRunning(false);
7115 int character::GetTirednessState () const {
7116 if (Stamina >= MaxStamina >> 2) return UNTIRED;
7117 if (Stamina >= MaxStamina >> 3) return EXHAUSTED;
7118 return FAINTING;
7122 void character::ReceiveBlackUnicorn (sLong Amount) {
7123 if (!(RAND() % 160)) game::DoEvilDeed(Amount / 50);
7124 BeginTemporaryState(TELEPORT, Amount / 100);
7125 for (int c = 0; c < STATES; ++c) {
7126 if (StateData[c].Flags & DUR_TEMPORARY) {
7127 BeginTemporaryState(1 << c, Amount / 100);
7128 if (!IsEnabled()) return;
7129 } else if (StateData[c].Flags & DUR_PERMANENT) {
7130 GainIntrinsic(1 << c);
7131 if (!IsEnabled()) return;
7137 void character::ReceiveGrayUnicorn (sLong Amount) {
7138 if (!(RAND() % 80)) game::DoEvilDeed(Amount / 50);
7139 BeginTemporaryState(TELEPORT, Amount / 100);
7140 for (int c = 0; c < STATES; ++c) {
7141 if (1 << c != TELEPORT) {
7142 DecreaseStateCounter(1 << c, -Amount / 100);
7143 if (!IsEnabled()) return;
7149 void character::ReceiveWhiteUnicorn (sLong Amount) {
7150 if (!(RAND() % 40)) game::DoEvilDeed(Amount / 50);
7151 BeginTemporaryState(TELEPORT, Amount / 100);
7152 DecreaseStateCounter(LYCANTHROPY, -Amount / 100);
7153 DecreaseStateCounter(POISONED, -Amount / 100);
7154 DecreaseStateCounter(PARASITIZED, -Amount / 100);
7155 DecreaseStateCounter(LEPROSY, -Amount / 100);
7156 DecreaseStateCounter(VAMPIRISM, -Amount / 100);
7160 /* Counter should be negative. Removes intrinsics. */
7161 void character::DecreaseStateCounter (sLong State, int Counter) {
7162 while (State != 0) {
7163 sLong st = 0, sidx;
7164 for (sidx = 0; sidx < STATES; ++sidx) if (State&(1<<sidx)) { st = (1<<sidx); break; }
7165 if (!st) { break; /*ABORT("BeginTemporaryState works only when State == 2^n!");*/ }
7166 State &= ~st;
7167 if (TemporaryState&st) {
7168 if (TemporaryStateCounter[sidx] == PERMANENT || (TemporaryStateCounter[sidx] += Counter) <= 0) {
7169 TemporaryState &= ~st;
7170 if (!(EquipmentState & st)) {
7171 if (StateData[sidx].EndHandler) {
7172 (this->*StateData[sidx].EndHandler)();
7173 if (!IsEnabled()) return;
7175 (this->*StateData[sidx].PrintEndMessage)();
7183 truth character::IsImmuneToLeprosy () const {
7184 return (DataBase->IsImmuneToLeprosy || UseMaterialAttributes() || StateIsActivated(DISEASE_IMMUNITY));
7188 void character::LeprosyHandler () {
7189 EditExperience(ARM_STRENGTH, -25, 1 << 1);
7190 EditExperience(LEG_STRENGTH, -25, 1 << 1);
7191 EditExperience(DEXTERITY, -25, 1 << 1);
7192 EditExperience(AGILITY, -25, 1 << 1);
7193 EditExperience(ENDURANCE, -25, 1 << 1);
7194 EditExperience(CHARISMA, -25, 1 << 1);
7195 CheckDeath(CONST_S("killed by leprosy"));
7199 bodypart *character::SearchForOriginalBodyPart (int I) const {
7200 for (stackiterator i1 = GetStackUnder()->GetBottom(); i1.HasItem(); ++i1) {
7201 for (std::list<feuLong>::iterator i2 = OriginalBodyPartID[I].begin(); i2 != OriginalBodyPartID[I].end(); ++i2)
7202 if (i1->GetID() == *i2) return static_cast<bodypart*>(*i1);
7204 return 0;
7208 void character::SetLifeExpectancy (int Base, int RandPlus) {
7209 int c;
7210 for (c = 0; c < BodyParts; ++c) {
7211 bodypart *BodyPart = GetBodyPart(c);
7212 if (BodyPart) BodyPart->SetLifeExpectancy(Base, RandPlus);
7214 for (c = 0; c < GetEquipments(); ++c) {
7215 item *Equipment = GetEquipment(c);
7216 if (Equipment) Equipment->SetLifeExpectancy(Base, RandPlus);
7221 /* Receiver should be a fresh duplicate of this */
7222 void character::DuplicateEquipment (character *Receiver, feuLong Flags) {
7223 for (int c = 0; c < GetEquipments(); ++c) {
7224 item *Equipment = GetEquipment(c);
7225 if (Equipment) {
7226 item *Duplicate = Equipment->Duplicate(Flags);
7227 Receiver->SetEquipment(c, Duplicate);
7233 void character::Disappear (corpse *Corpse, cchar *Verb, truth (item::*ClosePredicate)() const) {
7234 truth TorsoDisappeared = false;
7235 truth CanBeSeen = Corpse ? Corpse->CanBeSeenByPlayer() : IsPlayer() || CanBeSeenByPlayer();
7236 int c;
7237 if ((GetTorso()->*ClosePredicate)()) {
7238 if (CanBeSeen) {
7239 if (Corpse) ADD_MESSAGE("%s %ss.", Corpse->CHAR_NAME(DEFINITE), Verb);
7240 else if (IsPlayer()) ADD_MESSAGE("You %s.", Verb);
7241 else ADD_MESSAGE("%s %ss.", CHAR_NAME(DEFINITE), Verb);
7243 TorsoDisappeared = true;
7244 for (c = 0; c < GetEquipments(); ++c) {
7245 item *Equipment = GetEquipment(c);
7246 if (Equipment && (Equipment->*ClosePredicate)()) {
7247 Equipment->RemoveFromSlot();
7248 Equipment->SendToHell();
7251 itemvector ItemVector;
7252 GetStack()->FillItemVector(ItemVector);
7253 for (uInt c = 0; c < ItemVector.size(); ++c) {
7254 if (ItemVector[c] && (ItemVector[c]->*ClosePredicate)()) {
7255 ItemVector[c]->RemoveFromSlot();
7256 ItemVector[c]->SendToHell();
7260 for (c = 1; c < GetBodyParts(); ++c) {
7261 bodypart *BodyPart = GetBodyPart(c);
7262 if (BodyPart) {
7263 if ((BodyPart->*ClosePredicate)()) {
7264 if (!TorsoDisappeared && CanBeSeen) {
7265 if(IsPlayer()) ADD_MESSAGE("Your %s %ss.", GetBodyPartName(c).CStr(), Verb);
7266 else ADD_MESSAGE("The %s of %s %ss.", GetBodyPartName(c).CStr(), CHAR_NAME(DEFINITE), Verb);
7268 BodyPart->DropEquipment();
7269 item *BodyPart = SevereBodyPart(c);
7270 if (BodyPart) BodyPart->SendToHell();
7271 } else if (TorsoDisappeared) {
7272 BodyPart->DropEquipment();
7273 item *BodyPart = SevereBodyPart(c);
7274 if (BodyPart) {
7275 if (Corpse) Corpse->GetSlot()->AddFriendItem(BodyPart);
7276 else if (!game::IsInWilderness()) GetStackUnder()->AddItem(BodyPart);
7277 else BodyPart->SendToHell();
7282 if (TorsoDisappeared) {
7283 if (Corpse) {
7284 Corpse->RemoveFromSlot();
7285 Corpse->SendToHell();
7286 } else {
7287 CheckDeath(festring(Verb) + "ed", 0, FORCE_DEATH|DISALLOW_CORPSE|DISALLOW_MSG);
7289 } else {
7290 CheckDeath(festring(Verb) + "ed", 0, DISALLOW_MSG);
7295 void character::SignalDisappearance () {
7296 if (GetMotherEntity()) GetMotherEntity()->SignalDisappearance();
7297 else Disappear(0, "disappear", &item::IsVeryCloseToDisappearance);
7301 truth character::HornOfFearWorks () const {
7302 return CanHear() && GetPanicLevel() > RAND()%33 && !StateIsActivated(FEARLESS);
7306 void character::BeginLeprosy () {
7307 doforbodypartswithparam<truth>()(this, &bodypart::SetIsInfectedByLeprosy, true);
7311 void character::EndLeprosy () {
7312 doforbodypartswithparam<truth>()(this, &bodypart::SetIsInfectedByLeprosy, false);
7316 truth character::IsSameAs (ccharacter *What) const {
7317 return What->GetType() == GetType() && What->GetConfig() == GetConfig();
7321 feuLong character::GetCommandFlags () const {
7322 return !StateIsActivated(PANIC) ? CommandFlags : CommandFlags|FLEE_FROM_ENEMIES;
7326 feuLong character::GetConstantCommandFlags () const {
7327 return !StateIsActivated(PANIC) ? DataBase->ConstantCommandFlags : DataBase->ConstantCommandFlags|FLEE_FROM_ENEMIES;
7331 feuLong character::GetPossibleCommandFlags () const {
7332 int Int = GetAttribute(INTELLIGENCE);
7333 feuLong Flags = ALL_COMMAND_FLAGS;
7334 if (!CanMove() || Int < 4) Flags &= ~FOLLOW_LEADER;
7335 if (!CanMove() || Int < 6) Flags &= ~FLEE_FROM_ENEMIES;
7336 if (!CanUseEquipment() || Int < 8) Flags &= ~DONT_CHANGE_EQUIPMENT;
7337 if (!UsesNutrition() || Int < 8) Flags &= ~DONT_CONSUME_ANYTHING_VALUABLE;
7338 return Flags;
7342 truth character::IsRetreating () const {
7343 return StateIsActivated(PANIC) || (CommandFlags & FLEE_FROM_ENEMIES && IsPet());
7347 truth character::ChatMenu () {
7348 if (GetAction() && !GetAction()->CanBeTalkedTo()) {
7349 ADD_MESSAGE("%s is silent.", CHAR_DESCRIPTION(DEFINITE));
7350 PLAYER->EditAP(-200);
7351 return true;
7353 feuLong ManagementFlags = GetManagementFlags();
7354 if (ManagementFlags == CHAT_IDLY || !IsPet()) return ChatIdly();
7355 static cchar *const ChatMenuEntry[CHAT_MENU_ENTRIES] = {
7356 "Change equipment",
7357 "Take items",
7358 "Give items",
7359 "Issue commands",
7360 "Chat idly",
7362 static const petmanagementfunction PMF[CHAT_MENU_ENTRIES] = {
7363 &character::ChangePetEquipment,
7364 &character::TakePetItems,
7365 &character::GivePetItems,
7366 &character::IssuePetCommands,
7367 &character::ChatIdly
7369 felist List(CONST_S("Choose action:"));
7370 game::SetStandardListAttributes(List);
7371 List.AddFlags(SELECTABLE);
7372 int c, i;
7373 for (c = 0; c < CHAT_MENU_ENTRIES; ++c) if (1 << c & ManagementFlags) List.AddEntry(ChatMenuEntry[c], LIGHT_GRAY);
7374 int Chosen = List.Draw();
7375 if (Chosen & FELIST_ERROR_BIT) return false;
7376 for (c = 0, i = 0; c < CHAT_MENU_ENTRIES; ++c) {
7377 if (1 << c & ManagementFlags && i++ == Chosen) return (this->*PMF[c])();
7379 return false; // dummy
7383 truth character::ChangePetEquipment () {
7384 if (EquipmentScreen(PLAYER->GetStack(), GetStack())) {
7385 DexterityAction(3);
7386 return true;
7388 return false;
7392 truth character::TakePetItems () {
7393 truth Success = false;
7394 stack::SetSelected(0);
7395 for (;;) {
7396 itemvector ToTake;
7397 game::DrawEverythingNoBlit();
7398 GetStack()->DrawContents(
7399 ToTake,
7401 PLAYER,
7402 CONST_S("What do you want to take from ") + CHAR_DESCRIPTION(DEFINITE) + '?',
7403 CONST_S(""),
7404 CONST_S(""),
7405 GetDescription(DEFINITE) + " is " + GetVerbalBurdenState(),
7406 GetVerbalBurdenStateColor(),
7407 REMEMBER_SELECTED);
7408 if (ToTake.empty()) break;
7409 for (uInt c = 0; c < ToTake.size(); ++c) ToTake[c]->MoveTo(PLAYER->GetStack());
7410 ADD_MESSAGE("You take %s.", ToTake[0]->GetName(DEFINITE, ToTake.size()).CStr());
7411 Success = true;
7413 if (Success) {
7414 DexterityAction(2);
7415 PLAYER->DexterityAction(2);
7417 return Success;
7421 truth character::GivePetItems () {
7422 truth Success = false;
7423 stack::SetSelected(0);
7424 for (;;) {
7425 itemvector ToGive;
7426 game::DrawEverythingNoBlit();
7427 PLAYER->GetStack()->DrawContents(
7428 ToGive,
7430 this,
7431 CONST_S("What do you want to give to ") + CHAR_DESCRIPTION(DEFINITE) + '?',
7432 CONST_S(""),
7433 CONST_S(""),
7434 GetDescription(DEFINITE) + " is " + GetVerbalBurdenState(),
7435 GetVerbalBurdenStateColor(),
7436 REMEMBER_SELECTED);
7437 if (ToGive.empty()) break;
7438 for (uInt c = 0; c < ToGive.size(); ++c) ToGive[c]->MoveTo(GetStack());
7439 ADD_MESSAGE("You give %s to %s.", ToGive[0]->GetName(DEFINITE, ToGive.size()).CStr(), CHAR_DESCRIPTION(DEFINITE));
7440 Success = true;
7442 if (Success) {
7443 DexterityAction(2);
7444 PLAYER->DexterityAction(2);
7446 return Success;
7450 truth character::IssuePetCommands () {
7451 if (!IsConscious()) {
7452 ADD_MESSAGE("%s is unconscious.", CHAR_DESCRIPTION(DEFINITE));
7453 return false;
7455 feuLong PossibleC = GetPossibleCommandFlags();
7456 if (!PossibleC) {
7457 ADD_MESSAGE("%s cannot be commanded.", CHAR_DESCRIPTION(DEFINITE));
7458 return false;
7460 feuLong OldC = GetCommandFlags();
7461 feuLong NewC = OldC, VaryFlags = 0;
7462 game::CommandScreen(CONST_S("Issue commands to ")+GetDescription(DEFINITE), PossibleC, GetConstantCommandFlags(), VaryFlags, NewC);
7463 if (NewC == OldC) return false;
7464 SetCommandFlags(NewC);
7465 PLAYER->EditAP(-500);
7466 PLAYER->EditExperience(CHARISMA, 25, 1 << 7);
7467 return true;
7471 truth character::ChatIdly () {
7472 if (!TryToTalkAboutScience()) {
7473 BeTalkedTo();
7474 PLAYER->EditExperience(CHARISMA, 75, 1 << 7);
7476 PLAYER->EditAP(-1000);
7477 return true;
7481 int character::HasSomethingToEquipAt (int chosen, truth equippedIsTrue) {
7482 if (!GetBodyPartOfEquipment(chosen)) return 0;
7484 item *oldEquipment = GetEquipment(chosen);
7485 if (!IsPlayer() && oldEquipment && BoundToUse(oldEquipment, chosen)) return 0;
7487 stack *mainStack = GetStack();
7488 sorter Sorter = EquipmentSorter(chosen);
7489 auto count = mainStack->SortedItemsCount(this, Sorter);
7491 if (equippedIsTrue && oldEquipment) ++count;
7493 return count;
7497 // returns 0, 1 or pickup time
7498 feuLong character::HasSomethingToEquipAtRecentTime (int chosen, truth equippedIsTrue) {
7499 if (!GetBodyPartOfEquipment(chosen)) return 0;
7501 item *oldEquipment = GetEquipment(chosen);
7502 if (!IsPlayer() && oldEquipment && BoundToUse(oldEquipment, chosen)) return 0;
7504 stack *mainStack = GetStack();
7505 sorter Sorter = EquipmentSorter(chosen);
7506 feuLong highestTime = mainStack->SortedItemsRecentTime(this, Sorter);
7508 if (equippedIsTrue && oldEquipment && oldEquipment->pickupTime > highestTime) highestTime = oldEquipment->pickupTime;
7510 return highestTime;
7514 truth character::EquipmentScreen (stack *MainStack, stack *SecStack) {
7515 if (!CanUseEquipment()) {
7516 ADD_MESSAGE("%s cannot use equipment.", CHAR_DESCRIPTION(DEFINITE));
7517 return false;
7519 int Chosen = 0;
7520 truth EquipmentChanged = false;
7521 felist List(CONST_S("Equipment menu [ESC exits]"));
7522 festring Entry;
7523 for (;;) {
7524 List.Empty();
7525 List.EmptyDescription();
7526 if (!IsPlayer()) {
7527 List.AddDescription(CONST_S(""));
7528 List.AddDescription(festring(GetDescription(DEFINITE) + " is " + GetVerbalBurdenState()).CapitalizeCopy(), GetVerbalBurdenStateColor());
7529 } else {
7530 int totalWeight = 0;
7531 for (int c = 0; c < GetEquipments(); ++c) {
7532 item *Equipment = GetEquipment(c);
7533 totalWeight += (Equipment ? Equipment->GetWeight() : 0);
7535 festring Total("Total weight: ");
7536 Total << totalWeight;
7537 Total << "g";
7538 List.AddDescription(CONST_S(""));
7539 List.AddDescription(Total);
7541 int firstEmpty = -1, firstNonEmpty = -1, selected = -1;
7542 feuLong selPickTime = 1;
7543 feuLong armPickTime = 0;
7544 int armFirst = -1;
7545 //truth selectedIsEmpty = false;
7546 for (int c = 0; c < GetEquipments(); ++c) {
7547 truth isArm = (c == 5 || c == 6);
7548 //int bpidx = (GetBodyPartOfEquipment(c) ? GetBodyPartOfEquipment(c)->GetBodyPartIndex() : -1);
7549 truth equippable = !!GetBodyPartOfEquipment(c);
7550 Entry = GetEquipmentName(c);
7551 Entry << ':';
7552 Entry.Resize(20);
7553 item *Equipment = GetEquipment(c);
7554 feuLong pickTm = (equippable ? HasSomethingToEquipAtRecentTime(c, false) : 0);
7555 int availEquipCount = (equippable ? HasSomethingToEquipAt(c, false) : 0);
7556 if (pickTm > 1 && game::GetTick()-pickTm > game::PickTimeout) pickTm = 0;
7557 //fprintf(stderr, "c=%d; equippable=%d; availcount=%d; pickTm=%u; tick=%u\n", c, (int)equippable, availEquipCount, pickTm, game::GetTick());
7558 if (Equipment) {
7559 Equipment->AddInventoryEntry(this, Entry, 1, true);
7560 AddSpecialEquipmentInfo(Entry, c);
7561 int ImageKey = game::AddToItemDrawVector(itemvector(1, Equipment));
7562 if (firstNonEmpty < 0 && equippable && !isArm && availEquipCount > 0) firstNonEmpty = c;
7563 if (equippable) {
7564 if (availEquipCount > 0 && isArm && armPickTime < pickTm) { armFirst = c; armPickTime = pickTm; }
7565 if (selPickTime < pickTm && equippable && !isArm) { selected = c; selPickTime = pickTm; }
7567 List.AddEntry(Entry, (availEquipCount ? ORANGE : LIGHT_GRAY), 20, ImageKey, true);
7568 } else {
7569 truth canUse = !!GetBodyPartOfEquipment(c);
7570 Entry << (canUse ? "-" : "can't use");
7571 col16 color = RED;
7572 if (canUse && availEquipCount > 0) {
7573 if (firstEmpty < 0 && equippable && !isArm && availEquipCount > 0) firstEmpty = c;
7574 if (equippable && isArm && armFirst < 0) armFirst = c;
7575 switch (availEquipCount) {
7576 case 0: color = RED; break;
7577 case 1: color = LIGHT_GRAY; break;
7578 default: color = ORANGE; break;
7581 if (color != RED && equippable) {
7582 if (pickTm > selPickTime && !isArm) { selected = c; selPickTime = pickTm; }
7584 List.AddEntry(Entry, color, 20, game::AddToItemDrawVector(itemvector()));
7587 game::DrawEverythingNoBlit();
7588 game::SetStandardListAttributes(List);
7590 //fprintf(stderr, " selected=%d; firstEmpty=%d; firstNonEmpty=%d; armFirst=%d; armPickTime=%u\n", selected, firstEmpty, firstNonEmpty, armFirst, armPickTime);
7591 if (selected < 0) {
7592 if (armPickTime > 0) selected = armFirst;
7593 else if (firstEmpty >= 0) selected = firstEmpty;
7594 else if (firstNonEmpty >= 0) selected = firstNonEmpty;
7596 // 7: right ring; switch to left ring if that slot has nothing in it
7597 if (selected == 7 && GetBodyPartOfEquipment(8) && !GetEquipment(8)) selected = 8;
7598 if (selected >= 0) List.SetSelected(selected);
7600 List.SetFlags(SELECTABLE|DRAW_BACKGROUND_AFTERWARDS);
7601 List.SetEntryDrawer(game::ItemEntryDrawer);
7602 Chosen = List.Draw();
7603 game::ClearItemDrawVector();
7604 if (Chosen >= GetEquipments()) break;
7605 EquipmentChanged = TryToChangeEquipment(MainStack, SecStack, Chosen);
7607 if (EquipmentChanged) DexterityAction(5);
7608 return EquipmentChanged;
7612 feuLong character::GetManagementFlags () const {
7613 feuLong Flags = ALL_MANAGEMENT_FLAGS;
7614 if (!CanUseEquipment() || !AllowPlayerToChangeEquipment()) Flags &= ~CHANGE_EQUIPMENT;
7615 if (!GetStack()->GetItems()) Flags &= ~TAKE_ITEMS;
7616 if (!WillCarryItems()) Flags &= ~GIVE_ITEMS;
7617 if (!GetPossibleCommandFlags()) Flags &= ~ISSUE_COMMANDS;
7618 return Flags;
7622 cchar *VerbalBurdenState[] = { "overloaded", "stressed", "burdened", "unburdened" };
7623 col16 VerbalBurdenStateColor[] = { RED, BLUE, BLUE, WHITE };
7625 cchar *character::GetVerbalBurdenState () const { return VerbalBurdenState[BurdenState]; }
7626 col16 character::GetVerbalBurdenStateColor () const { return VerbalBurdenStateColor[BurdenState]; }
7627 int character::GetAttributeAverage () const { return GetSumOfAttributes()/7; }
7629 cfestring &character::GetStandVerb() const {
7630 if (ForceCustomStandVerb()) return DataBase->StandVerb;
7631 static festring Hovering = "hovering";
7632 static festring Swimming = "swimming";
7633 if (StateIsActivated(LEVITATION)) return Hovering;
7634 if (IsSwimming()) return Swimming;
7635 return DataBase->StandVerb;
7639 truth character::CheckApply () const {
7640 if (!CanApply()) {
7641 ADD_MESSAGE("This monster type cannot apply.");
7642 return false;
7644 return true;
7648 void character::EndLevitation () {
7649 if (!IsFlying() && GetSquareUnder()) {
7650 if (!game::IsInWilderness()) SignalStepFrom(0);
7651 if (game::IsInWilderness() || !GetLSquareUnder()->IsFreezed()) TestWalkability();
7656 truth character::CanMove () const {
7657 return !IsRooted() || StateIsActivated(LEVITATION);
7661 void character::CalculateEnchantments () {
7662 doforequipments()(this, &item::CalculateEnchantment);
7663 GetStack()->CalculateEnchantments();
7667 truth character::GetNewFormForPolymorphWithControl (character *&NewForm) {
7668 if (StateIsActivated(POLYMORPH_LOCK)) { ADD_MESSAGE("You feel uncertain about your body for a moment."); return false; }
7669 festring Topic, Temp;
7670 NewForm = 0;
7671 while (!NewForm) {
7672 festring Temp = game::DefaultQuestion(CONST_S("What do you want to become? [press '?' for a list]"), game::GetDefaultPolymorphTo(), &game::PolymorphControlKeyHandler);
7673 NewForm = protosystem::CreateMonster(Temp);
7674 if (NewForm) {
7675 if (NewForm->IsSameAs(this)) {
7676 delete NewForm;
7677 ADD_MESSAGE("You choose not to polymorph.");
7678 NewForm = this;
7679 return false;
7681 if (PolymorphBackup && NewForm->IsSameAs(PolymorphBackup)) {
7682 delete NewForm;
7683 NewForm = ForceEndPolymorph();
7684 return false;
7686 if (NewForm->GetPolymorphIntelligenceRequirement() > GetAttribute(INTELLIGENCE) && !game::WizardModeIsActive()) {
7687 ADD_MESSAGE("You feel your mind isn't yet powerful enough to call forth the form of %s.", NewForm->CHAR_NAME(INDEFINITE));
7688 delete NewForm;
7689 NewForm = 0;
7690 } else {
7691 NewForm->RemoveAllItems();
7695 return true;
7699 liquid *character::CreateSweat(sLong Volume) const {
7700 //return liquid::Spawn(GetSweatMaterial(), Volume);
7701 return liquid::Spawn(GetCurrentSweatMaterial(), Volume);
7705 truth character::TeleportRandomItem (truth TryToHinderVisibility) {
7706 if (IsImmuneToItemTeleport() || StateIsActivated(TELEPORT_LOCK)) return false;
7707 itemvector ItemVector;
7708 std::vector<sLong> PossibilityVector;
7709 int TotalPossibility = 0;
7710 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) {
7711 ItemVector.push_back(*i);
7712 int Possibility = i->GetTeleportPriority();
7713 if (TryToHinderVisibility) Possibility += i->GetHinderVisibilityBonus(this);
7714 PossibilityVector.push_back(Possibility);
7715 TotalPossibility += Possibility;
7717 for (int c = 0; c < GetEquipments(); ++c) {
7718 item *Equipment = GetEquipment(c);
7719 if (Equipment) {
7720 ItemVector.push_back(Equipment);
7721 int Possibility = Equipment->GetTeleportPriority();
7722 if (TryToHinderVisibility) Possibility += Equipment->GetHinderVisibilityBonus(this);
7723 PossibilityVector.push_back(Possibility <<= 1);
7724 TotalPossibility += Possibility;
7727 if (!TotalPossibility) return false;
7728 int Chosen = femath::WeightedRand(PossibilityVector, TotalPossibility);
7729 item *Item = ItemVector[Chosen];
7730 truth Equipped = PLAYER->Equips(Item);
7731 truth Seen = Item->CanBeSeenByPlayer();
7732 Item->RemoveFromSlot();
7733 if (Seen) ADD_MESSAGE("%s disappears.", Item->CHAR_NAME(DEFINITE));
7734 if (Equipped) game::AskForEscPress(CONST_S("Equipment lost!"));
7735 v2 Pos = GetPos();
7736 int Range = (Item->GetEmitation() && TryToHinderVisibility ? 25 : 5);
7737 rect Border(Pos + v2(-Range, -Range), Pos + v2(Range, Range));
7738 Pos = GetLevel()->GetRandomSquare(this, 0, &Border);
7739 if (Pos == ERROR_V2) {
7740 Pos = GetLevel()->GetRandomSquare();
7741 if (Pos == ERROR_V2) {
7742 fprintf(stderr, "oops. `character::TeleportRandomItem()` failed.\n");
7743 Item->SendToHell(); //???
7744 return true;
7747 GetNearLSquare(Pos)->GetStack()->AddItem(Item);
7748 if (Item->CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", Item->CHAR_NAME(INDEFINITE));
7749 return true;
7753 truth character::HasClearRouteTo (v2 Pos) const {
7754 pathcontroller::Map = GetLevel()->GetMap();
7755 pathcontroller::Character = this;
7756 v2 ThisPos = GetPos();
7757 return mapmath<pathcontroller>::DoLine(ThisPos.X, ThisPos.Y, Pos.X, Pos.Y, SKIP_FIRST|LINE_BOTH_DIRS);
7761 truth character::IsTransparent () const {
7762 return !IsEnormous() || GetTorso()->GetMainMaterial()->IsTransparent() || StateIsActivated(INVISIBLE);
7766 void character::SignalPossibleTransparencyChange () {
7767 if (!game::IsInWilderness()) {
7768 for (int c = 0; c < SquaresUnder; ++c) {
7769 lsquare *Square = GetLSquareUnder(c);
7770 if (Square) Square->SignalPossibleTransparencyChange();
7776 int character::GetCursorData () const {
7777 int Bad = 0;
7778 int Color = (game::PlayerIsRunning() ? BLUE_CURSOR : DARK_CURSOR);
7779 for (int c = 0; c < BodyParts; ++c) {
7780 bodypart *BodyPart = GetBodyPart(c);
7781 if (BodyPart && BodyPart->IsUsable()) {
7782 int ConditionColorIndex = BodyPart->GetConditionColorIndex();
7783 if ((BodyPartIsVital(c) && !ConditionColorIndex) || (ConditionColorIndex <= 1 && ++Bad == 2)) return Color|CURSOR_FLASH;
7784 } else if (++Bad == 2) {
7785 return Color|CURSOR_FLASH;
7788 Color = (game::PlayerIsRunning() ? YELLOW_CURSOR : RED_CURSOR);
7789 return (Bad ? Color|CURSOR_FLASH : Color);
7793 void character::TryToName () {
7794 if (!IsPet()) ADD_MESSAGE("%s refuses to let YOU decide what %s's called.", CHAR_NAME(DEFINITE), CHAR_PERSONAL_PRONOUN);
7795 else if (IsPlayer()) ADD_MESSAGE("You can't rename yourself.");
7796 else if (!IsNameable()) ADD_MESSAGE("%s refuses to be called anything else but %s.", CHAR_NAME(DEFINITE), CHAR_NAME(DEFINITE));
7797 else {
7798 festring Topic = CONST_S("What name will you give to ")+GetName(DEFINITE)+'?';
7799 festring Name = game::StringQuestion(Topic, WHITE, 0, 80, true);
7800 if (Name.GetSize()) SetAssignedName(Name);
7805 double character::GetSituationDanger (ccharacter *Enemy, v2 ThisPos, v2 EnemyPos, truth SeesEnemy) const {
7806 double Danger;
7807 if (IgnoreDanger() && !IsPlayer()) {
7808 if (Enemy->IgnoreDanger() && !Enemy->IsPlayer()) {
7809 Danger = double(GetHP())*GetHPRequirementForGeneration()/(Enemy->GetHP()*Enemy->GetHPRequirementForGeneration());
7811 else {
7812 Danger = 0.25*GetHPRequirementForGeneration()/Enemy->GetHP();
7814 } else if (Enemy->IgnoreDanger() && !Enemy->IsPlayer()) {
7815 Danger = 4.0*GetHP()/Enemy->GetHPRequirementForGeneration();
7816 } else {
7817 Danger = GetRelativeDanger(Enemy);
7819 Danger *= 3.0/((EnemyPos-ThisPos).GetManhattanLength()+2);
7820 if (!SeesEnemy) Danger *= 0.2;
7821 if (StateIsActivated(PANIC)) Danger *= 0.2;
7822 Danger *= double(GetHP())*Enemy->GetMaxHP()/(Enemy->GetHP()*GetMaxHP());
7823 return Danger;
7827 void character::ModifySituationDanger (double &Danger) const {
7828 switch (GetTirednessState()) {
7829 case FAINTING: Danger *= 1.5;
7830 case EXHAUSTED: Danger *= 1.25;
7832 for (int c = 0; c < STATES; ++c) {
7833 if (StateIsActivated(1 << c) && StateData[c].SituationDangerModifier != 0) (this->*StateData[c].SituationDangerModifier)(Danger);
7838 void character::LycanthropySituationDangerModifier (double &Danger) const {
7839 character *Wolf = werewolfwolf::Spawn();
7840 double DangerToWolf = GetRelativeDanger(Wolf);
7841 Danger *= pow(DangerToWolf, 0.1);
7842 delete Wolf;
7846 void character::PoisonedSituationDangerModifier (double &Danger) const {
7847 int C = GetTemporaryStateCounter(POISONED);
7848 Danger *= (1+(C*C)/(GetHP()*10000.0*(GetGlobalResistance(POISON)+1)));
7852 void character::PolymorphingSituationDangerModifier (double &Danger) const {
7853 if ((!StateIsActivated(POLYMORPH_CONTROL)) && (!StateIsActivated(POLYMORPH_LOCK))) Danger *= 1.5;
7857 void character::PanicSituationDangerModifier (double &Danger) const {
7858 Danger *= 1.5;
7862 void character::ConfusedSituationDangerModifier (double &Danger) const {
7863 Danger *= 1.5;
7867 void character::ParasitizedSituationDangerModifier (double &Danger) const {
7868 Danger *= 1.25;
7872 void character::LeprosySituationDangerModifier (double &Danger) const {
7873 Danger *= 1.5;
7877 void character::AddRandomScienceName (festring &String) const {
7878 festring Science = GetScienceTalkName().GetRandomElement().CStr();
7879 if (Science[0] == '!') {
7880 String << Science.CStr()+1;
7881 return;
7883 festring Attribute = GetScienceTalkAdjectiveAttribute().GetRandomElement();
7884 festring Prefix;
7885 truth NoAttrib = Attribute.IsEmpty(), NoSecondAdjective = false;
7886 if (!Attribute.IsEmpty() && Attribute[0] == '!') {
7887 NoSecondAdjective = true;
7888 Attribute.Erase(0, 1);
7890 if (!Science.Find("the ")) {
7891 Science.Erase(0, 4);
7892 if (!Attribute.Find("the ", 0, 4)) Attribute << " the"; else Attribute.Insert(0, "the ", 4);
7894 if (islower(Science[0]) && Science.Find(' ') == festring::NPos && Science.Find('-') == festring::NPos &&
7895 Science.Find("phobia") == festring::NPos) {
7896 Prefix = GetScienceTalkPrefix().GetRandomElement();
7897 if (!Prefix.IsEmpty() && Science.Find(Prefix) != festring::NPos) Prefix.Empty();
7899 int L = Prefix.GetSize();
7900 if (L && Prefix[L-1] == Science[0]) Science.Erase(0, 1);
7901 if (!NoAttrib && !NoSecondAdjective == !RAND_GOOD(3)) {
7902 int S1 = NoSecondAdjective ? 0 : GetScienceTalkAdjectiveAttribute().Size;
7903 int S2 = GetScienceTalkSubstantiveAttribute().Size;
7904 festring OtherAttribute;
7905 int Chosen = RAND_GOOD(S1+S2);
7906 if (Chosen < S1) OtherAttribute = GetScienceTalkAdjectiveAttribute()[Chosen];
7907 else OtherAttribute = GetScienceTalkSubstantiveAttribute()[Chosen - S1];
7908 if (!OtherAttribute.IsEmpty() && OtherAttribute.Find("the ", 0, 4) && Attribute.Find(OtherAttribute) == festring::NPos) {
7909 String << Attribute << ' ' << OtherAttribute << ' ' << Prefix << Science;
7910 return;
7913 String << Attribute;
7914 if (!NoAttrib) String << ' ';
7915 String << Prefix << Science;
7919 truth character::TryToTalkAboutScience () {
7920 if (GetRelation(PLAYER) == HOSTILE ||
7921 GetScienceTalkPossibility() <= RAND_GOOD(100) ||
7922 PLAYER->GetAttribute(INTELLIGENCE) < GetScienceTalkIntelligenceRequirement() ||
7923 PLAYER->GetAttribute(WISDOM) < GetScienceTalkWisdomRequirement() ||
7924 PLAYER->GetAttribute(CHARISMA) < GetScienceTalkCharismaRequirement())
7925 return false;
7926 festring Science;
7927 if (RAND_GOOD(3)) {
7928 AddRandomScienceName(Science);
7929 } else {
7930 festring S1, S2;
7931 AddRandomScienceName(S1);
7932 AddRandomScienceName(S2);
7933 if (S1.Find(S2) == festring::NPos && S2.Find(S1) == festring::NPos) {
7934 switch (RAND_GOOD(3)) {
7935 case 0: Science = "the relation of "; break;
7936 case 1: Science = "the differences of "; break;
7937 case 2: Science = "the similarities of "; break;
7939 Science << S1 << " and " << S2;
7941 else {
7942 AddRandomScienceName(Science);
7945 switch ((RAND() + GET_TICK()) % 10) {
7946 case 0:
7947 ADD_MESSAGE("You have a rather pleasant chat about %s with %s.", Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
7948 break;
7949 case 1:
7950 ADD_MESSAGE("%s explains a few of %s opinions regarding %s to you.", CHAR_DESCRIPTION(DEFINITE), CHAR_POSSESSIVE_PRONOUN, Science.CStr());
7951 break;
7952 case 2:
7953 ADD_MESSAGE("%s reveals a number of %s insightful views of %s to you.", CHAR_DESCRIPTION(DEFINITE), CHAR_POSSESSIVE_PRONOUN, Science.CStr());
7954 break;
7955 case 3:
7956 ADD_MESSAGE("You exchange some information pertaining to %s with %s.", Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
7957 break;
7958 case 4:
7959 ADD_MESSAGE("You engage in a pretty intriguing conversation about %s with %s.", Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
7960 break;
7961 case 5:
7962 ADD_MESSAGE("You discuss at length about %s with %s.", Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
7963 break;
7964 case 6:
7965 ADD_MESSAGE("You have a somewhat boring talk concerning %s with %s.", Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
7966 break;
7967 case 7:
7968 ADD_MESSAGE("You are drawn into a heated argument regarding %s with %s.", Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
7969 break;
7970 case 8:
7971 ADD_MESSAGE("%s delivers a long monologue concerning eg. %s.", CHAR_DESCRIPTION(DEFINITE), Science.CStr());
7972 break;
7973 case 9:
7974 ADD_MESSAGE("You dive into a brief but thought-provoking debate over %s with %s", Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
7975 break;
7977 PLAYER->EditExperience(INTELLIGENCE, 1000, 50. * GetScienceTalkIntelligenceModifier() / ++ScienceTalks);
7978 PLAYER->EditExperience(WISDOM, 1000, 50. * GetScienceTalkWisdomModifier() / ++ScienceTalks);
7979 PLAYER->EditExperience(CHARISMA, 1000, 50. * GetScienceTalkCharismaModifier() / ++ScienceTalks);
7980 return true;
7984 truth character::IsUsingWeaponOfCategory (int Category) const {
7985 return
7986 ((GetMainWielded() && GetMainWielded()->GetWeaponCategory() == Category) ||
7987 (GetSecondaryWielded() && GetSecondaryWielded()->GetWeaponCategory() == Category));
7991 truth character::TryToUnStickTraps (v2 Dir) {
7992 if (!TrapData) return true;
7993 std::vector<trapdata> TrapVector;
7994 for (const trapdata *T = TrapData; T; T = T->Next) TrapVector.push_back(*TrapData);
7995 for (uInt c = 0; c < TrapVector.size(); ++c) {
7996 if (IsEnabled()) {
7997 entity *Trap = game::SearchTrap(TrapVector[c].TrapID);
7998 /*k8:??? if(!Trap->Exists()) int esko = esko = 2; */
7999 if (!Trap->Exists()) continue; /*k8: ??? added by me; what this means? */
8000 if (Trap->GetVictimID() == GetID() && Trap->TryToUnStick(this, Dir)) break;
8003 return !TrapData && IsEnabled();
8007 struct trapidcomparer {
8008 trapidcomparer (feuLong ID) : ID(ID) {}
8009 truth operator () (const trapdata *T) const { return T->TrapID == ID; }
8010 feuLong ID;
8014 void character::RemoveTrap (feuLong ID) {
8015 trapdata *&T = ListFind(TrapData, trapidcomparer(ID));
8016 T = T->Next;
8017 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange);
8021 void character::AddTrap (feuLong ID, feuLong BodyParts) {
8022 trapdata *&T = ListFind(TrapData, trapidcomparer(ID));
8023 if (T) T->BodyParts |= BodyParts;
8024 else T = new trapdata(ID, GetID(), BodyParts);
8025 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange);
8029 truth character::IsStuckToTrap (feuLong ID) const {
8030 for (const trapdata *T = TrapData; T; T = T->Next) if (T->TrapID == ID) return true;
8031 return false;
8035 void character::RemoveTraps () {
8036 for (trapdata *T = TrapData; T; T = T->Next) {
8037 entity *Trap = game::SearchTrap(T->TrapID);
8038 if (Trap) Trap->UnStick();
8040 deleteList(TrapData);
8041 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange);
8045 void character::RemoveTraps (int BodyPartIndex) {
8046 feuLong Flag = 1 << BodyPartIndex;
8047 for (trapdata **T = &TrapData; *T;) {
8048 if ((*T)->BodyParts & Flag) {
8049 entity *Trap = game::SearchTrap((*T)->TrapID);
8050 if (!((*T)->BodyParts &= ~Flag)) {
8051 if (Trap) Trap->UnStick();
8052 trapdata *ToDel = *T;
8053 *T = (*T)->Next;
8054 delete ToDel;
8055 } else {
8056 if (Trap) Trap->UnStick(BodyPartIndex);
8057 T = &(*T)->Next;
8060 else {
8061 T = &(*T)->Next;
8064 if (GetBodyPart(BodyPartIndex)) GetBodyPart(BodyPartIndex)->SignalPossibleUsabilityChange();
8068 festring character::GetTrapDescription () const {
8069 festring Desc;
8070 std::pair<entity *, int> TrapStack[3];
8071 int Index = 0;
8072 for (const trapdata *T = TrapData; T; T = T->Next) {
8073 if (Index < 3) {
8074 entity *Trap = game::SearchTrap(T->TrapID);
8075 if (Trap) {
8076 int c;
8077 for (c = 0; c < Index; ++c) if (TrapStack[c].first->GetTrapType() == Trap->GetTrapType()) ++TrapStack[c].second;
8078 if (c == Index) TrapStack[Index++] = std::make_pair(Trap, 1);
8080 } else {
8081 ++Index;
8082 break;
8085 if (Index <= 3) {
8086 TrapStack[0].first->AddTrapName(Desc, TrapStack[0].second);
8087 if (Index == 2) {
8088 Desc << " and ";
8089 TrapStack[1].first->AddTrapName(Desc, TrapStack[1].second);
8090 } else if (Index == 3) {
8091 Desc << ", ";
8092 TrapStack[1].first->AddTrapName(Desc, TrapStack[1].second);
8093 Desc << " and ";
8094 TrapStack[2].first->AddTrapName(Desc, TrapStack[2].second);
8096 } else {
8097 Desc << "lots of traps";
8099 return Desc;
8103 int character::RandomizeHurtBodyPart (feuLong BodyParts) const {
8104 int BodyPartIndex[MAX_BODYPARTS];
8105 int Index = 0;
8106 for (int c = 0; c < GetBodyParts(); ++c) {
8107 if (1 << c & BodyParts) {
8108 /*k8: ??? if(!GetBodyPart(c)) int esko = esko = 2; */
8109 if (!GetBodyPart(c)) continue;
8110 BodyPartIndex[Index++] = c;
8112 /*k8: ??? if(!Index) int esko = esko = 2;*/
8114 if (!Index) {
8115 fprintf(stderr, "FATAL: RandomizeHurtBodyPart -- Index==0\n");
8116 abort();
8118 return BodyPartIndex[RAND_N(Index)];
8122 truth character::BodyPartIsStuck (int I) const {
8123 for (const trapdata *T = TrapData; T; T = T->Next) if (1 << I & T->BodyParts) return true;
8124 return false;
8128 void character::PrintAttribute (cchar *Desc, int I, int PanelPosX, int PanelPosY) const {
8129 int Attribute = GetAttribute(I);
8130 int NoBonusAttribute = GetAttribute(I, false);
8131 col16 C = game::GetAttributeColor(I);
8132 festring String = Desc;
8133 String.Resize(5);
8134 String << Attribute;
8135 String.Resize(8);
8136 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY * 10), C, "%s", String.CStr());
8137 if (Attribute != NoBonusAttribute) {
8138 int Where = PanelPosX + ((String.GetSize() + 1) << 3);
8139 FONT->Printf(DOUBLE_BUFFER, v2(Where, PanelPosY * 10), LIGHT_GRAY, "%d", NoBonusAttribute);
8144 truth character::AllowUnconsciousness () const {
8145 return (DataBase->AllowUnconsciousness && TorsoIsAlive());
8149 truth character::CanPanic () const {
8150 return (!Action || !Action->IsUnconsciousness() || !StateIsActivated(FEARLESS));
8154 int character::GetRandomBodyPart (feuLong Possible) const {
8155 int OKBodyPart[MAX_BODYPARTS];
8156 int OKBodyParts = 0;
8157 for (int c = 0; c < BodyParts; ++c) if (1 << c & Possible && GetBodyPart(c)) OKBodyPart[OKBodyParts++] = c;
8158 return OKBodyParts ? OKBodyPart[RAND_N(OKBodyParts)] : NONE_INDEX;
8162 void character::EditNP (sLong What) {
8163 int OldState = GetHungerState();
8164 NP += What;
8165 int NewState = GetHungerState();
8166 if (OldState > VERY_HUNGRY && NewState == VERY_HUNGRY) DeActivateVoluntaryAction(CONST_S("You are getting really hungry."));
8167 if (OldState > STARVING && NewState == STARVING) DeActivateVoluntaryAction(CONST_S("You are getting extremely hungry."));
8171 truth character::IsSwimming () const {
8172 return !IsFlying() && GetSquareUnder() && GetSquareUnder()->GetSquareWalkability() & SWIM;
8176 void character::AddBlackUnicornConsumeEndMessage () const {
8177 if (IsPlayer()) ADD_MESSAGE("You feel dirty and loathsome.");
8181 void character::AddGrayUnicornConsumeEndMessage () const {
8182 if (IsPlayer()) ADD_MESSAGE("You feel neutralized.");
8186 void character::AddWhiteUnicornConsumeEndMessage () const {
8187 if (IsPlayer()) ADD_MESSAGE("You feel purified.");
8191 void character::AddOmmelBoneConsumeEndMessage () const {
8192 if (IsPlayer()) ADD_MESSAGE("You feel the power of all your canine ancestors combining in your body.");
8193 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s looks extremely ferocious. You shudder.", CHAR_NAME(DEFINITE));
8197 void character::AddLiquidHorrorConsumeEndMessage () const {
8198 if (IsPlayer()) ADD_MESSAGE("Untold horrors flash before your eyes. The melancholy of the world is on your shoulders!");
8199 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks as if the melancholy of the world is on %s shoulders!.", CHAR_NAME(DEFINITE), GetPossessivePronoun().CStr());
8203 void character::AddAlienFleshConsumeEndMessage() const
8205 if (IsPlayer()) ADD_MESSAGE("You feel somehow sick by eating such acidic corpse...");
8206 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks like he eat something bad.", CHAR_NAME(DEFINITE));
8210 int character::GetBodyPartSparkleFlags (int) const {
8211 return
8212 ((GetNaturalSparkleFlags() & SKIN_COLOR ? SPARKLING_A : 0) |
8213 (GetNaturalSparkleFlags() & TORSO_MAIN_COLOR ? SPARKLING_B : 0) |
8214 (GetNaturalSparkleFlags() & TORSO_SPECIAL_COLOR ? SPARKLING_D : 0));
8218 truth character::IsAnimated () const {
8219 return combinebodypartpredicates()(this, &bodypart::IsAnimated, 1);
8223 double character::GetNaturalExperience (int Identifier) const {
8224 return DataBase->NaturalExperience[Identifier];
8228 truth character::HasBodyPart (sorter Sorter) const {
8229 if (Sorter == 0) return true;
8230 return combinebodypartpredicateswithparam<ccharacter*>()(this, Sorter, this, 1);
8234 truth character::PossessesItem (sorter Sorter) const {
8235 if (Sorter == 0) return true;
8236 return
8237 (GetStack()->SortedItems(this, Sorter) ||
8238 combinebodypartpredicateswithparam<ccharacter*>()(this, Sorter, this, 1) ||
8239 combineequipmentpredicateswithparam<ccharacter*>()(this, Sorter, this, 1));
8243 truth character::MoreThanOnePossessesItem (sorter Sorter) const {
8244 if (Sorter) {
8245 int count = 0;
8247 for (int c = 0; c < BodyParts; ++c) {
8248 bodypart *BodyPart = GetBodyPart(c);
8250 if (BodyPart && (Sorter == 0 || (BodyPart->*Sorter)(this))) {
8251 if (++count > 1) return true;
8254 for (int c = 0; c < GetEquipments(); ++c) {
8255 item *Equipment = GetEquipment(c);
8257 if (Equipment && (Sorter == 0 || (Equipment->*Sorter)(this))) {
8258 if (++count > 1) return true;
8261 for (int c = 0; c < GetStack()->GetItems(); ++c) {
8262 item *Stk = GetStack()->GetItem(c);
8264 if (Stk && (Sorter == 0 || (Stk->*Sorter)(this))) {
8265 if (++count > 1) return true;
8268 return false;
8270 return false;
8274 item *character::FirstPossessesItem (sorter Sorter) const {
8275 if (Sorter) {
8276 for (int c = 0; c < BodyParts; ++c) {
8277 bodypart *BodyPart = GetBodyPart(c);
8279 if (BodyPart && (Sorter == 0 || (BodyPart->*Sorter)(this))) return BodyPart;
8281 for (int c = 0; c < GetEquipments(); ++c) {
8282 item *Equipment = GetEquipment(c);
8284 if (Equipment && (Sorter == 0 || (Equipment->*Sorter)(this))) return Equipment;
8286 for (int c = 0; c < GetStack()->GetItems(); ++c) {
8287 item *Stk = GetStack()->GetItem(c);
8289 if (Stk && (Sorter == 0 || (Stk->*Sorter)(this))) return Stk;
8292 return 0;
8296 /* 0 <= I <= 1 */
8297 cchar *character::GetRunDescriptionLine (int I) const {
8298 if (!GetRunDescriptionLineOne().IsEmpty()) return !I ? GetRunDescriptionLineOne().CStr() : GetRunDescriptionLineTwo().CStr();
8299 if (IsFlying()) return !I ? "Flying" : "very fast";
8300 if (IsSwimming()) return !I ? "Swimming" : "very fast";
8301 return !I ? "Running" : "";
8305 void character::VomitAtRandomDirection (int Amount) {
8306 if (game::IsInWilderness()) return;
8307 /* Lacks support of multitile monsters */
8308 v2 Possible[9];
8309 int Index = 0;
8310 for (int d = 0; d < 9; ++d) {
8311 lsquare *Square = GetLSquareUnder()->GetNeighbourLSquare(d);
8312 if (Square && !Square->VomitingIsDangerous(this)) Possible[Index++] = Square->GetPos();
8314 if (Index) Vomit(Possible[RAND_N(Index)], Amount);
8315 else Vomit(GetPos(), Amount);
8319 void character::RemoveLifeSavers () {
8320 for (int c = 0; c < GetEquipments(); ++c) {
8321 item *Equipment = GetEquipment(c);
8322 if (Equipment && Equipment->IsInCorrectSlot(c) && Equipment->GetGearStates() & LIFE_SAVED) {
8323 Equipment->SendToHell();
8324 Equipment->RemoveFromSlot();
8330 ccharacter *character::FindCarrier () const {
8331 return this; //check
8335 void character::PrintBeginHiccupsMessage () const {
8336 if (IsPlayer()) ADD_MESSAGE("Your diaphragm is spasming vehemently.");
8340 void character::PrintEndHiccupsMessage () const {
8341 if (IsPlayer()) ADD_MESSAGE("You feel your annoying hiccoughs have finally subsided.");
8345 void character::HiccupsHandler () {
8347 if (!(RAND() % 2000)) {
8348 if (IsPlayer()) ADD_MESSAGE("");
8349 else if (CanBeSeenByPlayer()) ADD_MESSAGE("");
8350 else if ((PLAYER->GetPos()-GetPos()).GetLengthSquare() <= 400) ADD_MESSAGE("");
8351 game::CallForAttention(GetPos(), 400);
8357 void character::VampirismHandler () {
8358 //EditExperience(ARM_STRENGTH, -25, 1 << 1);
8359 //EditExperience(LEG_STRENGTH, -25, 1 << 1);
8360 //EditExperience(DEXTERITY, -25, 1 << 1);
8361 //EditExperience(AGILITY, -25, 1 << 1);
8362 //EditExperience(ENDURANCE, -25, 1 << 1);
8363 EditExperience(CHARISMA, -25, 1 << 1);
8364 EditExperience(WISDOM, -25, 1 << 1);
8365 EditExperience(INTELLIGENCE, -25, 1 << 1);
8366 CheckDeath(CONST_S("killed by vampirism"));
8370 void character::HiccupsSituationDangerModifier (double &Danger) const {
8371 Danger *= 1.25;
8375 void character::VampirismSituationDangerModifier (double &Danger) const {
8376 character *Vampire = vampire::Spawn();
8377 double DangerToVampire = GetRelativeDanger(Vampire);
8378 Danger *= pow(DangerToVampire, 0.1);
8379 delete Vampire;
8383 bool character::IsConscious () const {
8384 return !Action || !Action->IsUnconsciousness();
8388 wsquare *character::GetNearWSquare (v2 Pos) const {
8389 return static_cast<wsquare *>(GetSquareUnder()->GetArea()->GetSquare(Pos));
8393 wsquare *character::GetNearWSquare (int x, int y) const {
8394 return static_cast<wsquare *>(GetSquareUnder()->GetArea()->GetSquare(x, y));
8398 void character::ForcePutNear (v2 Pos) {
8399 /* GUM SOLUTION!!! */
8400 v2 NewPos = game::GetCurrentLevel()->GetNearestFreeSquare(PLAYER, Pos, false);
8401 if (NewPos == ERROR_V2) {
8402 do {
8403 NewPos = game::GetCurrentLevel()->GetRandomSquare(this, 0, nullptr, NewPos);
8404 if (NewPos == ERROR_V2) ABORT("No country for old fart...");
8405 } while (NewPos == Pos);
8407 PutTo(NewPos);
8411 void character::ReceiveMustardGas (int BodyPart, sLong Volume) {
8412 if (Volume) GetBodyPart(BodyPart)->AddFluid(liquid::Spawn(MUSTARD_GAS_LIQUID, Volume), CONST_S("skin"), 0, true);
8416 void character::ReceiveMustardGasLiquid (int BodyPartIndex, sLong Modifier) {
8417 bodypart *BodyPart = GetBodyPart(BodyPartIndex);
8418 if (BodyPart->GetMainMaterial()->GetInteractionFlags() & IS_AFFECTED_BY_MUSTARD_GAS) {
8419 sLong Tries = Modifier;
8420 Modifier -= Tries; //opt%?
8421 int Damage = 0;
8422 for (sLong c = 0; c < Tries; ++c) if (!(RAND() % 100)) ++Damage;
8423 if (Modifier && !(RAND() % 1000 / Modifier)) ++Damage;
8424 if (Damage) {
8425 feuLong Minute = game::GetTotalMinutes();
8426 if (GetLastAcidMsgMin() != Minute && (CanBeSeenByPlayer() || IsPlayer())) {
8427 SetLastAcidMsgMin(Minute);
8428 if (IsPlayer()) ADD_MESSAGE("Mustard gas dissolves the skin of your %s.", BodyPart->GetBodyPartName().CStr());
8429 else ADD_MESSAGE("Mustard gas dissolves %s.", CHAR_NAME(DEFINITE));
8431 ReceiveBodyPartDamage(0, Damage, MUSTARD_GAS_DAMAGE, BodyPartIndex, YOURSELF, false, false, false);
8432 CheckDeath(CONST_S("killed by a fatal exposure to mustard gas"));
8438 truth character::IsBadPath (v2 Pos) const {
8439 if (!IsGoingSomeWhere()) return false;
8440 v2 TPos = !Route.empty() ? Route.back() : GoingTo;
8441 return ((TPos - Pos).GetManhattanLength() > (TPos - GetPos()).GetManhattanLength());
8445 double &character::GetExpModifierRef (expid E) {
8446 return ExpModifierMap.insert(std::make_pair(E, 1.)).first->second;
8450 /* Should probably do more. Now only makes Player forget gods */
8451 truth character::ForgetRandomThing () {
8452 if (IsPlayer()) {
8453 /* hopefully this code isn't some where else */
8454 std::vector<god *> Known;
8455 for (int c = 1; c <= GODS; ++c) if (game::GetGod(c)->IsKnown()) Known.push_back(game::GetGod(c));
8456 if (Known.empty()) return false;
8457 int RandomGod = RAND_N(Known.size());
8458 Known.at(RAND_N(Known.size()))->SetIsKnown(false);
8459 ADD_MESSAGE("You forget how to pray to %s.", Known.at(RandomGod)->GetName());
8460 return true;
8462 return false;
8466 int character::CheckForBlock (character *Enemy, item *Weapon, double ToHitValue, int Damage, int Success, int Type) {
8467 return Damage;
8471 void character::ApplyAllGodsKnownBonus () {
8472 stack *AddPlace = GetStackUnder();
8473 if (game::IsInWilderness()) AddPlace = GetStack(); else AddPlace = GetStackUnder();
8474 pantheonbook *NewBook = pantheonbook::Spawn();
8475 AddPlace->AddItem(NewBook);
8476 ADD_MESSAGE("\"MORTAL! BEHOLD THE HOLY SAGA\"");
8477 ADD_MESSAGE("%s materializes near your feet.", NewBook->CHAR_NAME(INDEFINITE));
8481 truth character::ReceiveSirenSong (character *Siren) {
8482 if (Siren->GetTeam() == GetTeam()) return false;
8483 if (!RAND_N(4)) {
8484 if (IsPlayer()) ADD_MESSAGE("The beautiful melody of %s makes you feel sleepy.", Siren->CHAR_NAME(DEFINITE));
8485 else if (CanBeSeenByPlayer()) ADD_MESSAGE("The beautiful melody of %s makes %s look sleepy.", Siren->CHAR_NAME(DEFINITE), CHAR_NAME(DEFINITE)); /*k8*/
8486 Stamina -= (1+RAND_N(4))*10000;
8487 return true;
8489 if (!IsPlayer() && IsCharmable() && !RAND_N(5)) {
8490 ChangeTeam(Siren->GetTeam());
8491 ADD_MESSAGE("%s seems to be totally brainwashed by %s melodies.", CHAR_NAME(DEFINITE), Siren->CHAR_NAME(DEFINITE));
8492 return true;
8494 if (!RAND_N(4)) {
8495 item *What = GiveMostExpensiveItem(Siren);
8496 if (What) {
8497 if (IsPlayer()) {
8498 ADD_MESSAGE("%s music persuades you to give %s to %s as a present.", Siren->CHAR_NAME(DEFINITE), What->CHAR_NAME(DEFINITE), Siren->CHAR_OBJECT_PRONOUN);
8499 } else {
8500 ADD_MESSAGE("%s is persuated to give %s to %s because of %s beautiful singing.", CHAR_NAME(DEFINITE), What->CHAR_NAME(INDEFINITE), Siren->CHAR_NAME(DEFINITE), Siren->CHAR_OBJECT_PRONOUN);
8502 } else {
8503 if (IsPlayer()) ADD_MESSAGE("You would like to give something to %s.", Siren->CHAR_NAME(DEFINITE));
8505 return true;
8507 return false;
8511 item *character::findFirstEquippedItem (cfestring &aclassname, int aconfig) const {
8512 for (int f = 0; f < GetEquipments(); ++f) {
8513 item *it = GetEquipment(f);
8514 if (!it) continue;
8515 if (aclassname.CompareIgnoreCase(it->GetClassID()) == 0) {
8516 if (aconfig == -1 || it->GetConfig() == aconfig) return it;
8519 for (uInt c = 0; c < it->GetDataBase()->Alias.Size; ++c) {
8520 if (s.CompareIgnoreCase(it->GetDataBase()->Alias[c]) == 0) return it;
8524 return nullptr;
8528 item *character::findFirstInventoryItem (cfestring &aclassname, int aconfig) const {
8529 itemvector items;
8530 GetStack()->FillItemVector(items);
8531 for (int f = 0; f < (int)items.size(); ++f) {
8532 item *it = items[f];
8533 if (!it) continue;
8534 if (aclassname.CompareIgnoreCase(it->GetClassID()) == 0) {
8535 if (aconfig == -1 || it->GetConfig() == aconfig) return it;
8538 return nullptr;
8542 item *character::findFirstItem (cfestring &aclassname, int aconfig) const {
8543 item *it = findFirstInventoryItem(aclassname, aconfig);
8544 if (!it) it = findFirstEquippedItem(aclassname, aconfig);
8545 return it;
8549 // return 0, if no item found
8550 item *character::FindMostExpensiveItem () const {
8551 int MaxPrice = -1;
8552 item *MostExpensive = 0;
8553 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) {
8554 if ((*i)->GetPrice() > MaxPrice) {
8555 MaxPrice = (*i)->GetPrice();
8556 MostExpensive = (*i);
8559 for (int c = 0; c < GetEquipments(); ++c) {
8560 item *Equipment = GetEquipment(c);
8561 if (Equipment && Equipment->GetPrice() > MaxPrice) {
8562 MaxPrice = Equipment->GetPrice();
8563 MostExpensive = Equipment;
8566 return MostExpensive;
8570 // returns 0 if no items available
8571 item *character::GiveMostExpensiveItem(character *ToWhom) {
8572 item *ToGive = FindMostExpensiveItem();
8573 if (!ToGive) return 0;
8574 truth Equipped = PLAYER->Equips(ToGive);
8575 ToGive->RemoveFromSlot();
8576 if (Equipped) game::AskForEscPress(CONST_S("Equipment lost!"));
8577 ToWhom->ReceiveItemAsPresent(ToGive);
8578 EditAP(-1000);
8579 return ToGive;
8583 void character::ReceiveItemAsPresent (item *Present) {
8584 if (TestForPickup(Present)) GetStack()->AddItem(Present); else GetStackUnder()->AddItem(Present);
8588 /* returns 0 if no enemies in sight */
8589 character *character::GetNearestEnemy () const {
8590 character *NearestEnemy = 0;
8591 sLong NearestEnemyDistance = 0x7FFFFFFF;
8592 v2 Pos = GetPos();
8593 for (int c = 0; c < game::GetTeams(); ++c) {
8594 if (GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE) {
8595 for (std::list<character*>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i) {
8596 if ((*i)->IsEnabled()) {
8597 sLong ThisDistance = Max<sLong>(abs((*i)->GetPos().X - Pos.X), abs((*i)->GetPos().Y - Pos.Y));
8598 if ((ThisDistance < NearestEnemyDistance || (ThisDistance == NearestEnemyDistance && !(RAND() % 3))) && (*i)->CanBeSeenBy(this)) {
8599 NearestEnemy = *i;
8600 NearestEnemyDistance = ThisDistance;
8606 return NearestEnemy;
8610 truth character::MindWormCanPenetrateSkull (mindworm *) const {
8611 return false;
8615 truth character::CanTameWithDulcis (const character *Tamer) const {
8616 int TamingDifficulty = GetTamingDifficulty();
8617 if (TamingDifficulty == NO_TAMING) return false;
8618 if (GetAttachedGod() == DULCIS) return true;
8619 int Modifier = Tamer->GetAttribute(WISDOM) + Tamer->GetAttribute(CHARISMA);
8620 if (Tamer->IsPlayer()) Modifier += game::GetGod(DULCIS)->GetRelation() / 20;
8621 else if (Tamer->GetAttachedGod() == DULCIS) Modifier += 50;
8622 if (TamingDifficulty == 0) {
8623 if (!IgnoreDanger()) TamingDifficulty = int(10 * GetRelativeDanger(Tamer));
8624 else TamingDifficulty = 10 * GetHPRequirementForGeneration()/Max(Tamer->GetHP(), 1);
8626 return Modifier >= TamingDifficulty * 3;
8630 truth character::CanTameWithLyre (const character *Tamer) const {
8631 int TamingDifficulty = GetTamingDifficulty();
8632 if (TamingDifficulty == NO_TAMING) return false;
8633 if (TamingDifficulty == 0) {
8634 if (!IgnoreDanger()) TamingDifficulty = int(10 * GetRelativeDanger(Tamer));
8635 else TamingDifficulty = 10*GetHPRequirementForGeneration()/Max(Tamer->GetHP(), 1);
8637 return Tamer->GetAttribute(CHARISMA) >= TamingDifficulty;
8641 truth character::CanTameWithScroll (const character *Tamer) const {
8642 int TamingDifficulty = GetTamingDifficulty();
8643 return
8644 (TamingDifficulty != NO_TAMING &&
8645 (TamingDifficulty == 0 ||
8646 Tamer->GetAttribute(INTELLIGENCE) * 4 + Tamer->GetAttribute(CHARISMA) >= TamingDifficulty * 5));
8650 truth character::CanTameWithResurrection (const character *Tamer) const {
8651 int TamingDifficulty = GetTamingDifficulty();
8652 if (TamingDifficulty == NO_TAMING) return false;
8653 if (TamingDifficulty == 0) return true;
8654 return (Tamer->GetAttribute(CHARISMA) >= TamingDifficulty/2);
8658 truth character::CheckSadism () {
8659 if (!IsSadist() || !HasSadistAttackMode() || !IsSmall()) return false; // gum
8660 if (!RAND_N(10)) {
8661 for (int d = 0; d < MDIR_STAND; ++d) {
8662 square *Square = GetNeighbourSquare(d);
8663 if (Square) {
8664 character *Char = Square->GetCharacter();
8665 if (Char && Char->IsMasochist() && GetRelation(Char) == FRIEND &&
8666 Char->GetHP() * 3 >= Char->GetMaxHP() * 2 && Hit(Char, Square->GetPos(), d, SADIST_HIT)) {
8667 TerminateGoingTo();
8668 return true;
8673 return false;
8677 truth character::CheckForBeverage () {
8678 if (StateIsActivated(PANIC) || !IsEnabled() || !UsesNutrition() || CheckIfSatiated()) return false;
8679 itemvector ItemVector;
8680 GetStack()->FillItemVector(ItemVector);
8681 for (uInt c = 0; c < ItemVector.size(); ++c) if (ItemVector[c]->IsBeverage(this) && TryToConsume(ItemVector[c])) return true;
8682 return false;
8686 void character::Haste () {
8687 doforbodyparts()(this, &bodypart::Haste);
8688 doforequipments()(this, &item::Haste);
8689 BeginTemporaryState(HASTE, 500 + RAND() % 1000);
8693 void character::Slow () {
8694 doforbodyparts()(this, &bodypart::Slow);
8695 doforequipments()(this, &item::Slow);
8696 //BeginTemporaryState(HASTE, 500 + RAND() % 1000); // this seems to be a bug
8697 BeginTemporaryState(SLOW, 500 + RAND() % 1000);
8701 void character::SurgicallyDetachBodyPart () {
8702 ADD_MESSAGE("You haven't got any extra bodyparts.");
8706 truth character::CanHear() const
8708 return DataBase->CanHear && HasHead();
8712 truth character::IsAllowedInDungeon (int dunIndex) {
8713 const fearray<int> &dlist = GetAllowedDungeons();
8715 for (uInt f = 0; f < dlist.Size; ++f) {
8716 if (dlist[f] == ALL_DUNGEONS || dlist[f] == dunIndex) {
8717 //fprintf(stderr, "OK!\n");
8718 return true;
8721 //fprintf(stderr, "NO!\n");
8722 return false;
8726 truth character::IsESPBlockedByEquipment () const {
8727 for (int c = 0; c < GetEquipments(); ++c) {
8728 item *Item = GetEquipment(c);
8729 if (Item && Item->IsHelmet(this) &&
8730 ((Item->GetMainMaterial() && Item->GetMainMaterial()->BlockESP()) ||
8731 (Item->GetSecondaryMaterial() && Item->GetSecondaryMaterial()->BlockESP()))) return true;
8733 return false;
8737 truth character::TemporaryStateIsActivated (sLong What) const {
8738 if ((What&PANIC) && StateIsActivated(FEARLESS)) What &= ~PANIC;
8739 if ((What&ESP) && (TemporaryState&ESP) && IsESPBlockedByEquipment()) What &= ~ESP;
8740 return ((TemporaryState&What) != 0);
8744 truth character::StateIsActivated (sLong What) const {
8745 if ((What&PANIC) && ((TemporaryState|EquipmentState)&FEARLESS)) What &= ~PANIC;
8746 if ((What&ESP) && ((TemporaryState|EquipmentState)&ESP) && IsESPBlockedByEquipment()) What &= ~ESP;
8747 return (TemporaryState&What) || (EquipmentState&What);
8751 void character::PrintBeginFearlessMessage () const {
8752 if (!StateIsActivated(FEARLESS)) {
8753 if (IsPlayer()) ADD_MESSAGE("You feel very comfortable.");
8754 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s seems very comfortable.", CHAR_NAME(DEFINITE));
8758 void character::PrintEndFearlessMessage () const {
8759 if (!StateIsActivated(FEARLESS)) {
8760 if (IsPlayer()) ADD_MESSAGE("Everything looks more dangerous now.");
8761 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s seems to have lost his confidence.", CHAR_NAME(DEFINITE));
8765 void character::BeginFearless () {
8766 DeActivateTemporaryState(PANIC);
8769 void character::EndFearless () {
8770 CheckPanic(500);
8774 void character::PrintBeginEtherealityMessage () const {
8775 if (IsPlayer()) ADD_MESSAGE("You feel like many miscible droplets of ether.");
8776 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s melds into the surroundings.", CHAR_NAME(DEFINITE));
8779 void character::PrintEndEtherealityMessage () const {
8780 if (IsPlayer()) ADD_MESSAGE("You drop out of the firmament, feeling suddenly quite dense.");
8781 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s displaces the air with a puff.", CHAR_NAME(INDEFINITE));
8784 void character::BeginEthereality () {}
8786 void character::EndEthereality () {}
8789 void character::PrintBeginPolymorphLockMessage () const {
8790 if (IsPlayer()) ADD_MESSAGE("You feel incredibly stubborn about who you are.");
8793 void character::PrintEndPolymorphLockMessage () const {
8794 if (IsPlayer()) ADD_MESSAGE("You feel more open to new ideas.");
8797 void character::PolymorphLockHandler () {
8798 if (TemporaryStateIsActivated(POLYMORPHED)) {
8799 EditTemporaryStateCounter(POLYMORPHED, 1);
8800 if (GetTemporaryStateCounter(POLYMORPHED) < 1000) EditTemporaryStateCounter(POLYMORPHED, 1);
8804 void character::PrintBeginRegenerationMessage () const {
8805 if (IsPlayer()) ADD_MESSAGE("Your heart races.");
8808 void character::PrintEndRegenerationMessage () const {
8809 if (IsPlayer()) ADD_MESSAGE("Your rapid heartbeat calms down.");
8812 void character::PrintBeginDiseaseImmunityMessage () const {
8813 if (IsPlayer()) ADD_MESSAGE("You feel especially healthy.");
8816 void character::PrintEndDiseaseImmunityMessage () const {
8817 if (IsPlayer()) ADD_MESSAGE("You develop a sudden fear of germs.");
8820 void character::PrintBeginTeleportLockMessage () const {
8821 if (IsPlayer()) ADD_MESSAGE("You feel firmly planted in reality.");
8824 void character::PrintEndTeleportLockMessage () const {
8825 if (IsPlayer()) ADD_MESSAGE("Your mind soars far and wide.");
8828 void character::TeleportLockHandler () {
8829 if (StateIsActivated(TELEPORT_LOCK)) {
8830 EditTemporaryStateCounter(TELEPORT_LOCK, 1);
8831 if (GetTemporaryStateCounter(TELEPORT_LOCK) < 1000) EditTemporaryStateCounter(TELEPORT_LOCK, 1);
8836 void character::PrintBeginSwimmingMessage () const {
8837 if (IsPlayer()) ADD_MESSAGE("You fondly remember the sound of ocean waves.");
8838 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks wet.", CHAR_NAME(DEFINITE));
8841 void character::PrintEndSwimmingMessage () const {
8842 if (IsPlayer()) ADD_MESSAGE("You suddenly remember how you nearly drowned as a child.");
8843 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks less wet.", CHAR_NAME(INDEFINITE));
8846 void character::BeginSwimming () {}
8847 void character::EndSwimming () {}