equipment screen: autoselect first empty (or non-empty if there are no empty) slot...
[k8-i-v-a-n.git] / src / game / char.cpp
blob73d4f38d3feed21b9800bd202f1a0566cb3d57e6
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 const statedata StateData[STATES] =
69 "Polymorphed",
70 NO_FLAGS,
74 &character::EndPolymorph,
78 }, {
79 "Hasted",
80 RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL),
81 &character::PrintBeginHasteMessage,
82 &character::PrintEndHasteMessage,
88 }, {
89 "Slowed",
90 RANDOMIZABLE&~SRC_GOOD,
91 &character::PrintBeginSlowMessage,
92 &character::PrintEndSlowMessage,
98 }, {
99 "PolyControl",
100 RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL|SRC_GOOD),
101 &character::PrintBeginPolymorphControlMessage,
102 &character::PrintEndPolymorphControlMessage,
108 }, {
109 "LifeSaved",
110 SECRET,
111 &character::PrintBeginLifeSaveMessage,
112 &character::PrintEndLifeSaveMessage,
118 }, {
119 "Lycanthropy",
120 SECRET|SRC_FOUNTAIN|SRC_CONFUSE_READ|DUR_FLAGS,
121 &character::PrintBeginLycanthropyMessage,
122 &character::PrintEndLycanthropyMessage,
125 &character::LycanthropyHandler,
127 &character::LycanthropySituationDangerModifier
128 }, {
129 "Invisible",
130 RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL),
131 &character::PrintBeginInvisibilityMessage,
132 &character::PrintEndInvisibilityMessage,
133 &character::BeginInvisibility,
134 &character::EndInvisibility,
138 }, {
139 "Infravision",
140 RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL),
141 &character::PrintBeginInfraVisionMessage,
142 &character::PrintEndInfraVisionMessage,
143 &character::BeginInfraVision,
144 &character::EndInfraVision,
148 }, {
149 "ESP",
150 RANDOMIZABLE&~SRC_EVIL,
151 &character::PrintBeginESPMessage,
152 &character::PrintEndESPMessage,
153 &character::BeginESP,
154 &character::EndESP,
158 }, {
159 "Poisoned",
160 DUR_TEMPORARY,
161 &character::PrintBeginPoisonedMessage,
162 &character::PrintEndPoisonedMessage,
165 &character::PoisonedHandler,
166 &character::CanBePoisoned,
167 &character::PoisonedSituationDangerModifier
168 }, {
169 "Teleporting",
170 SECRET|(RANDOMIZABLE&~(SRC_MUSHROOM|SRC_GOOD)),
171 &character::PrintBeginTeleportMessage,
172 &character::PrintEndTeleportMessage,
175 &character::TeleportHandler,
178 }, {
179 "Polymorphing",
180 SECRET|(RANDOMIZABLE&~(SRC_MUSHROOM|SRC_GOOD)),
181 &character::PrintBeginPolymorphMessage,
182 &character::PrintEndPolymorphMessage,
185 &character::PolymorphHandler,
187 &character::PolymorphingSituationDangerModifier
188 }, {
189 "TeleControl",
190 RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL),
191 &character::PrintBeginTeleportControlMessage,
192 &character::PrintEndTeleportControlMessage,
198 }, {
199 "Panicked",
200 NO_FLAGS,
201 &character::PrintBeginPanicMessage,
202 &character::PrintEndPanicMessage,
203 &character::BeginPanic,
204 &character::EndPanic,
206 &character::CanPanic,
207 &character::PanicSituationDangerModifier
208 }, {
209 "Confused",
210 SECRET|(RANDOMIZABLE&~(DUR_PERMANENT|SRC_GOOD)),
211 &character::PrintBeginConfuseMessage,
212 &character::PrintEndConfuseMessage,
216 &character::CanBeConfused,
217 &character::ConfusedSituationDangerModifier
218 }, {
219 "Parasitized",
220 SECRET|(RANDOMIZABLE&~DUR_TEMPORARY),
221 &character::PrintBeginParasitizedMessage,
222 &character::PrintEndParasitizedMessage,
225 &character::ParasitizedHandler,
226 &character::CanBeParasitized,
227 &character::ParasitizedSituationDangerModifier
228 }, {
229 "Searching",
230 NO_FLAGS,
231 &character::PrintBeginSearchingMessage,
232 &character::PrintEndSearchingMessage,
235 &character::SearchingHandler,
238 }, {
239 "GasImmunity",
240 SECRET|(RANDOMIZABLE&~(SRC_GOOD|SRC_EVIL)),
241 &character::PrintBeginGasImmunityMessage,
242 &character::PrintEndGasImmunityMessage,
248 }, {
249 "Levitating",
250 RANDOMIZABLE&~SRC_EVIL,
251 &character::PrintBeginLevitationMessage,
252 &character::PrintEndLevitationMessage,
254 &character::EndLevitation,
258 }, {
259 "Leprosy",
260 SECRET|(RANDOMIZABLE&~DUR_TEMPORARY),
261 &character::PrintBeginLeprosyMessage,
262 &character::PrintEndLeprosyMessage,
263 &character::BeginLeprosy,
264 &character::EndLeprosy,
265 &character::LeprosyHandler,
267 &character::LeprosySituationDangerModifier
268 }, {
269 "Hiccups",
270 SRC_FOUNTAIN|SRC_CONFUSE_READ|DUR_FLAGS,
271 &character::PrintBeginHiccupsMessage,
272 &character::PrintEndHiccupsMessage,
275 &character::HiccupsHandler,
277 &character::HiccupsSituationDangerModifier
278 }, {
279 "Vampirism",
280 DUR_FLAGS, //perhaps no fountain, no secret and no confuse read either: SECRET|SRC_FOUNTAIN|SRC_CONFUSE_READ|
281 &character::PrintBeginVampirismMessage,
282 &character::PrintEndVampirismMessage,
285 &character::VampirismHandler,
287 &character::VampirismSituationDangerModifier
288 }, {
289 "Detecting",
290 SECRET|(RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL)),
291 &character::PrintBeginDetectMessage,
292 &character::PrintEndDetectMessage,
295 &character::DetectHandler,
298 }, {
299 "Ethereal",
300 NO_FLAGS,
301 &character::PrintBeginEtherealityMessage,
302 &character::PrintEndEtherealityMessage,
303 &character::BeginEthereality, &character::EndEthereality,
307 }, {
308 "Fearless",
309 RANDOMIZABLE&~SRC_EVIL,
310 &character::PrintBeginFearlessMessage,
311 &character::PrintEndFearlessMessage,
312 &character::BeginFearless,
313 &character::EndFearless,
321 characterprototype::characterprototype (const characterprototype *Base, characterspawner Spawner,
322 charactercloner Cloner, cchar *ClassID)
323 : Base(Base),
324 Spawner(Spawner),
325 Cloner(Cloner),
326 ClassID(ClassID)
328 Index = protocontainer<character>::Add(this);
332 void character::CreateInitialEquipment (int SpecialFlags) { AddToInventory(DataBase->Inventory, SpecialFlags); }
333 void character::EditAP (sLong What) { AP = Limit<sLong>(AP+What, -12000, 1200); }
334 int character::GetRandomStepperBodyPart () const { return TORSO_INDEX; }
335 void character::GainIntrinsic (sLong What) { BeginTemporaryState(What, PERMANENT); }
336 truth character::IsUsingArms () const { return GetAttackStyle() & USE_ARMS; }
337 truth character::IsUsingLegs () const { return GetAttackStyle() & USE_LEGS; }
338 truth character::IsUsingHead () const { return GetAttackStyle() & USE_HEAD; }
339 void character::CalculateAllowedWeaponSkillCategories () { AllowedWeaponSkillCategories = MARTIAL_SKILL_CATEGORIES; }
340 festring character::GetBeVerb () const { return IsPlayer() ? CONST_S("are") : CONST_S("is"); }
341 void character::SetEndurance (int What) { BaseExperience[ENDURANCE] = What * EXP_MULTIPLIER; }
342 void character::SetPerception (int What) { BaseExperience[PERCEPTION] = What * EXP_MULTIPLIER; }
343 void character::SetIntelligence (int What) { BaseExperience[INTELLIGENCE] = What * EXP_MULTIPLIER; }
344 void character::SetWisdom (int What) { BaseExperience[WISDOM] = What * EXP_MULTIPLIER; }
345 void character::SetWillPower (int What) { BaseExperience[WILL_POWER] = What * EXP_MULTIPLIER; }
346 void character::SetCharisma (int What) { BaseExperience[CHARISMA] = What * EXP_MULTIPLIER; }
347 void character::SetMana (int What) { BaseExperience[MANA] = What * EXP_MULTIPLIER; }
348 truth character::IsOnGround () const { return MotherEntity && MotherEntity->IsOnGround(); }
349 truth character::LeftOversAreUnique () const { return GetArticleMode() || AssignedName.GetSize(); }
350 truth character::HomeDataIsValid () const { return (HomeData && HomeData->Level == GetLSquareUnder()->GetLevelIndex() && HomeData->Dungeon == GetLSquareUnder()->GetDungeonIndex()); }
351 void character::SetHomePos (v2 Pos) { HomeData->Pos = Pos; }
352 cchar *character::FirstPersonUnarmedHitVerb () const { return "hit"; }
353 cchar *character::FirstPersonCriticalUnarmedHitVerb () const { return "critically hit"; }
354 cchar *character::ThirdPersonUnarmedHitVerb () const { return "hits"; }
355 cchar *character::ThirdPersonCriticalUnarmedHitVerb () const { return "critically hits"; }
356 cchar *character::FirstPersonKickVerb () const { return "kick"; }
357 cchar *character::FirstPersonCriticalKickVerb () const { return "critically kick"; }
358 cchar *character::ThirdPersonKickVerb () const { return "kicks"; }
359 cchar *character::ThirdPersonCriticalKickVerb () const { return "critically kicks"; }
360 cchar *character::FirstPersonBiteVerb () const { return "bite"; }
361 cchar *character::FirstPersonCriticalBiteVerb () const { return "critically bite"; }
362 cchar *character::ThirdPersonBiteVerb () const { return "bites"; }
363 cchar *character::ThirdPersonCriticalBiteVerb () const { return "critically bites"; }
364 cchar *character::UnarmedHitNoun () const { return "attack"; }
365 cchar *character::KickNoun () const { return "kick"; }
366 cchar *character::BiteNoun () const { return "attack"; }
367 cchar *character::GetEquipmentName (int) const { return ""; }
368 const std::list<feuLong> &character::GetOriginalBodyPartID (int I) const { return OriginalBodyPartID[I]; }
369 square *character::GetNeighbourSquare (int I) const { return GetSquareUnder()->GetNeighbourSquare(I); }
370 lsquare *character::GetNeighbourLSquare (int I) const { return static_cast<lsquare *>(GetSquareUnder())->GetNeighbourLSquare(I); }
371 wsquare *character::GetNeighbourWSquare (int I) const { return static_cast<wsquare *>(GetSquareUnder())->GetNeighbourWSquare(I); }
372 god *character::GetMasterGod () const { return game::GetGod(GetConfig()); }
373 col16 character::GetBodyPartColorA (int, truth) const { return GetSkinColor(); }
374 col16 character::GetBodyPartColorB (int, truth) const { return GetTorsoMainColor(); }
375 col16 character::GetBodyPartColorC (int, truth) const { return GetBeltColor(); } // sorry...
376 col16 character::GetBodyPartColorD (int, truth) const { return GetTorsoSpecialColor(); }
377 int character::GetRandomApplyBodyPart () const { return TORSO_INDEX; }
378 truth character::IsPet () const { return GetTeam()->GetID() == PLAYER_TEAM; }
379 character* character::GetLeader () const { return GetTeam()->GetLeader(); }
380 festring character::GetZombieDescription () const { return " of "+GetName(INDEFINITE); }
381 truth character::BodyPartCanBeSevered (int I) const { return I; }
382 truth character::HasBeenSeen () const { return DataBase->Flags & HAS_BEEN_SEEN; }
383 truth character::IsTemporary () const { return GetTorso()->GetLifeExpectancy(); }
384 cchar *character::GetNormalDeathMessage () const { return "killed @k"; }
387 truth character::IsHomeLevel (level *lvl) const {
388 if (!lvl) return false;
389 // check level homes
390 const fearray<festring> &hlist = DataBase->HomeLevel;
391 if (hlist.Size == 0) return false;
392 // has "*"?
393 for (uInt f = 0; f < hlist.Size; ++f) { if (hlist[f] == "*") return true; }
394 // taken from unique characters code
395 cfestring *tag = lvl->GetLevelScript()->GetTag();
396 if (!tag) return false; // not a possible home level
397 // check for home level
398 for (uInt f = 0; f < hlist.Size; ++f) { if (hlist[f] == *tag) return true; }
399 return false;
403 truth character::MustBeRemovedFromBone () const {
404 if (IsUnique() && !CanBeGenerated()) return true;
405 // check level homes
406 const fearray<festring> &hlist = DataBase->HomeLevel;
407 if (hlist.Size == 0) return false;
408 // has "*"?
409 for (uInt f = 0; f < hlist.Size; ++f) { if (hlist[f] == "*") return true; }
410 // taken from unique characters code
411 if (!IsEnabled() || GetTeam()->GetID() != DataBase->NaturalTeam) return true;
412 return IsHomeLevel(GetLevel());
416 int character::GetMoveType () const {// return (!StateIsActivated(LEVITATION) ? DataBase->MoveType : DataBase->MoveType | FLY); }
417 return
418 ((!StateIsActivated(LEVITATION) ? DataBase->MoveType : DataBase->MoveType|FLY)|
419 (!StateIsActivated(ETHEREAL_MOVING) ? DataBase->MoveType : DataBase->MoveType|ETHEREAL));
423 int characterdatabase::*ExpPtr[ATTRIBUTES] = {
424 &characterdatabase::DefaultEndurance,
425 &characterdatabase::DefaultPerception,
426 &characterdatabase::DefaultIntelligence,
427 &characterdatabase::DefaultWisdom,
428 &characterdatabase::DefaultWillPower,
429 &characterdatabase::DefaultCharisma,
430 &characterdatabase::DefaultMana,
431 &characterdatabase::DefaultArmStrength,
432 &characterdatabase::DefaultLegStrength,
433 &characterdatabase::DefaultDexterity,
434 &characterdatabase::DefaultAgility
438 contentscript<item> characterdatabase::*EquipmentDataPtr[EQUIPMENT_DATAS] = {
439 &characterdatabase::Helmet,
440 &characterdatabase::Amulet,
441 &characterdatabase::Cloak,
442 &characterdatabase::BodyArmor,
443 &characterdatabase::Belt,
444 &characterdatabase::RightWielded,
445 &characterdatabase::LeftWielded,
446 &characterdatabase::RightRing,
447 &characterdatabase::LeftRing,
448 &characterdatabase::RightGauntlet,
449 &characterdatabase::LeftGauntlet,
450 &characterdatabase::RightBoot,
451 &characterdatabase::LeftBoot
455 character::character (ccharacter &Char) :
456 entity(Char), id(Char), NP(Char.NP), AP(Char.AP),
457 TemporaryState(Char.TemporaryState&~POLYMORPHED),
458 Team(Char.Team), GoingTo(ERROR_V2), Money(0),
459 AssignedName(Char.AssignedName), Action(0),
460 DataBase(Char.DataBase), MotherEntity(0),
461 PolymorphBackup(0), EquipmentState(0), SquareUnder(0),
462 AllowedWeaponSkillCategories(Char.AllowedWeaponSkillCategories),
463 BodyParts(Char.BodyParts),
464 RegenerationCounter(Char.RegenerationCounter),
465 SquaresUnder(Char.SquaresUnder), LastAcidMsgMin(0),
466 Stamina(Char.Stamina), MaxStamina(Char.MaxStamina),
467 BlocksSinceLastTurn(0), GenerationDanger(Char.GenerationDanger),
468 CommandFlags(Char.CommandFlags), WarnFlags(0),
469 ScienceTalks(Char.ScienceTalks), TrapData(0), CounterToMindWormHatch(0)
471 int c;
472 Flags &= ~C_PLAYER;
473 Flags |= C_INITIALIZING|C_IN_NO_MSG_MODE;
474 Stack = new stack(0, this, HIDDEN);
475 for (c = 0; c < STATES; ++c) TemporaryStateCounter[c] = Char.TemporaryStateCounter[c];
476 if (Team) Team->Add(this);
477 for (c = 0; c < BASE_ATTRIBUTES; ++c) BaseExperience[c] = Char.BaseExperience[c];
478 BodyPartSlot = new bodypartslot[BodyParts];
479 OriginalBodyPartID = new std::list<feuLong>[BodyParts];
480 CWeaponSkill = new cweaponskill[AllowedWeaponSkillCategories];
481 SquareUnder = new square *[SquaresUnder];
482 if (SquaresUnder == 1) *SquareUnder = 0; else memset(SquareUnder, 0, SquaresUnder*sizeof(square *));
483 for (c = 0; c < BodyParts; ++c) {
484 BodyPartSlot[c].SetMaster(this);
485 bodypart *CharBodyPart = Char.GetBodyPart(c);
486 OriginalBodyPartID[c] = Char.OriginalBodyPartID[c];
487 if (CharBodyPart) {
488 bodypart *BodyPart = static_cast<bodypart *>(CharBodyPart->Duplicate());
489 SetBodyPart(c, BodyPart);
490 BodyPart->CalculateEmitation();
493 for (c = 0; c < AllowedWeaponSkillCategories; ++c) CWeaponSkill[c] = Char.CWeaponSkill[c];
494 HomeData = Char.HomeData ? new homedata(*Char.HomeData) : 0;
495 ID = game::CreateNewCharacterID(this);
499 character::character () :
500 entity(HAS_BE), NP(50000), AP(0), TemporaryState(0), Team(0),
501 GoingTo(ERROR_V2), Money(0), Action(0), MotherEntity(0),
502 PolymorphBackup(0), EquipmentState(0), SquareUnder(0),
503 RegenerationCounter(0), HomeData(0), LastAcidMsgMin(0),
504 BlocksSinceLastTurn(0), GenerationDanger(DEFAULT_GENERATION_DANGER),
505 WarnFlags(0), ScienceTalks(0), TrapData(0), CounterToMindWormHatch(0)
507 Stack = new stack(0, this, HIDDEN);
511 character::~character () {
512 if (Action) delete Action;
513 if (Team) Team->Remove(this);
514 delete Stack;
515 for (int c = 0; c < BodyParts; ++c) delete GetBodyPart(c);
516 delete [] BodyPartSlot;
517 delete [] OriginalBodyPartID;
518 delete PolymorphBackup;
519 delete [] SquareUnder;
520 delete [] CWeaponSkill;
521 delete HomeData;
522 deleteList(TrapData);
523 game::RemoveCharacterID(ID);
527 void character::Hunger () {
528 switch (GetBurdenState()) {
529 case OVER_LOADED:
530 case STRESSED:
531 EditNP(-8);
532 EditExperience(LEG_STRENGTH, 150, 1 << 2);
533 EditExperience(AGILITY, -50, 1 << 2);
534 break;
535 case BURDENED:
536 EditNP(-2);
537 EditExperience(LEG_STRENGTH, 75, 1 << 1);
538 EditExperience(AGILITY, -25, 1 << 1);
539 break;
540 case UNBURDENED:
541 EditNP(-1);
542 break;
545 switch (GetHungerState()) {
546 case STARVING:
547 EditExperience(ARM_STRENGTH, -75, 1 << 3);
548 EditExperience(LEG_STRENGTH, -75, 1 << 3);
549 break;
550 case VERY_HUNGRY:
551 EditExperience(ARM_STRENGTH, -50, 1 << 2);
552 EditExperience(LEG_STRENGTH, -50, 1 << 2);
553 break;
554 case HUNGRY:
555 EditExperience(ARM_STRENGTH, -25, 1 << 1);
556 EditExperience(LEG_STRENGTH, -25, 1 << 1);
557 break;
558 case SATIATED:
559 EditExperience(AGILITY, -25, 1 << 1);
560 break;
561 case BLOATED:
562 EditExperience(AGILITY, -50, 1 << 2);
563 break;
564 case OVER_FED:
565 EditExperience(AGILITY, -75, 1 << 3);
566 break;
568 CheckStarvationDeath(CONST_S("starved to death"));
572 int character::TakeHit (character *Enemy, item *Weapon, bodypart *EnemyBodyPart, v2 HitPos, double Damage,
573 double ToHitValue, int Success, int Type, int GivenDir, truth Critical, truth ForceHit)
575 //FIXME: args
576 game::ClearEventData();
577 game::mActor = Enemy;
578 game::mResult = DID_NO_DAMAGE;
579 if (game::RunOnCharEvent(this, CONST_S("take_hit"))) { game::ClearEventData(); return game::mResult; }
580 game::ClearEventData();
581 int Dir = Type == BITE_ATTACK ? YOURSELF : GivenDir;
582 double DodgeValue = GetDodgeValue();
583 if (!Enemy->IsPlayer() && GetAttackWisdomLimit() != NO_LIMIT) Enemy->EditExperience(WISDOM, 75, 1 << 13);
584 if (!Enemy->CanBeSeenBy(this)) ToHitValue *= 2;
585 if (!CanBeSeenBy(Enemy)) DodgeValue *= 2;
586 if (Enemy->StateIsActivated(CONFUSED)) ToHitValue *= 0.75;
588 switch (Enemy->GetTirednessState()) {
589 case FAINTING:
590 ToHitValue *= 0.50;
591 case EXHAUSTED:
592 ToHitValue *= 0.75;
594 switch (GetTirednessState()) {
595 case FAINTING:
596 DodgeValue *= 0.50;
597 case EXHAUSTED:
598 DodgeValue *= 0.75;
601 if (!ForceHit) {
602 if (!IsRetreating()) SetGoingTo(Enemy->GetPos());
603 else SetGoingTo(GetPos()-((Enemy->GetPos()-GetPos())<<4));
604 if (!Enemy->IsRetreating()) Enemy->SetGoingTo(GetPos());
605 else Enemy->SetGoingTo(Enemy->GetPos()-((GetPos()-Enemy->GetPos())<<4));
608 /* Effectively, the average chance to hit is 100% / (DV/THV + 1). */
609 if (RAND() % int(100+ToHitValue/DodgeValue*(100+Success)) < 100 && !Critical && !ForceHit) {
610 Enemy->AddMissMessage(this);
611 EditExperience(AGILITY, 150, 1 << 7);
612 EditExperience(PERCEPTION, 75, 1 << 7);
613 if (Enemy->CanBeSeenByPlayer())
614 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy->GetName(DEFINITE)+CONST_S(" interrupts you."));
615 else
616 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
617 return HAS_DODGED;
620 int TrueDamage = int(Damage*(100+Success)/100)+(RAND()%3 ? 1 : 0);
621 if (Critical) {
622 TrueDamage += TrueDamage >> 1;
623 ++TrueDamage;
626 int BodyPart = ChooseBodyPartToReceiveHit(ToHitValue, DodgeValue);
627 if (Critical) {
628 switch (Type) {
629 case UNARMED_ATTACK:
630 Enemy->AddPrimitiveHitMessage(this, Enemy->FirstPersonCriticalUnarmedHitVerb(), Enemy->ThirdPersonCriticalUnarmedHitVerb(), BodyPart);
631 break;
632 case WEAPON_ATTACK:
633 Enemy->AddWeaponHitMessage(this, Weapon, BodyPart, true);
634 break;
635 case KICK_ATTACK:
636 Enemy->AddPrimitiveHitMessage(this, Enemy->FirstPersonCriticalKickVerb(), Enemy->ThirdPersonCriticalKickVerb(), BodyPart);
637 break;
638 case BITE_ATTACK:
639 Enemy->AddPrimitiveHitMessage(this, Enemy->FirstPersonCriticalBiteVerb(), Enemy->ThirdPersonCriticalBiteVerb(), BodyPart);
640 break;
642 } else {
643 switch (Type) {
644 case UNARMED_ATTACK:
645 Enemy->AddPrimitiveHitMessage(this, Enemy->FirstPersonUnarmedHitVerb(), Enemy->ThirdPersonUnarmedHitVerb(), BodyPart);
646 break;
647 case WEAPON_ATTACK:
648 Enemy->AddWeaponHitMessage(this, Weapon, BodyPart, false);
649 break;
650 case KICK_ATTACK:
651 Enemy->AddPrimitiveHitMessage(this, Enemy->FirstPersonKickVerb(), Enemy->ThirdPersonKickVerb(), BodyPart);
652 break;
653 case BITE_ATTACK:
654 Enemy->AddPrimitiveHitMessage(this, Enemy->FirstPersonBiteVerb(), Enemy->ThirdPersonBiteVerb(), BodyPart);
655 break;
659 if (!Critical && TrueDamage && Enemy->AttackIsBlockable(Type)) {
660 TrueDamage = CheckForBlock(Enemy, Weapon, ToHitValue, TrueDamage, Success, Type);
661 if (!TrueDamage || (Weapon && !Weapon->Exists())) {
662 if (Enemy->CanBeSeenByPlayer())
663 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy->GetName(DEFINITE)+CONST_S(" interrupts you."));
664 else
665 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
666 return HAS_BLOCKED;
670 int WeaponSkillHits = CalculateWeaponSkillHits(Enemy);
671 int DoneDamage = ReceiveBodyPartDamage(Enemy, TrueDamage, PHYSICAL_DAMAGE, BodyPart, Dir, false, Critical, true, Type == BITE_ATTACK && Enemy->BiteCapturesBodyPart());
672 truth Succeeded = (GetBodyPart(BodyPart) && HitEffect(Enemy, Weapon, HitPos, Type, BodyPart, Dir, !DoneDamage, Critical, DoneDamage)) || DoneDamage;
673 if (Succeeded) Enemy->WeaponSkillHit(Weapon, Type, WeaponSkillHits);
675 if (Weapon) {
676 if (Weapon->Exists() && DoneDamage < TrueDamage) Weapon->ReceiveDamage(Enemy, TrueDamage-DoneDamage, PHYSICAL_DAMAGE);
677 if (Weapon->Exists() && DoneDamage && SpillsBlood() && GetBodyPart(BodyPart) &&
678 (GetBodyPart(BodyPart)->IsAlive() || GetBodyPart(BodyPart)->GetMainMaterial()->IsLiquid()))
679 Weapon->SpillFluid(0, CreateBlood(15+RAND()%15));
682 if (Enemy->AttackIsBlockable(Type)) SpecialBodyDefenceEffect(Enemy, EnemyBodyPart, Type);
684 if (!Succeeded) {
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."));
690 return DID_NO_DAMAGE;
693 if (CheckDeath(GetNormalDeathMessage(), Enemy, Enemy->IsPlayer() ? FORCE_MSG : 0)) return HAS_DIED;
695 if (Enemy->CanBeSeenByPlayer())
696 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy->GetName(DEFINITE)+CONST_S(" interrupts you."));
697 else
698 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
700 return HAS_HIT;
704 struct svpriorityelement {
705 svpriorityelement (int BodyPart, int StrengthValue) : BodyPart(BodyPart), StrengthValue(StrengthValue) {}
706 bool operator < (const svpriorityelement &AnotherPair) const { return StrengthValue > AnotherPair.StrengthValue; }
707 int BodyPart;
708 int StrengthValue;
712 int character::ChooseBodyPartToReceiveHit (double ToHitValue, double DodgeValue) {
713 if (BodyParts == 1) return 0;
714 std::priority_queue<svpriorityelement> SVQueue;
715 for (int c = 0; c < BodyParts; ++c) {
716 bodypart *BodyPart = GetBodyPart(c);
717 if (BodyPart && (BodyPart->GetHP() != 1 || BodyPart->CanBeSevered(PHYSICAL_DAMAGE)))
718 SVQueue.push(svpriorityelement(c, ModifyBodyPartHitPreference(c, BodyPart->GetStrengthValue()+BodyPart->GetHP())));
720 while (SVQueue.size()) {
721 svpriorityelement E = SVQueue.top();
722 int ToHitPercentage = int(GLOBAL_WEAK_BODYPART_HIT_MODIFIER*ToHitValue*GetBodyPart(E.BodyPart)->GetBodyPartVolume()/(DodgeValue*GetBodyVolume()));
723 ToHitPercentage = ModifyBodyPartToHitChance(E.BodyPart, ToHitPercentage);
724 if (ToHitPercentage < 1) ToHitPercentage = 1;
725 else if (ToHitPercentage > 95) ToHitPercentage = 95;
726 if (ToHitPercentage > RAND()%100) return E.BodyPart;
727 SVQueue.pop();
729 return 0;
733 void character::Be () {
734 if (game::ForceJumpToPlayerBe()) {
735 if (!IsPlayer()) return;
736 game::SetForceJumpToPlayerBe(false);
737 } else {
738 truth ForceBe = HP != MaxHP || AllowSpoil();
739 for (int c = 0; c < BodyParts; ++c) {
740 bodypart *BodyPart = GetBodyPart(c);
741 if (BodyPart && (ForceBe || BodyPart->NeedsBe())) BodyPart->Be();
743 HandleStates();
744 if (!IsEnabled()) return;
745 if (GetTeam() == PLAYER->GetTeam()) {
746 for (int c = 0; c < AllowedWeaponSkillCategories; ++c) {
747 if (CWeaponSkill[c].Tick() && IsPlayer()) CWeaponSkill[c].AddLevelDownMessage(c);
749 SWeaponSkillTick();
751 if (IsPlayer()) {
752 if (GetHungerState() == STARVING && !(RAND()%50)) LoseConsciousness(250+RAND_N(250), true);
753 if (!Action || Action->AllowFoodConsumption()) Hunger();
755 if (Stamina != MaxStamina) RegenerateStamina();
756 if (HP != MaxHP) Regenerate();
757 if (Action && AP >= 1000) ActionAutoTermination();
758 if (Action && AP >= 1000) {
759 Action->Handle();
760 if (!IsEnabled()) return;
761 } else {
762 EditAP(GetStateAPGain(100));
765 if (AP >= 1000) {
766 SpecialTurnHandler();
767 BlocksSinceLastTurn = 0;
768 if (IsPlayer()) {
769 static int Timer = 0;
771 if (ivanconfig::GetAutoSaveInterval() && !GetAction() && ++Timer >= ivanconfig::GetAutoSaveInterval()) {
772 //fprintf(stderr, "autosaving..."); fflush(stderr);
773 game::Save(game::GetAutoSaveFileName());
774 //fprintf(stderr, "done\n"); fflush(stderr);
775 Timer = 0;
777 game::CalculateNextDanger();
778 if (!StateIsActivated(POLYMORPHED)) game::UpdatePlayerAttributeAverage();
779 if (!game::IsInWilderness()) Search(GetAttribute(PERCEPTION));
780 if (!Action) {
781 GetPlayerCommand();
782 } else {
783 if (Action->ShowEnvironment()) {
784 static int Counter = 0;
785 if (++Counter == 10) {
786 game::DrawEverything();
787 Counter = 0;
790 msgsystem::ThyMessagesAreNowOld();
791 if (Action->IsVoluntary() && READ_KEY()) Action->Terminate(false);
793 } else {
794 if (!Action && !game::IsInWilderness()) GetAICommand();
800 void character::Move (v2 MoveTo, truth TeleportMove, truth Run) {
801 if (!IsEnabled()) return;
802 /* Test whether the player is stuck to something */
803 if (!TeleportMove && !TryToUnStickTraps(MoveTo-GetPos())) return;
804 if (Run && !IsPlayer() && TorsoIsAlive() &&
805 (Stamina <= 10000/Max(GetAttribute(LEG_STRENGTH), 1) || (!StateIsActivated(PANIC) && Stamina < MaxStamina>>2))) {
806 Run = false;
808 RemoveTraps();
809 if (GetBurdenState() != OVER_LOADED || TeleportMove) {
810 lsquare *OldSquareUnder[MAX_SQUARES_UNDER];
812 if (!game::IsInWilderness()) {
813 if (IsPlayer()) {
814 // idiotic code!
815 area *ca = GetSquareUnder()->GetArea();
817 for (int f = 0; f < MDIR_STAND; ++f) {
818 v2 np = GetPos()+game::GetMoveVector(f);
820 if (np.X >= 0 && np.Y >= 0 && np.X < ca->GetXSize() && np.Y < ca->GetYSize()) {
821 lsquare *sq = static_cast<lsquare *>(ca->GetSquare(np.X, np.Y));
823 sq->SetGoSeen(true);
828 for (int c = 0; c < GetSquaresUnder(); ++c) OldSquareUnder[c] = GetLSquareUnder(c);
830 Remove();
831 PutTo(MoveTo);
832 if (!TeleportMove) {
833 /* Multitiled creatures should behave differently, maybe? */
834 if (Run) {
835 int ED = GetSquareUnder()->GetEntryDifficulty(), Base = 10000;
837 EditAP(-GetMoveAPRequirement(ED)>>1);
838 EditNP(-24*ED);
839 EditExperience(AGILITY, 125, ED<<7);
840 if (IsPlayer()) {
841 switch (GetHungerState()) {
842 case SATIATED: Base = 11000; break;
843 case BLOATED: Base = 12500; break;
844 case OVER_FED: Base = 15000; break;
847 EditStamina(-Base/Max(GetAttribute(LEG_STRENGTH), 1), true);
848 } else {
849 int ED = GetSquareUnder()->GetEntryDifficulty();
851 EditAP(-GetMoveAPRequirement(ED));
852 EditNP(-12*ED);
853 EditExperience(AGILITY, 75, ED<<7);
856 if (IsPlayer()) ShowNewPosInfo();
857 if (IsPlayer() && !game::IsInWilderness()) GetStackUnder()->SetSteppedOn(true);
858 if (!game::IsInWilderness()) SignalStepFrom(OldSquareUnder);
859 } else {
860 if (IsPlayer()) {
861 cchar *CrawlVerb = StateIsActivated(LEVITATION) ? "float" : "crawl";
863 ADD_MESSAGE("You try very hard to %s forward. But your load is too heavy.", CrawlVerb);
865 EditAP(-1000);
870 void character::GetAICommand () {
871 SeekLeader(GetLeader());
872 if (FollowLeader(GetLeader())) return;
873 if (CheckForEnemies(true, true, true)) return;
874 if (CheckForUsefulItemsOnGround()) return;
875 if (CheckForDoors()) return;
876 if (CheckSadism()) return;
877 if (MoveRandomly()) return;
878 EditAP(-1000);
882 truth character::MoveTowardsTarget (truth Run) {
883 v2 MoveTo[3];
884 v2 TPos;
885 v2 Pos = GetPos();
887 if (!Route.empty()) {
888 TPos = Route.back();
889 Route.pop_back();
890 } else TPos = GoingTo;
892 MoveTo[0] = v2(0, 0);
893 MoveTo[1] = v2(0, 0);
894 MoveTo[2] = v2(0, 0);
896 if (TPos.X < Pos.X) {
897 if (TPos.Y < Pos.Y) {
898 MoveTo[0] = v2(-1, -1);
899 MoveTo[1] = v2(-1, 0);
900 MoveTo[2] = v2( 0, -1);
901 } else if (TPos.Y == Pos.Y) {
902 MoveTo[0] = v2(-1, 0);
903 MoveTo[1] = v2(-1, -1);
904 MoveTo[2] = v2(-1, 1);
905 } else if (TPos.Y > Pos.Y) {
906 MoveTo[0] = v2(-1, 1);
907 MoveTo[1] = v2(-1, 0);
908 MoveTo[2] = v2( 0, 1);
910 } else if (TPos.X == Pos.X) {
911 if (TPos.Y < Pos.Y) {
912 MoveTo[0] = v2( 0, -1);
913 MoveTo[1] = v2(-1, -1);
914 MoveTo[2] = v2( 1, -1);
915 } else if (TPos.Y == Pos.Y) {
916 TerminateGoingTo();
917 return false;
918 } else if (TPos.Y > Pos.Y) {
919 MoveTo[0] = v2( 0, 1);
920 MoveTo[1] = v2(-1, 1);
921 MoveTo[2] = v2( 1, 1);
923 } else if (TPos.X > Pos.X) {
924 if (TPos.Y < Pos.Y) {
925 MoveTo[0] = v2(1, -1);
926 MoveTo[1] = v2(1, 0);
927 MoveTo[2] = v2(0, -1);
928 } else if (TPos.Y == Pos.Y) {
929 MoveTo[0] = v2(1, 0);
930 MoveTo[1] = v2(1, -1);
931 MoveTo[2] = v2(1, 1);
932 } else if (TPos.Y > Pos.Y) {
933 MoveTo[0] = v2(1, 1);
934 MoveTo[1] = v2(1, 0);
935 MoveTo[2] = v2(0, 1);
939 v2 ModifiedMoveTo = ApplyStateModification(MoveTo[0]);
941 if (TryMove(ModifiedMoveTo, true, Run)) return true;
943 int L = (Pos-TPos).GetManhattanLength();
945 if (RAND()&1) Swap(MoveTo[1], MoveTo[2]);
947 if (Pos.IsAdjacent(TPos)) {
948 TerminateGoingTo();
949 return false;
952 if ((Pos+MoveTo[1]-TPos).GetManhattanLength() <= L && TryMove(ApplyStateModification(MoveTo[1]), true, Run)) return true;
953 if ((Pos+MoveTo[2]-TPos).GetManhattanLength() <= L && TryMove(ApplyStateModification(MoveTo[2]), true, Run)) return true;
954 Illegal.insert(Pos+ModifiedMoveTo);
955 if (CreateRoute()) return true;
956 return false;
960 int character::CalculateNewSquaresUnder (lsquare **NewSquare, v2 Pos) const {
961 if (GetLevel()->IsValidPos(Pos)) {
962 *NewSquare = GetNearLSquare(Pos);
963 return 1;
965 return 0;
969 truth character::TryMove (v2 MoveVector, truth Important, truth Run) {
970 lsquare *MoveToSquare[MAX_SQUARES_UNDER];
971 character *Pet[MAX_SQUARES_UNDER];
972 character *Neutral[MAX_SQUARES_UNDER];
973 character *Hostile[MAX_SQUARES_UNDER];
974 v2 PetPos[MAX_SQUARES_UNDER];
975 v2 NeutralPos[MAX_SQUARES_UNDER];
976 v2 HostilePos[MAX_SQUARES_UNDER];
977 v2 MoveTo = GetPos()+MoveVector;
978 int Direction = game::GetDirectionForVector(MoveVector);
979 if (Direction == DIR_ERROR) ABORT("Direction fault.");
980 if (!game::IsInWilderness()) {
981 int Squares = CalculateNewSquaresUnder(MoveToSquare, MoveTo);
982 if (Squares) {
983 int Pets = 0;
984 int Neutrals = 0;
985 int Hostiles = 0;
986 for (int c = 0; c < Squares; ++c) {
987 character* Char = MoveToSquare[c]->GetCharacter();
988 if (Char && Char != this) {
989 v2 Pos = MoveToSquare[c]->GetPos();
990 if (IsAlly(Char)) {
991 Pet[Pets] = Char;
992 PetPos[Pets++] = Pos;
993 } else if (Char->GetRelation(this) != HOSTILE) {
994 Neutral[Neutrals] = Char;
995 NeutralPos[Neutrals++] = Pos;
996 } else {
997 Hostile[Hostiles] = Char;
998 HostilePos[Hostiles++] = Pos;
1003 if (Hostiles == 1) return Hit(Hostile[0], HostilePos[0], Direction);
1004 if (Hostiles) {
1005 int Index = RAND() % Hostiles;
1006 return Hit(Hostile[Index], HostilePos[Index], Direction);
1009 if (Neutrals == 1) {
1010 if (!IsPlayer() && !Pets && Important && CanMoveOn(MoveToSquare[0]))
1011 return HandleCharacterBlockingTheWay(Neutral[0], NeutralPos[0], Direction);
1012 else
1013 return IsPlayer() && Hit(Neutral[0], NeutralPos[0], Direction);
1014 } else if (Neutrals) {
1015 if (IsPlayer()) {
1016 int Index = RAND() % Neutrals;
1017 return Hit(Neutral[Index], NeutralPos[Index], Direction);
1019 return false;
1022 if (!IsPlayer()) {
1023 for (int c = 0; c < Squares; ++c) if (MoveToSquare[c]->IsScary(this)) return false;
1026 if (Pets == 1) {
1027 if (IsPlayer() && !ivanconfig::GetBeNice() && Pet[0]->IsMasochist() && HasSadistAttackMode() &&
1028 game::TruthQuestion("Do you want to punish " + Pet[0]->GetObjectPronoun() + "?"))
1029 return Hit(Pet[0], PetPos[0], Direction, SADIST_HIT);
1030 else
1031 return (Important && (CanMoveOn(MoveToSquare[0]) ||
1032 (IsPlayer() && game::GoThroughWallsCheatIsActive())) && Displace(Pet[0]));
1033 } else if (Pets) return false;
1035 if ((CanMove() && CanMoveOn(MoveToSquare[0])) || ((game::GoThroughWallsCheatIsActive() && IsPlayer()))) {
1036 Move(MoveTo, false, Run);
1037 if (IsEnabled() && GetPos() == GoingTo) TerminateGoingTo();
1038 return true;
1039 } else {
1040 for (int c = 0; c < Squares; ++c) {
1041 olterrain *Terrain = MoveToSquare[c]->GetOLTerrain();
1042 if (Terrain && Terrain->CanBeOpened()) {
1043 if (CanOpen()) {
1044 if (Terrain->IsLocked()) {
1045 if (IsPlayer()) {
1046 /*k8*/
1047 if (ivanconfig::GetKickDownDoors()) {
1048 if (game::TruthQuestion(CONST_S("Locked! Do you want to kick ")+Terrain->GetName(DEFINITE)+"?", true, game::GetMoveCommandKeyBetweenPoints(PLAYER->GetPos(), MoveToSquare[0]->GetPos()))) {
1049 Kick(MoveToSquare[c], Direction);
1050 return true;
1051 } else {
1052 return false;
1055 /*k8*/
1056 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 */
1057 return false;
1058 } else if (Important && CheckKick()) {
1059 room *Room = MoveToSquare[c]->GetRoom();
1060 if (!Room || Room->AllowKick(this, MoveToSquare[c])) {
1061 int HP = Terrain->GetHP();
1062 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s kicks %s.", CHAR_NAME(DEFINITE), Terrain->CHAR_NAME(DEFINITE));
1063 Kick(MoveToSquare[c], Direction);
1064 olterrain *NewTerrain = MoveToSquare[c]->GetOLTerrain();
1065 if (NewTerrain == Terrain && Terrain->GetHP() == HP) { // BUG!
1066 Illegal.insert(MoveTo);
1067 CreateRoute();
1069 return true;
1072 } else { /* if (Terrain->IsLocked()) */
1073 /*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);*/
1074 /* Non-players always try to open it */
1075 if (!IsPlayer()) return MoveToSquare[c]->Open(this);
1076 if (game::TruthQuestion(CONST_S("Do you want to open ")+
1077 Terrain->GetName(DEFINITE)+"?", false, game::GetMoveCommandKeyBetweenPoints(PLAYER->GetPos(),
1078 MoveToSquare[0]->GetPos())))
1080 return MoveToSquare[c]->Open(this);
1082 return false;
1083 } /* if (Terrain->IsLocked()) */
1084 } else { /* if (CanOpen()) */
1085 if (IsPlayer()) {
1086 ADD_MESSAGE("This monster type cannot open doors.");
1087 return false;
1089 if (Important) {
1090 Illegal.insert(MoveTo);
1091 return CreateRoute();
1093 } /* if (CanOpen()) */
1094 } /* if (Terrain && Terrain->CanBeOpened()) */
1095 } /* for */
1096 } /* if */
1097 return false;
1098 } else {
1099 if (IsPlayer() && !IsStuck() && GetLevel()->IsOnGround() && game::TruthQuestion(CONST_S("Do you want to leave ")+game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex())+"?")) {
1100 if (HasPetrussNut() && !HasGoldenEagleShirt()) {
1101 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!"));
1102 game::GetCurrentArea()->SendNewDrawRequest();
1103 game::DrawEverything();
1104 ShowAdventureInfo();
1105 festring Msg = CONST_S("killed Petrus and became the Avatar of Chaos");
1106 PLAYER->AddScoreEntry(Msg, 3, false);
1107 game::End(Msg);
1108 return true;
1110 if (game::TryTravel(WORLD_MAP, WORLD_MAP, game::GetCurrentDungeonIndex())) return true;
1112 return false;
1114 } else {
1115 /** No multitile support */
1116 if (CanMove() && GetArea()->IsValidPos(MoveTo) && (CanMoveOn(GetNearWSquare(MoveTo)) || game::GoThroughWallsCheatIsActive())) {
1117 if (!game::GoThroughWallsCheatIsActive()) {
1118 charactervector &V = game::GetWorldMap()->GetPlayerGroup();
1119 truth Discard = false;
1120 for (uInt c = 0; c < V.size(); ++c) {
1121 if (!V[c]->CanMoveOn(GetNearWSquare(MoveTo))) {
1122 if (!Discard) {
1123 ADD_MESSAGE("One or more of your team members cannot cross this terrain.");
1124 if (!game::TruthQuestion("Discard them?")) return false;
1125 Discard = true;
1127 if (Discard) delete V[c];
1128 V.erase(V.begin() + c--);
1132 Move(MoveTo, false);
1133 return true;
1134 } else {
1135 return false;
1141 void character::CreateCorpse (lsquare *Square) {
1142 if (!BodyPartsDisappearWhenSevered() && !game::AllBodyPartsVanish()) {
1143 corpse *Corpse = corpse::Spawn(0, NO_MATERIALS);
1144 Corpse->SetDeceased(this);
1145 Square->AddItem(Corpse);
1146 Disable();
1147 } else {
1148 SendToHell();
1153 void character::Die (ccharacter *Killer, cfestring &Msg, feuLong DeathFlags) {
1154 /* Note: This function musn't delete any objects, since one of these may be
1155 the one currently processed by pool::Be()! */
1156 if (!IsEnabled()) return;
1157 game::ClearEventData();
1158 game::mActor = Killer;
1159 if (game::RunOnCharEvent(this, CONST_S("die"))) { game::ClearEventData(); RemoveTraps(); return; }
1160 game::ClearEventData();
1161 RemoveTraps();
1162 if (IsPlayer()) {
1163 ADD_MESSAGE("You die.");
1164 game::DrawEverything();
1165 if (game::TruthQuestion(CONST_S("Do you want to save screenshot?"), REQUIRES_ANSWER)) {
1166 festring dir;
1167 #ifdef LOCAL_SAVES
1168 dir << ivanconfig::GetMyDir() << "/save";
1169 mkdir(dir.CStr(), 0755);
1170 #else
1171 dir << getenv("HOME") << "/.ivan-save";
1172 mkdir(dir.CStr(), 0755);
1173 #endif
1174 dir << "/deathshots";
1175 mkdir(dir.CStr(), 0755);
1176 festring timestr;
1177 time_t t = time(NULL);
1178 struct tm *ts = localtime(&t);
1179 if (ts) {
1180 timestr << (int)(ts->tm_year%100);
1181 int t = ts->tm_mon+1;
1182 if (t < 10) timestr << '0'; timestr << t;
1183 t = ts->tm_mday; if (t < 10) timestr << '0'; timestr << t;
1184 timestr << '_';
1185 t = ts->tm_hour; if (t < 10) timestr << '0'; timestr << t;
1186 t = ts->tm_min; if (t < 10) timestr << '0'; timestr << t;
1187 t = ts->tm_sec; if (t < 10) timestr << '0'; timestr << t;
1188 } else {
1189 timestr = "heh";
1191 #if defined(HAVE_IMLIB2) || defined(HAVE_LIBPNG)
1192 festring ext = ".png";
1193 #else
1194 festring ext = ".bmp";
1195 #endif
1196 festring fname = dir+"/deathshot_"+timestr;
1197 if (inputfile::fileExists(fname+ext)) {
1198 for (int f = 0; f < 1000; f++) {
1199 char buf[16];
1200 sprintf(buf, "%03d", f);
1201 festring fn = fname+buf;
1202 if (!inputfile::fileExists(fn+ext)) {
1203 fname = fn;
1204 break;
1208 fname << ext;
1209 fprintf(stderr, "deathshot: %s\n", fname.CStr());
1210 #if defined(HAVE_IMLIB2) || defined(HAVE_LIBPNG)
1211 DOUBLE_BUFFER->SavePNG(fname);
1212 #else
1213 DOUBLE_BUFFER->SaveBMP(fname);
1214 #endif
1216 if (game::WizardModeIsActive()) {
1217 game::DrawEverything();
1218 if (!game::TruthQuestion(CONST_S("Do you want to do this, cheater?"), REQUIRES_ANSWER)) {
1219 RestoreBodyParts();
1220 ResetSpoiling();
1221 RestoreHP();
1222 RestoreStamina();
1223 ResetStates();
1224 SetNP(SATIATED_LEVEL);
1225 SendNewDrawRequest();
1226 return;
1229 } else if (CanBeSeenByPlayer() && !(DeathFlags & DISALLOW_MSG)) {
1230 ProcessAndAddMessage(GetDeathMessage());
1231 } else if (DeathFlags & FORCE_MSG) {
1232 ADD_MESSAGE("You sense the death of something.");
1235 if (!(DeathFlags & FORBID_REINCARNATION)) {
1236 if (StateIsActivated(LIFE_SAVED) && CanMoveOn(!game::IsInWilderness() ? GetSquareUnder() : PLAYER->GetSquareUnder())) {
1237 SaveLife();
1238 return;
1240 if (SpecialSaveLife()) return;
1241 } else if (StateIsActivated(LIFE_SAVED)) {
1242 RemoveLifeSavers();
1245 Flags |= C_IN_NO_MSG_MODE;
1246 character *Ghost = 0;
1247 if (IsPlayer()) {
1248 game::RemoveSaves();
1249 if (!game::IsInWilderness()) {
1250 Ghost = game::CreateGhost();
1251 Ghost->Disable();
1255 square *SquareUnder[MAX_SQUARES_UNDER];
1256 memset(SquareUnder, 0, sizeof(SquareUnder));
1257 Disable();
1258 if (IsPlayer() || !game::IsInWilderness()) {
1259 for (int c = 0; c < SquaresUnder; ++c) SquareUnder[c] = GetSquareUnder(c);
1260 Remove();
1261 } else {
1262 charactervector& V = game::GetWorldMap()->GetPlayerGroup();
1263 V.erase(std::find(V.begin(), V.end(), this));
1265 //lsquare **LSquareUnder = reinterpret_cast<lsquare **>(SquareUnder); /* warning; wtf? */
1266 lsquare *LSquareUnder[MAX_SQUARES_UNDER];
1267 memmove(LSquareUnder, SquareUnder, sizeof(SquareUnder));
1269 if (!game::IsInWilderness()) {
1270 if (!StateIsActivated(POLYMORPHED)) {
1271 if (!IsPlayer() && !IsTemporary() && !Msg.IsEmpty()) game::SignalDeath(this, Killer, Msg);
1272 if (!(DeathFlags & DISALLOW_CORPSE)) CreateCorpse(LSquareUnder[0]); else SendToHell();
1273 } else {
1274 if (!IsPlayer() && !IsTemporary() && !Msg.IsEmpty()) game::SignalDeath(GetPolymorphBackup(), Killer, Msg);
1275 GetPolymorphBackup()->CreateCorpse(LSquareUnder[0]);
1276 GetPolymorphBackup()->Flags &= ~C_POLYMORPHED;
1277 SetPolymorphBackup(0);
1278 SendToHell();
1280 } else {
1281 if (!IsPlayer() && !IsTemporary() && !Msg.IsEmpty()) game::SignalDeath(this, Killer, Msg);
1282 SendToHell();
1285 if (IsPlayer()) {
1286 if (!game::IsInWilderness()) {
1287 for (int c = 0; c < GetSquaresUnder(); ++c) LSquareUnder[c]->SetTemporaryEmitation(GetEmitation());
1289 ShowAdventureInfo();
1290 if (!game::IsInWilderness()) {
1291 for(int c = 0; c < GetSquaresUnder(); ++c) LSquareUnder[c]->SetTemporaryEmitation(0);
1295 if (!game::IsInWilderness()) {
1296 if (GetSquaresUnder() == 1) {
1297 stack *StackUnder = LSquareUnder[0]->GetStack();
1298 GetStack()->MoveItemsTo(StackUnder);
1299 doforbodypartswithparam<stack*>()(this, &bodypart::DropEquipment, StackUnder);
1300 } else {
1301 while (GetStack()->GetItems()) GetStack()->GetBottom()->MoveTo(LSquareUnder[RAND_N(GetSquaresUnder())]->GetStack());
1302 for (int c = 0; c < BodyParts; ++c) {
1303 bodypart *BodyPart = GetBodyPart(c);
1304 if (BodyPart) BodyPart->DropEquipment(LSquareUnder[RAND_N(GetSquaresUnder())]->GetStack());
1309 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(0);
1311 Flags &= ~C_IN_NO_MSG_MODE;
1313 if (IsPlayer()) {
1314 AddScoreEntry(Msg);
1315 if (!game::IsInWilderness()) {
1316 Ghost->PutTo(LSquareUnder[0]->GetPos());
1317 Ghost->Enable();
1318 game::CreateBone();
1320 game::TextScreen(CONST_S("Unfortunately you died."), ZERO_V2, WHITE, true, true, &game::ShowDeathSmiley);
1321 game::End(Msg);
1326 void character::AddMissMessage (ccharacter *Enemy) const {
1327 festring Msg;
1328 if (Enemy->IsPlayer()) Msg = GetDescription(DEFINITE)+" misses you!";
1329 else if (IsPlayer()) Msg = CONST_S("You miss ")+Enemy->GetDescription(DEFINITE)+'!';
1330 else if (CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer()) Msg = GetDescription(DEFINITE)+" misses "+Enemy->GetDescription(DEFINITE)+'!';
1331 else return;
1332 ADD_MESSAGE("%s", Msg.CStr());
1336 void character::AddBlockMessage (ccharacter *Enemy, citem *Blocker, cfestring &HitNoun, truth Partial) const {
1337 festring Msg;
1338 festring BlockVerb = (Partial ? " to partially block the " : " to block the ")+HitNoun;
1339 if (IsPlayer()) {
1340 Msg << "You manage" << BlockVerb << " with your " << Blocker->GetName(UNARTICLED) << '!';
1341 } else if (Enemy->IsPlayer() || Enemy->CanBeSeenByPlayer()) {
1342 if (CanBeSeenByPlayer())
1343 Msg << GetName(DEFINITE) << " manages" << BlockVerb << " with " << GetPossessivePronoun() << ' ' << Blocker->GetName(UNARTICLED) << '!';
1344 else
1345 Msg << "Something manages" << BlockVerb << " with something!";
1346 } else {
1347 return;
1349 ADD_MESSAGE("%s", Msg.CStr());
1353 void character::AddPrimitiveHitMessage (ccharacter *Enemy, cfestring &FirstPersonHitVerb,
1354 cfestring &ThirdPersonHitVerb, int BodyPart) const
1356 festring Msg;
1357 festring BodyPartDescription;
1358 if (BodyPart && (Enemy->CanBeSeenByPlayer() || Enemy->IsPlayer()))
1359 BodyPartDescription << " in the " << Enemy->GetBodyPartName(BodyPart);
1360 if (Enemy->IsPlayer())
1361 Msg << GetDescription(DEFINITE) << ' ' << ThirdPersonHitVerb << " you" << BodyPartDescription << '!';
1362 else if (IsPlayer())
1363 Msg << "You " << FirstPersonHitVerb << ' ' << Enemy->GetDescription(DEFINITE) << BodyPartDescription << '!';
1364 else if (CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer())
1365 Msg << GetDescription(DEFINITE) << ' ' << ThirdPersonHitVerb << ' ' << Enemy->GetDescription(DEFINITE) + BodyPartDescription << '!';
1366 else
1367 return;
1368 ADD_MESSAGE("%s", Msg.CStr());
1372 cchar *const HitVerb[] = { "strike", "slash", "stab" };
1373 cchar *const HitVerb3rdPersonEnd[] = { "s", "es", "s" };
1376 void character::AddWeaponHitMessage (ccharacter *Enemy, citem *Weapon, int BodyPart, truth Critical) const {
1377 festring Msg;
1378 festring BodyPartDescription;
1380 if (BodyPart && (Enemy->CanBeSeenByPlayer() || Enemy->IsPlayer()))
1381 BodyPartDescription << " in the " << Enemy->GetBodyPartName(BodyPart);
1383 int FittingTypes = 0;
1384 int DamageFlags = Weapon->GetDamageFlags();
1385 int DamageType = 0;
1387 for (int c = 0; c < DAMAGE_TYPES; ++c) {
1388 if (1 << c & DamageFlags) {
1389 if (!FittingTypes || !RAND_N(FittingTypes+1)) DamageType = c;
1390 ++FittingTypes;
1394 if (!FittingTypes) ABORT("No damage flags specified for %s!", Weapon->CHAR_NAME(UNARTICLED));
1396 festring NewHitVerb = Critical ? " critically " : " ";
1397 NewHitVerb << HitVerb[DamageType];
1398 cchar *const E = HitVerb3rdPersonEnd[DamageType];
1400 if (Enemy->IsPlayer()) {
1401 Msg << GetDescription(DEFINITE) << NewHitVerb << E << " you" << BodyPartDescription;
1402 if (CanBeSeenByPlayer()) Msg << " with " << GetPossessivePronoun() << ' ' << Weapon->GetName(UNARTICLED);
1403 Msg << '!';
1404 } else if (IsPlayer()) {
1405 Msg << "You" << NewHitVerb << ' ' << Enemy->GetDescription(DEFINITE) << BodyPartDescription << '!';
1406 } else if(CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer()) {
1407 Msg << GetDescription(DEFINITE) << NewHitVerb << E << ' ' << Enemy->GetDescription(DEFINITE) << BodyPartDescription;
1408 if (CanBeSeenByPlayer()) Msg << " with " << GetPossessivePronoun() << ' ' << Weapon->GetName(UNARTICLED);
1409 Msg << '!';
1410 } else {
1411 return;
1413 ADD_MESSAGE("%s", Msg.CStr());
1417 item *character::GeneralFindItem (ItemCheckerCB chk) const {
1418 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) {
1419 item *it = *i;
1420 if (it && chk(it)) return it;
1422 return 0;
1426 static truth isEncryptedScroll (item *i) { return i->IsEncryptedScroll(); }
1427 truth character::HasEncryptedScroll () const {
1428 if (GeneralFindItem(::isEncryptedScroll)) return true;
1429 return combineequipmentpredicates()(this, &item::IsEncryptedScroll, 1);
1433 static truth isElpuriHead (item *i) { return i->IsHeadOfElpuri(); }
1434 truth character::HasHeadOfElpuri () const {
1435 if (GeneralFindItem(::isElpuriHead)) return true;
1436 return combineequipmentpredicates()(this, &item::IsHeadOfElpuri, 1);
1440 static truth isPetrussNut (item *i) { return i->IsPetrussNut(); }
1441 truth character::HasPetrussNut () const {
1442 if (GeneralFindItem(::isPetrussNut)) return true;
1443 return combineequipmentpredicates()(this, &item::IsPetrussNut, 1);
1447 static truth isGoldenEagleShirt (item *i) { return i->IsGoldenEagleShirt(); }
1448 truth character::HasGoldenEagleShirt () const {
1449 if (GeneralFindItem(::isGoldenEagleShirt)) return true;
1450 return combineequipmentpredicates()(this, &item::IsGoldenEagleShirt, 1);
1454 static truth isShadowVeil (item *i) { return i->IsShadowVeil(); }
1455 truth character::HasShadowVeil () const {
1456 if (GeneralFindItem(::isShadowVeil)) return true;
1457 return combineequipmentpredicates()(this, &item::IsShadowVeil, 1);
1461 static truth isLostRubyFlamingSword (item *i) { return i->IsLostRubyFlamingSword(); }
1462 truth character::HasLostRubyFlamingSword () const {
1463 if (GeneralFindItem(::isLostRubyFlamingSword)) return true;
1464 return combineequipmentpredicates()(this, &item::IsLostRubyFlamingSword, 1);
1468 truth character::HasOmmelBlood () const {
1469 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
1470 if (i->IsKleinBottle() && i->GetSecondaryMaterial() && i->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD) return true;
1472 for (int c = 0; c < GetEquipments(); ++c) {
1473 item *Item = GetEquipment(c);
1475 if (Item && Item->IsKleinBottle() && Item->GetSecondaryMaterial() && Item->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD) return true;
1477 return false; //combineequipmentpredicates()(this, &item::IsKleinBottle, 1);
1481 truth character::HasCurdledBlood () const {
1482 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
1483 if (i->IsKleinBottle() && i->GetSecondaryMaterial() && i->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD) return true;
1485 for (int c = 0; c < GetEquipments(); ++c) {
1486 item *Item = GetEquipment(c);
1488 if (Item && Item->IsKleinBottle() && Item->GetSecondaryMaterial() && Item->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD) return true;
1490 return false; //combineequipmentpredicates()(this, &item::IsKleinBottle, 1);
1494 truth character::CurdleOmmelBlood () const {
1495 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) {
1496 if (i->IsKleinBottle() && i->GetSecondaryMaterial() && i->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD) {
1497 i->ChangeSecondaryMaterial(MAKE_MATERIAL(CURDLED_OMMEL_BLOOD));
1498 return true;
1502 for (int c = 0; c < GetEquipments(); ++c) {
1503 item *Item = GetEquipment(c);
1505 if (Item && Item->IsKleinBottle() && Item->GetSecondaryMaterial() && Item->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD) {
1506 Item->ChangeSecondaryMaterial(MAKE_MATERIAL(CURDLED_OMMEL_BLOOD));
1507 return true;
1510 return false; //combineequipmentpredicates()(this, &item::IsKleinBottle, 1);
1514 truth character::RemoveCurdledOmmelBlood () {
1515 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) {
1516 if (i->IsKleinBottle() && i->GetSecondaryMaterial() && i->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD) {
1517 (*i)->RemoveFromSlot();
1518 (*i)->SendToHell();
1519 return true;
1523 for (int c = 0; c < GetEquipments(); ++c) {
1524 item *Item = GetEquipment(c);
1526 if (Item && Item->IsKleinBottle() && Item->GetSecondaryMaterial() && Item->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD) {
1527 Item->RemoveFromSlot();
1528 Item->SendToHell();
1529 return true;
1532 return false;
1536 int character::GeneralRemoveItem (ItemCheckerCB chk, truth allItems) {
1537 truth done;
1538 int cnt = 0;
1539 // inventory
1540 do {
1541 done = true;
1542 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) {
1543 item *Item = *i;
1544 if (Item && chk(Item)) {
1545 Item->RemoveFromSlot();
1546 Item->SendToHell();
1547 cnt++;
1548 if (!allItems) return cnt;
1549 done = false;
1550 break;
1553 } while (!done);
1554 // equipments
1555 do {
1556 done = true;
1557 for (int c = 0; c < GetEquipments(); ++c) {
1558 item *Item = GetEquipment(c);
1559 if (Item && chk(Item)) {
1560 Item->RemoveFromSlot();
1561 Item->SendToHell();
1562 cnt++;
1563 if (!allItems) return cnt;
1564 done = false;
1565 break;
1568 } while (!done);
1569 return cnt;
1573 //static truth isEncryptedScroll (item *i) { return i->IsEncryptedScroll(); }
1574 truth character::RemoveEncryptedScroll () { return GeneralRemoveItem(::isEncryptedScroll) != 0; }
1577 static truth isMondedrPass (item *i) { return i->IsMondedrPass(); }
1578 truth character::RemoveMondedrPass () { return GeneralRemoveItem(::isMondedrPass) != 0; }
1581 static truth isRingOfThieves (item *i) { return i->IsRingOfThieves(); }
1582 truth character::RemoveRingOfThieves () { return GeneralRemoveItem(::isRingOfThieves) != 0; }
1585 truth character::RemoveShadowVeil () { return (GeneralRemoveItem(::isShadowVeil) != 0); }
1588 truth character::ReadItem (item *ToBeRead) {
1589 if (!ToBeRead->CanBeRead(this)) {
1590 if (IsPlayer()) ADD_MESSAGE("You can't read this.");
1591 return false;
1593 if (!GetLSquareUnder()->IsDark() || game::GetSeeWholeMapCheatMode()) {
1594 if (StateIsActivated(CONFUSED) && !(RAND()&7)) {
1595 if (!ToBeRead->IsDestroyable(this)) {
1596 ADD_MESSAGE("You read some words of %s and understand exactly nothing.", ToBeRead->CHAR_NAME(DEFINITE));
1597 } else {
1598 ADD_MESSAGE("%s is very confusing. Or perhaps you are just too confused?", ToBeRead->CHAR_NAME(DEFINITE));
1599 ActivateRandomState(SRC_CONFUSE_READ, 1000+RAND()%1500);
1600 ToBeRead->RemoveFromSlot();
1601 ToBeRead->SendToHell();
1603 EditAP(-1000);
1604 return true;
1606 if (ToBeRead->Read(this)) {
1607 if (!game::WizardModeIsActive()) {
1608 /* This AP is used to take the stuff out of backpack */
1609 DexterityAction(5);
1611 return true;
1613 return false;
1615 if (IsPlayer()) ADD_MESSAGE("It's too dark here to read.");
1616 return false;
1620 void character::CalculateBurdenState () {
1621 int OldBurdenState = BurdenState;
1622 sLong SumOfMasses = GetCarriedWeight();
1623 sLong CarryingStrengthUnits = sLong(GetCarryingStrength())*2500;
1624 if (SumOfMasses > (CarryingStrengthUnits << 1) + CarryingStrengthUnits) BurdenState = OVER_LOADED;
1625 else if (SumOfMasses > CarryingStrengthUnits << 1) BurdenState = STRESSED;
1626 else if (SumOfMasses > CarryingStrengthUnits) BurdenState = BURDENED;
1627 else BurdenState = UNBURDENED;
1628 if (!IsInitializing() && BurdenState != OldBurdenState) CalculateBattleInfo();
1632 void character::Save (outputfile &SaveFile) const {
1633 SaveFile << (uShort)GetType();
1634 Stack->Save(SaveFile);
1635 SaveFile << ID;
1636 for (int c = 0; c < BASE_ATTRIBUTES; ++c) SaveFile << BaseExperience[c];
1638 SaveFile << ExpModifierMap;
1639 SaveFile << NP << AP << Stamina << GenerationDanger << ScienceTalks << CounterToMindWormHatch;
1640 SaveFile << TemporaryState << EquipmentState << Money << GoingTo << RegenerationCounter << Route << Illegal;
1641 SaveFile << CurrentSweatMaterial;
1642 SaveFile.Put(!!IsEnabled());
1643 SaveFile << HomeData << BlocksSinceLastTurn << CommandFlags;
1644 SaveFile << WarnFlags << (uShort)Flags;
1646 for (int c = 0; c < BodyParts; ++c) SaveFile << BodyPartSlot[c] << OriginalBodyPartID[c];
1648 SaveLinkedList(SaveFile, TrapData);
1649 SaveFile << Action;
1651 for (int c = 0; c < STATES; ++c) SaveFile << TemporaryStateCounter[c];
1653 if (GetTeam()) {
1654 SaveFile.Put(true);
1655 SaveFile << Team->GetID(); // feuLong
1656 } else {
1657 SaveFile.Put(false);
1660 if (GetTeam() && GetTeam()->GetLeader() == this) SaveFile.Put(true); else SaveFile.Put(false);
1662 SaveFile << AssignedName << PolymorphBackup;
1664 for (int c = 0; c < AllowedWeaponSkillCategories; ++c) SaveFile << CWeaponSkill[c];
1666 SaveFile << (uShort)GetConfig();
1670 void character::Load (inputfile &SaveFile) {
1671 LoadSquaresUnder();
1672 Stack->Load(SaveFile);
1673 SaveFile >> ID;
1674 game::AddCharacterID(this, ID);
1676 for (int c = 0; c < BASE_ATTRIBUTES; ++c) SaveFile >> BaseExperience[c];
1678 SaveFile >> ExpModifierMap;
1679 SaveFile >> NP >> AP >> Stamina >> GenerationDanger >> ScienceTalks >> CounterToMindWormHatch;
1680 SaveFile >> TemporaryState >> EquipmentState >> Money >> GoingTo >> RegenerationCounter >> Route >> Illegal;
1681 SaveFile >> CurrentSweatMaterial;
1683 if (!SaveFile.Get()) Disable();
1685 SaveFile >> HomeData >> BlocksSinceLastTurn >> CommandFlags;
1686 SaveFile >> WarnFlags;
1687 WarnFlags &= ~WARNED;
1688 Flags |= ReadType(uShort, SaveFile) & ~ENTITY_FLAGS;
1690 for (int c = 0; c < BodyParts; ++c) {
1691 SaveFile >> BodyPartSlot[c] >> OriginalBodyPartID[c];
1692 item *BodyPart = *BodyPartSlot[c];
1693 if (BodyPart) BodyPart->Disable();
1696 LoadLinkedList(SaveFile, TrapData);
1697 SaveFile >> Action;
1699 if (Action) Action->SetActor(this);
1701 for (int c = 0; c < STATES; ++c) SaveFile >> TemporaryStateCounter[c];
1703 if (SaveFile.Get()) SetTeam(game::GetTeam(ReadType(feuLong, SaveFile)));
1705 if (SaveFile.Get()) GetTeam()->SetLeader(this);
1707 SaveFile >> AssignedName >> PolymorphBackup;
1709 for (int c = 0; c < AllowedWeaponSkillCategories; ++c) SaveFile >> CWeaponSkill[c];
1711 databasecreator<character>::InstallDataBase(this, ReadType(uShort, SaveFile));
1713 if (IsEnabled() && !game::IsInWilderness()) {
1714 for (int c = 1; c < GetSquaresUnder(); ++c) GetSquareUnder(c)->SetCharacter(this);
1718 const fearray<festring> &lt = GetLevelTags();
1719 if (lt.Size > 1) {
1720 fprintf(stderr, "====\n");
1721 for (uInt f = 0; f < lt.Size; ++f) fprintf(stderr, " %u: [%s]\n", f, lt[f].CStr());
1727 truth character::Engrave (cfestring &What) {
1728 GetLSquareUnder()->Engrave(What);
1729 return true;
1732 truth character::MoveRandomly () {
1733 if (!IsEnabled()) return false;
1734 for (int c = 0; c < 10; ++c) {
1735 v2 ToTry = game::GetMoveVector(RAND()&7);
1736 if (GetLevel()->IsValidPos(GetPos()+ToTry)) {
1737 lsquare *Square = GetNearLSquare(GetPos()+ToTry);
1738 if (!Square->IsDangerous(this) && !Square->IsScary(this) && TryMove(ToTry, false, false)) return true;
1741 return false;
1745 truth character::TestForPickup (item *ToBeTested) const {
1746 if (MakesBurdened(ToBeTested->GetWeight()+GetCarriedWeight())) return false;
1747 return true;
1751 void character::AddScoreEntry (cfestring &Description, double Multiplier, truth AddEndLevel) const {
1752 if (!game::WizardModeIsReallyActive()) {
1753 highscore HScore;
1754 if (!HScore.CheckVersion()) {
1755 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;
1756 HScore.Clear();
1758 festring Desc = game::GetPlayerName();
1759 Desc << ", " << Description;
1760 if (AddEndLevel) Desc << " in "+(game::IsInWilderness() ? "the world map" : game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex()));
1761 HScore.Add(sLong(game::GetScore()*Multiplier), Desc);
1762 HScore.Save();
1767 truth character::CheckDeath (cfestring &Msg, ccharacter *Murderer, feuLong DeathFlags) {
1768 if (!IsEnabled()) return true;
1769 if (game::IsSumoWrestling() && IsDead()) {
1770 game::EndSumoWrestling(!!IsPlayer());
1771 return true;
1773 if (DeathFlags & FORCE_DEATH || IsDead()) {
1774 if (Murderer && Murderer->IsPlayer() && GetTeam()->GetKillEvilness()) game::DoEvilDeed(GetTeam()->GetKillEvilness());
1775 festring SpecifierMsg;
1776 int SpecifierParts = 0;
1777 if (GetPolymorphBackup()) {
1778 SpecifierMsg << " polymorphed into ";
1779 id::AddName(SpecifierMsg, INDEFINITE);
1780 ++SpecifierParts;
1782 if (!(DeathFlags & IGNORE_TRAPS) && IsStuck()) {
1783 if (SpecifierParts++) SpecifierMsg << " and";
1784 SpecifierMsg << " caught in " << GetTrapDescription();
1786 if (GetAction() && !(DeathFlags & IGNORE_UNCONSCIOUSNESS && GetAction()->IsUnconsciousness())) {
1787 festring ActionMsg = GetAction()->GetDeathExplanation();
1788 if (!ActionMsg.IsEmpty()) {
1789 if (SpecifierParts > 1) {
1790 SpecifierMsg = ActionMsg << ',' << SpecifierMsg;
1791 } else {
1792 if (SpecifierParts) SpecifierMsg << " and";
1793 SpecifierMsg << ActionMsg;
1795 ++SpecifierParts;
1798 festring NewMsg = Msg;
1799 if (Murderer == this) {
1800 SEARCH_N_REPLACE(NewMsg, "@bkp", CONST_S("by ") + GetPossessivePronoun(false) + " own");
1801 SEARCH_N_REPLACE(NewMsg, "@bk", CONST_S("by ") + GetObjectPronoun(false) + "self");
1802 SEARCH_N_REPLACE(NewMsg, "@k", GetObjectPronoun(false) + "self");
1803 } else {
1804 SEARCH_N_REPLACE(NewMsg, "@bkp", CONST_S("by ") + Murderer->GetName(INDEFINITE) + "'s");
1805 SEARCH_N_REPLACE(NewMsg, "@bk", CONST_S("by ") + Murderer->GetName(INDEFINITE));
1806 SEARCH_N_REPLACE(NewMsg, "@k", CONST_S("by ") + Murderer->GetName(INDEFINITE));
1808 if (SpecifierParts) NewMsg << " while" << SpecifierMsg;
1809 if (IsPlayer() && game::WizardModeIsActive()) ADD_MESSAGE("Death message: %s. Score: %d.", NewMsg.CStr(), game::GetScore());
1810 Die(Murderer, NewMsg, DeathFlags);
1811 return true;
1813 return false;
1817 truth character::CheckStarvationDeath (cfestring &Msg) {
1818 if (GetNP() < 1 && UsesNutrition()) return CheckDeath(Msg, 0, FORCE_DEATH);
1819 return false;
1823 void character::ThrowItem (int Direction, item *ToBeThrown) {
1824 if (Direction > 7) ABORT("Throw in TOO odd direction...");
1825 ToBeThrown->Fly(this, Direction, GetAttribute(ARM_STRENGTH));
1829 void character::HasBeenHitByItem (character *Thrower, item *Thingy, int Damage, double ToHitValue, int Direction) {
1830 if (IsPlayer()) ADD_MESSAGE("%s hits you.", Thingy->CHAR_NAME(DEFINITE));
1831 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s hits %s.", Thingy->CHAR_NAME(DEFINITE), CHAR_NAME(DEFINITE));
1832 int BodyPart = ChooseBodyPartToReceiveHit(ToHitValue, DodgeValue);
1833 int WeaponSkillHits = Thrower ? CalculateWeaponSkillHits(Thrower) : 0;
1834 int DoneDamage = ReceiveBodyPartDamage(Thrower, Damage, PHYSICAL_DAMAGE, BodyPart, Direction);
1835 truth Succeeded = (GetBodyPart(BodyPart) && HitEffect(Thrower, Thingy, Thingy->GetPos(), THROW_ATTACK, BodyPart, Direction, !DoneDamage, false, DoneDamage)) || DoneDamage;
1836 if (Succeeded && Thrower) Thrower->WeaponSkillHit(Thingy, THROW_ATTACK, WeaponSkillHits);
1837 festring DeathMsg = CONST_S("killed by a flying ")+Thingy->GetName(UNARTICLED);
1838 if (CheckDeath(DeathMsg, Thrower)) return;
1839 if (Thrower) {
1840 if (Thrower->CanBeSeenByPlayer())
1841 DeActivateVoluntaryAction(CONST_S("The attack of ")+Thrower->GetName(DEFINITE)+CONST_S(" interrupts you."));
1842 else
1843 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
1844 } else {
1845 DeActivateVoluntaryAction(CONST_S("The hit interrupts you."));
1850 truth character::DodgesFlyingItem (item *Item, double ToHitValue) {
1851 return !Item->EffectIsGood() && RAND() % int(100+ToHitValue/DodgeValue*100) < 100;
1855 void character::GetPlayerCommand () {
1856 command *cmd;
1857 truth HasActed = false;
1858 while (!HasActed) {
1859 game::DrawEverything();
1860 if (game::GetDangerFound() && !StateIsActivated(FEARLESS)) {
1861 if (game::GetDangerFound() > 500.) {
1862 if (game::GetCausePanicFlag()) {
1863 game::SetCausePanicFlag(false);
1864 BeginTemporaryState(PANIC, 500+RAND_N(500));
1866 game::AskForEscPress(CONST_S("You are horrified by your situation!"));
1867 } else if (ivanconfig::GetWarnAboutDanger()) {
1868 if (game::GetDangerFound() > 50.) game::AskForEscPress(CONST_S("You sense great danger!"));
1869 else game::AskForEscPress(CONST_S("You sense danger!"));
1871 game::SetDangerFound(0);
1873 game::SetIsInGetCommand(true);
1874 int Key = GET_KEY();
1875 game::SetIsInGetCommand(false);
1876 if (Key != '+' && Key != '-' && Key != 'M') msgsystem::ThyMessagesAreNowOld(); // gum
1877 truth ValidKeyPressed = false;
1879 for (int c = 0; c < DIRECTION_COMMAND_KEYS; ++c) {
1880 if (Key == game::GetMoveCommandKey(c)) {
1881 HasActed = TryMove(ApplyStateModification(game::GetMoveVector(c)), true, game::PlayerIsRunning());
1882 ValidKeyPressed = true;
1886 if (!ValidKeyPressed) {
1887 for (int c = 0; (cmd = commandsystem::GetCommand(c)); ++c) {
1888 /* k8 */
1889 /* Numpad aliases for most commonly used commands */
1890 if (Key == KEY_DEL && cmd->GetName() == "Eat") Key = cmd->GetKey();
1891 if (Key == KEY_INS && cmd->GetName() == "PickUp") Key = cmd->GetKey();
1892 if (Key == KEY_PLUS && cmd->GetName() == "EquipmentScreen") Key = cmd->GetKey();
1893 /* k8 */
1894 if (Key == cmd->GetKey()) {
1895 if (game::IsInWilderness() && !commandsystem::GetCommand(c)->IsUsableInWilderness()) {
1896 ADD_MESSAGE("This function cannot be used while in wilderness.");
1897 } else if (!game::WizardModeIsActive() && commandsystem::GetCommand(c)->IsWizardModeFunction()) {
1898 ADD_MESSAGE("Activate wizardmode to use this function.");
1899 } else {
1900 HasActed = commandsystem::GetCommand(c)->GetLinkedFunction()(this);
1902 ValidKeyPressed = true;
1903 break;
1907 if (!ValidKeyPressed) ADD_MESSAGE("Unknown key. Press '?' for a list of commands.");
1909 game::IncreaseTurn();
1913 void character::Vomit (v2 Pos, int Amount, truth ShowMsg) {
1914 if (!CanVomit()) return;
1915 if (ShowMsg) {
1916 if (IsPlayer()) ADD_MESSAGE("You vomit.");
1917 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s vomits.", CHAR_NAME(DEFINITE));
1919 if (VomittingIsUnhealthy()) {
1920 EditExperience(ARM_STRENGTH, -75, 1 << 9);
1921 EditExperience(LEG_STRENGTH, -75, 1 << 9);
1923 if (IsPlayer()) {
1924 EditNP(-2500-RAND()%2501);
1925 CheckStarvationDeath(CONST_S("vomited himself to death"));
1927 if (StateIsActivated(PARASITIZED) && !(RAND() & 7)) {
1928 if (IsPlayer()) ADD_MESSAGE("You notice a dead broad tapeworm among your former stomach contents.");
1929 DeActivateTemporaryState(PARASITIZED);
1931 if (!game::IsInWilderness()) {
1932 GetNearLSquare(Pos)->ReceiveVomit(this, liquid::Spawn(GetVomitMaterial(), sLong(sqrt(GetBodyVolume())*Amount/1000)));
1937 truth character::Polymorph (character *NewForm, int Counter) {
1938 if (!IsPolymorphable() || (!IsPlayer() && game::IsInWilderness())) {
1939 delete NewForm;
1940 return false;
1942 RemoveTraps();
1943 if (GetAction()) GetAction()->Terminate(false);
1944 NewForm->SetAssignedName("");
1945 if (IsPlayer())
1946 ADD_MESSAGE("Your body glows in a crimson light. You transform into %s!", NewForm->CHAR_NAME(INDEFINITE));
1947 else if (CanBeSeenByPlayer())
1948 ADD_MESSAGE("%s glows in a crimson light and %s transforms into %s!", CHAR_NAME(DEFINITE), GetPersonalPronoun().CStr(), NewForm->CHAR_NAME(INDEFINITE));
1950 Flags |= C_IN_NO_MSG_MODE;
1951 NewForm->Flags |= C_IN_NO_MSG_MODE;
1952 NewForm->ChangeTeam(GetTeam());
1953 NewForm->GenerationDanger = GenerationDanger;
1954 NewForm->mOnEvents = this->mOnEvents;
1956 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(NewForm);
1958 v2 Pos = GetPos();
1959 Remove();
1960 NewForm->PutToOrNear(Pos);
1961 NewForm->SetAssignedName(GetAssignedName());
1962 NewForm->ActivateTemporaryState(POLYMORPHED);
1963 NewForm->SetTemporaryStateCounter(POLYMORPHED, Counter);
1965 if (TemporaryStateIsActivated(POLYMORPHED)) {
1966 NewForm->SetPolymorphBackup(GetPolymorphBackup());
1967 SetPolymorphBackup(0);
1968 SendToHell();
1969 } else {
1970 NewForm->SetPolymorphBackup(this);
1971 Flags |= C_POLYMORPHED;
1972 Disable();
1975 GetStack()->MoveItemsTo(NewForm->GetStack());
1976 NewForm->SetMoney(GetMoney());
1977 DonateEquipmentTo(NewForm);
1978 Flags &= ~C_IN_NO_MSG_MODE;
1979 NewForm->Flags &= ~C_IN_NO_MSG_MODE;
1980 NewForm->CalculateAll();
1982 if (IsPlayer()) {
1983 Flags &= ~C_PLAYER;
1984 game::SetPlayer(NewForm);
1985 game::SendLOSUpdateRequest();
1986 UpdateESPLOS();
1989 NewForm->TestWalkability();
1990 return true;
1994 void character::BeKicked (character *Kicker, item *Boot, bodypart *Leg, v2 HitPos, double KickDamage,
1995 double ToHitValue, int Success, int Direction, truth Critical, truth ForceHit)
1997 //FIXME: other args
1998 game::ClearEventData();
1999 game::mActor = Kicker;
2000 if (game::RunOnCharEvent(this, CONST_S("before_be_kicked"))) { game::ClearEventData(); return; }
2001 game::ClearEventData();
2002 game::mActor = 0;
2003 switch (TakeHit(Kicker, Boot, Leg, HitPos, KickDamage, ToHitValue, Success, KICK_ATTACK, Direction, Critical, ForceHit)) {
2004 case HAS_HIT:
2005 case HAS_BLOCKED:
2006 case DID_NO_DAMAGE:
2007 if (IsEnabled() && !CheckBalance(KickDamage)) {
2008 if (IsPlayer()) ADD_MESSAGE("The kick throws you off balance.");
2009 else if (Kicker->IsPlayer()) ADD_MESSAGE("The kick throws %s off balance.", CHAR_DESCRIPTION(DEFINITE));
2010 v2 FallToPos = GetPos()+game::GetMoveVector(Direction);
2011 FallTo(Kicker, FallToPos);
2017 /* Return true if still in balance */
2018 truth character::CheckBalance (double KickDamage) {
2019 return !CanMove() || IsStuck() || !KickDamage || (!IsFlying() && KickDamage*5 < RAND()%GetSize());
2023 void character::FallTo (character *GuiltyGuy, v2 Where) {
2024 EditAP(-500);
2025 lsquare *MoveToSquare[MAX_SQUARES_UNDER];
2026 int Squares = CalculateNewSquaresUnder(MoveToSquare, Where);
2027 if (Squares) {
2028 truth NoRoom = false;
2029 for (int c = 0; c < Squares; ++c) {
2030 olterrain *Terrain = MoveToSquare[c]->GetOLTerrain();
2031 if (Terrain && !CanMoveOn(Terrain)) { NoRoom = true; break; }
2033 if (NoRoom) {
2034 if (HasHead()) {
2035 if (IsPlayer()) ADD_MESSAGE("You hit your head on the wall.");
2036 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s hits %s head on the wall.", CHAR_NAME(DEFINITE), GetPossessivePronoun().CStr());
2038 ReceiveDamage(GuiltyGuy, 1+RAND()%5, PHYSICAL_DAMAGE, HEAD);
2039 CheckDeath(CONST_S("killed by hitting a wall due to being kicked @bk"), GuiltyGuy);
2040 } else {
2041 if (IsFreeForMe(MoveToSquare[0])) Move(Where, true);
2042 // Place code that handles characters bouncing to each other here
2048 truth character::CheckCannibalism (cmaterial *What) const {
2049 return GetTorso()->GetMainMaterial()->IsSameAs(What);
2053 void character::StandIdleAI () {
2054 SeekLeader(GetLeader());
2055 if (CheckForEnemies(true, true, true)) return;
2056 if (CheckForUsefulItemsOnGround()) return;
2057 if (FollowLeader(GetLeader())) return;
2058 if (CheckForDoors()) return;
2059 if (MoveTowardsHomePos()) return;
2060 if (CheckSadism()) return;
2061 EditAP(-1000);
2065 truth character::LoseConsciousness (int Counter, truth HungerFaint) {
2066 if (!AllowUnconsciousness()) return false;
2067 action *Action = GetAction();
2068 if (Action) {
2069 if (HungerFaint && !Action->AllowUnconsciousness()) return false;
2070 if (Action->IsUnconsciousness()) {
2071 static_cast<unconsciousness *>(Action)->RaiseCounterTo(Counter);
2072 return true;
2074 Action->Terminate(false);
2076 if (IsPlayer()) ADD_MESSAGE("You lose consciousness.");
2077 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s loses consciousness.", CHAR_NAME(DEFINITE));
2078 unconsciousness *Unconsciousness = unconsciousness::Spawn(this);
2079 Unconsciousness->SetCounter(Counter);
2080 SetAction(Unconsciousness);
2081 return true;
2085 void character::DeActivateVoluntaryAction (cfestring &Reason) {
2086 if (GetAction() && GetAction()->IsVoluntary()) {
2087 if (IsPlayer()) {
2088 if (Reason.GetSize()) ADD_MESSAGE("%s", Reason.CStr());
2089 if (game::TruthQuestion(CONST_S("Continue ")+GetAction()->GetDescription()+"?")) GetAction()->ActivateInDNDMode();
2090 else GetAction()->Terminate(false);
2091 } else {
2092 GetAction()->Terminate(false);
2098 void character::ActionAutoTermination () {
2099 if (!GetAction() || !GetAction()->IsVoluntary() || GetAction()->InDNDMode()) return;
2100 v2 Pos = GetPos();
2101 for (int c = 0; c < game::GetTeams(); ++c) {
2102 if (GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE) {
2103 for (std::list<character *>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i) {
2104 character *ch = *i;
2105 if (ch->IsEnabled() && ch->CanBeSeenBy(this, false, true) && (ch->CanMove() || ch->GetPos().IsAdjacent(Pos)) && ch->CanAttack()) {
2106 if (IsPlayer()) {
2107 ADD_MESSAGE("%s seems to be hostile.", ch->CHAR_NAME(DEFINITE));
2108 if (game::TruthQuestion(CONST_S("Continue ")+GetAction()->GetDescription()+"?")) GetAction()->ActivateInDNDMode();
2109 else GetAction()->Terminate(false);
2110 } else {
2111 GetAction()->Terminate(false);
2113 return;
2121 truth character::CheckForEnemies (truth CheckDoors, truth CheckGround, truth MayMoveRandomly, truth RunTowardsTarget) {
2122 if (!IsEnabled()) return false;
2123 truth HostileCharsNear = false;
2124 character *NearestChar = 0;
2125 sLong NearestDistance = 0x7FFFFFFF;
2126 v2 Pos = GetPos();
2127 for (int c = 0; c < game::GetTeams(); ++c) {
2128 if (GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE) {
2129 for (std::list<character*>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i) {
2130 character *ch = *i;
2131 if (ch->IsEnabled() && GetAttribute(WISDOM) < ch->GetAttackWisdomLimit()) {
2132 sLong ThisDistance = Max<sLong>(abs(ch->GetPos().X - Pos.X), abs(ch->GetPos().Y - Pos.Y));
2133 if (ThisDistance <= GetLOSRangeSquare()) HostileCharsNear = true;
2134 if ((ThisDistance < NearestDistance || (ThisDistance == NearestDistance && !(RAND() % 3))) &&
2135 ch->CanBeSeenBy(this, false, IsGoingSomeWhere()) &&
2136 (!IsGoingSomeWhere() || HasClearRouteTo(ch->GetPos()))) {
2137 NearestChar = ch;
2138 NearestDistance = ThisDistance;
2145 if (NearestChar) {
2146 if (GetAttribute(INTELLIGENCE) >= 10 || IsSpy()) game::CallForAttention(GetPos(), 100);
2147 if (SpecialEnemySightedReaction(NearestChar)) return true;
2148 if (IsExtraCoward() && !StateIsActivated(PANIC) && NearestChar->GetRelativeDanger(this) >= 0.5 && !StateIsActivated(FEARLESS)) {
2149 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s sees %s.", CHAR_NAME(DEFINITE), NearestChar->CHAR_DESCRIPTION(DEFINITE));
2150 BeginTemporaryState(PANIC, 500+RAND()%500);
2152 if (!IsRetreating()) {
2153 if (CheckGround && NearestDistance > 2 && CheckForUsefulItemsOnGround(false)) return true;
2154 SetGoingTo(NearestChar->GetPos());
2155 } else {
2156 SetGoingTo(Pos-((NearestChar->GetPos()-Pos)<<4));
2158 return MoveTowardsTarget(true);
2159 } else {
2160 character *Leader = GetLeader();
2161 if (Leader == this) Leader = 0;
2162 if (!Leader && IsGoingSomeWhere()) {
2163 if (!MoveTowardsTarget(RunTowardsTarget)) {
2164 TerminateGoingTo();
2165 return false;
2166 } else {
2167 if (!IsEnabled()) return true;
2168 if (GetPos() == GoingTo) TerminateGoingTo();
2169 return true;
2171 } else {
2172 if ((!Leader || (Leader && !IsGoingSomeWhere())) && HostileCharsNear) {
2173 if (CheckDoors && CheckForDoors()) return true;
2174 if (CheckGround && CheckForUsefulItemsOnGround()) return true;
2175 if (MayMoveRandomly && MoveRandomly()) return true; // one has heard that an enemy is near but doesn't know where
2177 return false;
2183 truth character::CheckForDoors () {
2184 if (!CanOpen() || !IsEnabled()) return false;
2185 for (int d = 0; d < GetNeighbourSquares(); ++d) {
2186 lsquare *Square = GetNeighbourLSquare(d);
2187 if (Square && Square->GetOLTerrain() && Square->GetOLTerrain()->Open(this)) return true;
2189 return false;
2193 truth character::CheckForUsefulItemsOnGround (truth CheckFood) {
2194 if (StateIsActivated(PANIC) || !IsEnabled()) return false;
2195 itemvector ItemVector;
2196 GetStackUnder()->FillItemVector(ItemVector);
2197 for (uInt c = 0; c < ItemVector.size(); ++c) {
2198 if (ItemVector[c]->CanBeSeenBy(this) && ItemVector[c]->IsPickable(this)) {
2199 if (!(CommandFlags & DONT_CHANGE_EQUIPMENT) && TryToEquip(ItemVector[c])) return true;
2200 if (CheckFood && UsesNutrition() && !CheckIfSatiated() && TryToConsume(ItemVector[c])) return true;
2201 if (IsRangedAttacker() && (ItemVector[c])->GetThrowItemTypes() && TryToAddToInventory(ItemVector[c])) return true;
2204 return false;
2208 truth character::TryToAddToInventory (item *Item) {
2209 if (!(GetBurdenState() > STRESSED) || !CanUseEquipment() || Item->GetSquaresUnder() != 1) return false;
2210 room *Room = GetRoom();
2211 if (!Room || Room->PickupItem(this, Item, 1)) {
2212 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s picks up %s from the ground.", CHAR_NAME(DEFINITE), Item->CHAR_NAME(INDEFINITE));
2213 Item->MoveTo(GetStack());
2214 DexterityAction(5);
2215 return true;
2217 return false;
2221 truth character::CheckInventoryForItemToThrow (item *ToBeChecked) {
2222 return (ToBeChecked->GetThrowItemTypes() & GetWhatThrowItemTypesToThrow()) ? true : false; //hehe
2226 truth character::CheckThrowItemOpportunity () {
2227 if (!IsRangedAttacker() || !CanThrow() || !IsHumanoid() || !IsSmall() || !IsEnabled()) return false; // total gum
2228 //fprintf(stderr, "character::CheckThrowItemOpportunity...\n");
2229 // Steps:
2230 // (1) - Acquire target as nearest enemy
2231 // (2) - Check that this enemy is in range, and is in appropriate direction; no friendly fire!
2232 // (3) - check inventory for throwing weapon, select this weapon
2233 // (4) - throw item in direction where the enemy is
2235 //Check the visible area for hostiles
2236 int ThrowDirection = 0;
2237 int TargetFound = 0;
2238 v2 Pos = GetPos();
2239 v2 TestPos;
2240 int RangeMax = GetLOSRange();
2241 int CandidateDirections[7] = {0, 0, 0, 0, 0, 0, 0};
2242 int HostileFound = 0;
2243 item *ToBeThrown = 0;
2244 level *Level = GetLevel();
2246 for (int r = 1; r <= RangeMax; ++r) {
2247 for (int dir = 0; dir < MDIR_STAND; ++dir) {
2249 switch (dir) {
2250 case 0: TestPos = v2(Pos.X-r, Pos.Y-r); break;
2251 case 1: TestPos = v2(Pos.X, Pos.Y-r); break;
2252 case 2: TestPos = v2(Pos.X+r, Pos.Y-r); break;
2253 case 3: TestPos = v2(Pos.X-r, Pos.Y); break;
2254 case 4: TestPos = v2(Pos.X+r, Pos.Y); break;
2255 case 5: TestPos = v2(Pos.X-r, Pos.Y+r); break;
2256 case 6: TestPos = v2(Pos.X, Pos.Y+r); break;
2257 case 7: TestPos = v2(Pos.X+r, Pos.Y+r); break;
2259 if (Level->IsValidPos(TestPos)) {
2260 square *TestSquare = GetNearSquare(TestPos);
2261 character *Dude = TestSquare->GetCharacter();
2263 if (Dude && Dude->IsEnabled() && Dude->CanBeSeenBy(this, false, true)) {
2264 if (GetRelation(Dude) != HOSTILE) CandidateDirections[dir] = BLOCKED;
2265 else if (GetRelation(Dude) == HOSTILE && CandidateDirections[dir] != BLOCKED) {
2266 //then load this candidate position direction into the vector of possible throw directions
2267 CandidateDirections[dir] = SUCCESS;
2268 HostileFound = 1;
2275 if (HostileFound) {
2276 for (int dir = 0; dir < MDIR_STAND; ++dir) {
2277 if (CandidateDirections[dir] == SUCCESS && !TargetFound) {
2278 ThrowDirection = dir;
2279 TargetFound = 1;
2280 break;
2283 if (!TargetFound) return false;
2284 } else {
2285 return false;
2287 //fprintf(stderr, "throw: has target.\n");
2288 // check inventory for throwing weapon
2289 itemvector ItemVector;
2290 GetStack()->FillItemVector(ItemVector);
2291 for (uInt c = 0; c < ItemVector.size(); ++c) {
2292 if (ItemVector[c]->IsThrowingWeapon()) {
2293 ToBeThrown = ItemVector[c];
2294 break;
2297 if (!ToBeThrown) return false;
2298 //fprintf(stderr, "throw: has throwing weapon.\n");
2299 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s throws %s.", CHAR_NAME(DEFINITE), ToBeThrown->CHAR_NAME(INDEFINITE));
2300 ThrowItem(ThrowDirection, ToBeThrown);
2301 EditExperience(ARM_STRENGTH, 75, 1<<8);
2302 EditExperience(DEXTERITY, 75, 1<<8);
2303 EditExperience(PERCEPTION, 75, 1<<8);
2304 EditNP(-50);
2305 DexterityAction(5);
2306 TerminateGoingTo();
2307 return true;
2311 truth character::CheckAIZapOpportunity () {
2312 if (/*!IsRangedAttacker() || */ !CanZap() || !IsHumanoid() || !IsSmall() || !IsEnabled()) return false; // total gum
2313 // Steps:
2314 // (1) - Acquire target as nearest enemy
2315 // (2) - Check that this enemy is in range, and is in appropriate direction; no friendly fire!
2316 // (3) - check inventory for zappable item
2317 // (4) - zap item in direction where the enemy is
2318 //Check the rest of the visible area for hostiles
2319 v2 Pos = GetPos();
2320 v2 TestPos;
2321 int SensibleRange = 5;
2322 int RangeMax = GetLOSRange();
2323 if (RangeMax < SensibleRange) SensibleRange = RangeMax;
2324 int CandidateDirections[7] = {0, 0, 0, 0, 0, 0, 0};
2325 int HostileFound = 0;
2326 int ZapDirection = 0;
2327 int TargetFound = 0;
2328 item *ToBeZapped = 0;
2329 level *Level = GetLevel();
2331 for (int r = 2; r <= SensibleRange; ++r) {
2332 for (int dir = 0; dir < MDIR_STAND; ++dir) {
2333 switch (dir) {
2334 case 0: TestPos = v2(Pos.X-r, Pos.Y-r); break;
2335 case 1: TestPos = v2(Pos.X, Pos.Y-r); break;
2336 case 2: TestPos = v2(Pos.X+r, Pos.Y-r); break;
2337 case 3: TestPos = v2(Pos.X-r, Pos.Y); break;
2338 case 4: TestPos = v2(Pos.X+r, Pos.Y); break;
2339 case 5: TestPos = v2(Pos.X-r, Pos.Y+r); break;
2340 case 6: TestPos = v2(Pos.X, Pos.Y+r); break;
2341 case 7: TestPos = v2(Pos.X+r, Pos.Y+r); break;
2343 if (Level->IsValidPos(TestPos)) {
2344 square *TestSquare = GetNearSquare(TestPos);
2345 character *Dude = TestSquare->GetCharacter();
2347 if (Dude && Dude->IsEnabled() && Dude->CanBeSeenBy(this, false, true)) {
2348 if (GetRelation(Dude) != HOSTILE) CandidateDirections[dir] = BLOCKED;
2349 else if (GetRelation(Dude) == HOSTILE && CandidateDirections[dir] != BLOCKED) {
2350 //then load this candidate position direction into the vector of possible zap directions
2351 CandidateDirections[dir] = SUCCESS;
2352 HostileFound = 1;
2359 if (HostileFound) {
2360 for (int dir = 0; dir < MDIR_STAND; ++dir) {
2361 if (CandidateDirections[dir] == SUCCESS && !TargetFound) {
2362 ZapDirection = dir;
2363 TargetFound = 1;
2364 break;
2367 if (!TargetFound) return false;
2368 } else {
2369 return false;
2371 // check inventory for zappable item
2372 itemvector ItemVector;
2373 GetStack()->FillItemVector(ItemVector);
2374 for (unsigned int c = 0; c < ItemVector.size(); ++c) {
2375 if (ItemVector[c]->GetMinCharges() > 0 && ItemVector[c]->GetPrice()) {
2376 // bald-faced gum solution for choosing zappables that have shots left.
2377 // MinCharges needs to be replaced. Empty wands have zero price!
2378 ToBeZapped = ItemVector[c];
2379 break;
2382 if (!ToBeZapped) return false;
2383 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s zaps %s.", CHAR_NAME(DEFINITE), ToBeZapped->CHAR_NAME(INDEFINITE));
2384 if (ToBeZapped->Zap(this, GetPos(), ZapDirection)) {
2385 EditAP(-100000/APBonus(GetAttribute(PERCEPTION)));
2386 return true;
2387 } else {
2388 return false;
2390 TerminateGoingTo();
2391 return true;
2395 truth character::FollowLeader (character *Leader) {
2396 if (!Leader || Leader == this || !IsEnabled()) return false;
2397 if ((CommandFlags&FOLLOW_LEADER) && Leader->CanBeSeenBy(this) && Leader->SquareUnderCanBeSeenBy(this, true)) {
2398 v2 Distance = GetPos()-GoingTo;
2399 if (abs(Distance.X) <= 2 && abs(Distance.Y) <= 2) return false;
2400 return MoveTowardsTarget(false);
2402 if (IsGoingSomeWhere()) {
2403 if (!MoveTowardsTarget(true)) {
2404 TerminateGoingTo();
2405 return false;
2407 return true;
2409 return false;
2413 void character::SeekLeader (ccharacter *Leader) {
2414 if (Leader && Leader != this) {
2415 if (Leader->CanBeSeenBy(this) && (Leader->SquareUnderCanBeSeenBy(this, true) || !IsGoingSomeWhere())) {
2416 if (CommandFlags&FOLLOW_LEADER) SetGoingTo(Leader->GetPos());
2417 } else if (!IsGoingSomeWhere()) {
2418 team *Team = GetTeam();
2419 for (std::list<character *>::const_iterator i = Team->GetMember().begin(); i != Team->GetMember().end(); ++i) {
2420 character *ch = *i;
2421 if (ch->IsEnabled() && ch->GetID() != GetID() &&
2422 (CommandFlags & FOLLOW_LEADER) == (ch->CommandFlags & FOLLOW_LEADER) && ch->CanBeSeenBy(this)) {
2423 v2 Pos = ch->GetPos();
2424 v2 Distance = GetPos()-Pos;
2425 if (abs(Distance.X) > 2 && abs(Distance.Y) > 2) {
2426 SetGoingTo(Pos);
2427 break;
2436 int character::GetMoveEase () const {
2437 switch (BurdenState) {
2438 case OVER_LOADED:
2439 case STRESSED: return 50;
2440 case BURDENED: return 75;
2441 case UNBURDENED: return 100;
2443 return 666;
2447 int character::GetLOSRange () const {
2448 if (!game::IsInWilderness()) return GetAttribute(PERCEPTION)*GetLevel()->GetLOSModifier()/48;
2449 return 3;
2453 truth character::Displace (character *Who, truth Forced) {
2454 if (GetBurdenState() == OVER_LOADED) {
2455 if (IsPlayer()) {
2456 cchar *CrawlVerb = StateIsActivated(LEVITATION) ? "float" : "crawl";
2457 ADD_MESSAGE("You try very hard to %s forward. But your load is too heavy.", CrawlVerb);
2458 EditAP(-1000);
2459 return true;
2461 return false;
2464 double Danger = GetRelativeDanger(Who);
2465 int PriorityDifference = Limit(GetDisplacePriority()-Who->GetDisplacePriority(), -31, 31);
2467 if (IsPlayer()) ++PriorityDifference;
2468 else if (Who->IsPlayer()) --PriorityDifference;
2470 if (PriorityDifference >= 0) Danger *= 1 << PriorityDifference;
2471 else Danger /= 1 << -PriorityDifference;
2473 if (IsSmall() && Who->IsSmall() &&
2474 (Forced || Danger > 1.0 || !(Who->IsPlayer() || Who->IsBadPath(GetPos()))) &&
2475 !IsStuck() && !Who->IsStuck() && (!Who->GetAction() || Who->GetAction()->TryDisplace()) &&
2476 CanMove() && Who->CanMove() && Who->CanMoveOn(GetLSquareUnder())) {
2477 if (IsPlayer()) ADD_MESSAGE("You displace %s!", Who->CHAR_DESCRIPTION(DEFINITE));
2478 else if (Who->IsPlayer()) ADD_MESSAGE("%s displaces you!", CHAR_DESCRIPTION(DEFINITE));
2479 else if (CanBeSeenByPlayer() || Who->CanBeSeenByPlayer()) ADD_MESSAGE("%s displaces %s!", CHAR_DESCRIPTION(DEFINITE), Who->CHAR_DESCRIPTION(DEFINITE));
2480 lsquare *OldSquareUnder1[MAX_SQUARES_UNDER];
2481 lsquare *OldSquareUnder2[MAX_SQUARES_UNDER];
2482 for (int c = 0; c < GetSquaresUnder(); ++c) OldSquareUnder1[c] = GetLSquareUnder(c);
2483 for (int c = 0; c < Who->GetSquaresUnder(); ++c) OldSquareUnder2[c] = Who->GetLSquareUnder(c);
2484 v2 Pos = GetPos();
2485 v2 WhoPos = Who->GetPos();
2486 Remove();
2487 Who->Remove();
2488 PutTo(WhoPos);
2489 Who->PutTo(Pos);
2490 EditAP(-GetMoveAPRequirement(GetSquareUnder()->GetEntryDifficulty()) - 500);
2491 EditNP(-12*GetSquareUnder()->GetEntryDifficulty());
2492 EditExperience(AGILITY, 75, GetSquareUnder()->GetEntryDifficulty() << 7);
2493 if (IsPlayer()) ShowNewPosInfo();
2494 if (Who->IsPlayer()) Who->ShowNewPosInfo();
2495 SignalStepFrom(OldSquareUnder1);
2496 Who->SignalStepFrom(OldSquareUnder2);
2497 return true;
2498 } else {
2499 if (IsPlayer()) {
2500 ADD_MESSAGE("%s resists!", Who->CHAR_DESCRIPTION(DEFINITE));
2501 EditAP(-1000);
2502 return true;
2504 return false;
2509 void character::SetNP (sLong What) {
2510 int OldState = GetHungerState();
2511 NP = What;
2512 if (IsPlayer()) {
2513 int NewState = GetHungerState();
2514 if (NewState == STARVING && OldState > STARVING) DeActivateVoluntaryAction(CONST_S("You are getting really hungry."));
2515 else if (NewState == VERY_HUNGRY && OldState > VERY_HUNGRY) DeActivateVoluntaryAction(CONST_S("You are getting very hungry."));
2516 else if (NewState == HUNGRY && OldState > HUNGRY) DeActivateVoluntaryAction(CONST_S("You are getting hungry."));
2521 void character::ShowNewPosInfo () const {
2522 msgsystem::EnterBigMessageMode();
2523 v2 Pos = GetPos();
2525 if (ivanconfig::GetAutoCenterMap()) {
2526 game::UpdateCameraX();
2527 game::UpdateCameraY();
2528 } else {
2529 if (Pos.X < game::GetCamera().X+3 || Pos.X >= game::GetCamera().X+game::GetScreenXSize()-3) game::UpdateCameraX();
2530 if (Pos.Y < game::GetCamera().Y+3 || Pos.Y >= game::GetCamera().Y+game::GetScreenYSize()-3) game::UpdateCameraY();
2533 game::SendLOSUpdateRequest();
2534 game::DrawEverythingNoBlit();
2535 UpdateESPLOS();
2537 if (!game::IsInWilderness()) {
2538 if (GetLSquareUnder()->IsDark() && !game::GetSeeWholeMapCheatMode()) ADD_MESSAGE("It's dark in here!");
2540 GetLSquareUnder()->ShowSmokeMessage();
2541 itemvectorvector PileVector;
2542 GetStackUnder()->Pile(PileVector, this, CENTER);
2544 if (PileVector.size()) {
2545 truth Feel = !GetLSquareUnder()->IsTransparent() || GetLSquareUnder()->IsDark();
2547 if (PileVector.size() == 1) {
2548 if (Feel) {
2549 ADD_MESSAGE("You feel %s lying here.", PileVector[0][0]->GetName(INDEFINITE, PileVector[0].size()).CStr());
2550 } else {
2551 if (ivanconfig::GetShowFullItemDesc() && PileVector[0][0]->AllowDetailedDescription()) {
2552 festring text;
2554 PileVector[0][0]->AddInventoryEntry(PLAYER, text, PileVector[0].size(), true);
2555 //fprintf(stderr, "invdsc : [%s]\n", text.CStr());
2556 ADD_MESSAGE("%s %s lying here.", text.CStr(), PileVector[0].size() == 1 ? "is" : "are");
2557 } else {
2558 ADD_MESSAGE("%s %s lying here.", PileVector[0][0]->GetName(INDEFINITE, PileVector[0].size()).CStr(), PileVector[0].size() == 1 ? "is" : "are");
2561 fprintf(stderr, "description: [%s]\n", PileVector[0][0]->GetDescription(INDEFINITE).CStr());
2562 fprintf(stderr, "strength : [%s]\n", PileVector[0][0]->GetStrengthValueDescription());
2563 fprintf(stderr, "basetohit : [%s]\n", PileVector[0][0]->GetBaseToHitValueDescription());
2564 fprintf(stderr, "baseblock : [%s]\n", PileVector[0][0]->GetBaseBlockValueDescription());
2565 fprintf(stderr, "extdsc : [%s]\n", PileVector[0][0]->GetExtendedDescription().CStr());
2568 } else {
2569 int Items = 0;
2570 for (uInt c = 0; c < PileVector.size(); ++c) {
2571 if ((Items += PileVector[c].size()) > 3) break;
2573 if (Items > 3) {
2574 if (Feel) ADD_MESSAGE("You feel several items lying here.");
2575 else ADD_MESSAGE("Several items are lying here.");
2576 } else if (Items) {
2577 if (Feel) ADD_MESSAGE("You feel a few items lying here.");
2578 else ADD_MESSAGE("A few items are lying here.");
2583 festring SideItems;
2584 GetLSquareUnder()->GetSideItemDescription(SideItems);
2586 if (!SideItems.IsEmpty()) ADD_MESSAGE("There is %s.", SideItems.CStr());
2588 if (GetLSquareUnder()->HasEngravings()) {
2589 if (CanRead()) ADD_MESSAGE("Something has been engraved here: \"%s\"", GetLSquareUnder()->GetEngraved());
2590 else ADD_MESSAGE("Something has been engraved here.");
2594 msgsystem::LeaveBigMessageMode();
2598 void character::Hostility (character *Enemy) {
2599 if (Enemy == this || !Enemy || !Team || !Enemy->Team) return;
2600 if (Enemy->IsMasochist() && GetRelation(Enemy) == FRIEND) return;
2601 if (!IsAlly(Enemy)) {
2602 GetTeam()->Hostility(Enemy->GetTeam());
2603 } else if (IsPlayer() && !Enemy->IsPlayer()) {
2604 // I believe both may be players due to polymorph feature...
2605 if (Enemy->CanBeSeenByPlayer()) ADD_MESSAGE("%s becomes enraged.", Enemy->CHAR_NAME(DEFINITE));
2606 Enemy->ChangeTeam(game::GetTeam(BETRAYED_TEAM));
2611 stack *character::GetGiftStack () const {
2612 if (GetLSquareUnder()->GetRoomIndex() && !GetLSquareUnder()->GetRoom()->AllowDropGifts()) return GetStack();
2613 return GetStackUnder();
2617 truth character::MoveRandomlyInRoom () {
2618 for (int c = 0; c < 10; ++c) {
2619 v2 ToTry = game::GetMoveVector(RAND()&7);
2620 if (GetLevel()->IsValidPos(GetPos()+ToTry)) {
2621 lsquare *Square = GetNearLSquare(GetPos()+ToTry);
2622 if (!Square->IsDangerous(this) && !Square->IsScary(this) &&
2623 (!Square->GetOLTerrain() || !Square->GetOLTerrain()->IsDoor()) &&
2624 TryMove(ToTry, false, false)) return true;
2627 return false;
2631 //#define dirlogf(...) do { fprintf(stderr, __VA_ARGS__); } while (0)
2632 #define dirlogf(...) ((void)0)
2635 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 };
2636 static const bool orthoDir[MDIR_STAND] = { false, true, false, true, true, false, true, false };
2639 // only for ortho moveDir
2640 static inline truth IsDirExcluded (int moveDir, int dir) {
2641 if (moveDir == dir) return true;
2642 switch (moveDir) {
2643 case MDIR_UP: return (dir == MDIR_UP_LEFT || dir == MDIR_UP_RIGHT);
2644 case MDIR_LEFT: return (dir == MDIR_UP_LEFT || dir == MDIR_DOWN_LEFT);
2645 case MDIR_RIGHT: return (dir == MDIR_UP_RIGHT || dir == MDIR_DOWN_RIGHT);
2646 case MDIR_DOWN: return (dir == MDIR_DOWN_LEFT || dir == MDIR_DOWN_RIGHT);
2648 return false;
2652 truth character::IsPassableSquare (int x, int y) const {
2653 if (x >= 0 && y >= 0) {
2654 area *ca = GetSquareUnder()->GetArea();
2655 lsquare *sq;
2657 if (x >= ca->GetXSize() || y >= ca->GetYSize()) return false;
2658 sq = static_cast<lsquare *>(ca->GetSquare(x, y));
2659 return sq && CanMoveOn(sq);
2661 return false;
2665 void character::CountPossibleMoveDirs (cv2 pos, int *odirs, int *ndirs, int exclideDir) const {
2666 if (odirs) *odirs = 0;
2667 if (ndirs) *ndirs = 0;
2668 for (int f = 0; f < MDIR_STAND; ++f) {
2669 if (!IsDirExcluded(exclideDir, f)) {
2670 if (IsPassableSquare(pos+game::GetMoveVector(f))) {
2671 if (orthoDir[f]) {
2672 if (odirs) ++(*odirs);
2673 } else {
2674 if (ndirs) ++(*ndirs);
2683 * in corridor (for orto-dirs):
2684 * count dirs excluding ortho-dir we going:
2685 * if there is one or less ortho-dirs and one or less non-ortho-dirs, we are in corridor
2687 // only for ortho-dirs
2688 truth character::IsInCorridor (int x, int y, int moveDir) const {
2689 int od = 0, nd = 0;
2691 dirlogf("IsInCorridor(%d,%d,%d)\n", x, y, moveDir);
2692 // reverse moveDir
2693 moveDir = (moveDir >= 0 && moveDir < MDIR_STAND ? revDir[moveDir] : -1);
2694 dirlogf(" reversedDir: %d\n", moveDir);
2695 CountPossibleMoveDirs(v2(x, y), &od, &nd, moveDir);
2696 dirlogf(" possibleDirs: (%d:%d)\n", od, nd);
2697 dirlogf(" IsInCorridor: %s\n", ((od <= 1 && nd <= 1) ? "yes" : "no"));
2698 return (od <= 1 && nd <= 1);
2702 cv2 character::GetDiagonalForDirs (int moveDir, int newDir) const {
2703 switch (moveDir) {
2704 case MDIR_UP:
2705 switch (newDir) {
2706 case MDIR_LEFT: return game::GetMoveVector(MDIR_UP_LEFT);
2707 case MDIR_RIGHT: return game::GetMoveVector(MDIR_UP_RIGHT);
2709 break;
2710 case MDIR_DOWN:
2711 switch (newDir) {
2712 case MDIR_LEFT: return game::GetMoveVector(MDIR_DOWN_LEFT);
2713 case MDIR_RIGHT: return game::GetMoveVector(MDIR_DOWN_RIGHT);
2715 break;
2716 case MDIR_LEFT:
2717 switch (newDir) {
2718 case MDIR_UP: return game::GetMoveVector(MDIR_UP_LEFT);
2719 case MDIR_DOWN: return game::GetMoveVector(MDIR_DOWN_LEFT);
2721 break;
2722 case MDIR_RIGHT:
2723 switch (newDir) {
2724 case MDIR_UP: return game::GetMoveVector(MDIR_UP_RIGHT);
2725 case MDIR_DOWN: return game::GetMoveVector(MDIR_DOWN_RIGHT);
2727 break;
2729 ABORT("wtf in character::GetDiagonalForDirs()");
2733 truth character::IsInTunnelDeadEnd () const {
2734 int od, nd;
2736 CountPossibleMoveDirs(GetPos(), &od, &nd, -1);
2737 return (od <= 1 && nd == 0);
2742 * try to walk in the given dir
2743 * can do two steps without a turn and still in corridor?
2744 * yes:
2745 * just go
2746 * no:
2747 * go in non-ortho dir, set prevdir to last ortho-dir from corridor tracing
2749 // only for ortho-dirs; assume that the char is in corridor
2750 int character::CheckCorridorMove (v2 &moveVector, cv2 pos, int moveDir, truth *markAsTurn) const {
2751 v2 ps1(pos+(moveVector = game::GetMoveVector(moveDir)));
2753 if (markAsTurn) *markAsTurn = true;
2755 if (IsPassableSquare(ps1)) {
2756 // we can do first step in the given dir
2757 // check if we will be in corridor after it
2758 dirlogf("CheckCorridorMove: can do first step\n");
2759 if (IsInCorridor(ps1, moveDir)) {
2760 // check second step
2761 v2 ps2(ps1+moveVector);
2762 dirlogf("CheckCorridorMove: still in corridor after the first step\n");
2763 if (IsPassableSquare(ps2)) {
2764 // can do second step
2765 dirlogf("CheckCorridorMove: can do second step\n");
2766 return moveDir;
2767 } else {
2768 // can't do second step; but we still in corridor, so we should make a turn
2769 int newDir = -1; // direction to turn
2770 for (int f = 0; f < MDIR_STAND; ++f) {
2771 if (f != moveDir && orthoDir[f] && f != revDir[moveDir] && IsPassableSquare(ps1+game::GetMoveVector(f))) {
2772 newDir = f;
2773 break;
2776 dirlogf("CheckCorridorMove: can't do second step; moveDir=%d; newDir=%d\n", moveDir, newDir);
2777 if (newDir < 0) {
2778 // dead end, will stop
2779 //ABORT("wtd in character::CheckCorridorMove()");
2780 return moveDir;
2782 // we should do diagonal move
2783 moveVector = GetDiagonalForDirs(moveDir, newDir);
2784 // if this is 'one-tile-turn', we should not change the direction to newDir
2785 if (IsPassableSquare(ps1+game::GetMoveVector(newDir)+game::GetMoveVector(moveDir))) {
2786 // yes, this is 'one-tile-turn'
2787 dirlogf("CheckCorridorMove: one-tile-turn, don't change dir\n");
2788 /* 'g'o bug:
2790 * ####.######
2791 * ####*......
2792 * ..@..######
2793 * ######
2795 * 'g'o right: should stop at '*', but it just goes right
2797 if (markAsTurn) *markAsTurn = IsInCorridor(ps1+game::GetMoveVector(newDir), newDir);
2798 newDir = moveDir;
2800 return newDir;
2803 dirlogf("CheckCorridorMove: can do one or two steps; move forward\n");
2804 // can do one or two steps: check for T-junction
2805 // we should stop if we have more than two open dirs, or one of open dirs is not moveDir
2806 int dcount = 0;
2807 for (int f = 0; f < MDIR_STAND; ++f) {
2808 if (f == revDir[moveDir]) continue; // skip "reverse dir" check
2809 v2 ps2(pos+game::GetMoveVector(f));
2810 if (IsPassableSquare(ps2)) {
2811 ++dcount;
2812 if (dcount > 2) return -1; // more than two open dirs, stop
2813 if (f != moveDir) return -1; // one of open dirs is not moveDir
2816 // just move forward
2817 return moveDir;
2819 dirlogf("CheckCorridorMove: dead end\n");
2820 // can't go, assume invalid direction
2821 return -1;
2825 truth character::IsDangerousSquare (v2 pos) const {
2826 lsquare *MoveToSquare[MAX_SQUARES_UNDER];
2827 auto Squares = CalculateNewSquaresUnder(MoveToSquare, pos);
2828 for (decltype(Squares) c = 0; c < Squares; ++c) {
2829 lsquare *Square = MoveToSquare[c];
2830 // check if someone is standing at the square
2831 if (Square->GetCharacter() && GetTeam() != Square->GetCharacter()->GetTeam() && Square->GetCharacter()->CanBeSeenBy(this)) return true;
2832 if (Square->IsDangerous(this) && Square->CanBeSeenBy(this)) return true;
2834 return false;
2838 void character::GoOn (go *Go, truth FirstStep) {
2839 dirlogf("=== character::GoOn; dir=%d; pos=(%d,%d) ===\n", Go->GetDirection(), GetPos().X, GetPos().Y);
2840 if (FirstStep) {
2841 dirlogf("FirstStep\n");
2842 mPrevMoveDir = Go->GetDirection();
2843 Go->SetIsWalkingInOpen(!IsInCorridor(Go->GetDirection()));
2846 v2 MoveVector = ApplyStateModification(game::GetMoveVector(Go->GetDirection()));
2847 lsquare *MoveToSquare[MAX_SQUARES_UNDER];
2848 lsquare *MoveToSquare2[MAX_SQUARES_UNDER];
2849 int Squares = CalculateNewSquaresUnder(MoveToSquare, GetPos()+MoveVector);
2850 int moveDir = game::MoveVectorToDirection(MoveVector);
2852 if (!Squares || !CanMoveOn(MoveToSquare[0])) {
2853 dirlogf("just can't move\n");
2854 Go->Terminate(false);
2855 return;
2858 if (!FirstStep) {
2859 if (!Go->GetPrevWasTurn() && Go->IsWalkingInOpen() != !IsInCorridor(GetPos(), moveDir)) {
2860 dirlogf("moved to/from open place\n");
2861 Go->Terminate(false);
2862 return;
2865 uInt OldRoomIndex = GetLSquareUnder()->GetRoomIndex();
2866 uInt CurrentRoomIndex = MoveToSquare[0]->GetRoomIndex();
2868 if (OldRoomIndex && (CurrentRoomIndex != OldRoomIndex)) {
2869 // room about to be changed, stop here
2870 dirlogf("room about to be changed\n");
2871 Go->Terminate(false);
2872 return;
2874 // stop near the dangerous square
2875 if (IsDangerousSquare(GetPos()+MoveVector)) {
2876 dirlogf("sense the danger\n");
2877 Go->Terminate(false);
2878 return;
2881 // if the state modified the direction, move and stop
2882 if (moveDir != Go->GetDirection()) {
2883 dirlogf("move affected by state\n");
2884 if (TryMove(MoveVector, true, game::PlayerIsRunning())) {
2885 game::DrawEverything();
2886 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
2888 Go->Terminate(false);
2889 return;
2892 truth doStop = false, markAsTurn = false;
2894 if (!FirstStep) {
2895 // continuous walking
2896 if (Go->IsWalkingInOpen() || !orthoDir[moveDir]) {
2897 // walking in open space or diagonal walking
2898 v2 newPos(GetPos()+MoveVector);
2899 int ood, ond, nod, nnd;
2901 * open: stop if # of possible dirs in next step != # of possible dirs in current step
2902 * (or next step is in corridor)
2904 dirlogf("open walking\n");
2905 if (IsInCorridor(newPos, moveDir)) {
2906 // trying to enter the corridor, stop right here
2907 dirlogf("entering the corridor\n");
2908 Go->Terminate(false);
2909 return;
2911 CountPossibleMoveDirs(GetPos(), &ood, &ond);
2912 CountPossibleMoveDirs(newPos, &nod, &nnd);
2913 if (ood != nod || ond != nnd) {
2914 // # of directions to walk to changed, stop right here
2915 dirlogf("# of directions changed from (%d:%d) to (%d:%d)\n", ood, ond, nod, nnd);
2916 //Go->Terminate(false);
2917 //return;
2918 doStop = true;
2920 // ok, we can do this move
2921 } else {
2922 // ortho-walking thru the corridor
2923 int newDir = CheckCorridorMove(MoveVector, GetPos(), moveDir, &markAsTurn);
2924 if (newDir < 0) {
2925 // ah, something weird; stop right here
2926 Go->Terminate(false);
2927 return;
2929 Go->SetDirection(newDir); // perform possible turn
2931 // stop near the dangerous square
2932 for (int mdv = 0; mdv < MDIR_STAND; ++mdv) {
2933 if (IsDangerousSquare(GetPos()+MoveVector+game::GetMoveVector(mdv))) {
2934 dirlogf(" danger!\n");
2935 Go->Terminate(false);
2936 return;
2939 } else {
2940 // first step, just do it
2943 // now try to perform the move
2944 dirlogf("trying to make the move\n");
2946 square *BeginSquare = GetSquareUnder();
2947 uInt OldRoomIndex = GetLSquareUnder()->GetRoomIndex();
2948 uInt CurrentRoomIndex = MoveToSquare[0]->GetRoomIndex();
2950 // stop on the square with something interesting
2951 if (!doStop) {
2952 // idiotic code!
2953 area *ca = GetSquareUnder()->GetArea();
2954 v2 npos = GetPos()+MoveVector;
2955 for (int f = 0; f < MDIR_STAND; ++f) {
2956 v2 np = npos+game::GetMoveVector(f);
2957 if (np.X >= 0 && np.Y >= 0 && np.X < ca->GetXSize() && np.Y < ca->GetYSize()) {
2958 lsquare *sq = static_cast<lsquare *>(ca->GetSquare(np.X, np.Y));
2959 if (IsPlayer() && !sq->HasBeenSeen()) continue;
2960 olterrain *terra = sq->GetOLTerrain();
2961 if (terra) {
2962 dirlogf("** OK terra at %d; door: %s; seen: %s\n", f, (terra->IsDoor() ? "yes" : "no"), (sq->IsGoSeen() ? "yes" : "no"));
2963 if (terra->IsDoor()) {
2964 if (ivanconfig::GetStopOnSeenDoors() || !sq->IsGoSeen()) {
2965 dirlogf(" *** stop near the door\n");
2966 doStop = true;
2967 break;
2973 // check items
2974 if (!doStop) {
2975 for (int c = 0; c < Squares; ++c) {
2976 lsquare *Square = MoveToSquare[c];
2977 if (IsPlayer() && !Square->HasBeenSeen()) continue;
2978 if (Square->GetStack()->HasSomethingFunny(this, ivanconfig::GetStopOnCorpses(), ivanconfig::GetStopOnSeenItems())) {
2979 dirlogf(" stepped near something interesting\n");
2980 doStop = true;
2981 break;
2985 // check items in adjacent squares too, so diagonal move won't miss any
2986 if (!doStop) {
2987 for (int f = 0; f < MDIR_STAND && !doStop; ++f) {
2988 v2 np = game::GetMoveVector(f);
2989 if (np == MoveVector) continue; // this will be checked on the next move
2990 if (!IsPassableSquare(GetPos()+np)) continue;
2991 int sq2 = CalculateNewSquaresUnder(MoveToSquare2, GetPos()+np);
2992 for (int c = 0; c < sq2; ++c) {
2993 lsquare *Square = MoveToSquare2[c];
2994 if (!Square->CanBeSeenBy(this)) continue;
2995 if (Square->GetStack()->HasSomethingFunny(this, ivanconfig::GetStopOnCorpses(), ivanconfig::GetStopOnSeenItems())) {
2996 dirlogf(" stepped near something interesting\n");
2997 //HACK: mark all items as stepped on
2998 for (int d = 0; d < MDIR_STAND; ++d) {
2999 np = game::GetMoveVector(d);
3000 if (!IsPassableSquare(GetPos()+np)) continue;
3001 sq2 = CalculateNewSquaresUnder(MoveToSquare2, GetPos()+np);
3002 for (int n = 0; n < sq2; ++n) MoveToSquare2[n]->GetStack()->SetSteppedOn(true);
3004 // and stop
3005 Go->Terminate(false);
3006 return;
3013 Go->SetPrevWasTurn(markAsTurn && MoveVector.X && MoveVector.Y); // diagonal move?
3015 truth moveOk = TryMove(MoveVector, true, game::PlayerIsRunning());
3017 if (!moveOk || BeginSquare == GetSquareUnder() || (CurrentRoomIndex && (OldRoomIndex != CurrentRoomIndex))) {
3018 dirlogf(" stopped\n");
3019 if (moveOk) {
3020 game::DrawEverything();
3021 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
3023 Go->Terminate(false);
3024 return;
3027 if (FirstStep) {
3028 mPrevMoveDir = Go->GetDirection();
3029 Go->SetIsWalkingInOpen(!IsInCorridor(moveDir));
3032 game::DrawEverything();
3033 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
3034 if (doStop) Go->Terminate(false);
3038 void character::SetTeam (team *What) {
3039 Team = What;
3040 What->Add(this);
3044 void character::ChangeTeam (team *What) {
3045 if (Team) Team->Remove(this);
3046 Team = What;
3047 SendNewDrawRequest();
3048 if (Team) Team->Add(this);
3052 truth character::ChangeRandomAttribute (int HowMuch) {
3053 for (int c = 0; c < 50; ++c) {
3054 int AttribID = RAND()%ATTRIBUTES;
3055 if (EditAttribute(AttribID, HowMuch)) return true;
3057 return false;
3061 int character::RandomizeReply (sLong &Said, int Replies) {
3062 truth NotSaid = false;
3063 for (int c = 0; c < Replies; ++c) {
3064 if (!(Said & (1 << c))) {
3065 NotSaid = true;
3066 break;
3069 if (!NotSaid) Said = 0;
3070 sLong ToSay;
3071 while (Said & 1 << (ToSay = RAND() % Replies));
3072 Said |= 1 << ToSay;
3073 return ToSay;
3077 void character::DisplayInfo (festring &Msg) {
3078 if (IsPlayer()) {
3079 Msg << " You are " << GetStandVerb() << " here.";
3080 } else {
3081 Msg << ' ' << GetName(INDEFINITE).CapitalizeCopy() << " is " << GetStandVerb() << " here. " << GetPersonalPronoun().CapitalizeCopy();
3082 cchar *Separator1 = GetAction() ? "," : " and";
3083 cchar *Separator2 = " and";
3084 if (GetTeam() == PLAYER->GetTeam()) {
3085 Msg << " is tame";
3086 } else {
3087 int Relation = GetRelation(PLAYER);
3088 if (Relation == HOSTILE) Msg << " is hostile";
3089 else if (Relation == UNCARING) {
3090 Msg << " does not care about you";
3091 Separator1 = Separator2 = " and is";
3092 } else {
3093 Msg << " is friendly";
3096 if (StateIsActivated(PANIC)) {
3097 Msg << Separator1 << " panicked";
3098 Separator2 = " and";
3100 if (GetAction()) Msg << Separator2 << ' ' << GetAction()->GetDescription();
3101 Msg << '.';
3106 void character::TestWalkability () {
3107 if (!IsEnabled()) return;
3108 square *SquareUnder = !game::IsInWilderness() ? GetSquareUnder() : PLAYER->GetSquareUnder();
3109 if (SquareUnder->IsFatalToStay() && !CanMoveOn(SquareUnder)) {
3110 truth Alive = false;
3111 if (!game::IsInWilderness() || IsPlayer()) {
3112 for (int d = 0; d < GetNeighbourSquares(); ++d) {
3113 square *Square = GetNeighbourSquare(d);
3114 if (Square && CanMoveOn(Square) && IsFreeForMe(Square)) {
3115 if (IsPlayer()) ADD_MESSAGE("%s.", SquareUnder->SurviveMessage(this));
3116 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE), SquareUnder->MonsterSurviveMessage(this));
3117 Move(Square->GetPos(), true); // actually, this shouldn't be a teleport move
3118 SquareUnder->SurviveEffect(this);
3119 Alive = true;
3120 break;
3124 if (!Alive) {
3125 if (IsPlayer()) {
3126 Remove();
3127 SendToHell();
3128 festring DeathMsg = festring(SquareUnder->DeathMessage(this));
3129 game::AskForEscPress(DeathMsg+".");
3130 festring Msg = SquareUnder->ScoreEntry(this);
3131 PLAYER->AddScoreEntry(Msg);
3132 game::End(Msg);
3133 } else {
3134 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE), SquareUnder->MonsterDeathVerb(this));
3135 Die(0, SquareUnder->ScoreEntry(this), DISALLOW_MSG);
3142 int character::GetSize () const {
3143 if (GetTorso()->GetSize() < 1) {
3144 fprintf(stderr, "WARNING: character::GetSize() is %d for %s!\n", GetTorso()->GetSize(), GetNameSingular().CStr());
3145 return 1;
3147 return GetTorso()->GetSize();
3151 void character::SetMainMaterial (material *NewMaterial, int SpecialFlags) {
3152 NewMaterial->SetVolume(GetBodyPart(0)->GetMainMaterial()->GetVolume());
3153 GetBodyPart(0)->SetMainMaterial(NewMaterial, SpecialFlags);
3154 for (int c = 1; c < BodyParts; ++c) {
3155 NewMaterial = NewMaterial->SpawnMore(GetBodyPart(c)->GetMainMaterial()->GetVolume());
3156 GetBodyPart(c)->SetMainMaterial(NewMaterial, SpecialFlags);
3161 void character::ChangeMainMaterial (material *NewMaterial, int SpecialFlags) {
3162 NewMaterial->SetVolume(GetBodyPart(0)->GetMainMaterial()->GetVolume());
3163 GetBodyPart(0)->ChangeMainMaterial(NewMaterial, SpecialFlags);
3164 for (int c = 1; c < BodyParts; ++c) {
3165 NewMaterial = NewMaterial->SpawnMore(GetBodyPart(c)->GetMainMaterial()->GetVolume());
3166 GetBodyPart(c)->ChangeMainMaterial(NewMaterial, SpecialFlags);
3171 void character::SetSecondaryMaterial (material *, int) {
3172 ABORT("Illegal character::SetSecondaryMaterial call!");
3176 void character::ChangeSecondaryMaterial (material *, int) {
3177 ABORT("Illegal character::ChangeSecondaryMaterial call!");
3181 void character::TeleportRandomly (truth Intentional) {
3182 v2 TelePos = ERROR_V2;
3183 if (StateIsActivated(TELEPORT_CONTROL)) {
3184 if (IsPlayer()) {
3185 v2 Input = game::PositionQuestion(CONST_S("Where do you wish to teleport? [direction keys move cursor, space accepts]"), GetPos(), &game::TeleportHandler, 0, false);
3186 if (Input == ERROR_V2) Input = GetPos(); // esc pressed
3187 lsquare *Square = GetNearLSquare(Input);
3188 if (CanMoveOn(Square) || game::GoThroughWallsCheatIsActive()) {
3189 if (Square->GetPos() == GetPos()) {
3190 ADD_MESSAGE("You disappear and reappear.");
3191 return;
3193 if (IsFreeForMe(Square)) {
3194 if ((Input-GetPos()).GetLengthSquare() <= GetTeleportRangeSquare()) {
3195 EditExperience(INTELLIGENCE, 100, 1 << 10);
3196 TelePos = Input;
3197 } else {
3198 ADD_MESSAGE("You cannot concentrate yourself enough to control a teleport that far.");
3200 } else {
3201 character *C = Square->GetCharacter();
3202 if (C) ADD_MESSAGE("For a moment you feel very much like %s.", C->CHAR_NAME(INDEFINITE));
3203 else ADD_MESSAGE("You feel that something weird has happened, but can't really tell what exactly.");
3205 } else {
3206 ADD_MESSAGE("You feel like having been hit by something really hard from the inside.");
3208 } else if (!Intentional) {
3209 if (IsGoingSomeWhere() && GetLevel()->IsValidPos(GoingTo)) {
3210 v2 Where = GetLevel()->GetNearestFreeSquare(this, GoingTo);
3211 if (Where != ERROR_V2 && (Where-GetPos()).GetLengthSquare() <= GetTeleportRangeSquare()) {
3212 EditExperience(INTELLIGENCE, 100, 1 << 10);
3213 Where = TelePos;
3219 if (IsPlayer()) {
3220 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.");
3223 //if (TelePos != ERROR_V2) Move(TelePos, true);
3224 //else Move(GetLevel()->GetRandomSquare(this), true);
3225 //if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE));
3226 //if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
3228 if (TelePos == ERROR_V2) TelePos = GetLevel()->GetRandomSquare(this);
3230 room *PossibleRoom = game::GetCurrentLevel()->GetLSquare(TelePos)->GetRoom();
3232 if (!PossibleRoom) {
3233 //if it's outside of a room
3234 if (TelePos != ERROR_V2) Move(TelePos, true); else Move(GetLevel()->GetRandomSquare(this), true);
3235 if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE));
3236 if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
3237 } else if (PossibleRoom && PossibleRoom->IsOKToTeleportInto()) {
3238 // If it's inside of a room, check whether a ward is active that might impede the player
3239 if (TelePos != ERROR_V2) Move(TelePos, true); else Move(GetLevel()->GetRandomSquare(this), true);
3240 if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE));
3241 if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
3242 } else {
3243 if (IsPlayer()){
3244 ADD_MESSAGE("A mighty force blasts you back to where you were standing. A ward prevents you from teleporting.");
3246 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);
3248 beamdata Beam
3250 this,
3251 CONST_S("killed by an explosion triggered when attempting to teleport into room protected by a ward"),
3252 YOURSELF,
3253 3 // or 0 ?
3255 lsquare* Square = GetNearLSquare(GetPos());
3256 Square->DrawParticles(RED);
3257 Square->FireBall(Beam);*/
3262 void character::DoDetecting () {
3263 material *TempMaterial;
3265 for (;;) {
3266 festring Temp = game::DefaultQuestion(CONST_S("What material do you want to detect?"), game::GetDefaultDetectMaterial());
3267 TempMaterial = protosystem::CreateMaterial(Temp);
3268 if (TempMaterial) break;
3269 game::DrawEverythingNoBlit();
3272 level *Level = GetLevel();
3273 int Squares = Level->DetectMaterial(TempMaterial);
3275 if (Squares > GetAttribute(INTELLIGENCE) * (25+RAND()%51)) {
3276 ADD_MESSAGE("An enormous burst of geographical information overwhelms your consciousness. Your mind cannot cope with it and your memories blur.");
3277 Level->BlurMemory();
3278 BeginTemporaryState(CONFUSED, 1000 + RAND() % 1000);
3279 EditExperience(INTELLIGENCE, -100, 1 << 12);
3280 } else if (!Squares) {
3281 ADD_MESSAGE("You feel a sudden urge to imagine the dark void of a starless night sky.");
3282 EditExperience(INTELLIGENCE, 200, 1 << 12);
3283 } else {
3284 ADD_MESSAGE("You feel attracted to all things made of %s.", TempMaterial->GetName(false, false).CStr());
3285 game::PositionQuestion(CONST_S("Detecting material [direction keys move cursor, space exits]"), GetPos(), 0, 0, false);
3286 EditExperience(INTELLIGENCE, 300, 1 << 12);
3289 delete TempMaterial;
3290 Level->CalculateLuminances();
3291 game::SendLOSUpdateRequest();
3295 void character::RestoreHP () {
3296 doforbodyparts()(this, &bodypart::FastRestoreHP);
3297 HP = MaxHP;
3301 void character::RestoreLivingHP () {
3302 HP = 0;
3303 for (int c = 0; c < BodyParts; ++c) {
3304 bodypart *BodyPart = GetBodyPart(c);
3305 if (BodyPart && BodyPart->CanRegenerate()) {
3306 BodyPart->FastRestoreHP();
3307 HP += BodyPart->GetHP();
3313 truth character::AllowDamageTypeBloodSpill (int Type) {
3314 switch (Type&0xFFF) {
3315 case PHYSICAL_DAMAGE:
3316 case SOUND:
3317 case ENERGY:
3318 return true;
3319 case ACID:
3320 case FIRE:
3321 case DRAIN:
3322 case POISON:
3323 case ELECTRICITY:
3324 case MUSTARD_GAS_DAMAGE:
3325 case PSI:
3326 return false;
3328 ABORT("Unknown blood effect destroyed the dungeon!");
3329 return false;
3333 /* Returns truly done damage */
3334 int character::ReceiveBodyPartDamage (character *Damager, int Damage, int Type, int BodyPartIndex,
3335 int Direction, truth PenetrateResistance, truth Critical, truth ShowNoDamageMsg, truth CaptureBodyPart)
3337 bodypart *BodyPart = GetBodyPart(BodyPartIndex);
3338 if (!Damager || Damager->AttackMayDamageArmor()) BodyPart->DamageArmor(Damager, Damage, Type);
3339 if (!PenetrateResistance) {
3340 Damage -= (BodyPart->GetTotalResistance(Type)>>1)+RAND()%((BodyPart->GetTotalResistance(Type)>>1)+1);
3342 if (int(Damage) < 1) {
3343 if (Critical) {
3344 Damage = 1;
3345 } else {
3346 if (ShowNoDamageMsg) {
3347 if (IsPlayer()) ADD_MESSAGE("You are not hurt.");
3348 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s is not hurt.", GetPersonalPronoun().CStr());
3350 return 0;
3354 if (Critical && AllowDamageTypeBloodSpill(Type) && !game::IsInWilderness()) {
3355 BodyPart->SpillBlood(2+(RAND()&1));
3356 for (int d = 0; d < GetNeighbourSquares(); ++d) {
3357 lsquare *Square = GetNeighbourLSquare(d);
3358 if (Square && Square->IsFlyable()) BodyPart->SpillBlood(1, Square->GetPos());
3362 if (BodyPart->ReceiveDamage(Damager, Damage, Type, Direction) && BodyPartCanBeSevered(BodyPartIndex)) {
3363 if (DamageTypeDestroysBodyPart(Type)) {
3364 if (IsPlayer()) ADD_MESSAGE("Your %s is destroyed!", BodyPart->GetBodyPartName().CStr());
3365 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s is destroyed!", GetPossessivePronoun().CStr(), BodyPart->GetBodyPartName().CStr());
3366 GetBodyPart(BodyPartIndex)->DropEquipment();
3367 item *Severed = SevereBodyPart(BodyPartIndex);
3368 if (Severed) Severed->DestroyBodyPart(!game::IsInWilderness() ? GetStackUnder() : GetStack());
3369 SendNewDrawRequest();
3370 if (IsPlayer()) game::AskForEscPress(CONST_S("Bodypart destroyed!"));
3371 } else {
3372 if (IsPlayer()) ADD_MESSAGE("Your %s is severed off!", BodyPart->GetBodyPartName().CStr());
3373 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s is severed off!", GetPossessivePronoun().CStr(), BodyPart->GetBodyPartName().CStr());
3374 item *Severed = SevereBodyPart(BodyPartIndex);
3375 SendNewDrawRequest();
3376 if (Severed) {
3377 if (CaptureBodyPart) {
3378 Damager->GetLSquareUnder()->AddItem(Severed);
3379 } else if (!game::IsInWilderness()) {
3380 /** No multi-tile humanoid support! */
3381 GetStackUnder()->AddItem(Severed);
3382 if (Direction != YOURSELF) Severed->Fly(0, Direction, Damage);
3383 } else {
3384 GetStack()->AddItem(Severed);
3386 Severed->DropEquipment();
3387 } else if (IsPlayer() || CanBeSeenByPlayer()) {
3388 ADD_MESSAGE("It vanishes.");
3390 if (IsPlayer()) game::AskForEscPress(CONST_S("Bodypart severed!"));
3392 if (CanPanicFromSeveredBodyPart() && RAND()%100 < GetPanicLevel() && !StateIsActivated(PANIC) && !IsDead() && !StateIsActivated(FEARLESS)) {
3393 BeginTemporaryState(PANIC, 1000+RAND()%1001);
3395 SpecialBodyPartSeverReaction();
3398 if (!IsDead()) CheckPanic(500);
3400 return Damage;
3404 /* Returns 0 if bodypart disappears */
3405 item *character::SevereBodyPart (int BodyPartIndex, truth ForceDisappearance, stack *EquipmentDropStack) {
3406 bodypart *BodyPart = GetBodyPart(BodyPartIndex);
3407 if (StateIsActivated(LEPROSY)) BodyPart->GetMainMaterial()->SetIsInfectedByLeprosy(true);
3408 if (ForceDisappearance || BodyPartsDisappearWhenSevered() || StateIsActivated(POLYMORPHED) || game::AllBodyPartsVanish()) {
3409 BodyPart->DropEquipment(EquipmentDropStack);
3410 BodyPart->RemoveFromSlot();
3411 CalculateAttributeBonuses();
3412 CalculateBattleInfo();
3413 BodyPart->SendToHell();
3414 SignalPossibleTransparencyChange();
3415 RemoveTraps(BodyPartIndex);
3416 return 0;
3418 BodyPart->SetOwnerDescription("of " + GetName(INDEFINITE));
3419 BodyPart->SetIsUnique(LeftOversAreUnique());
3420 UpdateBodyPartPicture(BodyPartIndex, true);
3421 BodyPart->RemoveFromSlot();
3422 BodyPart->RandomizePosition();
3423 CalculateAttributeBonuses();
3424 CalculateBattleInfo();
3425 BodyPart->Enable();
3426 SignalPossibleTransparencyChange();
3427 RemoveTraps(BodyPartIndex);
3428 return BodyPart;
3432 /* The second int is actually TargetFlags, which is not used here, but seems to be used in humanoid::ReceiveDamage.
3433 * Returns true if the character really receives damage */
3434 truth character::ReceiveDamage (character *Damager, int Damage, int Type, int, int Direction,
3435 truth, truth PenetrateArmor, truth Critical, truth ShowMsg)
3437 truth Affected = ReceiveBodyPartDamage(Damager, Damage, Type, 0, Direction, PenetrateArmor, Critical, ShowMsg);
3438 if (DamageTypeAffectsInventory(Type)) {
3439 for (int c = 0; c < GetEquipments(); ++c) {
3440 item *Equipment = GetEquipment(c);
3441 if (Equipment) Equipment->ReceiveDamage(Damager, Damage, Type);
3443 GetStack()->ReceiveDamage(Damager, Damage, Type);
3445 return Affected;
3449 festring character::GetDescription (int Case) const {
3450 if (IsPlayer()) return CONST_S("you");
3451 if (CanBeSeenByPlayer()) return GetName(Case);
3452 return CONST_S("something");
3456 festring character::GetPersonalPronoun (truth PlayersView) const {
3457 if (IsPlayer() && PlayersView) return CONST_S("you");
3458 if (GetSex() == UNDEFINED || (PlayersView && !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("it");
3459 if (GetSex() == MALE) return CONST_S("he");
3460 return CONST_S("she");
3464 festring character::GetPossessivePronoun (truth PlayersView) const {
3465 if (IsPlayer() && PlayersView) return CONST_S("your");
3466 if (GetSex() == UNDEFINED || (PlayersView && !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("its");
3467 if (GetSex() == MALE) return CONST_S("his");
3468 return CONST_S("her");
3472 festring character::GetObjectPronoun (truth PlayersView) const {
3473 if (IsPlayer() && PlayersView) return CONST_S("you");
3474 if (GetSex() == UNDEFINED || (PlayersView && !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("it");
3475 if (GetSex() == MALE) return CONST_S("him");
3476 return CONST_S("her");
3480 void character::AddName (festring &String, int Case) const {
3481 if (AssignedName.IsEmpty()) {
3482 id::AddName(String, Case);
3483 } else if (!(Case & PLURAL)) {
3484 if (!ShowClassDescription()) {
3485 String << AssignedName;
3486 } else {
3487 String << AssignedName << ' ';
3488 id::AddName(String, (Case|ARTICLE_BIT)&~INDEFINE_BIT);
3490 } else {
3491 id::AddName(String, Case);
3492 String << " named " << AssignedName;
3497 int character::GetHungerState () const {
3498 if (!UsesNutrition()) return NOT_HUNGRY;
3499 if (GetNP() > OVER_FED_LEVEL) return OVER_FED;
3500 if (GetNP() > BLOATED_LEVEL) return BLOATED;
3501 if (GetNP() > SATIATED_LEVEL) return SATIATED;
3502 if (GetNP() > NOT_HUNGER_LEVEL) return NOT_HUNGRY;
3503 if (GetNP() > HUNGER_LEVEL) return HUNGRY;
3504 if (GetNP() > VERY_HUNGER_LEVEL) return VERY_HUNGRY;
3505 return STARVING;
3509 truth character::CanConsume (material *Material) const {
3510 return GetConsumeFlags() & Material->GetConsumeType();
3514 void character::SetTemporaryStateCounter (sLong State, int What) {
3515 for (int c = 0; c < STATES; ++c) {
3516 if ((1 << c) & State) TemporaryStateCounter[c] = What;
3521 void character::EditTemporaryStateCounter (sLong State, int What) {
3522 for (int c = 0; c < STATES; ++c) {
3523 if ((1 << c) & State) TemporaryStateCounter[c] += What;
3528 int character::GetTemporaryStateCounter (sLong State) const {
3529 for (int c = 0; c < STATES; ++c) {
3530 if ((1 << c) & State) return TemporaryStateCounter[c];
3532 ABORT("Illegal GetTemporaryStateCounter request!");
3533 return 0;
3537 truth character::CheckKick () const {
3538 if (!CanKick()) {
3539 if (IsPlayer()) ADD_MESSAGE("This race can't kick.");
3540 return false;
3542 return true;
3546 int character::GetResistance (int Type) const {
3547 switch (Type&0xFFF) {
3548 case PHYSICAL_DAMAGE:
3549 case DRAIN:
3550 case MUSTARD_GAS_DAMAGE:
3551 case PSI:
3552 return 0;
3553 case ENERGY: return GetEnergyResistance();
3554 case FIRE: return GetFireResistance();
3555 case POISON: return GetPoisonResistance();
3556 case ELECTRICITY: return GetElectricityResistance();
3557 case ACID: return GetAcidResistance();
3558 case SOUND: return GetSoundResistance();
3560 ABORT("Resistance lack detected!");
3561 return 0;
3565 void character::Regenerate () {
3566 if (HP == MaxHP) return;
3567 sLong RegenerationBonus = 0;
3568 truth NoHealableBodyParts = true;
3569 for (int c = 0; c < BodyParts; ++c) {
3570 bodypart *BodyPart = GetBodyPart(c);
3571 if (BodyPart && BodyPart->CanRegenerate()) {
3572 RegenerationBonus += BodyPart->GetMaxHP();
3573 if (NoHealableBodyParts && BodyPart->GetHP() < BodyPart->GetMaxHP()) NoHealableBodyParts = false;
3576 if (!RegenerationBonus || NoHealableBodyParts) return;
3577 RegenerationBonus *= (50+GetAttribute(ENDURANCE));
3579 if (Action && Action->IsRest()) {
3580 if (SquaresUnder == 1) RegenerationBonus *= GetSquareUnder()->GetRestModifier() << 1;
3581 else {
3582 int Lowest = GetSquareUnder(0)->GetRestModifier();
3583 for (int c = 1; c < GetSquaresUnder(); ++c) {
3584 int Mod = GetSquareUnder(c)->GetRestModifier();
3585 if (Mod < Lowest) Lowest = Mod;
3587 RegenerationBonus *= Lowest << 1;
3591 RegenerationCounter += RegenerationBonus;
3593 while (RegenerationCounter > 1250000) {
3594 bodypart *BodyPart = HealHitPoint();
3595 if (!BodyPart) break;
3596 EditNP(-Max(7500/MaxHP, 1));
3597 RegenerationCounter -= 1250000;
3598 int HP = BodyPart->GetHP();
3599 EditExperience(ENDURANCE, Min(1000*BodyPart->GetMaxHP()/(HP*HP), 300), 1000);
3604 void character::PrintInfo () const {
3605 felist Info(CONST_S("Information about ")+GetName(DEFINITE));
3606 for (int c = 0; c < GetEquipments(); ++c) {
3607 item *Equipment = GetEquipment(c);
3608 if ((EquipmentEasilyRecognized(c) || game::WizardModeIsActive()) && Equipment) {
3609 int ImageKey = game::AddToItemDrawVector(itemvector(1, Equipment));
3610 Info.AddEntry(festring(GetEquipmentName(c))+": "+Equipment->GetName(INDEFINITE), LIGHT_GRAY, 0, ImageKey, true);
3613 if (Info.IsEmpty()) {
3614 ADD_MESSAGE("There's nothing special to tell about %s.", CHAR_NAME(DEFINITE));
3615 } else {
3616 game::SetStandardListAttributes(Info);
3617 Info.SetEntryDrawer(game::ItemEntryDrawer);
3618 Info.Draw();
3620 game::ClearItemDrawVector();
3624 truth character::TryToRiseFromTheDead () {
3625 for (int c = 0; c < BodyParts; ++c) {
3626 bodypart *BodyPart = GetBodyPart(c);
3627 if (BodyPart) {
3628 BodyPart->ResetSpoiling();
3629 if (BodyPart->CanRegenerate() || BodyPart->GetHP() < 1) BodyPart->SetHP(1);
3632 ResetStates();
3633 return true;
3637 truth character::RaiseTheDead (character *) {
3638 truth Useful = false;
3639 for (int c = 0; c < BodyParts; ++c) {
3640 bodypart *BodyPart = GetBodyPart(c);
3641 if (!BodyPart && CanCreateBodyPart(c)) {
3642 CreateBodyPart(c)->SetHP(1);
3643 if (IsPlayer()) ADD_MESSAGE("Suddenly you grow a new %s.", GetBodyPartName(c).CStr());
3644 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE), GetBodyPartName(c).CStr());
3645 Useful = true;
3646 } else if (BodyPart && BodyPart->CanRegenerate() && BodyPart->GetHP() < 1) {
3647 BodyPart->SetHP(1);
3650 if (!Useful) {
3651 if (IsPlayer()) ADD_MESSAGE("You shudder.");
3652 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s shudders.", CHAR_NAME(DEFINITE));
3654 return Useful;
3658 void character::SetSize (int Size) {
3659 for (int c = 0; c < BodyParts; ++c) {
3660 bodypart *BodyPart = GetBodyPart(c);
3661 if (BodyPart) BodyPart->SetSize(GetBodyPartSize(c, Size));
3666 sLong character::GetBodyPartSize (int I, int TotalSize) const {
3667 if (I == TORSO_INDEX) return TotalSize;
3668 ABORT("Weird bodypart size request for a character!");
3669 return 0;
3673 sLong character::GetBodyPartVolume (int I) const {
3674 if (I == TORSO_INDEX) return GetTotalVolume();
3675 ABORT("Weird bodypart volume request for a character!");
3676 return 0;
3680 void character::CreateBodyParts (int SpecialFlags) {
3681 for (int c = 0; c < BodyParts; ++c) if (CanCreateBodyPart(c)) CreateBodyPart(c, SpecialFlags);
3685 void character::RestoreBodyParts () {
3686 for (int c = 0; c < BodyParts; ++c) if (!GetBodyPart(c) && CanCreateBodyPart(c)) CreateBodyPart(c);
3690 void character::UpdatePictures () {
3691 if (!PictureUpdatesAreForbidden()) for (int c = 0; c < BodyParts; ++c) UpdateBodyPartPicture(c, false);
3695 bodypart *character::MakeBodyPart (int I) const {
3696 if (I == TORSO_INDEX) return normaltorso::Spawn(0, NO_MATERIALS);
3697 ABORT("Weird bodypart to make for a character!");
3698 return 0;
3702 bodypart *character::CreateBodyPart (int I, int SpecialFlags) {
3703 bodypart *BodyPart = MakeBodyPart(I);
3704 material *Material = CreateBodyPartMaterial(I, GetBodyPartVolume(I));
3705 BodyPart->InitMaterials(Material, false);
3706 BodyPart->SetSize(GetBodyPartSize(I, GetTotalSize()));
3707 BodyPart->SetBloodMaterial(GetBloodMaterial());
3708 BodyPart->SetNormalMaterial(Material->GetConfig());
3709 BodyPart->SetHP(1);
3710 SetBodyPart(I, BodyPart);
3711 BodyPart->InitSpecialAttributes();
3712 if (!(SpecialFlags & NO_PIC_UPDATE)) UpdateBodyPartPicture(I, false);
3713 if (!IsInitializing()) {
3714 CalculateBattleInfo();
3715 SendNewDrawRequest();
3716 SignalPossibleTransparencyChange();
3718 return BodyPart;
3722 v2 character::GetBodyPartBitmapPos (int I, truth) const {
3723 if (I == TORSO_INDEX) return GetTorsoBitmapPos();
3724 ABORT("Weird bodypart BitmapPos request for a character!");
3725 return v2();
3729 void character::UpdateBodyPartPicture (int I, truth Severed) {
3730 bodypart *BP = GetBodyPart(I);
3731 if (BP) {
3732 BP->SetBitmapPos(GetBodyPartBitmapPos(I, Severed));
3733 BP->GetMainMaterial()->SetSkinColor(GetBodyPartColorA(I, Severed));
3734 BP->GetMainMaterial()->SetSkinColorIsSparkling(GetBodyPartSparkleFlags(I) & SPARKLING_A);
3735 BP->SetMaterialColorB(GetBodyPartColorB(I, Severed));
3736 BP->SetMaterialColorC(GetBodyPartColorC(I, Severed));
3737 BP->SetMaterialColorD(GetBodyPartColorD(I, Severed));
3738 BP->SetSparkleFlags(GetBodyPartSparkleFlags(I));
3739 BP->SetSpecialFlags(GetSpecialBodyPartFlags(I));
3740 BP->SetWobbleData(GetBodyPartWobbleData(I));
3741 BP->UpdatePictures();
3746 void character::LoadDataBaseStats () {
3747 for (int c = 0; c < BASE_ATTRIBUTES; ++c) {
3748 BaseExperience[c] = DataBase->NaturalExperience[c];
3749 if (BaseExperience[c]) LimitRef(BaseExperience[c], MIN_EXP, MAX_EXP);
3751 SetMoney(GetDefaultMoney());
3752 SetInitialSweatMaterial(GetSweatMaterial());
3753 const fearray<sLong> &Skills = GetKnownCWeaponSkills();
3754 if (Skills.Size) {
3755 const fearray<sLong> &Hits = GetCWeaponSkillHits();
3756 if (Hits.Size == 1) {
3757 for (uInt c = 0; c < Skills.Size; ++c) {
3758 if (Skills[c] < AllowedWeaponSkillCategories) CWeaponSkill[Skills[c]].AddHit(Hits[0]*100);
3760 } else if (Hits.Size == Skills.Size) {
3761 for (uInt c = 0; c < Skills.Size; ++c) {
3762 if (Skills[c] < AllowedWeaponSkillCategories) CWeaponSkill[Skills[c]].AddHit(Hits[c]*100);
3764 } else {
3765 ABORT("Illegal weapon skill hit array size detected!");
3771 character *characterprototype::SpawnAndLoad (inputfile &SaveFile) const {
3772 character *Char = Spawner(0, LOAD);
3773 Char->Load(SaveFile);
3774 Char->CalculateAll();
3775 return Char;
3779 void character::Initialize (int NewConfig, int SpecialFlags) {
3780 Flags |= C_INITIALIZING|C_IN_NO_MSG_MODE;
3781 CalculateBodyParts();
3782 CalculateAllowedWeaponSkillCategories();
3783 CalculateSquaresUnder();
3784 BodyPartSlot = new bodypartslot[BodyParts];
3785 OriginalBodyPartID = new std::list<feuLong>[BodyParts];
3786 CWeaponSkill = new cweaponskill[AllowedWeaponSkillCategories];
3787 SquareUnder = new square*[SquaresUnder];
3789 if (SquaresUnder == 1) *SquareUnder = 0; else memset(SquareUnder, 0, SquaresUnder*sizeof(square *));
3791 for (int c = 0; c < BodyParts; ++c) BodyPartSlot[c].SetMaster(this);
3793 if (!(SpecialFlags & LOAD)) {
3794 ID = game::CreateNewCharacterID(this);
3795 databasecreator<character>::InstallDataBase(this, NewConfig);
3796 LoadDataBaseStats();
3797 TemporaryState |= GetClassStates();
3798 if (TemporaryState) {
3799 for (int c = 0; c < STATES; ++c) if (TemporaryState & (1 << c)) TemporaryStateCounter[c] = PERMANENT;
3802 CreateBodyParts(SpecialFlags | NO_PIC_UPDATE);
3803 InitSpecialAttributes();
3804 CommandFlags = GetDefaultCommandFlags();
3806 if (GetAttribute(INTELLIGENCE, false) < 8) CommandFlags &= ~DONT_CONSUME_ANYTHING_VALUABLE; // gum
3807 if (!GetDefaultName().IsEmpty()) SetAssignedName(GetDefaultName());
3810 if (!(SpecialFlags & LOAD)) PostConstruct();
3812 if (!(SpecialFlags & LOAD)) {
3813 if (!(SpecialFlags & NO_EQUIPMENT)) CreateInitialEquipment((SpecialFlags & NO_EQUIPMENT_PIC_UPDATE) >> 1);
3814 if (!(SpecialFlags & NO_PIC_UPDATE)) UpdatePictures();
3815 CalculateAll();
3816 RestoreHP();
3817 RestoreStamina();
3820 Flags &= ~(C_INITIALIZING|C_IN_NO_MSG_MODE);
3824 truth character::TeleportNear (character *Caller) {
3825 v2 Where = GetLevel()->GetNearestFreeSquare(this, Caller->GetPos());
3826 if (Where == ERROR_V2) return false;
3827 Move(Where, true);
3828 return true;
3832 void character::ReceiveHeal (sLong Amount) {
3833 int c;
3834 for (c = 0; c < Amount / 10; ++c) if (!HealHitPoint()) break;
3835 Amount -= c*10;
3836 if (RAND()%10 < Amount) HealHitPoint();
3837 if (Amount >= 250 || RAND()%250 < Amount) {
3838 bodypart *NewBodyPart = GenerateRandomBodyPart();
3839 if (!NewBodyPart) return;
3840 NewBodyPart->SetHP(1);
3841 if (IsPlayer()) ADD_MESSAGE("You grow a new %s.", NewBodyPart->GetBodyPartName().CStr());
3842 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE), NewBodyPart->GetBodyPartName().CStr());
3847 void character::AddHealingLiquidConsumeEndMessage () const {
3848 if (IsPlayer()) ADD_MESSAGE("You feel better.");
3849 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks healthier.", CHAR_NAME(DEFINITE));
3853 void character::ReceiveSchoolFood (sLong SizeOfEffect) {
3854 SizeOfEffect += RAND()%SizeOfEffect;
3855 if (SizeOfEffect >= 250) VomitAtRandomDirection(SizeOfEffect);
3856 if (!(RAND() % 3) && SizeOfEffect >= 500 && EditAttribute(ENDURANCE, SizeOfEffect/500)) {
3857 if (IsPlayer()) ADD_MESSAGE("You gain a little bit of toughness for surviving this stuff.");
3858 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks tougher.", CHAR_NAME(DEFINITE));
3860 BeginTemporaryState(POISONED, (SizeOfEffect>>1));
3864 void character::AddSchoolFoodConsumeEndMessage () const {
3865 if (IsPlayer()) ADD_MESSAGE("Yuck! This stuff tasted like vomit and old mousepads.");
3869 void character::AddSchoolFoodHitMessage () const {
3870 if (IsPlayer()) ADD_MESSAGE("Yuck! This stuff feels like vomit and old mousepads.");
3874 void character::ReceiveNutrition (sLong SizeOfEffect) {
3875 EditNP(SizeOfEffect);
3879 void character::ReceiveOmmelUrine (sLong Amount) {
3880 EditExperience(ARM_STRENGTH, 500, Amount<<4);
3881 EditExperience(LEG_STRENGTH, 500, Amount<<4);
3882 if (IsPlayer()) game::DoEvilDeed(Amount/25);
3886 void character::ReceiveOmmelCerumen (sLong Amount) {
3887 EditExperience(INTELLIGENCE, 500, Amount << 5);
3888 EditExperience(WISDOM, 500, Amount << 5);
3889 if (IsPlayer()) game::DoEvilDeed(Amount / 25);
3893 void character::ReceiveOmmelSweat (sLong Amount) {
3894 EditExperience(AGILITY, 500, Amount << 4);
3895 EditExperience(DEXTERITY, 500, Amount << 4);
3896 RestoreStamina();
3897 if (IsPlayer()) game::DoEvilDeed(Amount / 25);
3901 void character::ReceiveOmmelTears (sLong Amount) {
3902 EditExperience(PERCEPTION, 500, Amount << 4);
3903 EditExperience(CHARISMA, 500, Amount << 4);
3904 if (IsPlayer()) game::DoEvilDeed(Amount / 25);
3908 void character::ReceiveOmmelSnot (sLong Amount) {
3909 EditExperience(ENDURANCE, 500, Amount << 5);
3910 RestoreLivingHP();
3911 if (IsPlayer()) game::DoEvilDeed(Amount / 25);
3915 void character::ReceiveOmmelBone (sLong Amount) {
3916 EditExperience(ARM_STRENGTH, 500, Amount << 6);
3917 EditExperience(LEG_STRENGTH, 500, Amount << 6);
3918 EditExperience(DEXTERITY, 500, Amount << 6);
3919 EditExperience(AGILITY, 500, Amount << 6);
3920 EditExperience(ENDURANCE, 500, Amount << 6);
3921 EditExperience(PERCEPTION, 500, Amount << 6);
3922 EditExperience(INTELLIGENCE, 500, Amount << 6);
3923 EditExperience(WISDOM, 500, Amount << 6);
3924 EditExperience(CHARISMA, 500, Amount << 6);
3925 RestoreLivingHP();
3926 RestoreStamina();
3927 if (IsPlayer()) game::DoEvilDeed(Amount / 25);
3931 void character::AddOmmelConsumeEndMessage () const {
3932 if (IsPlayer()) ADD_MESSAGE("You feel a primitive force coursing through your veins.");
3933 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks more powerful.", CHAR_NAME(DEFINITE));
3937 void character::ReceivePepsi (sLong Amount) {
3938 ReceiveDamage(0, Amount / 100, POISON, TORSO);
3939 EditExperience(PERCEPTION, Amount, 1 << 14);
3940 if (CheckDeath(CONST_S("was poisoned by pepsi"), 0)) return;
3941 if (IsPlayer()) game::DoEvilDeed(Amount / 10);
3945 void character::AddPepsiConsumeEndMessage () const {
3946 if (IsPlayer()) ADD_MESSAGE("Urgh. You feel your guruism fading away.");
3947 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks very lame.", CHAR_NAME(DEFINITE));
3951 void character::ReceiveDarkness (sLong Amount) {
3952 EditExperience(INTELLIGENCE, -Amount / 5, 1 << 13);
3953 EditExperience(WISDOM, -Amount / 5, 1 << 13);
3954 EditExperience(CHARISMA, -Amount / 5, 1 << 13);
3955 if (IsPlayer()) game::DoEvilDeed(int(Amount / 50));
3959 void character::AddFrogFleshConsumeEndMessage () const {
3960 if (IsPlayer()) ADD_MESSAGE("Arg. You feel the fate of a navastater placed upon you...");
3961 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks like a navastater.", CHAR_NAME(DEFINITE));
3965 void character::ReceiveKoboldFlesh (sLong) {
3966 /* As it is commonly known, the possibility of fainting per 500 cubic
3967 centimeters of kobold flesh is exactly 5%. */
3968 if (!(RAND() % 20)) {
3969 if (IsPlayer()) ADD_MESSAGE("You lose control of your legs and fall down.");
3970 LoseConsciousness(250 + RAND_N(250));
3975 void character::AddKoboldFleshConsumeEndMessage () const {
3976 if (IsPlayer()) ADD_MESSAGE("This stuff tasted really funny.");
3980 void character::AddKoboldFleshHitMessage () const {
3981 if (IsPlayer()) ADD_MESSAGE("You feel very funny.");
3985 void character::AddBoneConsumeEndMessage () const {
3986 if (IsPlayer()) ADD_MESSAGE("You feel like a hippie.");
3987 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s barks happily.", CHAR_NAME(DEFINITE)); // this suspects that nobody except dogs can eat bones
3990 truth character::RawEditAttribute (double &Experience, int Amount) const {
3991 /* Check if the attribute is disabled for creature */
3992 if (!Experience) return false;
3993 if ((Amount < 0 && Experience < 2 * EXP_MULTIPLIER) || (Amount > 0 && Experience > 999 * EXP_MULTIPLIER)) return false;
3994 Experience += Amount * EXP_MULTIPLIER;
3995 LimitRef<double>(Experience, MIN_EXP, MAX_EXP);
3996 return true;
4000 void character::DrawPanel (truth AnimationDraw) const {
4001 if (AnimationDraw) { DrawStats(true); return; }
4002 igraph::BlitBackGround(v2(19 + (game::GetScreenXSize() << 4), 0), v2(RES.X - 19 - (game::GetScreenXSize() << 4), RES.Y));
4003 igraph::BlitBackGround(v2(16, 45 + (game::GetScreenYSize() << 4)), v2(game::GetScreenXSize() << 4, 9));
4004 FONT->Printf(DOUBLE_BUFFER, v2(16, 45 + (game::GetScreenYSize() << 4)), WHITE, "%s", GetPanelName().CStr());
4005 game::UpdateAttributeMemory();
4006 int PanelPosX = RES.X - 96;
4007 int PanelPosY = DrawStats(false);
4008 PrintAttribute("End", ENDURANCE, PanelPosX, PanelPosY++);
4009 PrintAttribute("Per", PERCEPTION, PanelPosX, PanelPosY++);
4010 PrintAttribute("Int", INTELLIGENCE, PanelPosX, PanelPosY++);
4011 PrintAttribute("Wis", WISDOM, PanelPosX, PanelPosY++);
4012 PrintAttribute("Wil", WILL_POWER, PanelPosX, PanelPosY++);
4013 PrintAttribute("Cha", CHARISMA, PanelPosX, PanelPosY++);
4014 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Siz %d", GetSize());
4015 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), IsInBadCondition() ? RED : WHITE, "HP %d/%d", GetHP(), GetMaxHP());
4016 ++PanelPosY;
4017 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Gold: %d", GetMoney());
4018 ++PanelPosY;
4020 if (game::IsInWilderness())
4021 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Worldmap");
4022 else
4023 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "%s", game::GetCurrentDungeon()->GetShortLevelDescription(game::GetCurrentLevelIndex()).CapitalizeCopy().CStr());
4025 ivantime Time;
4026 game::GetTime(Time);
4027 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Day %d", Time.Day);
4028 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Time %d:%s%d", Time.Hour, Time.Min < 10 ? "0" : "", Time.Min);
4029 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Turn %d", game::GetTurn());
4031 ++PanelPosY;
4033 if (GetAction()) {
4034 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "%s", festring(GetAction()->GetDescription()).CapitalizeCopy().CStr());
4037 //printf("========= STATES =========\n");
4038 for (int c = 0; c < STATES; ++c) {
4039 //printf(" %d: %s (%s)\n", c, StateData[c].Description, (StateIsActivated(1<<c) ? "TAN" : "ona"));
4040 if (!(StateData[c].Flags & SECRET) && StateIsActivated(1 << c) && (1 << c != HASTE || !StateIsActivated(SLOW)) && (1 << c != SLOW || !StateIsActivated(HASTE))) {
4041 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), (1 << c) & EquipmentState || TemporaryStateCounter[c] >= PERMANENT ? BLUE : WHITE, "%s", StateData[c].Description);
4045 /* Make this more elegant!!! */
4046 switch (GetHungerState()) {
4047 case STARVING: FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), RED, "Starving"); break;
4048 case VERY_HUNGRY: FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), BLUE, "Very hungry"); break;
4049 case HUNGRY: FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), BLUE, "Hungry"); break;
4050 case SATIATED: FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Satiated"); break;
4051 case BLOATED: FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Bloated"); break;
4052 case OVER_FED: FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Overfed!"); break;
4055 switch (GetBurdenState()) {
4056 case OVER_LOADED: FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), RED, "Overload!"); break;
4057 case STRESSED: FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), BLUE, "Stressed"); break;
4058 case BURDENED: FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), BLUE, "Burdened"); break;
4061 switch (GetTirednessState()) {
4062 case FAINTING: FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), RED, "Fainting"); break;
4063 case EXHAUSTED: FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Exhausted"); break;
4066 if (game::PlayerIsRunning()) {
4067 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "%s", GetRunDescriptionLine(0));
4068 cchar *SecondLine = GetRunDescriptionLine(1);
4069 if (strlen(SecondLine)) FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "%s", SecondLine);
4074 void character::CalculateDodgeValue () {
4075 DodgeValue = 0.05 * GetMoveEase() * GetAttribute(AGILITY) / sqrt(GetSize());
4076 if (IsFlying()) DodgeValue *= 2;
4077 if (DodgeValue < 1) DodgeValue = 1;
4081 truth character::DamageTypeAffectsInventory (int Type) {
4082 switch (Type&0xFFF) {
4083 case SOUND:
4084 case ENERGY:
4085 case ACID:
4086 case FIRE:
4087 case ELECTRICITY:
4088 return true;
4089 case PHYSICAL_DAMAGE:
4090 case POISON:
4091 case DRAIN:
4092 case MUSTARD_GAS_DAMAGE:
4093 case PSI:
4094 return false;
4096 ABORT("Unknown reaping effect destroyed dungeon!");
4097 return false;
4101 int character::CheckForBlockWithArm (character *Enemy, item *Weapon, arm *Arm,
4102 double WeaponToHitValue, int Damage, int Success, int Type)
4104 int BlockStrength = Arm->GetBlockCapability();
4105 double BlockValue = Arm->GetBlockValue();
4106 if (BlockStrength && BlockValue) {
4107 item *Blocker = Arm->GetWielded();
4108 if (RAND() % int(100+WeaponToHitValue/BlockValue/(1<<BlocksSinceLastTurn)*(100+Success)) < 100) {
4109 int NewDamage = BlockStrength < Damage ? Damage-BlockStrength : 0;
4110 switch (Type) {
4111 case UNARMED_ATTACK: AddBlockMessage(Enemy, Blocker, Enemy->UnarmedHitNoun(), NewDamage); break;
4112 case WEAPON_ATTACK: AddBlockMessage(Enemy, Blocker, "attack", NewDamage); break;
4113 case KICK_ATTACK: AddBlockMessage(Enemy, Blocker, Enemy->KickNoun(), NewDamage); break;
4114 case BITE_ATTACK: AddBlockMessage(Enemy, Blocker, Enemy->BiteNoun(), NewDamage); break;
4116 sLong Weight = Blocker->GetWeight();
4117 sLong StrExp = Limit(15 * Weight / 200, 75, 300);
4118 sLong DexExp = Weight ? Limit(75000 / Weight, 75, 300) : 300;
4119 Arm->EditExperience(ARM_STRENGTH, StrExp, 1 << 8);
4120 Arm->EditExperience(DEXTERITY, DexExp, 1 << 8);
4121 EditStamina(-10000 / GetAttribute(ARM_STRENGTH), false);
4122 if (Arm->TwoHandWieldIsActive()) {
4123 arm *PairArm = Arm->GetPairArm();
4124 PairArm->EditExperience(ARM_STRENGTH, StrExp, 1 << 8);
4125 PairArm->EditExperience(DEXTERITY, DexExp, 1 << 8);
4127 Blocker->WeaponSkillHit(Enemy->CalculateWeaponSkillHits(this));
4128 Blocker->ReceiveDamage(this, Damage, PHYSICAL_DAMAGE);
4129 Blocker->BlockEffect(this, Enemy, Weapon, Type);
4130 if (Weapon) Weapon->ReceiveDamage(Enemy, Damage - NewDamage, PHYSICAL_DAMAGE);
4131 if (BlocksSinceLastTurn < 16) ++BlocksSinceLastTurn;
4132 return NewDamage;
4135 return Damage;
4139 sLong character::GetStateAPGain (sLong BaseAPGain) const {
4140 if (!StateIsActivated(HASTE) == !StateIsActivated(SLOW)) return BaseAPGain;
4141 if (StateIsActivated(HASTE)) return (BaseAPGain * 5) >> 2;
4142 return (BaseAPGain << 2) / 5;
4146 void character::SignalEquipmentAdd (int EquipmentIndex) {
4147 item *Equipment = GetEquipment(EquipmentIndex);
4148 if (Equipment->IsInCorrectSlot(EquipmentIndex)) {
4149 sLong AddedStates = Equipment->GetGearStates();
4150 if (AddedStates) {
4151 for (int c = 0; c < STATES; ++c) {
4152 if (AddedStates & (1 << c)) {
4153 if (!StateIsActivated(1 << c)) {
4154 if (!IsInNoMsgMode()) (this->*StateData[c].PrintBeginMessage)();
4155 EquipmentState |= 1 << c;
4156 if (StateData[c].BeginHandler) (this->*StateData[c].BeginHandler)();
4157 } else {
4158 EquipmentState |= 1 << c;
4164 if (!IsInitializing() && Equipment->IsInCorrectSlot(EquipmentIndex)) ApplyEquipmentAttributeBonuses(Equipment);
4168 void character::SignalEquipmentRemoval (int, citem *Item) {
4169 CalculateEquipmentState();
4170 if (CalculateAttributeBonuses()) CheckDeath(festring("lost ")+GetPossessivePronoun(false)+" vital "+Item->GetName(INDEFINITE));
4174 void character::CalculateEquipmentState () {
4175 sLong Back = EquipmentState;
4176 EquipmentState = 0;
4177 for (int c = 0; c < GetEquipments(); ++c) {
4178 item *Equipment = GetEquipment(c);
4179 if (Equipment && Equipment->IsInCorrectSlot(c)) EquipmentState |= Equipment->GetGearStates();
4181 for (int c = 0; c < STATES; ++c) {
4182 if (Back & (1 << c) && !StateIsActivated(1 << c)) {
4183 if (StateData[c].EndHandler) {
4184 (this->*StateData[c].EndHandler)();
4185 if (!IsEnabled()) return;
4187 if (!IsInNoMsgMode()) (this->*StateData[c].PrintEndMessage)();
4193 /* Counter = duration in ticks */
4194 void character::BeginTemporaryState (sLong State, int Counter) {
4195 if (!Counter) return;
4196 int Index;
4197 if (State == POLYMORPHED) ABORT("No Polymorphing with BeginTemporaryState!");
4198 for (Index = 0; Index < STATES; ++Index) if (1 << Index == State) break;
4199 if (Index == STATES) ABORT("BeginTemporaryState works only when State == 2^n!");
4200 if (TemporaryStateIsActivated(State)) {
4201 int OldCounter = GetTemporaryStateCounter(State);
4202 if (OldCounter != PERMANENT) EditTemporaryStateCounter(State, Max(Counter, 50-OldCounter));
4203 } else if (StateData[Index].IsAllowed == 0 || (this->*StateData[Index].IsAllowed)()) {
4204 SetTemporaryStateCounter(State, Max(Counter, 50));
4205 if (!EquipmentStateIsActivated(State)) {
4206 if (!IsInNoMsgMode()) (this->*StateData[Index].PrintBeginMessage)();
4207 ActivateTemporaryState(State);
4208 if (StateData[Index].BeginHandler) (this->*StateData[Index].BeginHandler)();
4209 } else {
4210 ActivateTemporaryState(State);
4216 void character::HandleStates () {
4217 if (!TemporaryState && !EquipmentState) return;
4218 for (int c = 0; c < STATES; ++c) {
4219 if (TemporaryState & (1 << c) && TemporaryStateCounter[c] != PERMANENT) {
4220 if (!--TemporaryStateCounter[c]) {
4221 TemporaryState &= ~(1 << c);
4222 if (!(EquipmentState & (1 << c))) {
4223 if (StateData[c].EndHandler) {
4224 (this->*StateData[c].EndHandler)();
4225 if (!IsEnabled()) return;
4227 if (!TemporaryStateCounter[c]) (this->*StateData[c].PrintEndMessage)();
4231 if (StateIsActivated(1 << c)) {
4232 if (StateData[c].Handler) (this->*StateData[c].Handler)();
4234 if (!IsEnabled()) return;
4239 void character::PrintBeginPolymorphControlMessage () const {
4240 if (IsPlayer()) ADD_MESSAGE("You feel your mind has total control over your body.");
4244 void character::PrintEndPolymorphControlMessage () const {
4245 if (IsPlayer()) ADD_MESSAGE("You are somehow uncertain of your willpower.");
4249 void character::PrintBeginLifeSaveMessage () const {
4250 if (IsPlayer()) ADD_MESSAGE("You hear Hell's gates being locked just now.");
4254 void character::PrintEndLifeSaveMessage () const {
4255 if (IsPlayer()) ADD_MESSAGE("You feel the Afterlife is welcoming you once again.");
4259 void character::PrintBeginLycanthropyMessage () const {
4260 if (IsPlayer()) ADD_MESSAGE("You suddenly notice you've always loved full moons.");
4264 void character::PrintEndLycanthropyMessage () const {
4265 if (IsPlayer()) ADD_MESSAGE("You feel the wolf inside you has had enough of your bad habits.");
4269 void character::PrintBeginVampirismMessage () const {
4270 if (IsPlayer()) ADD_MESSAGE("You suddenly decide you have always hated garlic.");
4274 void character::PrintEndVampirismMessage () const {
4275 if (IsPlayer()) ADD_MESSAGE("You recall your delight of the morning sunshine back in New Attnam. You are a vampire no longer.");
4279 void character::PrintBeginInvisibilityMessage () const {
4280 if ((PLAYER->StateIsActivated(INFRA_VISION) && IsWarm()) || (PLAYER->StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5)) {
4281 if (IsPlayer()) ADD_MESSAGE("You seem somehow transparent.");
4282 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s seems somehow transparent.", CHAR_NAME(DEFINITE));
4283 } else {
4284 if (IsPlayer()) ADD_MESSAGE("You fade away.");
4285 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s disappears!", CHAR_NAME(DEFINITE));
4290 void character::PrintEndInvisibilityMessage () const {
4291 if ((PLAYER->StateIsActivated(INFRA_VISION) && IsWarm()) || (PLAYER->StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5)) {
4292 if (IsPlayer()) ADD_MESSAGE("Your notice your transparency has ended.");
4293 else if (CanBeSeenByPlayer()) ADD_MESSAGE("The appearance of %s seems far more solid now.", CHAR_NAME(INDEFINITE));
4294 } else {
4295 if (IsPlayer()) ADD_MESSAGE("You reappear.");
4296 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s appears from nowhere!", CHAR_NAME(INDEFINITE));
4301 void character::PrintBeginInfraVisionMessage () const {
4302 if (IsPlayer()) {
4303 if (StateIsActivated(INVISIBLE) && IsWarm() && !(StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5))
4304 ADD_MESSAGE("You reappear.");
4305 else
4306 ADD_MESSAGE("You feel your perception being magically altered.");
4311 void character::PrintEndInfraVisionMessage () const {
4312 if (IsPlayer()) {
4313 if (StateIsActivated(INVISIBLE) && IsWarm() && !(StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5))
4314 ADD_MESSAGE("You disappear.");
4315 else
4316 ADD_MESSAGE("You feel your perception returning to normal.");
4321 void character::PrintBeginESPMessage () const {
4322 if (IsPlayer()) ADD_MESSAGE("You suddenly feel like being only a tiny part of a great network of intelligent minds.");
4326 void character::PrintEndESPMessage () const {
4327 if (IsPlayer()) ADD_MESSAGE("You are filled with desire to be just yourself from now on.");
4331 void character::PrintBeginHasteMessage () const {
4332 if (IsPlayer()) ADD_MESSAGE("Time slows down to a crawl.");
4333 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks faster!", CHAR_NAME(DEFINITE));
4337 void character::PrintEndHasteMessage () const {
4338 if (IsPlayer()) ADD_MESSAGE("Everything seems to move much faster now.");
4339 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks slower!", CHAR_NAME(DEFINITE));
4343 void character::PrintBeginSlowMessage () const {
4344 if (IsPlayer()) ADD_MESSAGE("Everything seems to move much faster now.");
4345 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks slower!", CHAR_NAME(DEFINITE));
4349 void character::PrintEndSlowMessage () const {
4350 if (IsPlayer()) ADD_MESSAGE("Time slows down to a crawl.");
4351 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks faster!", CHAR_NAME(DEFINITE));
4355 void character::EndPolymorph () {
4356 ForceEndPolymorph();
4360 character *character::ForceEndPolymorph () {
4361 if (IsPlayer()) {
4362 ADD_MESSAGE("You return to your true form.");
4363 } else if (game::IsInWilderness()) {
4364 ActivateTemporaryState(POLYMORPHED);
4365 SetTemporaryStateCounter(POLYMORPHED, 10);
4366 return this; // fast gum solution, state ends when the player enters a dungeon
4368 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s returns to %s true form.", CHAR_NAME(DEFINITE), GetPossessivePronoun().CStr());
4369 RemoveTraps();
4370 if (GetAction()) GetAction()->Terminate(false);
4371 v2 Pos = GetPos();
4372 SendToHell();
4373 Remove();
4374 character *Char = GetPolymorphBackup();
4375 Flags |= C_IN_NO_MSG_MODE;
4376 Char->Flags |= C_IN_NO_MSG_MODE;
4377 Char->PutToOrNear(Pos);
4378 Char->ChangeTeam(GetTeam());
4379 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(Char);
4380 SetPolymorphBackup(0);
4381 Char->Enable();
4382 Char->Flags &= ~C_POLYMORPHED;
4383 GetStack()->MoveItemsTo(Char->GetStack());
4384 DonateEquipmentTo(Char);
4385 Char->SetMoney(GetMoney());
4386 Flags &= ~C_IN_NO_MSG_MODE;
4387 Char->Flags &= ~C_IN_NO_MSG_MODE;
4388 Char->CalculateAll();
4389 Char->SetAssignedName(GetAssignedName());
4390 if (IsPlayer()) {
4391 Flags &= ~C_PLAYER;
4392 game::SetPlayer(Char);
4393 game::SendLOSUpdateRequest();
4394 UpdateESPLOS();
4396 Char->TestWalkability();
4397 return Char;
4401 void character::LycanthropyHandler () {
4402 if (!(RAND() % 2000)) {
4403 if (StateIsActivated(POLYMORPH_CONTROL) && !game::TruthQuestion(CONST_S("Do you wish to change into a werewolf?"))) return;
4404 Polymorph(werewolfwolf::Spawn(), 1000 + RAND() % 2000);
4409 void character::SaveLife () {
4410 if (TemporaryStateIsActivated(LIFE_SAVED)) {
4411 if (IsPlayer())
4412 ADD_MESSAGE("But wait! You glow briefly red and seem to be in a better shape!");
4413 else if (CanBeSeenByPlayer())
4414 ADD_MESSAGE("But wait, suddenly %s glows briefly red and seems to be in a better shape!", GetPersonalPronoun().CStr());
4415 DeActivateTemporaryState(LIFE_SAVED);
4416 } else {
4417 item *LifeSaver = 0;
4418 for (int c = 0; c < GetEquipments(); ++c) {
4419 item *Equipment = GetEquipment(c);
4420 if (Equipment && Equipment->IsInCorrectSlot(c) && Equipment->GetGearStates() & LIFE_SAVED) LifeSaver = Equipment;
4422 if (!LifeSaver) ABORT("The Universe can only kill you once!");
4423 if (IsPlayer())
4424 ADD_MESSAGE("But wait! Your %s glows briefly red and disappears and you seem to be in a better shape!", LifeSaver->CHAR_NAME(UNARTICLED));
4425 else if (CanBeSeenByPlayer())
4426 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());
4427 LifeSaver->RemoveFromSlot();
4428 LifeSaver->SendToHell();
4431 if (IsPlayer()) game::AskForEscPress(CONST_S("Life saved!"));
4433 RestoreBodyParts();
4434 ResetSpoiling();
4435 RestoreHP();
4436 RestoreStamina();
4437 ResetStates();
4439 if (GetNP() < SATIATED_LEVEL) SetNP(SATIATED_LEVEL);
4441 SendNewDrawRequest();
4443 if (GetAction()) GetAction()->Terminate(false);
4447 character *character::PolymorphRandomly (int MinDanger, int MaxDanger, int Time) {
4448 character *NewForm = 0;
4449 if (StateIsActivated(POLYMORPH_CONTROL)) {
4450 if (IsPlayer()) {
4451 if (!GetNewFormForPolymorphWithControl(NewForm)) return NewForm;
4452 } else {
4453 NewForm = protosystem::CreateMonster(MinDanger*10, MaxDanger*10, NO_EQUIPMENT);
4455 } else {
4456 NewForm = protosystem::CreateMonster(MinDanger, MaxDanger, NO_EQUIPMENT);
4458 Polymorph(NewForm, Time);
4459 return NewForm;
4463 /* In reality, the reading takes Time / (Intelligence * 10) turns */
4464 void character::StartReading (item *Item, sLong Time) {
4465 study *Read = study::Spawn(this);
4466 Read->SetLiteratureID(Item->GetID());
4467 if (game::WizardModeIsActive()) Time = 1;
4468 Read->SetCounter(Time);
4469 SetAction(Read);
4470 if (IsPlayer()) ADD_MESSAGE("You start reading %s.", Item->CHAR_NAME(DEFINITE));
4471 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s starts reading %s.", CHAR_NAME(DEFINITE), Item->CHAR_NAME(DEFINITE));
4475 /* Call when one makes something with his/her/its hands.
4476 * Difficulty of 5 takes about one turn, so it's the most common to use. */
4477 void character::DexterityAction (int Difficulty) {
4478 EditAP(-20000 * Difficulty / APBonus(GetAttribute(DEXTERITY)));
4479 EditExperience(DEXTERITY, Difficulty * 15, 1 << 7);
4483 /* If Theoretically != false, range is not a factor. */
4484 truth character::CanBeSeenByPlayer (truth Theoretically, truth IgnoreESP) const {
4485 if (IsEnabled() && !game::IsGenerating() && (Theoretically || GetSquareUnder())) {
4486 truth MayBeESPSeen = PLAYER->IsEnabled() && !IgnoreESP && PLAYER->StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5;
4487 truth MayBeInfraSeen = PLAYER->IsEnabled() && PLAYER->StateIsActivated(INFRA_VISION) && IsWarm();
4488 truth Visible = !StateIsActivated(INVISIBLE) || MayBeESPSeen || MayBeInfraSeen;
4489 if (game::IsInWilderness()) return Visible;
4490 if (MayBeESPSeen && (Theoretically || GetDistanceSquareFrom(PLAYER) <= PLAYER->GetESPRangeSquare())) return true;
4491 if (!Visible) return false;
4492 return (Theoretically || SquareUnderCanBeSeenByPlayer(MayBeInfraSeen));
4494 return false;
4498 truth character::CanBeSeenBy (ccharacter *Who, truth Theoretically, truth IgnoreESP) const {
4499 if (Who->IsPlayer()) return CanBeSeenByPlayer(Theoretically, IgnoreESP);
4500 if (IsEnabled() && !game::IsGenerating() && (Theoretically || GetSquareUnder())) {
4501 truth MayBeESPSeen = Who->IsEnabled() && !IgnoreESP && Who->StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5;
4502 truth MayBeInfraSeen = Who->IsEnabled() && Who->StateIsActivated(INFRA_VISION) && IsWarm();
4503 truth Visible = !StateIsActivated(INVISIBLE) || MayBeESPSeen || MayBeInfraSeen;
4504 if (game::IsInWilderness()) return Visible;
4505 if (MayBeESPSeen && (Theoretically || GetDistanceSquareFrom(Who) <= Who->GetESPRangeSquare())) return true;
4506 if (!Visible) return false;
4507 return (Theoretically || SquareUnderCanBeSeenBy(Who, MayBeInfraSeen));
4509 return false;
4513 truth character::SquareUnderCanBeSeenByPlayer (truth IgnoreDarkness) const {
4514 if (!GetSquareUnder()) return false;
4515 int S1 = SquaresUnder, S2 = PLAYER->SquaresUnder;
4516 if (S1 == 1 && S2 == 1) {
4517 if (GetSquareUnder()->CanBeSeenByPlayer(IgnoreDarkness)) return true;
4518 if (IgnoreDarkness) {
4519 int LOSRangeSquare = PLAYER->GetLOSRangeSquare();
4520 if ((GetPos() - PLAYER->GetPos()).GetLengthSquare() <= LOSRangeSquare) {
4521 eyecontroller::Map = GetLevel()->GetMap();
4522 return mapmath<eyecontroller>::DoLine(PLAYER->GetPos().X, PLAYER->GetPos().Y, GetPos().X, GetPos().Y, SKIP_FIRST);
4525 return false;
4526 } else {
4527 for (int c1 = 0; c1 < S1; ++c1) {
4528 lsquare *Square = GetLSquareUnder(c1);
4529 if (Square->CanBeSeenByPlayer(IgnoreDarkness)) return true;
4530 else if (IgnoreDarkness) {
4531 v2 Pos = Square->GetPos();
4532 int LOSRangeSquare = PLAYER->GetLOSRangeSquare();
4533 for (int c2 = 0; c2 < S2; ++c2) {
4534 v2 PlayerPos = PLAYER->GetPos(c2);
4535 if ((Pos-PlayerPos).GetLengthSquare() <= LOSRangeSquare) {
4536 eyecontroller::Map = GetLevel()->GetMap();
4537 if (mapmath<eyecontroller>::DoLine(PlayerPos.X, PlayerPos.Y, Pos.X, Pos.Y, SKIP_FIRST)) return true;
4542 return false;
4547 truth character::SquareUnderCanBeSeenBy (ccharacter *Who, truth IgnoreDarkness) const {
4548 int S1 = SquaresUnder, S2 = Who->SquaresUnder;
4549 int LOSRangeSquare = Who->GetLOSRangeSquare();
4550 if (S1 == 1 && S2 == 1) return GetSquareUnder()->CanBeSeenFrom(Who->GetPos(), LOSRangeSquare, IgnoreDarkness);
4551 for (int c1 = 0; c1 < S1; ++c1) {
4552 lsquare *Square = GetLSquareUnder(c1);
4553 for (int c2 = 0; c2 < S2; ++c2) if (Square->CanBeSeenFrom(Who->GetPos(c2), LOSRangeSquare, IgnoreDarkness)) return true;
4555 return false;
4559 int character::GetDistanceSquareFrom (ccharacter *Who) const {
4560 int S1 = SquaresUnder, S2 = Who->SquaresUnder;
4561 if (S1 == 1 && S2 == 1) return (GetPos() - Who->GetPos()).GetLengthSquare();
4562 v2 MinDist(0x7FFF, 0x7FFF);
4563 int MinLength = 0xFFFF;
4564 for (int c1 = 0; c1 < S1; ++c1) {
4565 for (int c2 = 0; c2 < S2; ++c2) {
4566 v2 Dist = GetPos(c1)-Who->GetPos(c2);
4567 if (Dist.X < 0) Dist.X = -Dist.X;
4568 if (Dist.Y < 0) Dist.Y = -Dist.Y;
4569 if (Dist.X <= MinDist.X && Dist.Y <= MinDist.Y) {
4570 MinDist = Dist;
4571 MinLength = Dist.GetLengthSquare();
4572 } else if (Dist.X < MinDist.X || Dist.Y < MinDist.Y) {
4573 int Length = Dist.GetLengthSquare();
4574 if (Length < MinLength) {
4575 MinDist = Dist;
4576 MinLength = Length;
4581 return MinLength;
4585 void character::AttachBodyPart (bodypart *BodyPart) {
4586 SetBodyPart(BodyPart->GetBodyPartIndex(), BodyPart);
4587 if (!AllowSpoil()) BodyPart->ResetSpoiling();
4588 BodyPart->ResetPosition();
4589 BodyPart->UpdatePictures();
4590 CalculateAttributeBonuses();
4591 CalculateBattleInfo();
4592 SendNewDrawRequest();
4593 SignalPossibleTransparencyChange();
4597 /* Returns true if the character has all bodyparts, false if not. */
4598 truth character::HasAllBodyParts () const {
4599 for (int c = 0; c < BodyParts; ++c) if (!GetBodyPart(c) && CanCreateBodyPart(c)) return false;
4600 return true;
4604 bodypart *character::GenerateRandomBodyPart () {
4605 int NeededBodyPart[MAX_BODYPARTS];
4606 int Index = 0;
4607 for (int c = 0; c < BodyParts; ++c) if (!GetBodyPart(c) && CanCreateBodyPart(c)) NeededBodyPart[Index++] = c;
4608 return Index ? CreateBodyPart(NeededBodyPart[RAND() % Index]) : 0;
4612 /* Searches the character's Stack and if it find some bodyparts there that are the character's
4613 * old bodyparts returns a stackiterator to one of them (choosen in random).
4614 * If no fitting bodyparts are found the function returns 0 */
4615 bodypart *character::FindRandomOwnBodyPart (truth AllowNonLiving) const {
4616 itemvector LostAndFound;
4617 for (int c = 0; c < BodyParts; ++c) {
4618 if (!GetBodyPart(c)) {
4619 for (std::list<feuLong>::iterator i = OriginalBodyPartID[c].begin(); i != OriginalBodyPartID[c].end(); ++i) {
4620 bodypart *Found = static_cast<bodypart *>(SearchForItem(*i));
4621 if (Found && (AllowNonLiving || Found->CanRegenerate())) LostAndFound.push_back(Found);
4625 if (LostAndFound.empty()) return 0;
4626 return static_cast<bodypart *>(LostAndFound[RAND() % LostAndFound.size()]);
4630 void character::PrintBeginPoisonedMessage () const {
4631 if (IsPlayer()) ADD_MESSAGE("You seem to be very ill.");
4632 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks very ill.", CHAR_NAME(DEFINITE));
4636 void character::PrintEndPoisonedMessage () const {
4637 if (IsPlayer()) ADD_MESSAGE("You feel better again.");
4638 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks better.", CHAR_NAME(DEFINITE));
4642 void character::PoisonedHandler () {
4643 if (!(RAND() % 100)) VomitAtRandomDirection(500 + RAND_N(250));
4644 int Damage = 0;
4645 for (int Used = 0; Used < GetTemporaryStateCounter(POISONED); Used += 100) if (!(RAND() % 100)) ++Damage;
4646 if (Damage) {
4647 ReceiveDamage(0, Damage, POISON, ALL, 8, false, false, false, false);
4648 CheckDeath(CONST_S("died of acute poisoning"), 0);
4653 truth character::IsWarm () const {
4654 return combinebodypartpredicates()(this, &bodypart::IsWarm, 1);
4658 truth character::IsWarmBlooded() const
4660 return combinebodypartpredicates()(this, &bodypart::IsWarmBlooded, 1);
4664 void character::BeginInvisibility () {
4665 UpdatePictures();
4666 SendNewDrawRequest();
4667 SignalPossibleTransparencyChange();
4671 void character::BeginInfraVision () {
4672 if (IsPlayer()) GetArea()->SendNewDrawRequest();
4676 void character::BeginESP () {
4677 if (IsPlayer()) GetArea()->SendNewDrawRequest();
4681 void character::EndInvisibility () {
4682 UpdatePictures();
4683 SendNewDrawRequest();
4684 SignalPossibleTransparencyChange();
4688 void character::EndInfraVision () {
4689 if (IsPlayer() && IsEnabled()) GetArea()->SendNewDrawRequest();
4693 void character::EndESP () {
4694 if (IsPlayer() && IsEnabled()) GetArea()->SendNewDrawRequest();
4698 void character::Draw (blitdata &BlitData) const {
4699 col24 L = BlitData.Luminance;
4700 if (PLAYER->IsEnabled() &&
4701 ((PLAYER->StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5 &&
4702 (PLAYER->GetPos() - GetPos()).GetLengthSquare() <= PLAYER->GetESPRangeSquare()) ||
4703 (PLAYER->StateIsActivated(INFRA_VISION) && IsWarm())))
4704 BlitData.Luminance = ivanconfig::GetContrastLuminance();
4706 DrawBodyParts(BlitData);
4707 BlitData.Luminance = ivanconfig::GetContrastLuminance();
4708 BlitData.Src.Y = 16;
4709 cint SquareIndex = BlitData.CustomData & SQUARE_INDEX_MASK;
4711 if (GetTeam() == PLAYER->GetTeam() && !IsPlayer() && SquareIndex == GetTameSymbolSquareIndex()) {
4712 BlitData.Src.X = 32;
4713 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData);
4716 if (IsFlying() && SquareIndex == GetFlySymbolSquareIndex()) {
4717 BlitData.Src.X = 128;
4718 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData);
4721 if (IsSwimming() && SquareIndex == GetSwimmingSymbolSquareIndex()) {
4722 BlitData.Src.X = 240;
4723 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData);
4726 if (GetAction() && GetAction()->IsUnconsciousness() && SquareIndex == GetUnconsciousSymbolSquareIndex()) {
4727 BlitData.Src.X = 224;
4728 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData);
4731 BlitData.Src.X = BlitData.Src.Y = 0;
4732 BlitData.Luminance = L;
4736 void character::DrawBodyParts (blitdata &BlitData) const {
4737 GetTorso()->Draw(BlitData);
4741 void character::PrintBeginTeleportMessage () const {
4742 if (IsPlayer()) ADD_MESSAGE("You feel jumpy.");
4746 void character::PrintEndTeleportMessage () const {
4747 if (IsPlayer()) ADD_MESSAGE("You suddenly realize you've always preferred walking to jumping.");
4751 void character::PrintBeginDetectMessage () const {
4752 if (IsPlayer()) ADD_MESSAGE("You feel curious about your surroundings.");
4756 void character::PrintEndDetectMessage () const {
4757 if (IsPlayer()) ADD_MESSAGE("You decide to rely on your intuition from now on.");
4761 void character::TeleportHandler () {
4762 if (!(RAND() % 1500) && !game::IsInWilderness()) {
4763 if (IsPlayer()) ADD_MESSAGE("You feel an urgent spatial relocation is now appropriate.");
4764 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s disappears.", CHAR_NAME(DEFINITE));
4765 TeleportRandomly();
4770 void character::DetectHandler () {
4771 if (IsPlayer()) {
4772 //the AI can't be asked position questions! So only the player can hav this state really :/ a bit daft of me
4773 if (!(RAND()%3000) && !game::IsInWilderness()) {
4774 ADD_MESSAGE("Your mind wanders in search of something.");
4775 DoDetecting(); //in fact, who knows what would happen if a dark frog had the detecting state?
4781 void character::PrintBeginPolymorphMessage () const {
4782 if (IsPlayer()) ADD_MESSAGE("An unconfortable uncertainty of who you really are overwhelms you.");
4786 void character::PrintEndPolymorphMessage () const {
4787 if (IsPlayer()) ADD_MESSAGE("You feel you are you and no one else.");
4791 void character::PolymorphHandler () {
4792 if (!(RAND() % 1500)) PolymorphRandomly(1, 999999, 200 + RAND() % 800);
4795 void character::PrintBeginTeleportControlMessage () const {
4796 if (IsPlayer()) ADD_MESSAGE("You feel very controlled.");
4800 void character::PrintEndTeleportControlMessage () const {
4801 if (IsPlayer()) ADD_MESSAGE("You feel your control slipping.");
4805 void character::DisplayStethoscopeInfo (character *) const {
4806 felist Info(CONST_S("Information about ") + GetDescription(DEFINITE));
4807 AddSpecialStethoscopeInfo(Info);
4808 Info.AddEntry(CONST_S("Endurance: ") + GetAttribute(ENDURANCE), LIGHT_GRAY);
4809 Info.AddEntry(CONST_S("Perception: ") + GetAttribute(PERCEPTION), LIGHT_GRAY);
4810 Info.AddEntry(CONST_S("Intelligence: ") + GetAttribute(INTELLIGENCE), LIGHT_GRAY);
4811 Info.AddEntry(CONST_S("Wisdom: ") + GetAttribute(WISDOM), LIGHT_GRAY);
4812 //Info.AddEntry(CONST_S("Willpower: ") + GetAttribute(WILL_POWER), LIGHT_GRAY);
4813 Info.AddEntry(CONST_S("Charisma: ") + GetAttribute(CHARISMA), LIGHT_GRAY);
4814 Info.AddEntry(CONST_S("HP: ") + GetHP() + "/" + GetMaxHP(), IsInBadCondition() ? RED : LIGHT_GRAY);
4815 if (GetAction()) Info.AddEntry(festring(GetAction()->GetDescription()).CapitalizeCopy(), LIGHT_GRAY);
4816 for (int c = 0; c < STATES; ++c) {
4817 if (StateIsActivated(1 << c) && (1 << c != HASTE || !StateIsActivated(SLOW)) && (1 << c != SLOW || !StateIsActivated(HASTE)))
4818 Info.AddEntry(StateData[c].Description, LIGHT_GRAY);
4820 switch (GetTirednessState()) {
4821 case FAINTING: Info.AddEntry("Fainting", RED); break;
4822 case EXHAUSTED: Info.AddEntry("Exhausted", LIGHT_GRAY); break;
4824 game::SetStandardListAttributes(Info);
4825 Info.Draw();
4829 truth character::CanUseStethoscope (truth PrintReason) const {
4830 if (PrintReason) ADD_MESSAGE("This type of monster can't use a stethoscope.");
4831 return false;
4835 /* Effect used by at least Sophos.
4836 * NOTICE: Doesn't check for death! */
4837 void character::TeleportSomePartsAway (int NumberToTeleport) {
4838 for (int c = 0; c < NumberToTeleport; ++c) {
4839 int RandomBodyPart = GetRandomNonVitalBodyPart();
4840 if (RandomBodyPart == NONE_INDEX) {
4841 for (; c < NumberToTeleport; ++c) {
4842 GetTorso()->SetHP((GetTorso()->GetHP() << 2) / 5);
4843 sLong TorsosVolume = GetTorso()->GetMainMaterial()->GetVolume() / 10;
4844 if (!TorsosVolume) break;
4845 sLong Amount = (RAND() % TorsosVolume)+1;
4846 item *Lump = GetTorso()->GetMainMaterial()->CreateNaturalForm(Amount);
4847 GetTorso()->GetMainMaterial()->EditVolume(-Amount);
4848 Lump->MoveTo(GetNearLSquare(GetLevel()->GetRandomSquare())->GetStack());
4849 if (IsPlayer()) ADD_MESSAGE("Parts of you teleport away.");
4850 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Parts of %s teleport away.", CHAR_NAME(DEFINITE));
4852 } else {
4853 item *SeveredBodyPart = SevereBodyPart(RandomBodyPart);
4854 if (SeveredBodyPart) {
4855 GetNearLSquare(GetLevel()->GetRandomSquare())->AddItem(SeveredBodyPart);
4856 SeveredBodyPart->DropEquipment();
4857 if (IsPlayer()) ADD_MESSAGE("Your %s teleports away.", GetBodyPartName(RandomBodyPart).CStr());
4858 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s teleports away.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart).CStr());
4859 } else {
4860 if (IsPlayer()) ADD_MESSAGE("Your %s disappears.", GetBodyPartName(RandomBodyPart).CStr());
4861 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s disappears.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart).CStr());
4868 /* Returns an index of a random bodypart that is not vital. If no non-vital bodypart is found returns NONE_INDEX */
4869 int character::GetRandomNonVitalBodyPart () const {
4870 int OKBodyPart[MAX_BODYPARTS];
4871 int OKBodyParts = 0;
4872 for (int c = 0; c < BodyParts; ++c) if (GetBodyPart(c) && !BodyPartIsVital(c)) OKBodyPart[OKBodyParts++] = c;
4873 return OKBodyParts ? OKBodyPart[RAND() % OKBodyParts] : NONE_INDEX;
4877 void character::CalculateVolumeAndWeight () {
4878 Volume = Stack->GetVolume();
4879 Weight = Stack->GetWeight();
4880 BodyVolume = 0;
4881 CarriedWeight = Weight;
4882 for (int c = 0; c < BodyParts; ++c) {
4883 bodypart *BodyPart = GetBodyPart(c);
4884 if (BodyPart) {
4885 BodyVolume += BodyPart->GetBodyPartVolume();
4886 Volume += BodyPart->GetVolume();
4887 CarriedWeight += BodyPart->GetCarriedWeight();
4888 Weight += BodyPart->GetWeight();
4894 void character::SignalVolumeAndWeightChange () {
4895 if (!IsInitializing()) {
4896 CalculateVolumeAndWeight();
4897 if (IsEnabled()) CalculateBurdenState();
4898 if (MotherEntity) MotherEntity->SignalVolumeAndWeightChange();
4903 void character::SignalEmitationIncrease (col24 EmitationUpdate) {
4904 if (game::CompareLights(EmitationUpdate, Emitation) > 0) {
4905 game::CombineLights(Emitation, EmitationUpdate);
4906 if (MotherEntity) MotherEntity->SignalEmitationIncrease(EmitationUpdate);
4907 else if (SquareUnder[0] && !game::IsInWilderness()) {
4908 for(int c = 0; c < GetSquaresUnder(); ++c) GetLSquareUnder()->SignalEmitationIncrease(EmitationUpdate);
4914 void character::SignalEmitationDecrease (col24 EmitationUpdate) {
4915 if (game::CompareLights(EmitationUpdate, Emitation) >= 0 && Emitation) {
4916 col24 Backup = Emitation;
4917 CalculateEmitation();
4918 if (Backup != Emitation) {
4919 if (MotherEntity) MotherEntity->SignalEmitationDecrease(EmitationUpdate);
4920 else if (SquareUnder[0] && !game::IsInWilderness()) {
4921 for (int c = 0; c < GetSquaresUnder(); ++c) GetLSquareUnder(c)->SignalEmitationDecrease(EmitationUpdate);
4928 void character::CalculateEmitation () {
4929 Emitation = GetBaseEmitation();
4930 for (int c = 0; c < BodyParts; ++c) {
4931 bodypart *BodyPart = GetBodyPart(c);
4932 if (BodyPart) game::CombineLights(Emitation, BodyPart->GetEmitation());
4934 game::CombineLights(Emitation, Stack->GetEmitation());
4938 void character::CalculateAll () {
4939 Flags |= C_INITIALIZING;
4940 CalculateAttributeBonuses();
4941 CalculateVolumeAndWeight();
4942 CalculateEmitation();
4943 CalculateBodyPartMaxHPs(0);
4944 CalculateMaxStamina();
4945 CalculateBurdenState();
4946 CalculateBattleInfo();
4947 Flags &= ~C_INITIALIZING;
4951 void character::CalculateHP () {
4952 HP = sumbodypartproperties()(this, &bodypart::GetHP);
4956 void character::CalculateMaxHP () {
4957 MaxHP = sumbodypartproperties()(this, &bodypart::GetMaxHP);
4961 void character::CalculateBodyPartMaxHPs (feuLong Flags) {
4962 doforbodypartswithparam<feuLong>()(this, &bodypart::CalculateMaxHP, Flags);
4963 CalculateMaxHP();
4964 CalculateHP();
4968 truth character::EditAttribute (int Identifier, int Value) {
4969 if (Identifier == ENDURANCE && UseMaterialAttributes()) return false;
4970 if (RawEditAttribute(BaseExperience[Identifier], Value)) {
4971 if (!IsInitializing()) {
4972 if (Identifier == LEG_STRENGTH) CalculateBurdenState();
4973 else if (Identifier == ENDURANCE) CalculateBodyPartMaxHPs();
4974 else if (IsPlayer() && Identifier == PERCEPTION) game::SendLOSUpdateRequest();
4975 else if (IsPlayerKind() && (Identifier == INTELLIGENCE || Identifier == WISDOM || Identifier == CHARISMA)) UpdatePictures();
4976 CalculateBattleInfo();
4978 return true;
4980 return false;
4984 truth character::ActivateRandomState (int Flags, int Time, sLong Seed) {
4985 femath::SaveSeed();
4986 if (Seed) femath::SetSeed(Seed);
4987 sLong ToBeActivated = GetRandomState(Flags|DUR_TEMPORARY);
4988 femath::LoadSeed();
4989 if (!ToBeActivated) return false;
4990 BeginTemporaryState(ToBeActivated, Time);
4991 return true;
4995 truth character::GainRandomIntrinsic (int Flags) {
4996 sLong ToBeActivated = GetRandomState(Flags|DUR_PERMANENT);
4997 if (!ToBeActivated) return false;
4998 GainIntrinsic(ToBeActivated);
4999 return true;
5003 /* Returns 0 if state not found */
5004 sLong character::GetRandomState (int Flags) const {
5005 sLong OKStates[STATES];
5006 int NumberOfOKStates = 0;
5007 for (int c = 0; c < STATES; ++c) {
5008 if (StateData[c].Flags & Flags & DUR_FLAGS && StateData[c].Flags & Flags & SRC_FLAGS) OKStates[NumberOfOKStates++] = 1 << c;
5010 return NumberOfOKStates ? OKStates[RAND() % NumberOfOKStates] : 0;
5014 int characterprototype::CreateSpecialConfigurations (characterdatabase **TempConfig, int Configs, int Level) {
5015 if (Level == 0 && TempConfig[0]->CreateDivineConfigurations) {
5016 Configs = databasecreator<character>::CreateDivineConfigurations(this, TempConfig, Configs);
5018 if (Level == 1 && TempConfig[0]->CreateUndeadConfigurations) {
5019 for (int c = 1; c < protocontainer<character>::GetSize(); ++c) {
5020 const character::prototype *Proto = protocontainer<character>::GetProto(c);
5021 const character::database *const *CharacterConfigData = Proto->GetConfigData();
5022 if (!CharacterConfigData) ABORT("No database entry for character <%s>!", Proto->GetClassID());
5023 const character::database*const* End = CharacterConfigData + Proto->GetConfigSize();
5024 for (++CharacterConfigData; CharacterConfigData != End; ++CharacterConfigData) {
5025 const character::database *CharacterDataBase = *CharacterConfigData;
5026 if (CharacterDataBase->UndeadVersions) {
5027 character::database* ConfigDataBase = new character::database(**TempConfig);
5028 festring ucfgname = "undead ";
5029 ucfgname << CharacterDataBase->CfgStrName;
5030 ConfigDataBase->InitDefaults(this, (c << 8) | CharacterDataBase->Config, ucfgname);
5031 ConfigDataBase->PostFix << "of ";
5032 if (CharacterDataBase->Adjective.GetSize()) {
5033 if (CharacterDataBase->UsesLongAdjectiveArticle) ConfigDataBase->PostFix << "an ";
5034 else ConfigDataBase->PostFix << "a ";
5035 ConfigDataBase->PostFix << CharacterDataBase->Adjective << ' ';
5036 } else {
5037 if (CharacterDataBase->UsesLongArticle) ConfigDataBase->PostFix << "an ";
5038 else ConfigDataBase->PostFix << "a ";
5040 ConfigDataBase->PostFix << CharacterDataBase->NameSingular;
5041 if (CharacterDataBase->PostFix.GetSize()) ConfigDataBase->PostFix << ' ' << CharacterDataBase->PostFix;
5042 int P1 = TempConfig[0]->UndeadAttributeModifier;
5043 int P2 = TempConfig[0]->UndeadVolumeModifier;
5044 int c2;
5045 for (c2 = 0; c2 < ATTRIBUTES; ++c2) ConfigDataBase->*ExpPtr[c2] = CharacterDataBase->*ExpPtr[c2] * P1 / 100;
5046 for (c2 = 0; c2 < EQUIPMENT_DATAS; ++c2) ConfigDataBase->*EquipmentDataPtr[c2] = contentscript<item>();
5047 ConfigDataBase->DefaultIntelligence = 5;
5048 ConfigDataBase->DefaultWisdom = 5;
5049 ConfigDataBase->DefaultCharisma = 5;
5050 ConfigDataBase->TotalSize = CharacterDataBase->TotalSize;
5051 ConfigDataBase->Sex = CharacterDataBase->Sex;
5052 ConfigDataBase->AttributeBonus = CharacterDataBase->AttributeBonus;
5053 ConfigDataBase->TotalVolume = CharacterDataBase->TotalVolume * P2 / 100;
5054 if (TempConfig[0]->UndeadCopyMaterials) {
5055 ConfigDataBase->HeadBitmapPos = CharacterDataBase->HeadBitmapPos;
5056 ConfigDataBase->HairColor = CharacterDataBase->HairColor;
5057 ConfigDataBase->EyeColor = CharacterDataBase->EyeColor;
5058 ConfigDataBase->CapColor = CharacterDataBase->CapColor;
5059 ConfigDataBase->FleshMaterial = CharacterDataBase->FleshMaterial;
5060 ConfigDataBase->BloodMaterial = CharacterDataBase->BloodMaterial;
5061 ConfigDataBase->VomitMaterial = CharacterDataBase->VomitMaterial;
5062 ConfigDataBase->SweatMaterial = CharacterDataBase->SweatMaterial;
5064 ConfigDataBase->KnownCWeaponSkills = CharacterDataBase->KnownCWeaponSkills;
5065 ConfigDataBase->CWeaponSkillHits = CharacterDataBase->CWeaponSkillHits;
5066 ConfigDataBase->PostProcess();
5067 TempConfig[Configs++] = ConfigDataBase;
5072 if (Level == 0 && TempConfig[0]->CreateGolemMaterialConfigurations) {
5073 for (int c = 1; c < protocontainer<material>::GetSize(); ++c) {
5074 const material::prototype* Proto = protocontainer<material>::GetProto(c);
5075 const material::database*const* MaterialConfigData = Proto->GetConfigData();
5076 const material::database*const* End = MaterialConfigData + Proto->GetConfigSize();
5077 for (++MaterialConfigData; MaterialConfigData != End; ++MaterialConfigData) {
5078 const material::database* MaterialDataBase = *MaterialConfigData;
5079 if (MaterialDataBase->CategoryFlags & IS_GOLEM_MATERIAL) {
5080 character::database* ConfigDataBase = new character::database(**TempConfig);
5081 festring gcfgname;
5082 gcfgname << MaterialDataBase->CfgStrName;
5083 gcfgname << " golem";
5084 ConfigDataBase->InitDefaults(this, MaterialDataBase->Config, gcfgname);
5085 ConfigDataBase->Adjective = MaterialDataBase->NameStem;
5086 ConfigDataBase->UsesLongAdjectiveArticle = MaterialDataBase->NameFlags & USE_AN;
5087 ConfigDataBase->AttachedGod = MaterialDataBase->AttachedGod;
5088 TempConfig[Configs++] = ConfigDataBase;
5093 return Configs;
5097 double character::GetTimeToDie (ccharacter *Enemy, int Damage, double ToHitValue, truth AttackIsBlockable, truth UseMaxHP) const {
5098 double DodgeValue = GetDodgeValue();
5099 if (!Enemy->CanBeSeenBy(this, true)) ToHitValue *= 2;
5100 if (!CanBeSeenBy(Enemy, true)) DodgeValue *= 2;
5101 double MinHits = 1000;
5102 truth First = true;
5103 for (int c = 0; c < BodyParts; ++c) {
5104 if (BodyPartIsVital(c) && GetBodyPart(c)) {
5105 double Hits = GetBodyPart(c)->GetTimeToDie(Damage, ToHitValue, DodgeValue, AttackIsBlockable, UseMaxHP);
5106 if (First) { MinHits = Hits; First = false; } else MinHits = 1/(1/MinHits+1/Hits);
5109 return MinHits;
5113 double character::GetRelativeDanger (ccharacter *Enemy, truth UseMaxHP) const {
5114 double Danger = Enemy->GetTimeToKill(this, UseMaxHP)/GetTimeToKill(Enemy, UseMaxHP);
5115 int EnemyAP = Enemy->GetMoveAPRequirement(1);
5116 int ThisAP = GetMoveAPRequirement(1);
5117 if (EnemyAP > ThisAP) Danger *= 1.25; else if (ThisAP > EnemyAP) Danger *= 0.80;
5118 if (!Enemy->CanBeSeenBy(this, true)) Danger *= (Enemy->IsPlayer() ? 0.2 : 0.5);
5119 if (!CanBeSeenBy(Enemy, true)) Danger *= (IsPlayer() ? 5.0 : 2.0);
5120 if (GetAttribute(INTELLIGENCE) < 10 && !IsPlayer()) Danger *= 0.80;
5121 if (Enemy->GetAttribute(INTELLIGENCE) < 10 && !Enemy->IsPlayer()) Danger *= 1.25;
5122 return Limit(Danger, 0.001, 1000.0);
5126 festring character::GetBodyPartName (int I, truth Articled) const {
5127 if (I == TORSO_INDEX) return Articled ? CONST_S("a torso") : CONST_S("torso");
5128 ABORT("Illegal character bodypart name request!");
5129 return "";
5133 item *character::SearchForItem (feuLong ID) const {
5134 item *Equipment = findequipment<feuLong>()(this, &item::HasID, ID);
5135 if (Equipment) return Equipment;
5136 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) if (i->GetID() == ID) return *i;
5137 return 0;
5141 truth character::ContentsCanBeSeenBy (ccharacter *Viewer) const {
5142 return (Viewer == this);
5146 truth character::HitEffect (character *Enemy, item* Weapon, v2 HitPos, int Type, int BodyPartIndex,
5147 int Direction, truth BlockedByArmour, truth Critical, int DoneDamage)
5149 if (Weapon) return Weapon->HitEffect(this, Enemy, HitPos, BodyPartIndex, Direction, BlockedByArmour);
5150 switch (Type) {
5151 case UNARMED_ATTACK: return Enemy->SpecialUnarmedEffect(this, HitPos, BodyPartIndex, Direction, BlockedByArmour);
5152 case KICK_ATTACK: return Enemy->SpecialKickEffect(this, HitPos, BodyPartIndex, Direction, BlockedByArmour);
5153 case BITE_ATTACK: return Enemy->SpecialBiteEffect(this, HitPos, BodyPartIndex, Direction, BlockedByArmour, Critical, DoneDamage);
5155 return false;
5159 void character::WeaponSkillHit (item *Weapon, int Type, int Hits) {
5160 int Category;
5161 switch (Type) {
5162 case UNARMED_ATTACK: Category = UNARMED; break;
5163 case WEAPON_ATTACK: Weapon->WeaponSkillHit(Hits); return;
5164 case KICK_ATTACK: Category = KICK; break;
5165 case BITE_ATTACK: Category = BITE; break;
5166 case THROW_ATTACK:
5167 if (!IsHumanoid()) return;
5168 Category = Weapon->GetWeaponCategory();
5169 break;
5170 default:
5171 ABORT("Illegal Type %d passed to character::WeaponSkillHit()!", Type);
5172 return;
5174 if (GetCWeaponSkill(Category)->AddHit(Hits)) {
5175 CalculateBattleInfo();
5176 if (IsPlayer()) GetCWeaponSkill(Category)->AddLevelUpMessage(Category);
5181 /* Returns 0 if character cannot be duplicated */
5182 character *character::Duplicate (feuLong Flags) {
5183 if (!(Flags & IGNORE_PROHIBITIONS) && !CanBeCloned()) return 0;
5184 character *Char = GetProtoType()->Clone(this);
5185 if (Flags & MIRROR_IMAGE) {
5186 DuplicateEquipment(Char, Flags & ~IGNORE_PROHIBITIONS);
5187 Char->SetLifeExpectancy(Flags >> LE_BASE_SHIFT & LE_BASE_RANGE, Flags >> LE_RAND_SHIFT & LE_RAND_RANGE);
5189 Char->CalculateAll();
5190 Char->CalculateEmitation();
5191 Char->UpdatePictures();
5192 Char->Flags &= ~(C_INITIALIZING|C_IN_NO_MSG_MODE);
5193 return Char;
5197 truth character::TryToEquip (item *Item) {
5198 if (!Item->AllowEquip() || !CanUseEquipment() || GetAttribute(WISDOM) >= Item->GetWearWisdomLimit() || Item->GetSquaresUnder() != 1) {
5199 return false;
5202 for (int e = 0; e < GetEquipments(); ++e) {
5203 if (GetBodyPartOfEquipment(e) && EquipmentIsAllowed(e)) {
5204 sorter Sorter = EquipmentSorter(e);
5205 if ((Sorter == 0 || (Item->*Sorter)(this)) &&
5206 ((e != RIGHT_WIELDED_INDEX && e != LEFT_WIELDED_INDEX) ||
5207 Item->IsWeapon(this) || Item->IsShield(this)) && AllowEquipment(Item, e))
5209 item *OldEquipment = GetEquipment(e);
5210 if (BoundToUse(OldEquipment, e)) continue;
5211 lsquare *LSquareUnder = GetLSquareUnder();
5212 stack *StackUnder = LSquareUnder->GetStack();
5213 msgsystem::DisableMessages();
5214 Flags |= C_PICTURE_UPDATES_FORBIDDEN;
5215 LSquareUnder->Freeze();
5216 StackUnder->Freeze();
5217 double Danger = GetRelativeDanger(PLAYER);
5218 if (OldEquipment) OldEquipment->RemoveFromSlot();
5219 Item->RemoveFromSlot();
5220 SetEquipment(e, Item);
5221 double NewDanger = GetRelativeDanger(PLAYER);
5222 Item->RemoveFromSlot();
5223 StackUnder->AddItem(Item);
5224 if (OldEquipment) SetEquipment(e, OldEquipment);
5225 msgsystem::EnableMessages();
5226 Flags &= ~C_PICTURE_UPDATES_FORBIDDEN;
5227 LSquareUnder->UnFreeze();
5228 StackUnder->UnFreeze();
5229 if (OldEquipment) {
5230 if (NewDanger > Danger || BoundToUse(Item, e)) {
5231 room *Room = GetRoom();
5232 if (!Room || Room->PickupItem(this, Item, 1)) {
5233 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));
5234 if (Room) Room->DropItem(this, OldEquipment, 1);
5235 OldEquipment->MoveTo(StackUnder);
5236 Item->RemoveFromSlot();
5237 SetEquipment(e, Item);
5238 DexterityAction(5);
5239 return true;
5242 } else {
5243 if (NewDanger > Danger || (NewDanger == Danger && e != RIGHT_WIELDED_INDEX && e != LEFT_WIELDED_INDEX) || BoundToUse(Item, e)) {
5244 room *Room = GetRoom();
5245 if (!Room || Room->PickupItem(this, Item, 1)) {
5246 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s picks up and equips %s.", CHAR_NAME(DEFINITE), Item->CHAR_NAME(INDEFINITE));
5247 Item->RemoveFromSlot();
5248 SetEquipment(e, Item);
5249 DexterityAction(5);
5250 return true;
5257 return false;
5261 truth character::TryToConsume (item *Item) {
5262 return Item->CanBeEatenByAI(this) && ConsumeItem(Item, Item->GetConsumeMaterial(this)->GetConsumeVerb());
5266 void character::UpdateESPLOS () const {
5267 if (StateIsActivated(ESP) && !game::IsInWilderness()) {
5268 for (int c = 0; c < game::GetTeams(); ++c) {
5269 for (std::list<character *>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i) {
5270 const character *ch = *i;
5271 if (ch->IsEnabled()) ch->SendNewDrawRequest();
5278 int character::GetCWeaponSkillLevel (citem *Item) const {
5279 if (Item->GetWeaponCategory() < GetAllowedWeaponSkillCategories()) return GetCWeaponSkill(Item->GetWeaponCategory())->GetLevel();
5280 return 0;
5284 void character::PrintBeginPanicMessage () const {
5285 if (IsPlayer()) ADD_MESSAGE("You panic!");
5286 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s panics.", CHAR_NAME(DEFINITE));
5290 void character::PrintEndPanicMessage () const {
5291 if (IsPlayer()) ADD_MESSAGE("You finally calm down.");
5292 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s calms down.", CHAR_NAME(DEFINITE));
5296 void character::CheckPanic (int Ticks) {
5297 if (GetPanicLevel() > 1 && !StateIsActivated(PANIC) && GetHP()*100 < RAND()%(GetPanicLevel()*GetMaxHP()<<1) && !StateIsActivated(FEARLESS)) {
5298 BeginTemporaryState(PANIC, ((Ticks * 3) >> 2) + RAND() % ((Ticks >> 1) + 1)); // 25% randomness to ticks...
5303 /* returns 0 if fails else the newly created character */
5304 character *character::DuplicateToNearestSquare (character *Cloner, feuLong Flags) {
5305 character *NewlyCreated = Duplicate(Flags);
5306 if (!NewlyCreated) return 0;
5307 if (Flags & CHANGE_TEAM && Cloner) NewlyCreated->ChangeTeam(Cloner->GetTeam());
5308 NewlyCreated->PutNear(GetPos());
5309 return NewlyCreated;
5313 void character::SignalSpoil (material *m) {
5314 if (GetMotherEntity()) GetMotherEntity()->SignalSpoil(m);
5315 else Disappear(0, "spoil", &item::IsVeryCloseToSpoiling);
5319 truth character::CanHeal () const {
5320 for (int c = 0; c < BodyParts; ++c) {
5321 bodypart *BodyPart = GetBodyPart(c);
5322 if (BodyPart && BodyPart->CanRegenerate() && BodyPart->GetHP() < BodyPart->GetMaxHP()) return true;
5324 return false;
5328 int character::GetRelation (ccharacter *Who) const {
5329 return GetTeam()->GetRelation(Who->GetTeam());
5333 truth (item::*AffectTest[BASE_ATTRIBUTES])() const = {
5334 &item::AffectsEndurance,
5335 &item::AffectsPerception,
5336 &item::AffectsIntelligence,
5337 &item::AffectsWisdom,
5338 &item::AffectsWillPower,
5339 &item::AffectsCharisma,
5340 &item::AffectsMana
5344 /* Returns nonzero if endurance has decreased and death may occur */
5345 truth character::CalculateAttributeBonuses () {
5346 doforbodyparts()(this, &bodypart::CalculateAttributeBonuses);
5347 int BackupBonus[BASE_ATTRIBUTES];
5348 int BackupCarryingBonus = CarryingBonus;
5349 CarryingBonus = 0;
5350 int c1;
5351 for (c1 = 0; c1 < BASE_ATTRIBUTES; ++c1) {
5352 BackupBonus[c1] = AttributeBonus[c1];
5353 AttributeBonus[c1] = 0;
5355 for (c1 = 0; c1 < GetEquipments(); ++c1) {
5356 item *Equipment = GetEquipment(c1);
5357 if (!Equipment || !Equipment->IsInCorrectSlot(c1)) continue;
5358 for (int c2 = 0; c2 < BASE_ATTRIBUTES; ++c2) {
5359 if ((Equipment->*AffectTest[c2])()) AttributeBonus[c2] += Equipment->GetEnchantment();
5361 if (Equipment->AffectsCarryingCapacity()) CarryingBonus += Equipment->GetCarryingBonus();
5364 ApplySpecialAttributeBonuses();
5366 if (IsPlayer() && !IsInitializing() && AttributeBonus[PERCEPTION] != BackupBonus[PERCEPTION]) game::SendLOSUpdateRequest();
5367 if (IsPlayer() && !IsInitializing() && AttributeBonus[INTELLIGENCE] != BackupBonus[INTELLIGENCE]) UpdateESPLOS();
5369 if (!IsInitializing() && CarryingBonus != BackupCarryingBonus) CalculateBurdenState();
5371 if (!IsInitializing() && AttributeBonus[ENDURANCE] != BackupBonus[ENDURANCE]) {
5372 CalculateBodyPartMaxHPs();
5373 CalculateMaxStamina();
5374 return AttributeBonus[ENDURANCE] < BackupBonus[ENDURANCE];
5377 return false;
5381 void character::ApplyEquipmentAttributeBonuses (item *Equipment) {
5382 if (Equipment->AffectsEndurance()) {
5383 AttributeBonus[ENDURANCE] += Equipment->GetEnchantment();
5384 CalculateBodyPartMaxHPs();
5385 CalculateMaxStamina();
5387 if (Equipment->AffectsPerception()) {
5388 AttributeBonus[PERCEPTION] += Equipment->GetEnchantment();
5389 if (IsPlayer()) game::SendLOSUpdateRequest();
5391 if (Equipment->AffectsIntelligence()) {
5392 AttributeBonus[INTELLIGENCE] += Equipment->GetEnchantment();
5393 if (IsPlayer()) UpdateESPLOS();
5395 if (Equipment->AffectsWisdom()) AttributeBonus[WISDOM] += Equipment->GetEnchantment();
5396 if (Equipment->AffectsWillPower()) AttributeBonus[WILL_POWER] += Equipment->GetEnchantment();
5397 if (Equipment->AffectsCharisma()) AttributeBonus[CHARISMA] += Equipment->GetEnchantment();
5398 if (Equipment->AffectsMana()) AttributeBonus[MANA] += Equipment->GetEnchantment();
5399 if (Equipment->AffectsCarryingCapacity()) {
5400 CarryingBonus += Equipment->GetCarryingBonus();
5401 CalculateBurdenState();
5406 void character::ReceiveAntidote (sLong Amount) {
5407 if (StateIsActivated(POISONED)) {
5408 if (GetTemporaryStateCounter(POISONED) > Amount) {
5409 EditTemporaryStateCounter(POISONED, -Amount);
5410 Amount = 0;
5411 } else {
5412 if (IsPlayer()) ADD_MESSAGE("Aaaah... You feel much better.");
5413 Amount -= GetTemporaryStateCounter(POISONED);
5414 DeActivateTemporaryState(POISONED);
5417 if ((Amount >= 100 || RAND_N(100) < Amount) && StateIsActivated(PARASITIZED)) {
5418 if (IsPlayer()) ADD_MESSAGE("Something in your belly didn't seem to like this stuff.");
5419 DeActivateTemporaryState(PARASITIZED);
5420 Amount -= Min(100, Amount);
5422 if ((Amount >= 100 || RAND_N(100) < Amount) && StateIsActivated(LEPROSY)) {
5423 if (IsPlayer()) ADD_MESSAGE("You are not falling to pieces anymore.");
5424 DeActivateTemporaryState(LEPROSY);
5425 Amount -= Min(100, Amount);
5430 void character::AddAntidoteConsumeEndMessage () const {
5431 if (StateIsActivated(POISONED)) {
5432 // true only if the antidote didn't cure the poison completely
5433 if (IsPlayer()) ADD_MESSAGE("Your body processes the poison in your veins with rapid speed.");
5438 truth character::IsDead () const {
5439 for (int c = 0; c < BodyParts; ++c) {
5440 bodypart *BodyPart = GetBodyPart(c);
5441 if (BodyPartIsVital(c) && (!BodyPart || BodyPart->GetHP() < 1)) return true;
5443 return false;
5447 void character::SignalSpoilLevelChange (material *m) {
5448 if (GetMotherEntity()) GetMotherEntity()->SignalSpoilLevelChange(m); else UpdatePictures();
5452 void character::AddOriginalBodyPartID (int I, feuLong What) {
5453 if (std::find(OriginalBodyPartID[I].begin(), OriginalBodyPartID[I].end(), What) == OriginalBodyPartID[I].end()) {
5454 OriginalBodyPartID[I].push_back(What);
5455 if (OriginalBodyPartID[I].size() > 100) OriginalBodyPartID[I].erase(OriginalBodyPartID[I].begin());
5460 void character::AddToInventory (const fearray<contentscript<item> > &ItemArray, int SpecialFlags) {
5461 for (uInt c1 = 0; c1 < ItemArray.Size; ++c1) {
5462 if (ItemArray[c1].IsValid()) {
5463 const interval *TimesPtr = ItemArray[c1].GetTimes();
5464 int Times = TimesPtr ? TimesPtr->Randomize() : 1;
5465 for (int c2 = 0; c2 < Times; ++c2) {
5466 item *Item = ItemArray[c1].Instantiate(SpecialFlags);
5467 if (Item) {
5468 Stack->AddItem(Item);
5469 Item->SpecialGenerationHandler();
5477 truth character::HasHadBodyPart (citem *Item) const {
5478 for (int c = 0; c < BodyParts; ++c)
5479 if (std::find(OriginalBodyPartID[c].begin(), OriginalBodyPartID[c].end(), Item->GetID()) != OriginalBodyPartID[c].end())
5480 return true;
5481 return GetPolymorphBackup() && GetPolymorphBackup()->HasHadBodyPart(Item);
5485 festring &character::ProcessMessage (festring &Msg) const {
5486 SEARCH_N_REPLACE(Msg, "@nu", GetName(UNARTICLED));
5487 SEARCH_N_REPLACE(Msg, "@ni", GetName(INDEFINITE));
5488 SEARCH_N_REPLACE(Msg, "@nd", GetName(DEFINITE));
5489 SEARCH_N_REPLACE(Msg, "@du", GetDescription(UNARTICLED));
5490 SEARCH_N_REPLACE(Msg, "@di", GetDescription(INDEFINITE));
5491 SEARCH_N_REPLACE(Msg, "@dd", GetDescription(DEFINITE));
5492 SEARCH_N_REPLACE(Msg, "@pp", GetPersonalPronoun());
5493 SEARCH_N_REPLACE(Msg, "@sp", GetPossessivePronoun());
5494 SEARCH_N_REPLACE(Msg, "@op", GetObjectPronoun());
5495 SEARCH_N_REPLACE(Msg, "@Nu", GetName(UNARTICLED).CapitalizeCopy());
5496 SEARCH_N_REPLACE(Msg, "@Ni", GetName(INDEFINITE).CapitalizeCopy());
5497 SEARCH_N_REPLACE(Msg, "@Nd", GetName(DEFINITE).CapitalizeCopy());
5498 SEARCH_N_REPLACE(Msg, "@Du", GetDescription(UNARTICLED).CapitalizeCopy());
5499 SEARCH_N_REPLACE(Msg, "@Di", GetDescription(INDEFINITE).CapitalizeCopy());
5500 SEARCH_N_REPLACE(Msg, "@Dd", GetDescription(DEFINITE).CapitalizeCopy());
5501 SEARCH_N_REPLACE(Msg, "@Pp", GetPersonalPronoun().CapitalizeCopy());
5502 SEARCH_N_REPLACE(Msg, "@Sp", GetPossessivePronoun().CapitalizeCopy());
5503 SEARCH_N_REPLACE(Msg, "@Op", GetObjectPronoun().CapitalizeCopy());
5504 SEARCH_N_REPLACE(Msg, "@Gd", GetMasterGod()->GetName());
5505 return Msg;
5509 void character::ProcessAndAddMessage (festring Msg) const {
5510 ADD_MESSAGE("%s", ProcessMessage(Msg).CStr());
5514 void character::BeTalkedTo () {
5515 static sLong Said;
5516 if (GetRelation(PLAYER) == HOSTILE)
5517 ProcessAndAddMessage(GetHostileReplies()[RandomizeReply(Said, GetHostileReplies().Size)]);
5518 else
5519 ProcessAndAddMessage(GetFriendlyReplies()[RandomizeReply(Said, GetFriendlyReplies().Size)]);
5523 truth character::CheckZap () {
5524 if (!CanZap()) {
5525 ADD_MESSAGE("This monster type can't zap.");
5526 return false;
5528 return true;
5532 void character::DamageAllItems (character *Damager, int Damage, int Type) {
5533 GetStack()->ReceiveDamage(Damager, Damage, Type);
5534 for (int c = 0; c < GetEquipments(); ++c) {
5535 item *Equipment = GetEquipment(c);
5536 if (Equipment) Equipment->ReceiveDamage(Damager, Damage, Type);
5541 truth character::Equips (citem *Item) const {
5542 return combineequipmentpredicateswithparam<feuLong>()(this, &item::HasID, Item->GetID(), 1);
5546 void character::PrintBeginConfuseMessage () const {
5547 if (IsPlayer()) ADD_MESSAGE("You feel quite happy.");
5551 void character::PrintEndConfuseMessage () const {
5552 if (IsPlayer()) ADD_MESSAGE("The world is boring again.");
5556 v2 character::ApplyStateModification (v2 TryDirection) const {
5557 if (!StateIsActivated(CONFUSED) || RAND() & 15 || game::IsInWilderness()) return TryDirection;
5558 v2 To = GetLevel()->GetFreeAdjacentSquare(this, GetPos(), true);
5559 if (To == ERROR_V2) return TryDirection;
5560 To -= GetPos();
5561 if (To != TryDirection && IsPlayer()) ADD_MESSAGE("Whoa! You somehow don't manage to walk straight.");
5562 return To;
5566 void character::AddConfuseHitMessage () const {
5567 if (IsPlayer()) ADD_MESSAGE("This stuff is confusing.");
5571 item *character::SelectFromPossessions (cfestring &Topic, sorter Sorter) {
5572 itemvector ReturnVector;
5573 SelectFromPossessions(ReturnVector, Topic, NO_MULTI_SELECT, Sorter);
5574 return !ReturnVector.empty() ? ReturnVector[0] : 0;
5578 truth character::SelectFromPossessions (itemvector &ReturnVector, cfestring &Topic, int Flags, sorter Sorter) {
5579 felist List(Topic);
5580 truth InventoryPossible = GetStack()->SortedItems(this, Sorter);
5581 if (InventoryPossible) List.AddEntry(CONST_S("choose from inventory"), LIGHT_GRAY, 20, game::AddToItemDrawVector(itemvector()));
5582 truth Any = false;
5583 itemvector Item;
5584 festring Entry;
5585 int c;
5586 for (c = 0; c < BodyParts; ++c) {
5587 bodypart *BodyPart = GetBodyPart(c);
5588 if (BodyPart && (Sorter == 0 || (BodyPart->*Sorter)(this))) {
5589 Item.push_back(BodyPart);
5590 Entry.Empty();
5591 BodyPart->AddName(Entry, UNARTICLED);
5592 int ImageKey = game::AddToItemDrawVector(itemvector(1, BodyPart));
5593 List.AddEntry(Entry, LIGHT_GRAY, 20, ImageKey, true);
5594 Any = true;
5597 for (c = 0; c < GetEquipments(); ++c) {
5598 item *Equipment = GetEquipment(c);
5599 if (Equipment && (Sorter == 0 || (Equipment->*Sorter)(this))) {
5600 Item.push_back(Equipment);
5601 Entry = GetEquipmentName(c);
5602 Entry << ':';
5603 Entry.Resize(20);
5604 Equipment->AddInventoryEntry(this, Entry, 1, true);
5605 AddSpecialEquipmentInfo(Entry, c);
5606 int ImageKey = game::AddToItemDrawVector(itemvector(1, Equipment));
5607 List.AddEntry(Entry, LIGHT_GRAY, 20, ImageKey, true);
5608 Any = true;
5611 if (Any) {
5612 game::SetStandardListAttributes(List);
5613 List.SetFlags(SELECTABLE|DRAW_BACKGROUND_AFTERWARDS);
5614 List.SetEntryDrawer(game::ItemEntryDrawer);
5615 game::DrawEverythingNoBlit();
5616 int Chosen = List.Draw();
5617 game::ClearItemDrawVector();
5618 if (Chosen != ESCAPED) {
5619 if ((InventoryPossible && !Chosen) || Chosen & FELIST_ERROR_BIT) {
5620 GetStack()->DrawContents(ReturnVector, this, Topic, Flags, Sorter);
5621 } else {
5622 ReturnVector.push_back(Item[InventoryPossible ? Chosen - 1 : Chosen]);
5623 if (Flags & SELECT_PAIR && ReturnVector[0]->HandleInPairs()) {
5624 item *PairEquipment = GetPairEquipment(ReturnVector[0]->GetEquipmentIndex());
5625 if (PairEquipment && PairEquipment->CanBePiledWith(ReturnVector[0], this)) ReturnVector.push_back(PairEquipment);
5629 } else {
5630 if (!GetStack()->SortedItems(this, Sorter)) return false;
5631 game::ClearItemDrawVector();
5632 GetStack()->DrawContents(ReturnVector, this, Topic, Flags, Sorter);
5634 return true;
5638 truth character::EquipsSomething (sorter Sorter) {
5639 for (int c = 0; c < GetEquipments(); ++c) {
5640 item *Equipment = GetEquipment(c);
5641 if (Equipment && (Sorter == 0 || (Equipment->*Sorter)(this))) return true;
5643 return false;
5647 material *character::CreateBodyPartMaterial (int, sLong Volume) const {
5648 return MAKE_MATERIAL(GetFleshMaterial(), Volume);
5652 truth character::CheckTalk () {
5653 if (!CanTalk()) {
5654 ADD_MESSAGE("This monster does not know the art of talking.");
5655 return false;
5657 return true;
5661 truth character::MoveTowardsHomePos () {
5662 if (HomeDataIsValid() && IsEnabled()) {
5663 SetGoingTo(HomeData->Pos);
5664 return MoveTowardsTarget(false) || (!GetPos().IsAdjacent(HomeData->Pos) && MoveRandomly());
5666 return false;
5670 truth character::TryToChangeEquipment (stack *MainStack, stack *SecStack, int Chosen) {
5671 if (!GetBodyPartOfEquipment(Chosen)) {
5672 ADD_MESSAGE("Bodypart missing!");
5673 return false;
5676 item *OldEquipment = GetEquipment(Chosen);
5677 if (!IsPlayer() && BoundToUse(OldEquipment, Chosen)) {
5678 ADD_MESSAGE("%s refuses to unequip %s.", CHAR_DESCRIPTION(DEFINITE), OldEquipment->CHAR_NAME(DEFINITE));
5679 return false;
5681 if (OldEquipment) OldEquipment->MoveTo(MainStack);
5683 sorter Sorter = EquipmentSorter(Chosen);
5684 if (!MainStack->SortedItems(this, Sorter) && (!SecStack || !SecStack->SortedItems(this, Sorter))) {
5685 ADD_MESSAGE("You haven't got any item that could be used for this purpose.");
5686 return false;
5689 game::DrawEverythingNoBlit();
5690 itemvector ItemVector;
5691 int Return = MainStack->DrawContents(ItemVector, SecStack, this,
5692 CONST_S("Choose ")+GetEquipmentName(Chosen)+':',
5693 (SecStack ? CONST_S("Items in your inventory") : CONST_S("")),
5694 (SecStack ? festring(CONST_S("Items in ")+GetPossessivePronoun()+" inventory") : CONST_S("")),
5695 (SecStack ? festring(GetDescription(DEFINITE)+" is "+GetVerbalBurdenState()) : CONST_S("")),
5696 GetVerbalBurdenStateColor(),
5697 NONE_AS_CHOICE|NO_MULTI_SELECT|SELECT_PAIR|SKIP_FIRST_IF_NO_OLD,
5698 Sorter, OldEquipment);
5699 if (Return == ESCAPED) {
5700 if (OldEquipment) {
5701 OldEquipment->RemoveFromSlot();
5702 SetEquipment(Chosen, OldEquipment);
5704 return false;
5707 item *Item = (ItemVector.empty() ? 0 : ItemVector[0]);
5709 if (Item) {
5710 if (!IsPlayer() && !AllowEquipment(Item, Chosen)) {
5711 ADD_MESSAGE("%s refuses to equip %s.", CHAR_DESCRIPTION(DEFINITE), Item->CHAR_NAME(DEFINITE));
5712 return false;
5714 int otherChosen = -1;
5715 if (ItemVector[0]->HandleInPairs() && ItemVector.size() > 1) {
5716 switch (Chosen) {
5717 case RIGHT_GAUNTLET_INDEX: otherChosen = LEFT_GAUNTLET_INDEX; break;
5718 case LEFT_GAUNTLET_INDEX: otherChosen = RIGHT_GAUNTLET_INDEX; break;
5719 case RIGHT_BOOT_INDEX: otherChosen = LEFT_BOOT_INDEX; break;
5720 case LEFT_BOOT_INDEX: otherChosen = RIGHT_BOOT_INDEX; break;
5721 default: break;
5723 if (otherChosen != -1) {
5724 if (GetBodyPartOfEquipment(otherChosen)) {
5725 if (!game::TruthQuestion("Wear both items?", NO)) otherChosen = -1;
5726 } else {
5727 otherChosen = -1;
5731 // wear/wield first item
5732 Item->RemoveFromSlot();
5733 SetEquipment(Chosen, Item);
5734 if (CheckIfEquipmentIsNotUsable(Chosen)) { Item->MoveTo(MainStack); Item = 0; } // small bug?
5735 // wear/wield possible second item
5736 if (Item && otherChosen != -1 && ItemVector[0]->HandleInPairs() && ItemVector.size() > 1 && GetBodyPartOfEquipment(otherChosen)) {
5737 item *otherOld = GetEquipment(otherChosen);
5738 if (otherOld && !IsPlayer() && BoundToUse(otherOld, otherChosen)) {
5739 ADD_MESSAGE("%s refuses to unequip %s.", CHAR_DESCRIPTION(DEFINITE), otherOld->CHAR_NAME(DEFINITE));
5740 } else if (otherOld) {
5741 otherOld->MoveTo(MainStack);
5743 ItemVector[1]->RemoveFromSlot();
5744 SetEquipment(otherChosen, ItemVector[1]);
5745 if (CheckIfEquipmentIsNotUsable(otherChosen)) { ItemVector[1]->MoveTo(MainStack); } // small bug?
5749 return (Item != OldEquipment);
5753 void character::PrintBeginParasitizedMessage () const {
5754 if (IsPlayer()) ADD_MESSAGE("You feel you are no longer alone.");
5758 void character::PrintEndParasitizedMessage () const {
5759 if (IsPlayer()) ADD_MESSAGE("A feeling of long welcome emptiness overwhelms you.");
5763 void character::ParasitizedHandler () {
5764 EditNP(-5);
5765 if (!(RAND() % 250)) {
5766 if (IsPlayer()) ADD_MESSAGE("Ugh. You feel something violently carving its way through your intestines.");
5767 ReceiveDamage(0, 1, POISON, TORSO, 8, false, false, false, false);
5768 CheckDeath(CONST_S("killed by a vile parasite"), 0);
5773 truth character::CanFollow () const {
5774 return CanMove() && !StateIsActivated(PANIC) && !IsStuck();
5778 festring character::GetKillName () const {
5779 if (!GetPolymorphBackup()) return GetName(INDEFINITE);
5780 festring KillName;
5781 GetPolymorphBackup()->AddName(KillName, INDEFINITE);
5782 KillName << " polymorphed into ";
5783 id::AddName(KillName, INDEFINITE);
5784 return KillName;
5788 festring character::GetPanelName () const {
5789 festring Name;
5790 Name << AssignedName << " the " << game::GetVerbalPlayerAlignment() << ' ';
5791 id::AddName(Name, UNARTICLED);
5792 return Name;
5796 sLong character::GetMoveAPRequirement (int Difficulty) const {
5797 return (!StateIsActivated(PANIC) ? 10000000 : 8000000) * Difficulty / (APBonus(GetAttribute(AGILITY)) * GetMoveEase());
5801 bodypart *character::HealHitPoint() {
5802 int NeedHeal = 0, NeedHealIndex[MAX_BODYPARTS];
5803 for (int c = 0; c < BodyParts; ++c) {
5804 bodypart *BodyPart = GetBodyPart(c);
5805 if (BodyPart && BodyPart->CanRegenerate() && BodyPart->GetHP() < BodyPart->GetMaxHP()) NeedHealIndex[NeedHeal++] = c;
5807 if (NeedHeal) {
5808 bodypart *BodyPart = GetBodyPart(NeedHealIndex[RAND() % NeedHeal]);
5809 BodyPart->IncreaseHP();
5810 ++HP;
5811 return BodyPart;
5813 return 0;
5817 void character::CreateHomeData () {
5818 HomeData = new homedata;
5819 lsquare *Square = GetLSquareUnder();
5820 HomeData->Pos = Square->GetPos();
5821 HomeData->Dungeon = Square->GetDungeonIndex();
5822 HomeData->Level = Square->GetLevelIndex();
5823 HomeData->Room = Square->GetRoomIndex();
5827 room *character::GetHomeRoom() const {
5828 if (HomeDataIsValid() && HomeData->Room) return GetLevel()->GetRoom(HomeData->Room);
5829 return 0;
5833 void character::RemoveHomeData () {
5834 delete HomeData;
5835 HomeData = 0;
5839 void character::AddESPConsumeMessage () const {
5840 if (IsPlayer()) ADD_MESSAGE("You feel a strange mental activity.");
5844 void character::SetBodyPart (int I, bodypart *What) {
5845 BodyPartSlot[I].PutInItem(What);
5846 if (What) {
5847 What->SignalPossibleUsabilityChange();
5848 What->Disable();
5849 AddOriginalBodyPartID(I, What->GetID());
5850 if (What->GetMainMaterial()->IsInfectedByLeprosy()) GainIntrinsic(LEPROSY);
5851 else if (StateIsActivated(LEPROSY)) What->GetMainMaterial()->SetIsInfectedByLeprosy(true);
5856 truth character::ConsumeItem (item *Item, cfestring &ConsumeVerb) {
5857 if (IsPlayer() && HasHadBodyPart(Item) && !game::TruthQuestion(CONST_S("Are you sure? You may be able to put it back...")))
5858 return false;
5859 if (Item->IsOnGround() && GetRoom() && !GetRoom()->ConsumeItem(this, Item, 1))
5860 return false;
5861 if (IsPlayer()) ADD_MESSAGE("You begin %s %s.", ConsumeVerb.CStr(), Item->CHAR_NAME(DEFINITE));
5862 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s begins %s %s.", CHAR_NAME(DEFINITE), ConsumeVerb.CStr(), Item->CHAR_NAME(DEFINITE));
5863 consume *Consume = consume::Spawn(this);
5864 Consume->SetDescription(ConsumeVerb);
5865 Consume->SetConsumingID(Item->GetID());
5866 SetAction(Consume);
5867 DexterityAction(5);
5868 return true;
5872 truth character::CheckThrow () const {
5873 if (!CanThrow()) {
5874 ADD_MESSAGE("This monster type cannot throw.");
5875 return false;
5877 return true;
5881 void character::GetHitByExplosion (const explosion *Explosion, int Damage) {
5882 int DamageDirection = GetPos() == Explosion->Pos ? RANDOM_DIR : game::CalculateRoughDirection(GetPos() - Explosion->Pos);
5883 if (!IsPet() && Explosion->Terrorist && Explosion->Terrorist->IsPet()) Explosion->Terrorist->Hostility(this);
5884 GetTorso()->SpillBlood((8 - Explosion->Size + RAND() % (8 - Explosion->Size)) >> 1);
5885 if (DamageDirection == RANDOM_DIR) DamageDirection = RAND()&7;
5886 v2 SpillPos = GetPos() + game::GetMoveVector(DamageDirection);
5887 if (SquareUnder[0] && GetArea()->IsValidPos(SpillPos)) GetTorso()->SpillBlood((8-Explosion->Size+RAND()%(8-Explosion->Size))>>1, SpillPos);
5888 if (IsPlayer()) ADD_MESSAGE("You are hit by the explosion!");
5889 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s is hit by the explosion.", CHAR_NAME(DEFINITE));
5890 truth WasUnconscious = GetAction() && GetAction()->IsUnconsciousness();
5891 ReceiveDamage(Explosion->Terrorist, Damage >> 1, FIRE, ALL, DamageDirection, true, false, false, false);
5892 if (IsEnabled()) {
5893 ReceiveDamage(Explosion->Terrorist, Damage >> 1, PHYSICAL_DAMAGE, ALL, DamageDirection, true, false, false, false);
5894 CheckDeath(Explosion->DeathMsg, Explosion->Terrorist, !WasUnconscious ? IGNORE_UNCONSCIOUSNESS : 0);
5899 void character::SortAllItems (const sortdata &SortData) {
5900 GetStack()->SortAllItems(SortData);
5901 doforequipmentswithparam<const sortdata&>()(this, &item::SortAllItems, SortData);
5905 void character::PrintBeginSearchingMessage () const {
5906 if (IsPlayer()) ADD_MESSAGE("You feel you can now notice even the very smallest details around you.");
5910 void character::PrintEndSearchingMessage () const {
5911 if (IsPlayer()) ADD_MESSAGE("You feel less perceptive.");
5915 void character::SearchingHandler () {
5916 if (!game::IsInWilderness()) Search(15);
5920 void character::Search (int Perception) {
5921 for (int d = 0; d < GetExtendedNeighbourSquares(); ++d) {
5922 lsquare *LSquare = GetNeighbourLSquare(d);
5923 if (LSquare) LSquare->GetStack()->Search(this, Min(Perception, 200));
5928 // surprisingly returns 0 if fails
5929 character *character::GetRandomNeighbour (int RelationFlags) const {
5930 character *Chars[MAX_NEIGHBOUR_SQUARES];
5931 int Index = 0;
5932 for (int d = 0; d < GetNeighbourSquares(); ++d) {
5933 lsquare *LSquare = GetNeighbourLSquare(d);
5934 if (LSquare) {
5935 character *Char = LSquare->GetCharacter();
5936 if (Char && (GetRelation(Char) & RelationFlags)) Chars[Index++] = Char;
5939 return Index ? Chars[RAND() % Index] : 0;
5943 void character::ResetStates () {
5944 for (int c = 0; c < STATES; ++c) {
5945 if (1 << c != POLYMORPHED && TemporaryStateIsActivated(1 << c) && TemporaryStateCounter[c] != PERMANENT) {
5946 TemporaryState &= ~(1 << c);
5947 if (StateData[c].EndHandler) {
5948 (this->*StateData[c].EndHandler)();
5949 if (!IsEnabled())return;
5956 void characterdatabase::InitDefaults (const characterprototype *NewProtoType, int NewConfig, cfestring &acfgstrname) {
5957 IsAbstract = false;
5958 ProtoType = NewProtoType;
5959 Config = NewConfig;
5960 CfgStrName = acfgstrname;
5961 Alias.Clear();
5965 void character::PrintBeginGasImmunityMessage () const {
5966 if (IsPlayer()) ADD_MESSAGE("All smells fade away.");
5970 void character::PrintEndGasImmunityMessage () const {
5971 if (IsPlayer()) ADD_MESSAGE("Yuck! The world smells bad again.");
5975 void character::ShowAdventureInfo () const {
5976 static const char *lists[4][4] = {
5977 { "Show massacre history",
5978 "Show inventory",
5979 "Show message history",
5980 NULL },
5981 { "Show inventory",
5982 "Show message history",
5983 NULL,
5984 NULL },
5985 { "Show message history",
5986 NULL,
5987 NULL,
5988 NULL },
5989 { "Show massacre history",
5990 "Show message history",
5991 NULL,
5992 NULL }
5994 // massacre, inventory, messages
5995 static const int nums[4][3] = {
5996 { 0, 1, 2},
5997 {-1, 0, 1},
5998 {-1,-1, 0},
5999 { 0,-1, 0}
6001 int idx = 0;
6002 if (GetStack()->GetItems()) {
6003 idx = game::MassacreListsEmpty() ? 1 : 0;
6004 } else {
6005 idx = game::MassacreListsEmpty() ? 2 : 3;
6007 int sel = -1;
6008 for (;;) {
6009 sel = game::ListSelectorArray(sel, CONST_S("Do you want to see some funny history?"), lists[idx]);
6010 if (sel < 0) break;
6011 if (sel == nums[idx][0] && !game::MassacreListsEmpty()) {
6012 game::DisplayMassacreLists();
6014 if (sel == nums[idx][1] && GetStack()->GetItems()) {
6015 GetStack()->DrawContents(this, CONST_S("Your inventory"), NO_SELECT);
6016 for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) i->DrawContents(this);
6017 doforequipmentswithparam<ccharacter *>()(this, &item::DrawContents, this);
6019 if (sel == nums[idx][2]) {
6020 msgsystem::DrawMessageHistory();
6026 truth character::EditAllAttributes (int Amount) {
6027 if (!Amount) return true;
6028 int c;
6029 truth MayEditMore = false;
6030 for (c = 0; c < BodyParts; ++c) {
6031 bodypart *BodyPart = GetBodyPart(c);
6032 if (BodyPart && BodyPart->EditAllAttributes(Amount)) MayEditMore = true;
6034 for (c = 0; c < BASE_ATTRIBUTES; ++c) {
6035 if (BaseExperience[c]) {
6036 BaseExperience[c] += Amount * EXP_MULTIPLIER;
6037 LimitRef(BaseExperience[c], MIN_EXP, MAX_EXP);
6038 if ((Amount < 0 && BaseExperience[c] != MIN_EXP) || (Amount > 0 && BaseExperience[c] != MAX_EXP)) MayEditMore = true;
6041 CalculateAll();
6042 RestoreHP();
6043 RestoreStamina();
6044 if (IsPlayer()) {
6045 game::SendLOSUpdateRequest();
6046 UpdateESPLOS();
6048 if (IsPlayerKind()) UpdatePictures();
6049 return MayEditMore;
6053 #ifdef WIZARD
6054 void character::AddAttributeInfo (festring &Entry) const {
6055 Entry.Resize(57);
6056 Entry << GetAttribute(ENDURANCE);
6057 Entry.Resize(60);
6058 Entry << GetAttribute(PERCEPTION);
6059 Entry.Resize(63);
6060 Entry << GetAttribute(INTELLIGENCE);
6061 Entry.Resize(66);
6062 Entry << GetAttribute(WISDOM);
6063 Entry.Resize(69);
6064 Entry << GetAttribute(CHARISMA);
6065 Entry.Resize(72);
6066 Entry << GetAttribute(MANA);
6070 void character::AddDefenceInfo (felist &List) const {
6071 festring Entry;
6072 for (int c = 0; c < BodyParts; ++c) {
6073 bodypart *BodyPart = GetBodyPart(c);
6074 if (BodyPart) {
6075 Entry = CONST_S(" ");
6076 BodyPart->AddName(Entry, UNARTICLED);
6077 Entry.Resize(60);
6078 Entry << BodyPart->GetMaxHP();
6079 Entry.Resize(70);
6080 Entry << BodyPart->GetTotalResistance(PHYSICAL_DAMAGE);
6081 List.AddEntry(Entry, LIGHT_GRAY);
6087 void character::DetachBodyPart () {
6088 ADD_MESSAGE("You haven't got any extra bodyparts.");
6090 #endif
6093 void character::ReceiveHolyBanana (sLong Amount) {
6094 Amount <<= 1;
6095 EditExperience(ARM_STRENGTH, Amount, 1 << 13);
6096 EditExperience(LEG_STRENGTH, Amount, 1 << 13);
6097 EditExperience(DEXTERITY, Amount, 1 << 13);
6098 EditExperience(AGILITY, Amount, 1 << 13);
6099 EditExperience(ENDURANCE, Amount, 1 << 13);
6100 EditExperience(PERCEPTION, Amount, 1 << 13);
6101 EditExperience(INTELLIGENCE, Amount, 1 << 13);
6102 EditExperience(WISDOM, Amount, 1 << 13);
6103 EditExperience(CHARISMA, Amount, 1 << 13);
6104 RestoreLivingHP();
6108 void character::AddHolyBananaConsumeEndMessage () const {
6109 if (IsPlayer()) ADD_MESSAGE("You feel a mysterious strengthening fire coursing through your body.");
6110 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s is surrounded by a swirling fire aura.", CHAR_NAME(DEFINITE));
6114 void character::ReceiveHolyMango (sLong Amount) {
6115 Amount <<= 1;
6116 EditExperience(ARM_STRENGTH, Amount, 1 << 13);
6117 EditExperience(LEG_STRENGTH, Amount, 1 << 13);
6118 EditExperience(DEXTERITY, Amount, 1 << 13);
6119 EditExperience(AGILITY, Amount, 1 << 13);
6120 EditExperience(ENDURANCE, Amount, 1 << 13);
6121 EditExperience(PERCEPTION, Amount, 1 << 13);
6122 EditExperience(INTELLIGENCE, Amount, 1 << 13);
6123 EditExperience(WISDOM, Amount, 1 << 13);
6124 EditExperience(CHARISMA, Amount, 1 << 13);
6125 RestoreLivingHP();
6129 void character::AddHolyMangoConsumeEndMessage () const {
6130 if (IsPlayer()) ADD_MESSAGE("You feel a mysterious strengthening fire coursing through your body.");
6131 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s is surrounded by a swirling fire aura.", CHAR_NAME(DEFINITE));
6135 truth character::PreProcessForBone () {
6136 if (IsPet() && IsEnabled()) {
6137 Die(0, CONST_S(""), FORBID_REINCARNATION);
6138 return true;
6140 if (GetAction()) GetAction()->Terminate(false);
6141 if (TemporaryStateIsActivated(POLYMORPHED)) {
6142 character *PolymorphBackup = GetPolymorphBackup();
6143 EndPolymorph();
6144 PolymorphBackup->PreProcessForBone();
6145 return true;
6147 if (MustBeRemovedFromBone()) return false;
6148 if (IsUnique() && !CanBeGenerated()) game::SignalQuestMonsterFound();
6149 RestoreLivingHP();
6150 ResetStates();
6151 RemoveTraps();
6152 GetStack()->PreProcessForBone();
6153 doforequipments()(this, &item::PreProcessForBone);
6154 doforbodyparts()(this, &bodypart::PreProcessForBone);
6155 game::RemoveCharacterID(ID);
6156 ID = -ID;
6157 game::AddCharacterID(this, ID);
6158 return true;
6162 truth character::PostProcessForBone (double &DangerSum, int& Enemies) {
6163 if (PostProcessForBone()) {
6164 if (GetRelation(PLAYER) == HOSTILE) {
6165 double Danger = GetRelativeDanger(PLAYER, true);
6166 if (Danger > 99.0) game::SetTooGreatDangerFound(true);
6167 else if (!IsUnique() && !IgnoreDanger()) {
6168 DangerSum += Danger;
6169 ++Enemies;
6172 return true;
6174 return false;
6178 truth character::PostProcessForBone () {
6179 feuLong NewID = game::CreateNewCharacterID(this);
6180 game::GetBoneCharacterIDMap().insert(std::make_pair(-ID, NewID));
6181 game::RemoveCharacterID(ID);
6182 ID = NewID;
6183 if (IsUnique() && CanBeGenerated()) {
6184 if (DataBase->Flags & HAS_BEEN_GENERATED) return false;
6185 SignalGeneration();
6187 GetStack()->PostProcessForBone();
6188 doforequipments()(this, &item::PostProcessForBone);
6189 doforbodyparts()(this, &bodypart::PostProcessForBone);
6190 return true;
6194 void character::FinalProcessForBone () {
6195 Flags &= ~C_PLAYER;
6196 GetStack()->FinalProcessForBone();
6197 doforequipments()(this, &item::FinalProcessForBone);
6198 int c;
6199 for (c = 0; c < BodyParts; ++c) {
6200 for (std::list<feuLong>::iterator i = OriginalBodyPartID[c].begin(); i != OriginalBodyPartID[c].end();) {
6201 boneidmap::iterator BI = game::GetBoneItemIDMap().find(*i);
6202 if (BI == game::GetBoneItemIDMap().end()) {
6203 std::list<feuLong>::iterator Dirt = i++;
6204 OriginalBodyPartID[c].erase(Dirt);
6205 } else {
6206 *i = BI->second;
6207 ++i;
6214 void character::SetSoulID (feuLong What) {
6215 if (GetPolymorphBackup()) GetPolymorphBackup()->SetSoulID(What);
6219 truth character::SearchForItem (citem *Item) const {
6220 if (combineequipmentpredicateswithparam<feuLong>()(this, &item::HasID, Item->GetID(), 1)) return true;
6221 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) if (*i == Item) return true;
6222 return false;
6226 item *character::SearchForItem (const sweaponskill *SWeaponSkill) const {
6227 for (int c = 0; c < GetEquipments(); ++c) {
6228 item *Equipment = GetEquipment(c);
6229 if (Equipment && SWeaponSkill->IsSkillOf(Equipment)) return Equipment;
6231 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) if (SWeaponSkill->IsSkillOf(*i)) return *i;
6232 return 0;
6236 void character::PutNear (v2 Pos) {
6237 v2 NewPos = game::GetCurrentLevel()->GetNearestFreeSquare(this, Pos, false);
6238 if (NewPos == ERROR_V2) {
6239 do { NewPos = game::GetCurrentLevel()->GetRandomSquare(this); } while(NewPos == Pos);
6241 PutTo(NewPos);
6245 void character::PutToOrNear (v2 Pos) {
6246 if (game::IsInWilderness() || (CanMoveOn(game::GetCurrentLevel()->GetLSquare(Pos)) && IsFreeForMe(game::GetCurrentLevel()->GetLSquare(Pos))))
6247 PutTo(Pos);
6248 else
6249 PutNear(Pos);
6253 void character::PutTo (v2 Pos) {
6254 SquareUnder[0] = game::GetCurrentArea()->GetSquare(Pos);
6255 SquareUnder[0]->AddCharacter(this);
6259 void character::Remove () {
6260 SquareUnder[0]->RemoveCharacter();
6261 SquareUnder[0] = 0;
6265 void character::SendNewDrawRequest () const {
6266 for (int c = 0; c < SquaresUnder; ++c) {
6267 square *Square = GetSquareUnder(c);
6268 if (Square) Square->SendNewDrawRequest();
6273 truth character::IsOver (v2 Pos) const {
6274 for (int c = 0; c < SquaresUnder; ++c) {
6275 square *Square = GetSquareUnder(c);
6276 if (Square && Square->GetPos() == Pos) return true;
6278 return false;
6282 truth character::CanTheoreticallyMoveOn (const lsquare *LSquare) const { return GetMoveType() & LSquare->GetTheoreticalWalkability(); }
6283 truth character::CanMoveOn (const lsquare *LSquare) const { return GetMoveType() & LSquare->GetWalkability(); }
6284 truth character::CanMoveOn (const square *Square) const { return GetMoveType() & Square->GetSquareWalkability(); }
6285 truth character::CanMoveOn (const olterrain *OLTerrain) const { return GetMoveType() & OLTerrain->GetWalkability(); }
6286 truth character::CanMoveOn (const oterrain *OTerrain) const { return GetMoveType() & OTerrain->GetWalkability(); }
6287 truth character::IsFreeForMe(square *Square) const { return !Square->GetCharacter() || Square->GetCharacter() == this; }
6288 void character::LoadSquaresUnder () { SquareUnder[0] = game::GetSquareInLoad(); }
6290 truth character::AttackAdjacentEnemyAI () {
6291 if (!IsEnabled()) return false;
6292 character *Char[MAX_NEIGHBOUR_SQUARES];
6293 v2 Pos[MAX_NEIGHBOUR_SQUARES];
6294 int Dir[MAX_NEIGHBOUR_SQUARES];
6295 int Index = 0;
6296 for (int d = 0; d < GetNeighbourSquares(); ++d) {
6297 square *Square = GetNeighbourSquare(d);
6298 if (Square) {
6299 character *Enemy = Square->GetCharacter();
6300 if (Enemy && (GetRelation(Enemy) == HOSTILE || StateIsActivated(CONFUSED))) {
6301 Dir[Index] = d;
6302 Pos[Index] = Square->GetPos();
6303 Char[Index++] = Enemy;
6307 if (Index) {
6308 int ChosenIndex = RAND() % Index;
6309 Hit(Char[ChosenIndex], Pos[ChosenIndex], Dir[ChosenIndex]);
6310 return true;
6312 return false;
6316 void character::SignalStepFrom (lsquare **OldSquareUnder) {
6317 int c;
6318 lsquare *NewSquareUnder[MAX_SQUARES_UNDER];
6319 for (c = 0; c < GetSquaresUnder(); ++c) NewSquareUnder[c] = GetLSquareUnder(c);
6320 for (c = 0; c < GetSquaresUnder(); ++c) {
6321 if (IsEnabled() && GetLSquareUnder(c) == NewSquareUnder[c]) NewSquareUnder[c]->StepOn(this, OldSquareUnder);
6326 int character::GetSumOfAttributes () const {
6327 return GetAttribute(ENDURANCE) + GetAttribute(PERCEPTION) + GetAttribute(INTELLIGENCE) + GetAttribute(WISDOM) + GetAttribute(CHARISMA) + GetAttribute(ARM_STRENGTH) + GetAttribute(AGILITY);
6331 void character::IntelligenceAction (int Difficulty) {
6332 EditAP(-20000 * Difficulty / APBonus(GetAttribute(INTELLIGENCE)));
6333 EditExperience(INTELLIGENCE, Difficulty * 15, 1 << 7);
6337 struct walkabilitycontroller {
6338 static truth Handler (int x, int y) {
6339 return x >= 0 && y >= 0 && x < LevelXSize && y < LevelYSize && Map[x][y]->GetTheoreticalWalkability() & MoveType;
6341 static lsquare ***Map;
6342 static int LevelXSize, LevelYSize;
6343 static int MoveType;
6347 lsquare ***walkabilitycontroller::Map;
6348 int walkabilitycontroller::LevelXSize, walkabilitycontroller::LevelYSize;
6349 int walkabilitycontroller::MoveType;
6352 truth character::CreateRoute () {
6353 Route.clear();
6354 if (GetAttribute(INTELLIGENCE) >= 10 && !StateIsActivated(CONFUSED)) {
6355 v2 Pos = GetPos();
6356 walkabilitycontroller::Map = GetLevel()->GetMap();
6357 walkabilitycontroller::LevelXSize = GetLevel()->GetXSize();
6358 walkabilitycontroller::LevelYSize = GetLevel()->GetYSize();
6359 walkabilitycontroller::MoveType = GetMoveType();
6360 node *Node;
6361 for (int c = 0; c < game::GetTeams(); ++c)
6362 for (std::list<character *>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i) {
6363 character *Char = *i;
6364 if (Char->IsEnabled() && !Char->Route.empty() && (Char->GetMoveType()&GetMoveType()) == Char->GetMoveType()) {
6365 v2 CharGoingTo = Char->Route[0];
6366 v2 iPos = Char->Route.back();
6367 if ((GoingTo-CharGoingTo).GetLengthSquare() <= 100 && (Pos - iPos).GetLengthSquare() <= 100 &&
6368 mapmath<walkabilitycontroller>::DoLine(CharGoingTo.X, CharGoingTo.Y, GoingTo.X, GoingTo.Y, SKIP_FIRST) &&
6369 mapmath<walkabilitycontroller>::DoLine(Pos.X, Pos.Y, iPos.X, iPos.Y, SKIP_FIRST)) {
6370 if (!Illegal.empty() && Illegal.find(Char->Route.back()) != Illegal.end()) continue;
6371 Node = GetLevel()->FindRoute(CharGoingTo, GoingTo, Illegal, GetMoveType());
6372 if (Node) { while(Node->Last) { Route.push_back(Node->Pos); Node = Node->Last; } }
6373 else { Route.clear(); continue; }
6374 Route.insert(Route.end(), Char->Route.begin(), Char->Route.end());
6375 Node = GetLevel()->FindRoute(Pos, iPos, Illegal, GetMoveType());
6376 if (Node) { while (Node->Last) { Route.push_back(Node->Pos); Node = Node->Last; } }
6377 else { Route.clear(); continue; }
6378 IntelligenceAction(1);
6379 return true;
6383 Node = GetLevel()->FindRoute(Pos, GoingTo, Illegal, GetMoveType());
6384 if (Node) { while(Node->Last) { Route.push_back(Node->Pos); Node = Node->Last; } }
6385 else TerminateGoingTo();
6386 IntelligenceAction(5);
6387 return true;
6389 return false;
6393 void character::SetGoingTo (v2 What) {
6394 if (GoingTo != What) {
6395 GoingTo = What;
6396 Route.clear();
6397 Illegal.clear();
6402 void character::TerminateGoingTo () {
6403 GoingTo = ERROR_V2;
6404 Route.clear();
6405 Illegal.clear();
6409 truth character::CheckForFood (int Radius) {
6410 if (StateIsActivated(PANIC) || !UsesNutrition() || !IsEnabled()) return false;
6411 v2 Pos = GetPos();
6412 int x, y;
6413 for (int r = 1; r <= Radius; ++r) {
6414 x = Pos.X-r;
6415 if (x >= 0) {
6416 for (y = Pos.Y-r; y <= Pos.Y+r; ++y) if (CheckForFoodInSquare(v2(x, y))) return true;
6418 x = Pos.X+r;
6419 if (x < GetLevel()->GetXSize()) {
6420 for (y = Pos.Y-r; y <= Pos.Y+r; ++y) if (CheckForFoodInSquare(v2(x, y))) return true;
6422 y = Pos.Y-r;
6423 if (y >= 0) {
6424 for (x = Pos.X-r; x <= Pos.X+r; ++x) if (CheckForFoodInSquare(v2(x, y))) return true;
6426 y = Pos.Y+r;
6427 if (y < GetLevel()->GetYSize()) {
6428 for (x = Pos.X-r; x <= Pos.X+r; ++x) if (CheckForFoodInSquare(v2(x, y))) return true;
6431 return false;
6435 truth character::CheckForFoodInSquare (v2 Pos) {
6436 level *Level = GetLevel();
6437 if (Level->IsValidPos(Pos)) {
6438 lsquare *Square = Level->GetLSquare(Pos);
6439 stack *Stack = Square->GetStack();
6440 if (Stack->GetItems()) {
6441 for (stackiterator i = Stack->GetBottom(); i.HasItem(); ++i) {
6442 if (i->IsPickable(this) && i->CanBeSeenBy(this) && i->CanBeEatenByAI(this) && (!Square->GetRoomIndex() || Square->GetRoom()->AllowFoodSearch())) {
6443 SetGoingTo(Pos);
6444 return MoveTowardsTarget(false);
6449 return false;
6453 void character::SetConfig (int NewConfig, int SpecialFlags) {
6454 databasecreator<character>::InstallDataBase(this, NewConfig);
6455 CalculateAll();
6456 CheckIfSeen();
6457 if (!(SpecialFlags & NO_PIC_UPDATE)) UpdatePictures();
6461 truth character::IsOver (citem *Item) const {
6462 for (int c1 = 0; c1 < Item->GetSquaresUnder(); ++c1)
6463 for (int c2 = 0; c2 < SquaresUnder; ++c2)
6464 if (Item->GetPos(c1) == GetPos(c2)) return true;
6465 return false;
6469 truth character::CheckConsume (cfestring &Verb) const {
6470 if (!UsesNutrition()) {
6471 if (IsPlayer()) ADD_MESSAGE("In this form you can't and don't need to %s.", Verb.CStr());
6472 return false;
6474 return true;
6478 void character::PutTo (lsquare *To) {
6479 PutTo(To->GetPos());
6483 double character::RandomizeBabyExperience (double SumE) {
6484 if (!SumE) return 0;
6485 double E = (SumE / 4) - (SumE / 32) + (double(RAND()) / MAX_RAND) * (SumE / 16 + 1);
6486 return Limit(E, MIN_EXP, MAX_EXP);
6490 liquid *character::CreateBlood (sLong Volume) const {
6491 return liquid::Spawn(GetBloodMaterial(), Volume);
6495 void character::SpillFluid (character *Spiller, liquid *Liquid, int SquareIndex) {
6496 sLong ReserveVolume = Liquid->GetVolume() >> 1;
6497 Liquid->EditVolume(-ReserveVolume);
6498 GetStack()->SpillFluid(Spiller, Liquid, sLong(Liquid->GetVolume() * sqrt(double(GetStack()->GetVolume()) / GetVolume())));
6499 Liquid->EditVolume(ReserveVolume);
6500 int c;
6501 sLong Modifier[MAX_BODYPARTS], ModifierSum = 0;
6502 for (c = 0; c < BodyParts; ++c) {
6503 if (GetBodyPart(c)) {
6504 Modifier[c] = sLong(sqrt(GetBodyPart(c)->GetVolume()));
6505 if (Modifier[c]) Modifier[c] *= 1 + (RAND() & 3);
6506 ModifierSum += Modifier[c];
6507 } else {
6508 Modifier[c] = 0;
6511 for (c = 1; c < GetBodyParts(); ++c) {
6512 if (GetBodyPart(c) && IsEnabled())
6513 GetBodyPart(c)->SpillFluid(Spiller, Liquid->SpawnMoreLiquid(Liquid->GetVolume() * Modifier[c] / ModifierSum), SquareIndex);
6515 if (IsEnabled()) {
6516 Liquid->SetVolume(Liquid->GetVolume() * Modifier[TORSO_INDEX] / ModifierSum);
6517 GetTorso()->SpillFluid(Spiller, Liquid, SquareIndex);
6522 void character::StayOn (liquid *Liquid) {
6523 Liquid->TouchEffect(this, TORSO_INDEX);
6527 truth character::IsAlly (ccharacter *Char) const {
6528 return Char->GetTeam()->GetID() == GetTeam()->GetID();
6532 void character::ResetSpoiling () {
6533 doforbodyparts()(this, &bodypart::ResetSpoiling);
6537 item *character::SearchForItem (ccharacter *Char, sorter Sorter) const {
6538 item *Equipment = findequipment<ccharacter *>()(this, Sorter, Char);
6539 if (Equipment) return Equipment;
6540 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) if (((*i)->*Sorter)(Char)) return *i;
6541 return 0;
6545 truth character::DetectMaterial (cmaterial *Material) const {
6546 return GetStack()->DetectMaterial(Material) ||
6547 combinebodypartpredicateswithparam<cmaterial*>()(this, &bodypart::DetectMaterial, Material, 1) ||
6548 combineequipmentpredicateswithparam<cmaterial*>()(this, &item::DetectMaterial, Material, 1);
6552 truth character::DamageTypeDestroysBodyPart (int Type) {
6553 return (Type&0xFFF) != PHYSICAL_DAMAGE;
6557 truth character::CheckIfTooScaredToHit (ccharacter *Enemy) const {
6558 if (IsPlayer() && StateIsActivated(PANIC)) {
6559 for (int d = 0; d < GetNeighbourSquares(); ++d) {
6560 square *Square = GetNeighbourSquare(d);
6561 if (Square) {
6562 if(CanMoveOn(Square) && (!Square->GetCharacter() || Square->GetCharacter()->IsPet())) {
6563 ADD_MESSAGE("You are too scared to attack %s.", Enemy->CHAR_DESCRIPTION(DEFINITE));
6564 return true;
6569 return false;
6573 void character::PrintBeginLevitationMessage () const {
6574 if (!IsFlying()) {
6575 if (IsPlayer()) ADD_MESSAGE("You rise into the air like a small hot-air balloon.");
6576 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s begins to float.", CHAR_NAME(DEFINITE));
6581 void character::PrintEndLevitationMessage () const {
6582 if (!IsFlying()) {
6583 if (IsPlayer()) ADD_MESSAGE("You descend gently onto the ground.");
6584 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s drops onto the ground.", CHAR_NAME(DEFINITE));
6589 truth character::IsLimbIndex (int I) {
6590 switch (I) {
6591 case RIGHT_ARM_INDEX:
6592 case LEFT_ARM_INDEX:
6593 case RIGHT_LEG_INDEX:
6594 case LEFT_LEG_INDEX:
6595 return true;
6597 return false;
6601 void character::EditExperience (int Identifier, double Value, double Speed) {
6602 if (!AllowExperience() || (Identifier == ENDURANCE && UseMaterialAttributes())) return;
6603 int Change = RawEditExperience(BaseExperience[Identifier], GetNaturalExperience(Identifier), Value, Speed);
6604 if (!Change) return;
6605 cchar *PlayerMsg = 0, *NPCMsg = 0;
6606 switch (Identifier) {
6607 case ENDURANCE:
6608 if (Change > 0) {
6609 PlayerMsg = "You feel tougher than anything!";
6610 if (IsPet()) NPCMsg = "Suddenly %s looks tougher.";
6611 } else {
6612 PlayerMsg = "You feel less healthy.";
6613 if (IsPet()) NPCMsg = "Suddenly %s looks less healthy.";
6615 CalculateBodyPartMaxHPs();
6616 CalculateMaxStamina();
6617 break;
6618 case PERCEPTION:
6619 if (IsPlayer()) {
6620 if (Change > 0) {
6621 PlayerMsg = "You now see the world in much better detail than before.";
6622 } else {
6623 PlayerMsg = "You feel very guru.";
6624 game::GetGod(VALPURUS)->AdjustRelation(100);
6626 game::SendLOSUpdateRequest();
6628 break;
6629 case INTELLIGENCE:
6630 if (IsPlayer()) {
6631 if (Change > 0) PlayerMsg = "Suddenly the inner structure of the Multiverse around you looks quite simple.";
6632 else PlayerMsg = "It surely is hard to think today.";
6633 UpdateESPLOS();
6635 if (IsPlayerKind()) UpdatePictures();
6636 break;
6637 case WISDOM:
6638 if (IsPlayer()) {
6639 if (Change > 0) PlayerMsg = "You feel your life experience increasing all the time.";
6640 else PlayerMsg = "You feel like having done something unwise.";
6642 if (IsPlayerKind()) UpdatePictures();
6643 break;
6644 case CHARISMA:
6645 if (Change > 0) {
6646 PlayerMsg = "You feel very confident of your social skills.";
6647 if (IsPet()) {
6648 if (GetAttribute(CHARISMA) <= 15) NPCMsg = "%s looks less ugly.";
6649 else NPCMsg = "%s looks more attractive.";
6651 } else {
6652 PlayerMsg = "You feel somehow disliked.";
6653 if (IsPet()) {
6654 if (GetAttribute(CHARISMA) < 15) NPCMsg = "%s looks more ugly.";
6655 else NPCMsg = "%s looks less attractive.";
6658 if (IsPlayerKind()) UpdatePictures();
6659 break;
6660 case MANA:
6661 if (Change > 0) {
6662 PlayerMsg = "You feel magical forces coursing through your body!";
6663 NPCMsg = "You notice an odd glow around %s.";
6664 } else {
6665 PlayerMsg = "You feel your magical abilities withering slowly.";
6666 NPCMsg = "You notice strange vibrations in the air around %s. But they disappear rapidly.";
6668 break;
6671 if (IsPlayer()) ADD_MESSAGE("%s", PlayerMsg);
6672 else if (NPCMsg && CanBeSeenByPlayer()) ADD_MESSAGE(NPCMsg, CHAR_NAME(DEFINITE));
6674 CalculateBattleInfo();
6678 int character::RawEditExperience (double &Exp, double NaturalExp, double Value, double Speed) const {
6679 double OldExp = Exp;
6680 if (Speed < 0) {
6681 Speed = -Speed;
6682 Value = -Value;
6684 if(!OldExp || !Value || (Value > 0 && OldExp >= NaturalExp * (100 + Value) / 100) ||
6685 (Value < 0 && OldExp <= NaturalExp * (100 + Value) / 100)) return 0;
6686 if (!IsPlayer()) Speed *= 1.5;
6687 Exp += (NaturalExp * (100 + Value) - 100 * OldExp) * Speed * EXP_DIVISOR;
6688 LimitRef(Exp, MIN_EXP, MAX_EXP);
6689 int NewA = int(Exp * EXP_DIVISOR);
6690 int OldA = int(OldExp * EXP_DIVISOR);
6691 int Delta = NewA - OldA;
6692 if (Delta > 0) Exp = Max(Exp, (NewA + 0.05) * EXP_MULTIPLIER);
6693 else if (Delta < 0) Exp = Min(Exp, (NewA + 0.95) * EXP_MULTIPLIER);
6694 LimitRef(Exp, MIN_EXP, MAX_EXP);
6695 return Delta;
6699 int character::GetAttribute (int Identifier, truth AllowBonus) const {
6700 int A = int(BaseExperience[Identifier] * EXP_DIVISOR);
6701 if (AllowBonus && Identifier == INTELLIGENCE && BrainsHurt()) return Max((A + AttributeBonus[INTELLIGENCE]) / 3, 1);
6702 return A && AllowBonus ? Max(A + AttributeBonus[Identifier], 1) : A;
6706 void characterdatabase::PostProcess () {
6707 double AM = (100 + AttributeBonus) * EXP_MULTIPLIER / 100;
6708 for (int c = 0; c < ATTRIBUTES; ++c) NaturalExperience[c] = this->*ExpPtr[c] * AM;
6712 void character::EditDealExperience (sLong Price) {
6713 EditExperience(CHARISMA, sqrt(Price) / 5, 1 << 9);
6717 void character::PrintBeginLeprosyMessage () const {
6718 if (IsPlayer()) ADD_MESSAGE("You feel you're falling in pieces.");
6722 void character::PrintEndLeprosyMessage () const {
6723 if (IsPlayer()) ADD_MESSAGE("You feel your limbs are stuck in place tightly."); // CHANGE OR DIE
6727 void character::TryToInfectWithLeprosy (ccharacter *Infector) {
6728 if (!IsImmuneToLeprosy() &&
6729 ((GetRelation(Infector) == HOSTILE && !RAND_N(50 * GetAttribute(ENDURANCE))) ||
6730 !RAND_N(500 * GetAttribute(ENDURANCE)))) GainIntrinsic(LEPROSY);
6734 void character::SignalGeneration () {
6735 const_cast<database *>(DataBase)->Flags |= HAS_BEEN_GENERATED;
6739 void character::CheckIfSeen () {
6740 if (IsPlayer() || CanBeSeenByPlayer()) SignalSeen();
6744 void character::SignalSeen () {
6745 if (!(WarnFlags & WARNED) && GetRelation(PLAYER) == HOSTILE && !StateIsActivated(FEARLESS)) {
6746 double Danger = GetRelativeDanger(PLAYER);
6747 if (Danger > 5.0) {
6748 game::SetDangerFound(Max(game::GetDangerFound(), Danger));
6749 if (Danger > 500.0 && !(WarnFlags & HAS_CAUSED_PANIC)) {
6750 WarnFlags |= HAS_CAUSED_PANIC;
6751 game::SetCausePanicFlag(true);
6753 WarnFlags |= WARNED;
6756 const_cast<database *>(DataBase)->Flags |= HAS_BEEN_SEEN;
6760 int character::GetPolymorphIntelligenceRequirement () const {
6761 if (DataBase->PolymorphIntelligenceRequirement == DEPENDS_ON_ATTRIBUTES) return Max(GetAttributeAverage() - 5, 0);
6762 return DataBase->PolymorphIntelligenceRequirement;
6766 void character::RemoveAllItems () {
6767 GetStack()->Clean();
6768 for (int c = 0; c < GetEquipments(); ++c) {
6769 item *Equipment = GetEquipment(c);
6770 if (Equipment) {
6771 Equipment->RemoveFromSlot();
6772 Equipment->SendToHell();
6778 int character::CalculateWeaponSkillHits (ccharacter *Enemy) const {
6779 if (Enemy->IsPlayer()) {
6780 configid ConfigID(GetType(), GetConfig());
6781 const dangerid& DangerID = game::GetDangerMap().find(ConfigID)->second;
6782 return Min(int(DangerID.EquippedDanger * 2000), 1000);
6784 return Min(int(GetRelativeDanger(Enemy, true) * 2000), 1000);
6788 truth character::CanUseEquipment (int I) const {
6789 return CanUseEquipment() && I < GetEquipments() && GetBodyPartOfEquipment(I) && EquipmentIsAllowed(I);
6793 /* Target mustn't have any equipment */
6794 void character::DonateEquipmentTo (character *Character) {
6795 if (IsPlayer()) {
6796 feuLong *EquipmentMemory = game::GetEquipmentMemory();
6797 for (int c = 0; c < MAX_EQUIPMENT_SLOTS; ++c) {
6798 item *Item = GetEquipment(c);
6799 if (Item) {
6800 if (Character->CanUseEquipment(c)) {
6801 Item->RemoveFromSlot();
6802 Character->SetEquipment(c, Item);
6803 } else {
6804 EquipmentMemory[c] = Item->GetID();
6805 Item->MoveTo(Character->GetStack());
6807 } else if (CanUseEquipment(c)) {
6808 EquipmentMemory[c] = 0;
6809 } else if (EquipmentMemory[c] && Character->CanUseEquipment(c)) {
6810 for (stackiterator i = Character->GetStack()->GetBottom(); i.HasItem(); ++i) {
6811 if (i->GetID() == EquipmentMemory[c]) {
6812 item *Item = *i;
6813 Item->RemoveFromSlot();
6814 Character->SetEquipment(c, Item);
6815 break;
6818 EquipmentMemory[c] = 0;
6821 } else {
6822 for (int c = 0; c < GetEquipments(); ++c) {
6823 item *Item = GetEquipment(c);
6824 if (Item) {
6825 if (Character->CanUseEquipment(c)) {
6826 Item->RemoveFromSlot();
6827 Character->SetEquipment(c, Item);
6828 } else {
6829 Item->MoveTo(Character->GetStackUnder());
6837 void character::ReceivePeaSoup (sLong) {
6838 if (!game::IsInWilderness()) {
6839 lsquare *Square = GetLSquareUnder();
6840 if (Square->IsFlyable()) Square->AddSmoke(gas::Spawn(FART, 250));
6845 void character::AddPeaSoupConsumeEndMessage () const {
6846 if (IsPlayer()) {
6847 if (CanHear()) ADD_MESSAGE("Mmmh! The soup is very tasty. You hear a small puff.");
6848 else ADD_MESSAGE("Mmmh! The soup is very tasty.");
6849 } else if (CanBeSeenByPlayer() && PLAYER->CanHear()) {
6850 // change someday
6851 ADD_MESSAGE("You hear a small puff.");
6856 void character::CalculateMaxStamina () {
6857 MaxStamina = TorsoIsAlive() ? GetAttribute(ENDURANCE) * 10000 : 0;
6861 void character::EditStamina (int Amount, truth CanCauseUnconsciousness) {
6862 if (!TorsoIsAlive()) return;
6863 int UnconsciousnessStamina = MaxStamina >> 3;
6864 if (!CanCauseUnconsciousness && Amount < 0) {
6865 if (Stamina > UnconsciousnessStamina) {
6866 Stamina += Amount;
6867 if (Stamina < UnconsciousnessStamina) Stamina = UnconsciousnessStamina;
6869 return;
6871 int OldStamina = Stamina;
6872 Stamina += Amount;
6873 if (Stamina > MaxStamina) {
6874 Stamina = MaxStamina;
6875 } else if (Stamina < 0) {
6876 Stamina = 0;
6877 LoseConsciousness(250 + RAND_N(250));
6878 } else if (IsPlayer()) {
6879 if (OldStamina >= MaxStamina >> 2 && Stamina < MaxStamina >> 2) {
6880 ADD_MESSAGE("You are getting a little tired.");
6881 } else if(OldStamina >= UnconsciousnessStamina && Stamina < UnconsciousnessStamina) {
6882 ADD_MESSAGE("You are seriously out of breath!");
6883 game::SetPlayerIsRunning(false);
6886 if (IsPlayer() && StateIsActivated(PANIC) && GetTirednessState() != FAINTING) game::SetPlayerIsRunning(true);
6890 void character::RegenerateStamina () {
6891 if (GetTirednessState() != UNTIRED) {
6892 EditExperience(ENDURANCE, 50, 1);
6893 if (Sweats() && TorsoIsAlive() && !RAND_N(30) && !game::IsInWilderness()) {
6894 // Sweat amount proportional to endurance also
6895 //sLong Volume = sLong(0.05 * sqrt(GetBodyVolume()));
6896 sLong Volume = long(0.05*sqrt(GetBodyVolume()*GetAttribute(ENDURANCE)/10));
6897 if (GetTirednessState() == FAINTING) Volume <<= 1;
6898 for (int c = 0; c < SquaresUnder; ++c) GetLSquareUnder(c)->SpillFluid(0, CreateSweat(Volume), false, false);
6901 int Bonus = 1;
6902 if (Action) {
6903 if (Action->IsRest()) {
6904 if (SquaresUnder == 1) {
6905 Bonus = GetSquareUnder()->GetRestModifier() << 1;
6906 } else {
6907 int Lowest = GetSquareUnder(0)->GetRestModifier();
6908 for (int c = 1; c < GetSquaresUnder(); ++c) {
6909 int Mod = GetSquareUnder(c)->GetRestModifier();
6910 if (Mod < Lowest) Lowest = Mod;
6912 Bonus = Lowest << 1;
6914 } else if (Action->IsUnconsciousness()) Bonus = 2;
6916 int Plus1 = 100;
6917 switch (GetBurdenState()) {
6918 case OVER_LOADED: Plus1 = 25; break;
6919 case STRESSED: Plus1 = 50; break;
6920 case BURDENED: Plus1 = 75; break;
6922 int Plus2 = 100;
6923 if (IsPlayer()) {
6924 switch (GetHungerState()) {
6925 case STARVING: Plus2 = 25; break;
6926 case VERY_HUNGRY: Plus2 = 50; break;
6927 case HUNGRY: Plus2 = 75; break;
6930 Stamina += Plus1 * Plus2 * Bonus / 1000;
6931 if (Stamina > MaxStamina) Stamina = MaxStamina;
6932 if (IsPlayer() && StateIsActivated(PANIC) && GetTirednessState() != FAINTING) game::SetPlayerIsRunning(true);
6936 void character::BeginPanic () {
6937 if (IsPlayer() && GetTirednessState() != FAINTING) game::SetPlayerIsRunning(true);
6938 DeActivateVoluntaryAction();
6942 void character::EndPanic () {
6943 if (IsPlayer()) game::SetPlayerIsRunning(false);
6947 int character::GetTirednessState () const {
6948 if (Stamina >= MaxStamina >> 2) return UNTIRED;
6949 if (Stamina >= MaxStamina >> 3) return EXHAUSTED;
6950 return FAINTING;
6954 void character::ReceiveBlackUnicorn (sLong Amount) {
6955 if (!(RAND() % 160)) game::DoEvilDeed(Amount / 50);
6956 BeginTemporaryState(TELEPORT, Amount / 100);
6957 for (int c = 0; c < STATES; ++c) {
6958 if (StateData[c].Flags & DUR_TEMPORARY) {
6959 BeginTemporaryState(1 << c, Amount / 100);
6960 if (!IsEnabled()) return;
6961 } else if (StateData[c].Flags & DUR_PERMANENT) {
6962 GainIntrinsic(1 << c);
6963 if (!IsEnabled()) return;
6969 void character::ReceiveGrayUnicorn (sLong Amount) {
6970 if (!(RAND() % 80)) game::DoEvilDeed(Amount / 50);
6971 BeginTemporaryState(TELEPORT, Amount / 100);
6972 for (int c = 0; c < STATES; ++c) {
6973 if (1 << c != TELEPORT) {
6974 DecreaseStateCounter(1 << c, -Amount / 100);
6975 if (!IsEnabled()) return;
6981 void character::ReceiveWhiteUnicorn (sLong Amount) {
6982 if (!(RAND() % 40)) game::DoEvilDeed(Amount / 50);
6983 BeginTemporaryState(TELEPORT, Amount / 100);
6984 DecreaseStateCounter(LYCANTHROPY, -Amount / 100);
6985 DecreaseStateCounter(POISONED, -Amount / 100);
6986 DecreaseStateCounter(PARASITIZED, -Amount / 100);
6987 DecreaseStateCounter(LEPROSY, -Amount / 100);
6988 DecreaseStateCounter(VAMPIRISM, -Amount / 100);
6992 /* Counter should be negative. Removes intrinsics. */
6993 void character::DecreaseStateCounter (sLong State, int Counter) {
6994 int Index;
6995 for (Index = 0; Index < STATES; ++Index) if (1 << Index == State) break;
6996 if (Index == STATES) ABORT("DecreaseTemporaryStateCounter works only when State == 2^n!");
6997 if (TemporaryState & State) {
6998 if (TemporaryStateCounter[Index] == PERMANENT || (TemporaryStateCounter[Index] += Counter) <= 0) {
6999 TemporaryState &= ~State;
7000 if (!(EquipmentState & State)) {
7001 if (StateData[Index].EndHandler) {
7002 (this->*StateData[Index].EndHandler)();
7003 if (!IsEnabled()) return;
7005 (this->*StateData[Index].PrintEndMessage)();
7012 truth character::IsImmuneToLeprosy () const {
7013 return DataBase->IsImmuneToLeprosy || UseMaterialAttributes();
7017 void character::LeprosyHandler () {
7018 EditExperience(ARM_STRENGTH, -25, 1 << 1);
7019 EditExperience(LEG_STRENGTH, -25, 1 << 1);
7020 EditExperience(DEXTERITY, -25, 1 << 1);
7021 EditExperience(AGILITY, -25, 1 << 1);
7022 EditExperience(ENDURANCE, -25, 1 << 1);
7023 EditExperience(CHARISMA, -25, 1 << 1);
7024 CheckDeath(CONST_S("killed by leprosy"));
7028 bodypart *character::SearchForOriginalBodyPart (int I) const {
7029 for (stackiterator i1 = GetStackUnder()->GetBottom(); i1.HasItem(); ++i1) {
7030 for (std::list<feuLong>::iterator i2 = OriginalBodyPartID[I].begin(); i2 != OriginalBodyPartID[I].end(); ++i2)
7031 if (i1->GetID() == *i2) return static_cast<bodypart*>(*i1);
7033 return 0;
7037 void character::SetLifeExpectancy (int Base, int RandPlus) {
7038 int c;
7039 for (c = 0; c < BodyParts; ++c) {
7040 bodypart *BodyPart = GetBodyPart(c);
7041 if (BodyPart) BodyPart->SetLifeExpectancy(Base, RandPlus);
7043 for (c = 0; c < GetEquipments(); ++c) {
7044 item *Equipment = GetEquipment(c);
7045 if (Equipment) Equipment->SetLifeExpectancy(Base, RandPlus);
7050 /* Receiver should be a fresh duplicate of this */
7051 void character::DuplicateEquipment (character *Receiver, feuLong Flags) {
7052 for (int c = 0; c < GetEquipments(); ++c) {
7053 item *Equipment = GetEquipment(c);
7054 if (Equipment) {
7055 item *Duplicate = Equipment->Duplicate(Flags);
7056 Receiver->SetEquipment(c, Duplicate);
7062 void character::Disappear (corpse *Corpse, cchar *Verb, truth (item::*ClosePredicate)() const) {
7063 truth TorsoDisappeared = false;
7064 truth CanBeSeen = Corpse ? Corpse->CanBeSeenByPlayer() : IsPlayer() || CanBeSeenByPlayer();
7065 int c;
7066 if ((GetTorso()->*ClosePredicate)()) {
7067 if (CanBeSeen) {
7068 if (Corpse) ADD_MESSAGE("%s %ss.", Corpse->CHAR_NAME(DEFINITE), Verb);
7069 else if (IsPlayer()) ADD_MESSAGE("You %s.", Verb);
7070 else ADD_MESSAGE("%s %ss.", CHAR_NAME(DEFINITE), Verb);
7072 TorsoDisappeared = true;
7073 for (c = 0; c < GetEquipments(); ++c) {
7074 item *Equipment = GetEquipment(c);
7075 if (Equipment && (Equipment->*ClosePredicate)()) {
7076 Equipment->RemoveFromSlot();
7077 Equipment->SendToHell();
7080 itemvector ItemVector;
7081 GetStack()->FillItemVector(ItemVector);
7082 for (uInt c = 0; c < ItemVector.size(); ++c) {
7083 if (ItemVector[c] && (ItemVector[c]->*ClosePredicate)()) {
7084 ItemVector[c]->RemoveFromSlot();
7085 ItemVector[c]->SendToHell();
7089 for (c = 1; c < GetBodyParts(); ++c) {
7090 bodypart *BodyPart = GetBodyPart(c);
7091 if (BodyPart) {
7092 if ((BodyPart->*ClosePredicate)()) {
7093 if (!TorsoDisappeared && CanBeSeen) {
7094 if(IsPlayer()) ADD_MESSAGE("Your %s %ss.", GetBodyPartName(c).CStr(), Verb);
7095 else ADD_MESSAGE("The %s of %s %ss.", GetBodyPartName(c).CStr(), CHAR_NAME(DEFINITE), Verb);
7097 BodyPart->DropEquipment();
7098 item *BodyPart = SevereBodyPart(c);
7099 if (BodyPart) BodyPart->SendToHell();
7100 } else if (TorsoDisappeared) {
7101 BodyPart->DropEquipment();
7102 item *BodyPart = SevereBodyPart(c);
7103 if (BodyPart) {
7104 if (Corpse) Corpse->GetSlot()->AddFriendItem(BodyPart);
7105 else if (!game::IsInWilderness()) GetStackUnder()->AddItem(BodyPart);
7106 else BodyPart->SendToHell();
7111 if (TorsoDisappeared) {
7112 if (Corpse) {
7113 Corpse->RemoveFromSlot();
7114 Corpse->SendToHell();
7115 } else {
7116 CheckDeath(festring(Verb) + "ed", 0, FORCE_DEATH|DISALLOW_CORPSE|DISALLOW_MSG);
7118 } else {
7119 CheckDeath(festring(Verb) + "ed", 0, DISALLOW_MSG);
7124 void character::SignalDisappearance () {
7125 if (GetMotherEntity()) GetMotherEntity()->SignalDisappearance();
7126 else Disappear(0, "disappear", &item::IsVeryCloseToDisappearance);
7130 truth character::HornOfFearWorks () const {
7131 return CanHear() && GetPanicLevel() > RAND()%33 && !StateIsActivated(FEARLESS);
7135 void character::BeginLeprosy () {
7136 doforbodypartswithparam<truth>()(this, &bodypart::SetIsInfectedByLeprosy, true);
7140 void character::EndLeprosy () {
7141 doforbodypartswithparam<truth>()(this, &bodypart::SetIsInfectedByLeprosy, false);
7145 truth character::IsSameAs (ccharacter *What) const {
7146 return What->GetType() == GetType() && What->GetConfig() == GetConfig();
7150 feuLong character::GetCommandFlags () const {
7151 return !StateIsActivated(PANIC) ? CommandFlags : CommandFlags|FLEE_FROM_ENEMIES;
7155 feuLong character::GetConstantCommandFlags () const {
7156 return !StateIsActivated(PANIC) ? DataBase->ConstantCommandFlags : DataBase->ConstantCommandFlags|FLEE_FROM_ENEMIES;
7160 feuLong character::GetPossibleCommandFlags () const {
7161 int Int = GetAttribute(INTELLIGENCE);
7162 feuLong Flags = ALL_COMMAND_FLAGS;
7163 if (!CanMove() || Int < 4) Flags &= ~FOLLOW_LEADER;
7164 if (!CanMove() || Int < 6) Flags &= ~FLEE_FROM_ENEMIES;
7165 if (!CanUseEquipment() || Int < 8) Flags &= ~DONT_CHANGE_EQUIPMENT;
7166 if (!UsesNutrition() || Int < 8) Flags &= ~DONT_CONSUME_ANYTHING_VALUABLE;
7167 return Flags;
7171 truth character::IsRetreating () const {
7172 return StateIsActivated(PANIC) || (CommandFlags & FLEE_FROM_ENEMIES && IsPet());
7176 truth character::ChatMenu () {
7177 if (GetAction() && !GetAction()->CanBeTalkedTo()) {
7178 ADD_MESSAGE("%s is silent.", CHAR_DESCRIPTION(DEFINITE));
7179 PLAYER->EditAP(-200);
7180 return true;
7182 feuLong ManagementFlags = GetManagementFlags();
7183 if (ManagementFlags == CHAT_IDLY || !IsPet()) return ChatIdly();
7184 static cchar *const ChatMenuEntry[CHAT_MENU_ENTRIES] = {
7185 "Change equipment",
7186 "Take items",
7187 "Give items",
7188 "Issue commands",
7189 "Chat idly",
7191 static const petmanagementfunction PMF[CHAT_MENU_ENTRIES] = {
7192 &character::ChangePetEquipment,
7193 &character::TakePetItems,
7194 &character::GivePetItems,
7195 &character::IssuePetCommands,
7196 &character::ChatIdly
7198 felist List(CONST_S("Choose action:"));
7199 game::SetStandardListAttributes(List);
7200 List.AddFlags(SELECTABLE);
7201 int c, i;
7202 for (c = 0; c < CHAT_MENU_ENTRIES; ++c) if (1 << c & ManagementFlags) List.AddEntry(ChatMenuEntry[c], LIGHT_GRAY);
7203 int Chosen = List.Draw();
7204 if (Chosen & FELIST_ERROR_BIT) return false;
7205 for (c = 0, i = 0; c < CHAT_MENU_ENTRIES; ++c) {
7206 if (1 << c & ManagementFlags && i++ == Chosen) return (this->*PMF[c])();
7208 return false; // dummy
7212 truth character::ChangePetEquipment () {
7213 if (EquipmentScreen(PLAYER->GetStack(), GetStack())) {
7214 DexterityAction(3);
7215 return true;
7217 return false;
7221 truth character::TakePetItems () {
7222 truth Success = false;
7223 stack::SetSelected(0);
7224 for (;;) {
7225 itemvector ToTake;
7226 game::DrawEverythingNoBlit();
7227 GetStack()->DrawContents(
7228 ToTake,
7230 PLAYER,
7231 CONST_S("What do you want to take from ") + CHAR_DESCRIPTION(DEFINITE) + '?',
7232 CONST_S(""),
7233 CONST_S(""),
7234 GetDescription(DEFINITE) + " is " + GetVerbalBurdenState(),
7235 GetVerbalBurdenStateColor(),
7236 REMEMBER_SELECTED);
7237 if (ToTake.empty()) break;
7238 for (uInt c = 0; c < ToTake.size(); ++c) ToTake[c]->MoveTo(PLAYER->GetStack());
7239 ADD_MESSAGE("You take %s.", ToTake[0]->GetName(DEFINITE, ToTake.size()).CStr());
7240 Success = true;
7242 if (Success) {
7243 DexterityAction(2);
7244 PLAYER->DexterityAction(2);
7246 return Success;
7250 truth character::GivePetItems () {
7251 truth Success = false;
7252 stack::SetSelected(0);
7253 for (;;) {
7254 itemvector ToGive;
7255 game::DrawEverythingNoBlit();
7256 PLAYER->GetStack()->DrawContents(
7257 ToGive,
7259 this,
7260 CONST_S("What do you want to give to ") + CHAR_DESCRIPTION(DEFINITE) + '?',
7261 CONST_S(""),
7262 CONST_S(""),
7263 GetDescription(DEFINITE) + " is " + GetVerbalBurdenState(),
7264 GetVerbalBurdenStateColor(),
7265 REMEMBER_SELECTED);
7266 if (ToGive.empty()) break;
7267 for (uInt c = 0; c < ToGive.size(); ++c) ToGive[c]->MoveTo(GetStack());
7268 ADD_MESSAGE("You give %s to %s.", ToGive[0]->GetName(DEFINITE, ToGive.size()).CStr(), CHAR_DESCRIPTION(DEFINITE));
7269 Success = true;
7271 if (Success) {
7272 DexterityAction(2);
7273 PLAYER->DexterityAction(2);
7275 return Success;
7279 truth character::IssuePetCommands () {
7280 if (!IsConscious()) {
7281 ADD_MESSAGE("%s is unconscious.", CHAR_DESCRIPTION(DEFINITE));
7282 return false;
7284 feuLong PossibleC = GetPossibleCommandFlags();
7285 if (!PossibleC) {
7286 ADD_MESSAGE("%s cannot be commanded.", CHAR_DESCRIPTION(DEFINITE));
7287 return false;
7289 feuLong OldC = GetCommandFlags();
7290 feuLong NewC = OldC, VaryFlags = 0;
7291 game::CommandScreen(CONST_S("Issue commands to ")+GetDescription(DEFINITE), PossibleC, GetConstantCommandFlags(), VaryFlags, NewC);
7292 if (NewC == OldC) return false;
7293 SetCommandFlags(NewC);
7294 PLAYER->EditAP(-500);
7295 PLAYER->EditExperience(CHARISMA, 25, 1 << 7);
7296 return true;
7300 truth character::ChatIdly () {
7301 if (!TryToTalkAboutScience()) {
7302 BeTalkedTo();
7303 PLAYER->EditExperience(CHARISMA, 75, 1 << 7);
7305 PLAYER->EditAP(-1000);
7306 return true;
7310 int character::HasSomethingToEquipAt (int chosen, truth equippedIsTrue) {
7311 if (!GetBodyPartOfEquipment(chosen)) return 0;
7313 item *oldEquipment = GetEquipment(chosen);
7314 if (!IsPlayer() && oldEquipment && BoundToUse(oldEquipment, chosen)) return 0;
7316 stack *mainStack = GetStack();
7317 sorter Sorter = EquipmentSorter(chosen);
7318 auto count = mainStack->SortedItemsCount(this, Sorter);
7320 if (equippedIsTrue && oldEquipment) ++count;
7322 return count;
7326 truth character::EquipmentScreen (stack *MainStack, stack *SecStack) {
7327 if (!CanUseEquipment()) {
7328 ADD_MESSAGE("%s cannot use equipment.", CHAR_DESCRIPTION(DEFINITE));
7329 return false;
7331 int Chosen = 0;
7332 truth EquipmentChanged = false;
7333 felist List(CONST_S("Equipment menu [ESC exits]"));
7334 festring Entry;
7335 for (;;) {
7336 List.Empty();
7337 List.EmptyDescription();
7338 if (!IsPlayer()) {
7339 List.AddDescription(CONST_S(""));
7340 List.AddDescription(festring(GetDescription(DEFINITE) + " is " + GetVerbalBurdenState()).CapitalizeCopy(), GetVerbalBurdenStateColor());
7342 int selected = -1, curit = 0;
7343 truth selectedIsEmpty = false;
7344 for (int c = 0; c < GetEquipments(); ++c, ++curit) {
7345 int bpidx = (GetBodyPartOfEquipment(c) ? GetBodyPartOfEquipment(c)->GetBodyPartIndex() : -1);
7346 Entry = GetEquipmentName(c);
7347 Entry << ':';
7348 Entry.Resize(20);
7349 item *Equipment = GetEquipment(c);
7350 if (Equipment) {
7351 Equipment->AddInventoryEntry(this, Entry, 1, true);
7352 AddSpecialEquipmentInfo(Entry, c);
7353 int ImageKey = game::AddToItemDrawVector(itemvector(1, Equipment));
7354 if (selected < 0 && bpidx >= 0 && bpidx != RIGHT_ARM_INDEX && bpidx != LEFT_ARM_INDEX) selected = curit;
7355 List.AddEntry(Entry, (HasSomethingToEquipAt(c, false) ? ORANGE : LIGHT_GRAY), 20, ImageKey, true);
7356 } else {
7357 truth canUse = !!GetBodyPartOfEquipment(c);
7358 Entry << (canUse ? "-" : "can't use");
7359 col16 color = RED;
7360 if (canUse) {
7361 switch (HasSomethingToEquipAt(c, false)) {
7362 case 0: color = RED; break;
7363 case 1: color = LIGHT_GRAY; break;
7364 default: color = ORANGE; break;
7367 if (color != RED && bpidx >= 0 && bpidx != RIGHT_ARM_INDEX && bpidx != LEFT_ARM_INDEX) {
7368 if (selected < 0 || !selectedIsEmpty) { selected = curit; selectedIsEmpty = true; }
7370 List.AddEntry(Entry, color, 20, game::AddToItemDrawVector(itemvector()));
7373 game::DrawEverythingNoBlit();
7374 game::SetStandardListAttributes(List);
7375 if (selected >= 0) List.SetSelected(selected);
7376 List.SetFlags(SELECTABLE|DRAW_BACKGROUND_AFTERWARDS);
7377 List.SetEntryDrawer(game::ItemEntryDrawer);
7378 Chosen = List.Draw();
7379 game::ClearItemDrawVector();
7380 if (Chosen >= GetEquipments()) break;
7381 EquipmentChanged = TryToChangeEquipment(MainStack, SecStack, Chosen);
7383 if (EquipmentChanged) DexterityAction(5);
7384 return EquipmentChanged;
7388 feuLong character::GetManagementFlags () const {
7389 feuLong Flags = ALL_MANAGEMENT_FLAGS;
7390 if (!CanUseEquipment() || !AllowPlayerToChangeEquipment()) Flags &= ~CHANGE_EQUIPMENT;
7391 if (!GetStack()->GetItems()) Flags &= ~TAKE_ITEMS;
7392 if (!WillCarryItems()) Flags &= ~GIVE_ITEMS;
7393 if (!GetPossibleCommandFlags()) Flags &= ~ISSUE_COMMANDS;
7394 return Flags;
7398 cchar *VerbalBurdenState[] = { "overloaded", "stressed", "burdened", "unburdened" };
7399 col16 VerbalBurdenStateColor[] = { RED, BLUE, BLUE, WHITE };
7401 cchar *character::GetVerbalBurdenState () const { return VerbalBurdenState[BurdenState]; }
7402 col16 character::GetVerbalBurdenStateColor () const { return VerbalBurdenStateColor[BurdenState]; }
7403 int character::GetAttributeAverage () const { return GetSumOfAttributes()/7; }
7405 cfestring &character::GetStandVerb() const {
7406 if (ForceCustomStandVerb()) return DataBase->StandVerb;
7407 static festring Hovering = "hovering";
7408 static festring Swimming = "swimming";
7409 if (StateIsActivated(LEVITATION)) return Hovering;
7410 if (IsSwimming()) return Swimming;
7411 return DataBase->StandVerb;
7415 truth character::CheckApply () const {
7416 if (!CanApply()) {
7417 ADD_MESSAGE("This monster type cannot apply.");
7418 return false;
7420 return true;
7424 void character::EndLevitation () {
7425 if (!IsFlying() && GetSquareUnder()) {
7426 if (!game::IsInWilderness()) SignalStepFrom(0);
7427 if (game::IsInWilderness() || !GetLSquareUnder()->IsFreezed()) TestWalkability();
7432 truth character::CanMove () const {
7433 return !IsRooted() || StateIsActivated(LEVITATION);
7437 void character::CalculateEnchantments () {
7438 doforequipments()(this, &item::CalculateEnchantment);
7439 GetStack()->CalculateEnchantments();
7443 truth character::GetNewFormForPolymorphWithControl (character *&NewForm) {
7444 festring Topic, Temp;
7445 NewForm = 0;
7446 while (!NewForm) {
7447 festring Temp = game::DefaultQuestion(CONST_S("What do you want to become? [press '?' for a list]"), game::GetDefaultPolymorphTo(), &game::PolymorphControlKeyHandler);
7448 NewForm = protosystem::CreateMonster(Temp);
7449 if (NewForm) {
7450 if (NewForm->IsSameAs(this)) {
7451 delete NewForm;
7452 ADD_MESSAGE("You choose not to polymorph.");
7453 NewForm = this;
7454 return false;
7456 if (PolymorphBackup && NewForm->IsSameAs(PolymorphBackup)) {
7457 delete NewForm;
7458 NewForm = ForceEndPolymorph();
7459 return false;
7461 if (NewForm->GetPolymorphIntelligenceRequirement() > GetAttribute(INTELLIGENCE) && !game::WizardModeIsActive()) {
7462 ADD_MESSAGE("You feel your mind isn't yet powerful enough to call forth the form of %s.", NewForm->CHAR_NAME(INDEFINITE));
7463 delete NewForm;
7464 NewForm = 0;
7465 } else {
7466 NewForm->RemoveAllItems();
7470 return true;
7474 liquid *character::CreateSweat(sLong Volume) const {
7475 //return liquid::Spawn(GetSweatMaterial(), Volume);
7476 return liquid::Spawn(GetCurrentSweatMaterial(), Volume);
7480 truth character::TeleportRandomItem (truth TryToHinderVisibility) {
7481 if (IsImmuneToItemTeleport()) return false;
7482 itemvector ItemVector;
7483 std::vector<sLong> PossibilityVector;
7484 int TotalPossibility = 0;
7485 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) {
7486 ItemVector.push_back(*i);
7487 int Possibility = i->GetTeleportPriority();
7488 if (TryToHinderVisibility) Possibility += i->GetHinderVisibilityBonus(this);
7489 PossibilityVector.push_back(Possibility);
7490 TotalPossibility += Possibility;
7492 for (int c = 0; c < GetEquipments(); ++c) {
7493 item *Equipment = GetEquipment(c);
7494 if (Equipment) {
7495 ItemVector.push_back(Equipment);
7496 int Possibility = Equipment->GetTeleportPriority();
7497 if (TryToHinderVisibility) Possibility += Equipment->GetHinderVisibilityBonus(this);
7498 PossibilityVector.push_back(Possibility <<= 1);
7499 TotalPossibility += Possibility;
7502 if (!TotalPossibility) return false;
7503 int Chosen = femath::WeightedRand(PossibilityVector, TotalPossibility);
7504 item *Item = ItemVector[Chosen];
7505 truth Equipped = PLAYER->Equips(Item);
7506 truth Seen = Item->CanBeSeenByPlayer();
7507 Item->RemoveFromSlot();
7508 if (Seen) ADD_MESSAGE("%s disappears.", Item->CHAR_NAME(DEFINITE));
7509 if (Equipped) game::AskForEscPress(CONST_S("Equipment lost!"));
7510 v2 Pos = GetPos();
7511 int Range = Item->GetEmitation() && TryToHinderVisibility ? 25 : 5;
7512 rect Border(Pos + v2(-Range, -Range), Pos + v2(Range, Range));
7513 Pos = GetLevel()->GetRandomSquare(this, 0, &Border);
7514 if (Pos == ERROR_V2) Pos = GetLevel()->GetRandomSquare();
7515 GetNearLSquare(Pos)->GetStack()->AddItem(Item);
7516 if (Item->CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", Item->CHAR_NAME(INDEFINITE));
7517 return true;
7521 truth character::HasClearRouteTo (v2 Pos) const {
7522 pathcontroller::Map = GetLevel()->GetMap();
7523 pathcontroller::Character = this;
7524 v2 ThisPos = GetPos();
7525 return mapmath<pathcontroller>::DoLine(ThisPos.X, ThisPos.Y, Pos.X, Pos.Y, SKIP_FIRST);
7529 truth character::IsTransparent () const {
7530 return !IsEnormous() || GetTorso()->GetMainMaterial()->IsTransparent() || StateIsActivated(INVISIBLE);
7534 void character::SignalPossibleTransparencyChange () {
7535 if (!game::IsInWilderness()) {
7536 for (int c = 0; c < SquaresUnder; ++c) {
7537 lsquare *Square = GetLSquareUnder(c);
7538 if (Square) Square->SignalPossibleTransparencyChange();
7544 int character::GetCursorData () const {
7545 int Bad = 0;
7546 int Color = game::PlayerIsRunning() ? BLUE_CURSOR : DARK_CURSOR;
7547 for (int c = 0; c < BodyParts; ++c) {
7548 bodypart *BodyPart = GetBodyPart(c);
7549 if (BodyPart && BodyPart->IsUsable()) {
7550 int ConditionColorIndex = BodyPart->GetConditionColorIndex();
7551 if ((BodyPartIsVital(c) && !ConditionColorIndex) || (ConditionColorIndex <= 1 && ++Bad == 2)) return Color|CURSOR_FLASH;
7552 } else if (++Bad == 2) return Color|CURSOR_FLASH;
7554 Color = game::PlayerIsRunning() ? YELLOW_CURSOR : RED_CURSOR;
7555 return Bad ? Color|CURSOR_FLASH : Color;
7559 void character::TryToName () {
7560 if (!IsPet()) ADD_MESSAGE("%s refuses to let YOU decide what %s's called.", CHAR_NAME(DEFINITE), CHAR_PERSONAL_PRONOUN);
7561 else if (IsPlayer()) ADD_MESSAGE("You can't rename yourself.");
7562 else if (!IsNameable()) ADD_MESSAGE("%s refuses to be called anything else but %s.", CHAR_NAME(DEFINITE), CHAR_NAME(DEFINITE));
7563 else {
7564 festring Topic = CONST_S("What name will you give to ")+GetName(DEFINITE)+'?';
7565 festring Name = game::StringQuestion(Topic, WHITE, 0, 80, true);
7566 if (Name.GetSize()) SetAssignedName(Name);
7571 double character::GetSituationDanger (ccharacter *Enemy, v2 ThisPos, v2 EnemyPos, truth SeesEnemy) const {
7572 double Danger;
7573 if (IgnoreDanger() && !IsPlayer()) {
7574 if (Enemy->IgnoreDanger() && !Enemy->IsPlayer()) {
7575 Danger = double(GetHP())*GetHPRequirementForGeneration()/(Enemy->GetHP()*Enemy->GetHPRequirementForGeneration());
7577 else {
7578 Danger = 0.25*GetHPRequirementForGeneration()/Enemy->GetHP();
7580 } else if (Enemy->IgnoreDanger() && !Enemy->IsPlayer()) {
7581 Danger = 4.0*GetHP()/Enemy->GetHPRequirementForGeneration();
7582 } else {
7583 Danger = GetRelativeDanger(Enemy);
7585 Danger *= 3.0/((EnemyPos-ThisPos).GetManhattanLength()+2);
7586 if (!SeesEnemy) Danger *= 0.2;
7587 if (StateIsActivated(PANIC)) Danger *= 0.2;
7588 Danger *= double(GetHP())*Enemy->GetMaxHP()/(Enemy->GetHP()*GetMaxHP());
7589 return Danger;
7593 void character::ModifySituationDanger (double &Danger) const {
7594 switch (GetTirednessState()) {
7595 case FAINTING: Danger *= 1.5;
7596 case EXHAUSTED: Danger *= 1.25;
7598 for (int c = 0; c < STATES; ++c) {
7599 if (StateIsActivated(1 << c) && StateData[c].SituationDangerModifier != 0) (this->*StateData[c].SituationDangerModifier)(Danger);
7604 void character::LycanthropySituationDangerModifier (double &Danger) const {
7605 character *Wolf = werewolfwolf::Spawn();
7606 double DangerToWolf = GetRelativeDanger(Wolf);
7607 Danger *= pow(DangerToWolf, 0.1);
7608 delete Wolf;
7612 void character::PoisonedSituationDangerModifier (double &Danger) const {
7613 int C = GetTemporaryStateCounter(POISONED);
7614 Danger *= (1+(C*C)/(GetHP()*10000.0*(GetGlobalResistance(POISON)+1)));
7618 void character::PolymorphingSituationDangerModifier (double &Danger) const {
7619 if (!StateIsActivated(POLYMORPH_CONTROL)) Danger *= 1.5;
7623 void character::PanicSituationDangerModifier (double &Danger) const {
7624 Danger *= 1.5;
7628 void character::ConfusedSituationDangerModifier (double &Danger) const {
7629 Danger *= 1.5;
7633 void character::ParasitizedSituationDangerModifier (double &Danger) const {
7634 Danger *= 1.25;
7638 void character::LeprosySituationDangerModifier (double &Danger) const {
7639 Danger *= 1.5;
7643 void character::AddRandomScienceName (festring &String) const {
7644 festring Science = GetScienceTalkName().GetRandomElement().CStr();
7645 if (Science[0] == '!') {
7646 String << Science.CStr()+1;
7647 return;
7649 festring Attribute = GetScienceTalkAdjectiveAttribute().GetRandomElement();
7650 festring Prefix;
7651 truth NoAttrib = Attribute.IsEmpty(), NoSecondAdjective = false;
7652 if (!Attribute.IsEmpty() && Attribute[0] == '!') {
7653 NoSecondAdjective = true;
7654 Attribute.Erase(0, 1);
7656 if (!Science.Find("the ")) {
7657 Science.Erase(0, 4);
7658 if (!Attribute.Find("the ", 0, 4)) Attribute << " the"; else Attribute.Insert(0, "the ", 4);
7660 if (islower(Science[0]) && Science.Find(' ') == festring::NPos && Science.Find('-') == festring::NPos &&
7661 Science.Find("phobia") == festring::NPos) {
7662 Prefix = GetScienceTalkPrefix().GetRandomElement();
7663 if (!Prefix.IsEmpty() && Science.Find(Prefix) != festring::NPos) Prefix.Empty();
7665 int L = Prefix.GetSize();
7666 if (L && Prefix[L-1] == Science[0]) Science.Erase(0, 1);
7667 if (!NoAttrib && !NoSecondAdjective == !RAND_GOOD(3)) {
7668 int S1 = NoSecondAdjective ? 0 : GetScienceTalkAdjectiveAttribute().Size;
7669 int S2 = GetScienceTalkSubstantiveAttribute().Size;
7670 festring OtherAttribute;
7671 int Chosen = RAND_GOOD(S1+S2);
7672 if (Chosen < S1) OtherAttribute = GetScienceTalkAdjectiveAttribute()[Chosen];
7673 else OtherAttribute = GetScienceTalkSubstantiveAttribute()[Chosen - S1];
7674 if (!OtherAttribute.IsEmpty() && OtherAttribute.Find("the ", 0, 4) && Attribute.Find(OtherAttribute) == festring::NPos) {
7675 String << Attribute << ' ' << OtherAttribute << ' ' << Prefix << Science;
7676 return;
7679 String << Attribute;
7680 if (!NoAttrib) String << ' ';
7681 String << Prefix << Science;
7685 truth character::TryToTalkAboutScience () {
7686 if (GetRelation(PLAYER) == HOSTILE ||
7687 GetScienceTalkPossibility() <= RAND_GOOD(100) ||
7688 PLAYER->GetAttribute(INTELLIGENCE) < GetScienceTalkIntelligenceRequirement() ||
7689 PLAYER->GetAttribute(WISDOM) < GetScienceTalkWisdomRequirement() ||
7690 PLAYER->GetAttribute(CHARISMA) < GetScienceTalkCharismaRequirement())
7691 return false;
7692 festring Science;
7693 if (RAND_GOOD(3)) {
7694 AddRandomScienceName(Science);
7695 } else {
7696 festring S1, S2;
7697 AddRandomScienceName(S1);
7698 AddRandomScienceName(S2);
7699 if (S1.Find(S2) == festring::NPos && S2.Find(S1) == festring::NPos) {
7700 switch (RAND_GOOD(3)) {
7701 case 0: Science = "the relation of "; break;
7702 case 1: Science = "the differences of "; break;
7703 case 2: Science = "the similarities of "; break;
7705 Science << S1 << " and " << S2;
7707 else {
7708 AddRandomScienceName(Science);
7711 switch ((RAND() + GET_TICK()) % 10) {
7712 case 0:
7713 ADD_MESSAGE("You have a rather pleasant chat about %s with %s.", Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
7714 break;
7715 case 1:
7716 ADD_MESSAGE("%s explains a few of %s opinions regarding %s to you.", CHAR_DESCRIPTION(DEFINITE), CHAR_POSSESSIVE_PRONOUN, Science.CStr());
7717 break;
7718 case 2:
7719 ADD_MESSAGE("%s reveals a number of %s insightful views of %s to you.", CHAR_DESCRIPTION(DEFINITE), CHAR_POSSESSIVE_PRONOUN, Science.CStr());
7720 break;
7721 case 3:
7722 ADD_MESSAGE("You exhange some information pertaining to %s with %s.", Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
7723 break;
7724 case 4:
7725 ADD_MESSAGE("You engage in a pretty intriguing conversation about %s with %s.", Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
7726 break;
7727 case 5:
7728 ADD_MESSAGE("You discuss at length about %s with %s.", Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
7729 break;
7730 case 6:
7731 ADD_MESSAGE("You have a somewhat boring talk concerning %s with %s.", Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
7732 break;
7733 case 7:
7734 ADD_MESSAGE("You are drawn into a heated argument regarding %s with %s.", Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
7735 break;
7736 case 8:
7737 ADD_MESSAGE("%s delivers a long monologue concerning eg. %s.", CHAR_DESCRIPTION(DEFINITE), Science.CStr());
7738 break;
7739 case 9:
7740 ADD_MESSAGE("You dive into a brief but thought-provoking debate over %s with %s", Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
7741 break;
7743 PLAYER->EditExperience(INTELLIGENCE, 1000, 50. * GetScienceTalkIntelligenceModifier() / ++ScienceTalks);
7744 PLAYER->EditExperience(WISDOM, 1000, 50. * GetScienceTalkWisdomModifier() / ++ScienceTalks);
7745 PLAYER->EditExperience(CHARISMA, 1000, 50. * GetScienceTalkCharismaModifier() / ++ScienceTalks);
7746 return true;
7750 truth character::IsUsingWeaponOfCategory (int Category) const {
7751 return
7752 ((GetMainWielded() && GetMainWielded()->GetWeaponCategory() == Category) ||
7753 (GetSecondaryWielded() && GetSecondaryWielded()->GetWeaponCategory() == Category));
7757 truth character::TryToUnStickTraps (v2 Dir) {
7758 if (!TrapData) return true;
7759 std::vector<trapdata> TrapVector;
7760 for (const trapdata *T = TrapData; T; T = T->Next) TrapVector.push_back(*TrapData);
7761 for (uInt c = 0; c < TrapVector.size(); ++c) {
7762 if (IsEnabled()) {
7763 entity *Trap = game::SearchTrap(TrapVector[c].TrapID);
7764 /*k8:??? if(!Trap->Exists()) int esko = esko = 2; */
7765 if (!Trap->Exists()) continue; /*k8: ??? added by me; what this means? */
7766 if (Trap->GetVictimID() == GetID() && Trap->TryToUnStick(this, Dir)) break;
7769 return !TrapData && IsEnabled();
7773 struct trapidcomparer {
7774 trapidcomparer (feuLong ID) : ID(ID) {}
7775 truth operator () (const trapdata *T) const { return T->TrapID == ID; }
7776 feuLong ID;
7780 void character::RemoveTrap (feuLong ID) {
7781 trapdata *&T = ListFind(TrapData, trapidcomparer(ID));
7782 T = T->Next;
7783 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange);
7787 void character::AddTrap (feuLong ID, feuLong BodyParts) {
7788 trapdata *&T = ListFind(TrapData, trapidcomparer(ID));
7789 if (T) T->BodyParts |= BodyParts;
7790 else T = new trapdata(ID, GetID(), BodyParts);
7791 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange);
7795 truth character::IsStuckToTrap (feuLong ID) const {
7796 for (const trapdata *T = TrapData; T; T = T->Next) if (T->TrapID == ID) return true;
7797 return false;
7801 void character::RemoveTraps () {
7802 for (trapdata *T = TrapData; T; T = T->Next) {
7803 entity *Trap = game::SearchTrap(T->TrapID);
7804 if (Trap) Trap->UnStick();
7806 deleteList(TrapData);
7807 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange);
7811 void character::RemoveTraps (int BodyPartIndex) {
7812 feuLong Flag = 1 << BodyPartIndex;
7813 for (trapdata **T = &TrapData; *T;) {
7814 if ((*T)->BodyParts & Flag) {
7815 entity *Trap = game::SearchTrap((*T)->TrapID);
7816 if (!((*T)->BodyParts &= ~Flag)) {
7817 if (Trap) Trap->UnStick();
7818 trapdata *ToDel = *T;
7819 *T = (*T)->Next;
7820 delete ToDel;
7821 } else {
7822 if (Trap) Trap->UnStick(BodyPartIndex);
7823 T = &(*T)->Next;
7826 else {
7827 T = &(*T)->Next;
7830 if (GetBodyPart(BodyPartIndex)) GetBodyPart(BodyPartIndex)->SignalPossibleUsabilityChange();
7834 festring character::GetTrapDescription () const {
7835 festring Desc;
7836 std::pair<entity *, int> TrapStack[3];
7837 int Index = 0;
7838 for (const trapdata *T = TrapData; T; T = T->Next) {
7839 if (Index < 3) {
7840 entity *Trap = game::SearchTrap(T->TrapID);
7841 if (Trap) {
7842 int c;
7843 for (c = 0; c < Index; ++c) if (TrapStack[c].first->GetTrapType() == Trap->GetTrapType()) ++TrapStack[c].second;
7844 if (c == Index) TrapStack[Index++] = std::make_pair(Trap, 1);
7846 } else {
7847 ++Index;
7848 break;
7851 if (Index <= 3) {
7852 TrapStack[0].first->AddTrapName(Desc, TrapStack[0].second);
7853 if (Index == 2) {
7854 Desc << " and ";
7855 TrapStack[1].first->AddTrapName(Desc, TrapStack[1].second);
7856 } else if (Index == 3) {
7857 Desc << ", ";
7858 TrapStack[1].first->AddTrapName(Desc, TrapStack[1].second);
7859 Desc << " and ";
7860 TrapStack[2].first->AddTrapName(Desc, TrapStack[2].second);
7862 } else {
7863 Desc << "lots of traps";
7865 return Desc;
7869 int character::RandomizeHurtBodyPart (feuLong BodyParts) const {
7870 int BodyPartIndex[MAX_BODYPARTS];
7871 int Index = 0;
7872 for (int c = 0; c < GetBodyParts(); ++c) {
7873 if (1 << c & BodyParts) {
7874 /*k8: ??? if(!GetBodyPart(c)) int esko = esko = 2; */
7875 if (!GetBodyPart(c)) continue;
7876 BodyPartIndex[Index++] = c;
7878 /*k8: ??? if(!Index) int esko = esko = 2;*/
7880 if (!Index) {
7881 fprintf(stderr, "FATAL: RandomizeHurtBodyPart -- Index==0\n");
7882 abort();
7884 return BodyPartIndex[RAND_N(Index)];
7888 truth character::BodyPartIsStuck (int I) const {
7889 for (const trapdata *T = TrapData; T; T = T->Next) if (1 << I & T->BodyParts) return true;
7890 return false;
7894 void character::PrintAttribute (cchar *Desc, int I, int PanelPosX, int PanelPosY) const {
7895 int Attribute = GetAttribute(I);
7896 int NoBonusAttribute = GetAttribute(I, false);
7897 col16 C = game::GetAttributeColor(I);
7898 festring String = Desc;
7899 String.Resize(5);
7900 String << Attribute;
7901 String.Resize(8);
7902 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY * 10), C, "%s", String.CStr());
7903 if (Attribute != NoBonusAttribute) {
7904 int Where = PanelPosX + ((String.GetSize() + 1) << 3);
7905 FONT->Printf(DOUBLE_BUFFER, v2(Where, PanelPosY * 10), LIGHT_GRAY, "%d", NoBonusAttribute);
7910 truth character::AllowUnconsciousness () const {
7911 return DataBase->AllowUnconsciousness && TorsoIsAlive();
7915 truth character::CanPanic () const {
7916 return !Action || !Action->IsUnconsciousness() || !StateIsActivated(FEARLESS);
7920 int character::GetRandomBodyPart (feuLong Possible) const {
7921 int OKBodyPart[MAX_BODYPARTS];
7922 int OKBodyParts = 0;
7923 for (int c = 0; c < BodyParts; ++c) if (1 << c & Possible && GetBodyPart(c)) OKBodyPart[OKBodyParts++] = c;
7924 return OKBodyParts ? OKBodyPart[RAND_N(OKBodyParts)] : NONE_INDEX;
7928 void character::EditNP (sLong What) {
7929 int OldState = GetHungerState();
7930 NP += What;
7931 int NewState = GetHungerState();
7932 if (OldState > VERY_HUNGRY && NewState == VERY_HUNGRY) DeActivateVoluntaryAction(CONST_S("You are getting really hungry."));
7933 if (OldState > STARVING && NewState == STARVING) DeActivateVoluntaryAction(CONST_S("You are getting extremely hungry."));
7937 truth character::IsSwimming () const {
7938 return !IsFlying() && GetSquareUnder() && GetSquareUnder()->GetSquareWalkability() & SWIM;
7942 void character::AddBlackUnicornConsumeEndMessage () const {
7943 if (IsPlayer()) ADD_MESSAGE("You feel dirty and loathsome.");
7947 void character::AddGrayUnicornConsumeEndMessage () const {
7948 if (IsPlayer()) ADD_MESSAGE("You feel neutralized.");
7952 void character::AddWhiteUnicornConsumeEndMessage () const {
7953 if (IsPlayer()) ADD_MESSAGE("You feel purified.");
7957 void character::AddOmmelBoneConsumeEndMessage () const {
7958 if (IsPlayer()) ADD_MESSAGE("You feel the power of all your canine ancestors combining in your body.");
7959 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s looks extremely ferocious. You shudder.", CHAR_NAME(DEFINITE));
7963 void character::AddLiquidHorrorConsumeEndMessage () const {
7964 if (IsPlayer()) ADD_MESSAGE("Untold horrors flash before your eyes. The melancholy of the world is on your shoulders!");
7965 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks as if the melancholy of the world is on %s shoulders!.", CHAR_NAME(DEFINITE), GetPossessivePronoun().CStr());
7969 void character::AddAlienFleshConsumeEndMessage() const
7971 if (IsPlayer()) ADD_MESSAGE("You feel somehow sick by eating such acidic corpse...");
7972 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks like he eat something bad.", CHAR_NAME(DEFINITE));
7976 int character::GetBodyPartSparkleFlags (int) const {
7977 return
7978 ((GetNaturalSparkleFlags() & SKIN_COLOR ? SPARKLING_A : 0) |
7979 (GetNaturalSparkleFlags() & TORSO_MAIN_COLOR ? SPARKLING_B : 0) |
7980 (GetNaturalSparkleFlags() & TORSO_SPECIAL_COLOR ? SPARKLING_D : 0));
7984 truth character::IsAnimated () const {
7985 return combinebodypartpredicates()(this, &bodypart::IsAnimated, 1);
7989 double character::GetNaturalExperience (int Identifier) const {
7990 return DataBase->NaturalExperience[Identifier];
7994 truth character::HasBodyPart (sorter Sorter) const {
7995 if (Sorter == 0) return true;
7996 return combinebodypartpredicateswithparam<ccharacter*>()(this, Sorter, this, 1);
8000 truth character::PossessesItem (sorter Sorter) const {
8001 if (Sorter == 0) return true;
8002 return
8003 (GetStack()->SortedItems(this, Sorter) ||
8004 combinebodypartpredicateswithparam<ccharacter*>()(this, Sorter, this, 1) ||
8005 combineequipmentpredicateswithparam<ccharacter*>()(this, Sorter, this, 1));
8009 truth character::MoreThanOnePossessesItem (sorter Sorter) const {
8010 if (Sorter) {
8011 int count = 0;
8013 for (int c = 0; c < BodyParts; ++c) {
8014 bodypart *BodyPart = GetBodyPart(c);
8016 if (BodyPart && (Sorter == 0 || (BodyPart->*Sorter)(this))) {
8017 if (++count > 1) return true;
8020 for (int c = 0; c < GetEquipments(); ++c) {
8021 item *Equipment = GetEquipment(c);
8023 if (Equipment && (Sorter == 0 || (Equipment->*Sorter)(this))) {
8024 if (++count > 1) return true;
8027 for (int c = 0; c < GetStack()->GetItems(); ++c) {
8028 item *Stk = GetStack()->GetItem(c);
8030 if (Stk && (Sorter == 0 || (Stk->*Sorter)(this))) {
8031 if (++count > 1) return true;
8034 return false;
8036 return false;
8040 item *character::FirstPossessesItem (sorter Sorter) const {
8041 if (Sorter) {
8042 for (int c = 0; c < BodyParts; ++c) {
8043 bodypart *BodyPart = GetBodyPart(c);
8045 if (BodyPart && (Sorter == 0 || (BodyPart->*Sorter)(this))) return BodyPart;
8047 for (int c = 0; c < GetEquipments(); ++c) {
8048 item *Equipment = GetEquipment(c);
8050 if (Equipment && (Sorter == 0 || (Equipment->*Sorter)(this))) return Equipment;
8052 for (int c = 0; c < GetStack()->GetItems(); ++c) {
8053 item *Stk = GetStack()->GetItem(c);
8055 if (Stk && (Sorter == 0 || (Stk->*Sorter)(this))) return Stk;
8058 return 0;
8062 /* 0 <= I <= 1 */
8063 cchar *character::GetRunDescriptionLine (int I) const {
8064 if (!GetRunDescriptionLineOne().IsEmpty()) return !I ? GetRunDescriptionLineOne().CStr() : GetRunDescriptionLineTwo().CStr();
8065 if (IsFlying()) return !I ? "Flying" : "very fast";
8066 if (IsSwimming()) return !I ? "Swimming" : "very fast";
8067 return !I ? "Running" : "";
8071 void character::VomitAtRandomDirection (int Amount) {
8072 if (game::IsInWilderness()) return;
8073 /* Lacks support of multitile monsters */
8074 v2 Possible[9];
8075 int Index = 0;
8076 for (int d = 0; d < 9; ++d) {
8077 lsquare *Square = GetLSquareUnder()->GetNeighbourLSquare(d);
8078 if (Square && !Square->VomitingIsDangerous(this)) Possible[Index++] = Square->GetPos();
8080 if (Index) Vomit(Possible[RAND_N(Index)], Amount);
8081 else Vomit(GetPos(), Amount);
8085 void character::RemoveLifeSavers () {
8086 for (int c = 0; c < GetEquipments(); ++c) {
8087 item *Equipment = GetEquipment(c);
8088 if (Equipment && Equipment->IsInCorrectSlot(c) && Equipment->GetGearStates() & LIFE_SAVED) {
8089 Equipment->SendToHell();
8090 Equipment->RemoveFromSlot();
8096 ccharacter *character::FindCarrier () const {
8097 return this; //check
8101 void character::PrintBeginHiccupsMessage () const {
8102 if (IsPlayer()) ADD_MESSAGE("Your diaphragm is spasming vehemently.");
8106 void character::PrintEndHiccupsMessage () const {
8107 if (IsPlayer()) ADD_MESSAGE("You feel your annoying hiccoughs have finally subsided.");
8111 void character::HiccupsHandler () {
8113 if (!(RAND() % 2000)) {
8114 if (IsPlayer()) ADD_MESSAGE("");
8115 else if (CanBeSeenByPlayer()) ADD_MESSAGE("");
8116 else if ((PLAYER->GetPos()-GetPos()).GetLengthSquare() <= 400) ADD_MESSAGE("");
8117 game::CallForAttention(GetPos(), 400);
8123 void character::VampirismHandler () {
8124 //EditExperience(ARM_STRENGTH, -25, 1 << 1);
8125 //EditExperience(LEG_STRENGTH, -25, 1 << 1);
8126 //EditExperience(DEXTERITY, -25, 1 << 1);
8127 //EditExperience(AGILITY, -25, 1 << 1);
8128 //EditExperience(ENDURANCE, -25, 1 << 1);
8129 EditExperience(CHARISMA, -25, 1 << 1);
8130 EditExperience(WISDOM, -25, 1 << 1);
8131 EditExperience(INTELLIGENCE, -25, 1 << 1);
8132 CheckDeath(CONST_S("killed by vampirism"));
8136 void character::HiccupsSituationDangerModifier (double &Danger) const {
8137 Danger *= 1.25;
8141 void character::VampirismSituationDangerModifier (double &Danger) const {
8142 character *Vampire = vampire::Spawn();
8143 double DangerToVampire = GetRelativeDanger(Vampire);
8144 Danger *= pow(DangerToVampire, 0.1);
8145 delete Vampire;
8149 bool character::IsConscious () const {
8150 return !Action || !Action->IsUnconsciousness();
8154 wsquare *character::GetNearWSquare (v2 Pos) const {
8155 return static_cast<wsquare *>(GetSquareUnder()->GetArea()->GetSquare(Pos));
8159 wsquare *character::GetNearWSquare (int x, int y) const {
8160 return static_cast<wsquare *>(GetSquareUnder()->GetArea()->GetSquare(x, y));
8164 void character::ForcePutNear (v2 Pos) {
8165 /* GUM SOLUTION!!! */
8166 v2 NewPos = game::GetCurrentLevel()->GetNearestFreeSquare(PLAYER, Pos, false);
8167 if (NewPos == ERROR_V2) do { NewPos = game::GetCurrentLevel()->GetRandomSquare(this); } while(NewPos == Pos);
8168 PutTo(NewPos);
8172 void character::ReceiveMustardGas (int BodyPart, sLong Volume) {
8173 if (Volume) GetBodyPart(BodyPart)->AddFluid(liquid::Spawn(MUSTARD_GAS_LIQUID, Volume), CONST_S("skin"), 0, true);
8177 void character::ReceiveMustardGasLiquid (int BodyPartIndex, sLong Modifier) {
8178 bodypart *BodyPart = GetBodyPart(BodyPartIndex);
8179 if (BodyPart->GetMainMaterial()->GetInteractionFlags() & IS_AFFECTED_BY_MUSTARD_GAS) {
8180 sLong Tries = Modifier;
8181 Modifier -= Tries; //opt%?
8182 int Damage = 0;
8183 for (sLong c = 0; c < Tries; ++c) if (!(RAND() % 100)) ++Damage;
8184 if (Modifier && !(RAND() % 1000 / Modifier)) ++Damage;
8185 if (Damage) {
8186 feuLong Minute = game::GetTotalMinutes();
8187 if (GetLastAcidMsgMin() != Minute && (CanBeSeenByPlayer() || IsPlayer())) {
8188 SetLastAcidMsgMin(Minute);
8189 if (IsPlayer()) ADD_MESSAGE("Mustard gas dissolves the skin of your %s.", BodyPart->GetBodyPartName().CStr());
8190 else ADD_MESSAGE("Mustard gas dissolves %s.", CHAR_NAME(DEFINITE));
8192 ReceiveBodyPartDamage(0, Damage, MUSTARD_GAS_DAMAGE, BodyPartIndex, YOURSELF, false, false, false);
8193 CheckDeath(CONST_S("killed by a fatal exposure to mustard gas"));
8199 truth character::IsBadPath (v2 Pos) const {
8200 if (!IsGoingSomeWhere()) return false;
8201 v2 TPos = !Route.empty() ? Route.back() : GoingTo;
8202 return ((TPos - Pos).GetManhattanLength() > (TPos - GetPos()).GetManhattanLength());
8206 double &character::GetExpModifierRef (expid E) {
8207 return ExpModifierMap.insert(std::make_pair(E, 1.)).first->second;
8211 /* Should probably do more. Now only makes Player forget gods */
8212 truth character::ForgetRandomThing () {
8213 if (IsPlayer()) {
8214 /* hopefully this code isn't some where else */
8215 std::vector<god *> Known;
8216 for (int c = 1; c <= GODS; ++c) if (game::GetGod(c)->IsKnown()) Known.push_back(game::GetGod(c));
8217 if (Known.empty()) return false;
8218 int RandomGod = RAND_N(Known.size());
8219 Known.at(RAND_N(Known.size()))->SetIsKnown(false);
8220 ADD_MESSAGE("You forget how to pray to %s.", Known.at(RandomGod)->GetName());
8221 return true;
8223 return false;
8227 int character::CheckForBlock (character *Enemy, item *Weapon, double ToHitValue, int Damage, int Success, int Type) {
8228 return Damage;
8232 void character::ApplyAllGodsKnownBonus () {
8233 stack *AddPlace = GetStackUnder();
8234 if (game::IsInWilderness()) AddPlace = GetStack(); else AddPlace = GetStackUnder();
8235 pantheonbook *NewBook = pantheonbook::Spawn();
8236 AddPlace->AddItem(NewBook);
8237 ADD_MESSAGE("\"MORTAL! BEHOLD THE HOLY SAGA\"");
8238 ADD_MESSAGE("%s materializes near your feet.", NewBook->CHAR_NAME(INDEFINITE));
8242 void character::ReceiveSirenSong (character *Siren) {
8243 if (Siren->GetTeam() == GetTeam()) return;
8244 if (!RAND_N(4)) {
8245 if (IsPlayer()) ADD_MESSAGE("The beautiful melody of %s makes you feel sleepy.", Siren->CHAR_NAME(DEFINITE));
8246 else if (CanBeSeenByPlayer()) ADD_MESSAGE("The beautiful melody of %s makes %s look sleepy.", Siren->CHAR_NAME(DEFINITE), CHAR_NAME(DEFINITE)); /*k8*/
8247 Stamina -= (1 + RAND_N(4)) * 10000;
8248 return;
8250 if (!IsPlayer() && IsCharmable() && !RAND_N(5)) {
8251 ChangeTeam(Siren->GetTeam());
8252 ADD_MESSAGE("%s seems to be totally brainwashed by %s melodies.", CHAR_NAME(DEFINITE), Siren->CHAR_NAME(DEFINITE));
8253 return;
8255 if (!RAND_N(4)) {
8256 item *What = GiveMostExpensiveItem(Siren);
8257 if (What) {
8258 if (IsPlayer()) {
8259 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);
8260 } else {
8261 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);
8263 } else {
8264 if (IsPlayer()) ADD_MESSAGE("You would like to give something to %s.", Siren->CHAR_NAME(DEFINITE));
8266 return;
8271 // return 0, if no item found
8272 item *character::FindMostExpensiveItem () const {
8273 int MaxPrice = -1;
8274 item *MostExpensive = 0;
8275 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) {
8276 if ((*i)->GetPrice() > MaxPrice) {
8277 MaxPrice = (*i)->GetPrice();
8278 MostExpensive = (*i);
8281 for (int c = 0; c < GetEquipments(); ++c) {
8282 item *Equipment = GetEquipment(c);
8283 if (Equipment && Equipment->GetPrice() > MaxPrice) {
8284 MaxPrice = Equipment->GetPrice();
8285 MostExpensive = Equipment;
8288 return MostExpensive;
8292 // returns 0 if no items available
8293 item *character::GiveMostExpensiveItem(character *ToWhom) {
8294 item *ToGive = FindMostExpensiveItem();
8295 if (!ToGive) return 0;
8296 truth Equipped = PLAYER->Equips(ToGive);
8297 ToGive->RemoveFromSlot();
8298 if (Equipped) game::AskForEscPress(CONST_S("Equipment lost!"));
8299 ToWhom->ReceiveItemAsPresent(ToGive);
8300 EditAP(-1000);
8301 return ToGive;
8305 void character::ReceiveItemAsPresent (item *Present) {
8306 if (TestForPickup(Present)) GetStack()->AddItem(Present); else GetStackUnder()->AddItem(Present);
8310 /* returns 0 if no enemies in sight */
8311 character *character::GetNearestEnemy () const {
8312 character *NearestEnemy = 0;
8313 sLong NearestEnemyDistance = 0x7FFFFFFF;
8314 v2 Pos = GetPos();
8315 for (int c = 0; c < game::GetTeams(); ++c) {
8316 if (GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE) {
8317 for (std::list<character*>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i) {
8318 if ((*i)->IsEnabled()) {
8319 sLong ThisDistance = Max<sLong>(abs((*i)->GetPos().X - Pos.X), abs((*i)->GetPos().Y - Pos.Y));
8320 if ((ThisDistance < NearestEnemyDistance || (ThisDistance == NearestEnemyDistance && !(RAND() % 3))) && (*i)->CanBeSeenBy(this)) {
8321 NearestEnemy = *i;
8322 NearestEnemyDistance = ThisDistance;
8328 return NearestEnemy;
8332 truth character::MindWormCanPenetrateSkull (mindworm *) const {
8333 return false;
8337 truth character::CanTameWithDulcis (const character *Tamer) const {
8338 int TamingDifficulty = GetTamingDifficulty();
8339 if (TamingDifficulty == NO_TAMING) return false;
8340 if (GetAttachedGod() == DULCIS) return true;
8341 int Modifier = Tamer->GetAttribute(WISDOM) + Tamer->GetAttribute(CHARISMA);
8342 if (Tamer->IsPlayer()) Modifier += game::GetGod(DULCIS)->GetRelation() / 20;
8343 else if (Tamer->GetAttachedGod() == DULCIS) Modifier += 50;
8344 if (TamingDifficulty == 0) {
8345 if (!IgnoreDanger()) TamingDifficulty = int(10 * GetRelativeDanger(Tamer));
8346 else TamingDifficulty = 10 * GetHPRequirementForGeneration()/Max(Tamer->GetHP(), 1);
8348 return Modifier >= TamingDifficulty * 3;
8352 truth character::CanTameWithLyre (const character *Tamer) const {
8353 int TamingDifficulty = GetTamingDifficulty();
8354 if (TamingDifficulty == NO_TAMING) return false;
8355 if (TamingDifficulty == 0) {
8356 if (!IgnoreDanger()) TamingDifficulty = int(10 * GetRelativeDanger(Tamer));
8357 else TamingDifficulty = 10*GetHPRequirementForGeneration()/Max(Tamer->GetHP(), 1);
8359 return Tamer->GetAttribute(CHARISMA) >= TamingDifficulty;
8363 truth character::CanTameWithScroll (const character *Tamer) const {
8364 int TamingDifficulty = GetTamingDifficulty();
8365 return
8366 (TamingDifficulty != NO_TAMING &&
8367 (TamingDifficulty == 0 ||
8368 Tamer->GetAttribute(INTELLIGENCE) * 4 + Tamer->GetAttribute(CHARISMA) >= TamingDifficulty * 5));
8372 truth character::CheckSadism () {
8373 if (!IsSadist() || !HasSadistAttackMode() || !IsSmall()) return false; // gum
8374 if (!RAND_N(10)) {
8375 for (int d = 0; d < MDIR_STAND; ++d) {
8376 square *Square = GetNeighbourSquare(d);
8377 if (Square) {
8378 character *Char = Square->GetCharacter();
8379 if (Char && Char->IsMasochist() && GetRelation(Char) == FRIEND &&
8380 Char->GetHP() * 3 >= Char->GetMaxHP() * 2 && Hit(Char, Square->GetPos(), d, SADIST_HIT)) {
8381 TerminateGoingTo();
8382 return true;
8387 return false;
8391 truth character::CheckForBeverage () {
8392 if (StateIsActivated(PANIC) || !IsEnabled() || !UsesNutrition() || CheckIfSatiated()) return false;
8393 itemvector ItemVector;
8394 GetStack()->FillItemVector(ItemVector);
8395 for (uInt c = 0; c < ItemVector.size(); ++c) if (ItemVector[c]->IsBeverage(this) && TryToConsume(ItemVector[c])) return true;
8396 return false;
8400 void character::Haste () {
8401 doforbodyparts()(this, &bodypart::Haste);
8402 doforequipments()(this, &item::Haste);
8403 BeginTemporaryState(HASTE, 500 + RAND() % 1000);
8407 void character::Slow () {
8408 doforbodyparts()(this, &bodypart::Slow);
8409 doforequipments()(this, &item::Slow);
8410 //BeginTemporaryState(HASTE, 500 + RAND() % 1000); // this seems to be a bug
8411 BeginTemporaryState(SLOW, 500 + RAND() % 1000);
8415 void character::SurgicallyDetachBodyPart () {
8416 ADD_MESSAGE("You haven't got any extra bodyparts.");
8420 truth character::CanHear() const
8422 return DataBase->CanHear && HasHead();
8426 truth character::IsAllowedInDungeon (int dunIndex) {
8427 const fearray<int> &dlist = GetAllowedDungeons();
8429 for (uInt f = 0; f < dlist.Size; ++f) {
8430 if (dlist[f] == ALL_DUNGEONS || dlist[f] == dunIndex) {
8431 fprintf(stderr, "OK!\n");
8432 return true;
8435 fprintf(stderr, "NO!\n");
8436 return false;
8440 truth character::IsESPBlockedByEquipment () const {
8441 for (int c = 0; c < GetEquipments(); ++c) {
8442 item *Item = GetEquipment(c);
8443 if (Item && Item->IsHelmet(this) &&
8444 ((Item->GetMainMaterial() && Item->GetMainMaterial()->BlockESP()) ||
8445 (Item->GetSecondaryMaterial() && Item->GetSecondaryMaterial()->BlockESP()))) return true;
8447 return false;
8451 truth character::TemporaryStateIsActivated (sLong What) const {
8452 if ((What&ESP) && (TemporaryState&ESP) && IsESPBlockedByEquipment()) {
8453 return ((TemporaryState&What)&(~ESP));
8455 return (TemporaryState & What);
8459 truth character::StateIsActivated (sLong What) const {
8460 if ((What&ESP) && ((TemporaryState|EquipmentState)&ESP) && IsESPBlockedByEquipment()) {
8461 return ((TemporaryState&What)&(~ESP)) || ((EquipmentState&What)&(~ESP));
8463 return (TemporaryState & What) || (EquipmentState & What);
8467 void character::PrintBeginFearlessMessage () const {
8468 if (!StateIsActivated(FEARLESS)) {
8469 if (IsPlayer()) ADD_MESSAGE("You feel very comfortable.");
8470 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s seems very comfortable.", CHAR_NAME(DEFINITE));
8474 void character::PrintEndFearlessMessage () const {
8475 if (!StateIsActivated(FEARLESS)) {
8476 if (IsPlayer()) ADD_MESSAGE("Everything looks more dangerous now.");
8477 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s seems to have lost his confidence.", CHAR_NAME(DEFINITE));
8481 void character::BeginFearless () {
8482 DeActivateTemporaryState(PANIC);
8485 void character::EndFearless () {
8486 CheckPanic(500);
8490 void character::PrintBeginEtherealityMessage () const {
8491 if (IsPlayer()) ADD_MESSAGE("You feel like many miscible droplets of ether.");
8492 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s melds into the surroundings.", CHAR_NAME(DEFINITE));
8495 void character::PrintEndEtherealityMessage () const {
8496 if (IsPlayer()) ADD_MESSAGE("You drop out of the firmament, feeling suddenly quite dense.");
8497 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s displaces the air with a puff.", CHAR_NAME(INDEFINITE));
8500 void character::BeginEthereality () {}
8502 void character::EndEthereality () {}