renamed alot of types; hope this will not break the game
[k8-i-v-a-n.git] / src / game / char.cpp
blob005f118c3d98b061dbfdb9739c7994e7a548bbd8
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>
19 /* These statedata structs contain functions and values used for handling
20 * states. Remember to update them. All normal states must have
21 * PrintBeginMessage and PrintEndMessage functions and a Description string.
22 * BeginHandler, EndHandler, Handler (called each tick) and IsAllowed are
23 * optional, enter zero if the state doesn't need one. If the SECRET flag
24 * is set, Description is not shown in the panel without magical means.
25 * You can also set some source (SRC_*) and duration (DUR_*) flags, which
26 * control whether the state can be randomly activated in certain situations.
27 * These flags can be found in ivandef.h. RANDOMIZABLE sets all source
28 * & duration flags at once. */
29 struct statedata {
30 const char *Description;
31 int Flags;
32 void (character::*PrintBeginMessage) () const;
33 void (character::*PrintEndMessage) () const;
34 void (character::*BeginHandler) ();
35 void (character::*EndHandler) ();
36 void (character::*Handler) ();
37 truth (character::*IsAllowed) () const;
38 void (character::*SituationDangerModifier) (double &) const;
42 const statedata StateData[STATES] =
45 "Polymorphed",
46 NO_FLAGS,
50 &character::EndPolymorph,
54 }, {
55 "Hasted",
56 RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL),
57 &character::PrintBeginHasteMessage,
58 &character::PrintEndHasteMessage,
64 }, {
65 "Slowed",
66 RANDOMIZABLE&~SRC_GOOD,
67 &character::PrintBeginSlowMessage,
68 &character::PrintEndSlowMessage,
74 }, {
75 "PolyControl",
76 RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL|SRC_GOOD),
77 &character::PrintBeginPolymorphControlMessage,
78 &character::PrintEndPolymorphControlMessage,
84 }, {
85 "LifeSaved",
86 SECRET,
87 &character::PrintBeginLifeSaveMessage,
88 &character::PrintEndLifeSaveMessage,
94 }, {
95 "Lycanthropy",
96 SECRET|SRC_FOUNTAIN|SRC_CONFUSE_READ|DUR_FLAGS,
97 &character::PrintBeginLycanthropyMessage,
98 &character::PrintEndLycanthropyMessage,
101 &character::LycanthropyHandler,
103 &character::LycanthropySituationDangerModifier
104 }, {
105 "Invisible",
106 RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL),
107 &character::PrintBeginInvisibilityMessage,
108 &character::PrintEndInvisibilityMessage,
109 &character::BeginInvisibility, &character::EndInvisibility,
113 }, {
114 "Infravision",
115 RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL),
116 &character::PrintBeginInfraVisionMessage,
117 &character::PrintEndInfraVisionMessage,
118 &character::BeginInfraVision,
119 &character::EndInfraVision,
123 }, {
124 "ESP",
125 RANDOMIZABLE&~SRC_EVIL,
126 &character::PrintBeginESPMessage,
127 &character::PrintEndESPMessage,
128 &character::BeginESP,
129 &character::EndESP,
133 }, {
134 "Poisoned",
135 DUR_TEMPORARY,
136 &character::PrintBeginPoisonedMessage,
137 &character::PrintEndPoisonedMessage,
140 &character::PoisonedHandler,
141 &character::CanBePoisoned,
142 &character::PoisonedSituationDangerModifier
143 }, {
144 "Teleporting",
145 SECRET|(RANDOMIZABLE&~(SRC_MUSHROOM|SRC_GOOD)),
146 &character::PrintBeginTeleportMessage,
147 &character::PrintEndTeleportMessage,
150 &character::TeleportHandler,
153 }, {
154 "Polymorphing",
155 SECRET|(RANDOMIZABLE&~(SRC_MUSHROOM|SRC_GOOD)),
156 &character::PrintBeginPolymorphMessage,
157 &character::PrintEndPolymorphMessage,
160 &character::PolymorphHandler,
162 &character::PolymorphingSituationDangerModifier
163 }, {
164 "TeleControl",
165 RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL),
166 &character::PrintBeginTeleportControlMessage,
167 &character::PrintEndTeleportControlMessage,
173 }, {
174 "Panicked",
175 NO_FLAGS,
176 &character::PrintBeginPanicMessage,
177 &character::PrintEndPanicMessage,
178 &character::BeginPanic,
179 &character::EndPanic,
181 &character::CanPanic,
182 &character::PanicSituationDangerModifier
183 }, {
184 "Confused",
185 SECRET|(RANDOMIZABLE&~(DUR_PERMANENT|SRC_GOOD)),
186 &character::PrintBeginConfuseMessage,
187 &character::PrintEndConfuseMessage,
191 &character::CanBeConfused,
192 &character::ConfusedSituationDangerModifier
193 }, {
194 "Parasitized",
195 SECRET|(RANDOMIZABLE&~DUR_TEMPORARY),
196 &character::PrintBeginParasitizedMessage,
197 &character::PrintEndParasitizedMessage,
200 &character::ParasitizedHandler,
201 &character::CanBeParasitized,
202 &character::ParasitizedSituationDangerModifier
203 }, {
204 "Searching",
205 NO_FLAGS,
206 &character::PrintBeginSearchingMessage,
207 &character::PrintEndSearchingMessage,
210 &character::SearchingHandler,
213 }, {
214 "GasImmunity",
215 SECRET|(RANDOMIZABLE&~(SRC_GOOD|SRC_EVIL)),
216 &character::PrintBeginGasImmunityMessage,
217 &character::PrintEndGasImmunityMessage,
223 }, {
224 "Levitating",
225 RANDOMIZABLE&~SRC_EVIL,
226 &character::PrintBeginLevitationMessage,
227 &character::PrintEndLevitationMessage,
229 &character::EndLevitation,
233 }, {
234 "Leprosy",
235 SECRET|(RANDOMIZABLE&~DUR_TEMPORARY),
236 &character::PrintBeginLeprosyMessage,
237 &character::PrintEndLeprosyMessage,
238 &character::BeginLeprosy,
239 &character::EndLeprosy,
240 &character::LeprosyHandler,
242 &character::LeprosySituationDangerModifier
243 }, {
244 "Hiccups",
245 SRC_FOUNTAIN|SRC_CONFUSE_READ|DUR_FLAGS,
246 &character::PrintBeginHiccupsMessage,
247 &character::PrintEndHiccupsMessage,
250 &character::HiccupsHandler,
252 &character::HiccupsSituationDangerModifier
257 characterprototype::characterprototype (const characterprototype *Base, characterspawner Spawner,
258 charactercloner Cloner, cchar *ClassID)
259 : Base(Base),
260 Spawner(Spawner),
261 Cloner(Cloner),
262 ClassID(ClassID)
264 Index = protocontainer<character>::Add(this);
268 std::list<character *>::iterator character::GetTeamIterator () { return TeamIterator; }
269 void character::SetTeamIterator (std::list<character *>::iterator What) { TeamIterator = What; }
270 void character::CreateInitialEquipment (int SpecialFlags) { AddToInventory(DataBase->Inventory, SpecialFlags); }
271 void character::EditAP (sLong What) { AP = Limit<sLong>(AP+What, -12000, 1200); }
272 int character::GetRandomStepperBodyPart () const { return TORSO_INDEX; }
273 void character::GainIntrinsic (sLong What) { BeginTemporaryState(What, PERMANENT); }
274 truth character::IsUsingArms () const { return GetAttackStyle() & USE_ARMS; }
275 truth character::IsUsingLegs () const { return GetAttackStyle() & USE_LEGS; }
276 truth character::IsUsingHead () const { return GetAttackStyle() & USE_HEAD; }
277 void character::CalculateAllowedWeaponSkillCategories () { AllowedWeaponSkillCategories = MARTIAL_SKILL_CATEGORIES; }
278 festring character::GetBeVerb () const { return IsPlayer() ? CONST_S("are") : CONST_S("is"); }
279 void character::SetEndurance (int What) { BaseExperience[ENDURANCE] = What * EXP_MULTIPLIER; }
280 void character::SetPerception (int What) { BaseExperience[PERCEPTION] = What * EXP_MULTIPLIER; }
281 void character::SetIntelligence (int What) { BaseExperience[INTELLIGENCE] = What * EXP_MULTIPLIER; }
282 void character::SetWisdom (int What) { BaseExperience[WISDOM] = What * EXP_MULTIPLIER; }
283 void character::SetWillPower (int What) { BaseExperience[WILL_POWER] = What * EXP_MULTIPLIER; }
284 void character::SetCharisma (int What) { BaseExperience[CHARISMA] = What * EXP_MULTIPLIER; }
285 void character::SetMana (int What) { BaseExperience[MANA] = What * EXP_MULTIPLIER; }
286 truth character::IsOnGround () const { return MotherEntity && MotherEntity->IsOnGround(); }
287 truth character::LeftOversAreUnique () const { return GetArticleMode() || AssignedName.GetSize(); }
288 truth character::HomeDataIsValid () const { return (HomeData && HomeData->Level == GetLSquareUnder()->GetLevelIndex() && HomeData->Dungeon == GetLSquareUnder()->GetDungeonIndex()); }
289 void character::SetHomePos (v2 Pos) { HomeData->Pos = Pos; }
290 cchar *character::FirstPersonUnarmedHitVerb () const { return "hit"; }
291 cchar *character::FirstPersonCriticalUnarmedHitVerb () const { return "critically hit"; }
292 cchar *character::ThirdPersonUnarmedHitVerb () const { return "hits"; }
293 cchar *character::ThirdPersonCriticalUnarmedHitVerb () const { return "critically hits"; }
294 cchar *character::FirstPersonKickVerb () const { return "kick"; }
295 cchar *character::FirstPersonCriticalKickVerb () const { return "critically kick"; }
296 cchar *character::ThirdPersonKickVerb () const { return "kicks"; }
297 cchar *character::ThirdPersonCriticalKickVerb () const { return "critically kicks"; }
298 cchar *character::FirstPersonBiteVerb () const { return "bite"; }
299 cchar *character::FirstPersonCriticalBiteVerb () const { return "critically bite"; }
300 cchar *character::ThirdPersonBiteVerb () const { return "bites"; }
301 cchar *character::ThirdPersonCriticalBiteVerb () const { return "critically bites"; }
302 cchar *character::UnarmedHitNoun () const { return "attack"; }
303 cchar *character::KickNoun () const { return "kick"; }
304 cchar *character::BiteNoun () const { return "attack"; }
305 cchar *character::GetEquipmentName (int) const { return ""; }
306 const std::list<uLong> &character::GetOriginalBodyPartID (int I) const { return OriginalBodyPartID[I]; }
307 square *character::GetNeighbourSquare (int I) const { return GetSquareUnder()->GetNeighbourSquare(I); }
308 lsquare *character::GetNeighbourLSquare (int I) const { return static_cast<lsquare *>(GetSquareUnder())->GetNeighbourLSquare(I); }
309 wsquare *character::GetNeighbourWSquare (int I) const { return static_cast<wsquare *>(GetSquareUnder())->GetNeighbourWSquare(I); }
310 god *character::GetMasterGod () const { return game::GetGod(GetConfig()); }
311 col16 character::GetBodyPartColorA (int, truth) const { return GetSkinColor(); }
312 col16 character::GetBodyPartColorB (int, truth) const { return GetTorsoMainColor(); }
313 col16 character::GetBodyPartColorC (int, truth) const { return GetBeltColor(); } // sorry...
314 col16 character::GetBodyPartColorD (int, truth) const { return GetTorsoSpecialColor(); }
315 int character::GetRandomApplyBodyPart () const { return TORSO_INDEX; }
316 truth character::MustBeRemovedFromBone () const { return IsUnique() && !CanBeGenerated(); }
317 truth character::IsPet () const { return GetTeam()->GetID() == PLAYER_TEAM; }
318 character* character::GetLeader () const { return GetTeam()->GetLeader(); }
319 int character::GetMoveType () const { return (!StateIsActivated(LEVITATION) ? DataBase->MoveType : DataBase->MoveType | FLY); }
320 festring character::GetZombieDescription () const { return " of "+GetName(INDEFINITE); }
321 truth character::BodyPartCanBeSevered (int I) const { return I; }
322 truth character::HasBeenSeen () const { return DataBase->Flags & HAS_BEEN_SEEN; }
323 truth character::IsTemporary () const { return GetTorso()->GetLifeExpectancy(); }
324 cchar *character::GetNormalDeathMessage () const { return "killed @k"; }
327 int characterdatabase::*ExpPtr[ATTRIBUTES] = {
328 &characterdatabase::DefaultEndurance,
329 &characterdatabase::DefaultPerception,
330 &characterdatabase::DefaultIntelligence,
331 &characterdatabase::DefaultWisdom,
332 &characterdatabase::DefaultWillPower,
333 &characterdatabase::DefaultCharisma,
334 &characterdatabase::DefaultMana,
335 &characterdatabase::DefaultArmStrength,
336 &characterdatabase::DefaultLegStrength,
337 &characterdatabase::DefaultDexterity,
338 &characterdatabase::DefaultAgility
342 contentscript<item> characterdatabase::*EquipmentDataPtr[EQUIPMENT_DATAS] = {
343 &characterdatabase::Helmet,
344 &characterdatabase::Amulet,
345 &characterdatabase::Cloak,
346 &characterdatabase::BodyArmor,
347 &characterdatabase::Belt,
348 &characterdatabase::RightWielded,
349 &characterdatabase::LeftWielded,
350 &characterdatabase::RightRing,
351 &characterdatabase::LeftRing,
352 &characterdatabase::RightGauntlet,
353 &characterdatabase::LeftGauntlet,
354 &characterdatabase::RightBoot,
355 &characterdatabase::LeftBoot
359 character::character (ccharacter &Char) :
360 entity(Char), id(Char), NP(Char.NP), AP(Char.AP),
361 TemporaryState(Char.TemporaryState&~POLYMORPHED),
362 Team(Char.Team), GoingTo(ERROR_V2), Money(0),
363 AssignedName(Char.AssignedName), Action(0),
364 DataBase(Char.DataBase), MotherEntity(0),
365 PolymorphBackup(0), EquipmentState(0), SquareUnder(0),
366 AllowedWeaponSkillCategories(Char.AllowedWeaponSkillCategories),
367 BodyParts(Char.BodyParts),
368 RegenerationCounter(Char.RegenerationCounter),
369 SquaresUnder(Char.SquaresUnder), LastAcidMsgMin(0),
370 Stamina(Char.Stamina), MaxStamina(Char.MaxStamina),
371 BlocksSinceLastTurn(0), GenerationDanger(Char.GenerationDanger),
372 CommandFlags(Char.CommandFlags), WarnFlags(0),
373 ScienceTalks(Char.ScienceTalks), TrapData(0), CounterToMindWormHatch(0)
375 int c;
376 Flags &= ~C_PLAYER;
377 Flags |= C_INITIALIZING|C_IN_NO_MSG_MODE;
378 Stack = new stack(0, this, HIDDEN);
379 for (c = 0; c < STATES; ++c) TemporaryStateCounter[c] = Char.TemporaryStateCounter[c];
380 if (Team) TeamIterator = Team->Add(this);
381 for (c = 0; c < BASE_ATTRIBUTES; ++c) BaseExperience[c] = Char.BaseExperience[c];
382 BodyPartSlot = new bodypartslot[BodyParts];
383 OriginalBodyPartID = new std::list<uLong>[BodyParts];
384 CWeaponSkill = new cweaponskill[AllowedWeaponSkillCategories];
385 SquareUnder = new square *[SquaresUnder];
386 if (SquaresUnder == 1) *SquareUnder = 0; else memset(SquareUnder, 0, SquaresUnder*sizeof(square *));
387 for (c = 0; c < BodyParts; ++c) {
388 BodyPartSlot[c].SetMaster(this);
389 bodypart *CharBodyPart = Char.GetBodyPart(c);
390 OriginalBodyPartID[c] = Char.OriginalBodyPartID[c];
391 if (CharBodyPart) {
392 bodypart *BodyPart = static_cast<bodypart *>(CharBodyPart->Duplicate());
393 SetBodyPart(c, BodyPart);
394 BodyPart->CalculateEmitation();
397 for (c = 0; c < AllowedWeaponSkillCategories; ++c) CWeaponSkill[c] = Char.CWeaponSkill[c];
398 HomeData = Char.HomeData ? new homedata(*Char.HomeData) : 0;
399 ID = game::CreateNewCharacterID(this);
403 character::character () :
404 entity(HAS_BE), NP(50000), AP(0), TemporaryState(0), Team(0),
405 GoingTo(ERROR_V2), Money(0), Action(0), MotherEntity(0),
406 PolymorphBackup(0), EquipmentState(0), SquareUnder(0),
407 RegenerationCounter(0), HomeData(0), LastAcidMsgMin(0),
408 BlocksSinceLastTurn(0), GenerationDanger(DEFAULT_GENERATION_DANGER),
409 WarnFlags(0), ScienceTalks(0), TrapData(0), CounterToMindWormHatch(0)
411 Stack = new stack(0, this, HIDDEN);
415 character::~character () {
416 if (Action) delete Action;
417 if (Team) Team->Remove(GetTeamIterator());
418 delete Stack;
419 for (int c = 0; c < BodyParts; ++c) delete GetBodyPart(c);
420 delete [] BodyPartSlot;
421 delete [] OriginalBodyPartID;
422 delete PolymorphBackup;
423 delete [] SquareUnder;
424 delete [] CWeaponSkill;
425 delete HomeData;
426 for (trapdata *T = TrapData; T;) {
427 trapdata *ToDel = T;
428 T = T->Next;
429 delete ToDel;
431 game::RemoveCharacterID(ID);
435 void character::Hunger () {
436 switch (GetBurdenState()) {
437 case OVER_LOADED:
438 case STRESSED:
439 EditNP(-8);
440 EditExperience(LEG_STRENGTH, 150, 1 << 2);
441 EditExperience(AGILITY, -50, 1 << 2);
442 break;
443 case BURDENED:
444 EditNP(-2);
445 EditExperience(LEG_STRENGTH, 75, 1 << 1);
446 EditExperience(AGILITY, -25, 1 << 1);
447 break;
448 case UNBURDENED:
449 EditNP(-1);
450 break;
453 switch (GetHungerState()) {
454 case STARVING:
455 EditExperience(ARM_STRENGTH, -75, 1 << 3);
456 EditExperience(LEG_STRENGTH, -75, 1 << 3);
457 break;
458 case VERY_HUNGRY:
459 EditExperience(ARM_STRENGTH, -50, 1 << 2);
460 EditExperience(LEG_STRENGTH, -50, 1 << 2);
461 break;
462 case HUNGRY:
463 EditExperience(ARM_STRENGTH, -25, 1 << 1);
464 EditExperience(LEG_STRENGTH, -25, 1 << 1);
465 break;
466 case SATIATED:
467 EditExperience(AGILITY, -25, 1 << 1);
468 break;
469 case BLOATED:
470 EditExperience(AGILITY, -50, 1 << 2);
471 break;
472 case OVER_FED:
473 EditExperience(AGILITY, -75, 1 << 3);
474 break;
476 CheckStarvationDeath(CONST_S("starved to death"));
480 int character::TakeHit (character *Enemy, item *Weapon, bodypart *EnemyBodyPart, v2 HitPos, double Damage,
481 double ToHitValue, int Success, int Type, int GivenDir, truth Critical, truth ForceHit)
483 //FIXME: args
484 game::mActor = Enemy;
485 game::RunOnCharEvent(this, CONST_S("take_hit"));
486 game::mActor = 0;
487 int Dir = Type == BITE_ATTACK ? YOURSELF : GivenDir;
488 double DodgeValue = GetDodgeValue();
489 if (!Enemy->IsPlayer() && GetAttackWisdomLimit() != NO_LIMIT) Enemy->EditExperience(WISDOM, 75, 1 << 13);
490 if (!Enemy->CanBeSeenBy(this)) ToHitValue *= 2;
491 if (!CanBeSeenBy(Enemy)) DodgeValue *= 2;
492 if (Enemy->StateIsActivated(CONFUSED)) ToHitValue *= 0.75;
494 switch (Enemy->GetTirednessState()) {
495 case FAINTING:
496 ToHitValue *= 0.50;
497 case EXHAUSTED:
498 ToHitValue *= 0.75;
500 switch (GetTirednessState()) {
501 case FAINTING:
502 DodgeValue *= 0.50;
503 case EXHAUSTED:
504 DodgeValue *= 0.75;
507 if (!ForceHit) {
508 if (!IsRetreating()) SetGoingTo(Enemy->GetPos());
509 else SetGoingTo(GetPos()-((Enemy->GetPos()-GetPos())<<4));
510 if (!Enemy->IsRetreating()) Enemy->SetGoingTo(GetPos());
511 else Enemy->SetGoingTo(Enemy->GetPos()-((GetPos()-Enemy->GetPos())<<4));
514 /* Effectively, the average chance to hit is 100% / (DV/THV + 1). */
515 if (RAND() % int(100+ToHitValue/DodgeValue*(100+Success)) < 100 && !Critical && !ForceHit) {
516 Enemy->AddMissMessage(this);
517 EditExperience(AGILITY, 150, 1 << 7);
518 EditExperience(PERCEPTION, 75, 1 << 7);
519 if (Enemy->CanBeSeenByPlayer())
520 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy->GetName(DEFINITE)+CONST_S(" interrupts you."));
521 else
522 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
523 return HAS_DODGED;
526 int TrueDamage = int(Damage*(100+Success)/100)+(RAND()%3 ? 1 : 0);
527 if (Critical) {
528 TrueDamage += TrueDamage >> 1;
529 ++TrueDamage;
532 int BodyPart = ChooseBodyPartToReceiveHit(ToHitValue, DodgeValue);
533 if (Critical) {
534 switch (Type) {
535 case UNARMED_ATTACK:
536 Enemy->AddPrimitiveHitMessage(this, Enemy->FirstPersonCriticalUnarmedHitVerb(), Enemy->ThirdPersonCriticalUnarmedHitVerb(), BodyPart);
537 break;
538 case WEAPON_ATTACK:
539 Enemy->AddWeaponHitMessage(this, Weapon, BodyPart, true);
540 break;
541 case KICK_ATTACK:
542 Enemy->AddPrimitiveHitMessage(this, Enemy->FirstPersonCriticalKickVerb(), Enemy->ThirdPersonCriticalKickVerb(), BodyPart);
543 break;
544 case BITE_ATTACK:
545 Enemy->AddPrimitiveHitMessage(this, Enemy->FirstPersonCriticalBiteVerb(), Enemy->ThirdPersonCriticalBiteVerb(), BodyPart);
546 break;
548 } else {
549 switch (Type) {
550 case UNARMED_ATTACK:
551 Enemy->AddPrimitiveHitMessage(this, Enemy->FirstPersonUnarmedHitVerb(), Enemy->ThirdPersonUnarmedHitVerb(), BodyPart);
552 break;
553 case WEAPON_ATTACK:
554 Enemy->AddWeaponHitMessage(this, Weapon, BodyPart, false);
555 break;
556 case KICK_ATTACK:
557 Enemy->AddPrimitiveHitMessage(this, Enemy->FirstPersonKickVerb(), Enemy->ThirdPersonKickVerb(), BodyPart);
558 break;
559 case BITE_ATTACK:
560 Enemy->AddPrimitiveHitMessage(this, Enemy->FirstPersonBiteVerb(), Enemy->ThirdPersonBiteVerb(), BodyPart);
561 break;
565 if (!Critical && TrueDamage && Enemy->AttackIsBlockable(Type)) {
566 TrueDamage = CheckForBlock(Enemy, Weapon, ToHitValue, TrueDamage, Success, Type);
567 if (!TrueDamage || (Weapon && !Weapon->Exists())) {
568 if (Enemy->CanBeSeenByPlayer())
569 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy->GetName(DEFINITE)+CONST_S(" interrupts you."));
570 else
571 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
572 return HAS_BLOCKED;
576 int WeaponSkillHits = CalculateWeaponSkillHits(Enemy);
577 int DoneDamage = ReceiveBodyPartDamage(Enemy, TrueDamage, PHYSICAL_DAMAGE, BodyPart, Dir, false, Critical, true, Type == BITE_ATTACK && Enemy->BiteCapturesBodyPart());
578 truth Succeeded = (GetBodyPart(BodyPart) && HitEffect(Enemy, Weapon, HitPos, Type, BodyPart, Dir, !DoneDamage)) || DoneDamage;
579 if (Succeeded) Enemy->WeaponSkillHit(Weapon, Type, WeaponSkillHits);
581 if (Weapon) {
582 if (Weapon->Exists() && DoneDamage < TrueDamage) Weapon->ReceiveDamage(Enemy, TrueDamage-DoneDamage, PHYSICAL_DAMAGE);
583 if (Weapon->Exists() && DoneDamage && SpillsBlood() && GetBodyPart(BodyPart) &&
584 (GetBodyPart(BodyPart)->IsAlive() || GetBodyPart(BodyPart)->GetMainMaterial()->IsLiquid()))
585 Weapon->SpillFluid(0, CreateBlood(15+RAND()%15));
588 if (Enemy->AttackIsBlockable(Type)) SpecialBodyDefenceEffect(Enemy, EnemyBodyPart, Type);
590 if (!Succeeded) {
591 if (Enemy->CanBeSeenByPlayer())
592 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy->GetName(DEFINITE)+CONST_S(" interrupts you."));
593 else
594 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
596 return DID_NO_DAMAGE;
599 if (CheckDeath(GetNormalDeathMessage(), Enemy, Enemy->IsPlayer() ? FORCE_MSG : 0)) return HAS_DIED;
601 if (Enemy->CanBeSeenByPlayer())
602 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy->GetName(DEFINITE)+CONST_S(" interrupts you."));
603 else
604 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
606 return HAS_HIT;
610 struct svpriorityelement {
611 svpriorityelement (int BodyPart, int StrengthValue) : BodyPart(BodyPart), StrengthValue(StrengthValue) {}
612 bool operator < (const svpriorityelement &AnotherPair) const { return StrengthValue > AnotherPair.StrengthValue; }
613 int BodyPart;
614 int StrengthValue;
618 int character::ChooseBodyPartToReceiveHit (double ToHitValue, double DodgeValue) {
619 if (BodyParts == 1) return 0;
620 std::priority_queue<svpriorityelement> SVQueue;
621 for (int c = 0; c < BodyParts; ++c) {
622 bodypart *BodyPart = GetBodyPart(c);
623 if (BodyPart && (BodyPart->GetHP() != 1 || BodyPart->CanBeSevered(PHYSICAL_DAMAGE)))
624 SVQueue.push(svpriorityelement(c, ModifyBodyPartHitPreference(c, BodyPart->GetStrengthValue()+BodyPart->GetHP())));
626 while (SVQueue.size()) {
627 svpriorityelement E = SVQueue.top();
628 int ToHitPercentage = int(GLOBAL_WEAK_BODYPART_HIT_MODIFIER*ToHitValue*GetBodyPart(E.BodyPart)->GetBodyPartVolume()/(DodgeValue*GetBodyVolume()));
629 ToHitPercentage = ModifyBodyPartToHitChance(E.BodyPart, ToHitPercentage);
630 if (ToHitPercentage < 1) ToHitPercentage = 1;
631 else if (ToHitPercentage > 95) ToHitPercentage = 95;
632 if (ToHitPercentage > RAND()%100) return E.BodyPart;
633 SVQueue.pop();
635 return 0;
639 void character::Be () {
640 if (game::ForceJumpToPlayerBe()) {
641 if (!IsPlayer()) return;
642 game::SetForceJumpToPlayerBe(false);
643 } else {
644 truth ForceBe = HP != MaxHP || AllowSpoil();
645 for (int c = 0; c < BodyParts; ++c) {
646 bodypart *BodyPart = GetBodyPart(c);
647 if (BodyPart && (ForceBe || BodyPart->NeedsBe())) BodyPart->Be();
649 HandleStates();
650 if (!IsEnabled()) return;
651 if (GetTeam() == PLAYER->GetTeam()) {
652 for (int c = 0; c < AllowedWeaponSkillCategories; ++c) {
653 if (CWeaponSkill[c].Tick() && IsPlayer()) CWeaponSkill[c].AddLevelDownMessage(c);
655 SWeaponSkillTick();
657 if (IsPlayer()) {
658 if (GetHungerState() == STARVING && !(RAND()%50)) LoseConsciousness(250+RAND_N(250), true);
659 if (!Action || Action->AllowFoodConsumption()) Hunger();
661 if (Stamina != MaxStamina) RegenerateStamina();
662 if (HP != MaxHP) Regenerate();
663 if (Action && AP >= 1000) ActionAutoTermination();
664 if (Action && AP >= 1000) {
665 Action->Handle();
666 if (!IsEnabled()) return;
667 } else {
668 EditAP(GetStateAPGain(100));
671 if (AP >= 1000) {
672 SpecialTurnHandler();
673 BlocksSinceLastTurn = 0;
674 if (IsPlayer()) {
675 static int Timer = 0;
676 if (ivanconfig::GetAutoSaveInterval() && !GetAction() && ++Timer >= ivanconfig::GetAutoSaveInterval()) {
677 game::Save(game::GetAutoSaveFileName());
678 Timer = 0;
680 game::CalculateNextDanger();
681 if (!StateIsActivated(POLYMORPHED)) game::UpdatePlayerAttributeAverage();
682 if (!game::IsInWilderness()) Search(GetAttribute(PERCEPTION));
683 if (!Action) {
684 GetPlayerCommand();
685 } else {
686 if (Action->ShowEnvironment()) {
687 static int Counter = 0;
688 if (++Counter == 10) {
689 game::DrawEverything();
690 Counter = 0;
693 msgsystem::ThyMessagesAreNowOld();
694 if (Action->IsVoluntary() && READ_KEY()) Action->Terminate(false);
696 } else {
697 if (!Action && !game::IsInWilderness()) GetAICommand();
703 void character::Move (v2 MoveTo, truth TeleportMove, truth Run) {
704 if (!IsEnabled()) return;
705 /* Test whether the player is stuck to something */
706 if (!TeleportMove && !TryToUnStickTraps(MoveTo-GetPos())) return;
707 if (Run && !IsPlayer() && TorsoIsAlive() && (Stamina <= 10000 / Max(GetAttribute(LEG_STRENGTH), 1) ||
708 (!StateIsActivated(PANIC) && Stamina < MaxStamina >> 2)))
709 Run = false;
710 RemoveTraps();
711 if (GetBurdenState() != OVER_LOADED || TeleportMove) {
712 lsquare *OldSquareUnder[MAX_SQUARES_UNDER];
713 if (!game::IsInWilderness()) {
714 for (int c = 0; c < GetSquaresUnder(); ++c) OldSquareUnder[c] = GetLSquareUnder(c);
716 Remove();
717 PutTo(MoveTo);
718 if (!TeleportMove) {
719 /* Multitiled creatures should behave differently, maybe? */
720 if (Run) {
721 int ED = GetSquareUnder()->GetEntryDifficulty();
722 EditAP(-GetMoveAPRequirement(ED) >> 1);
723 EditNP(-24 * ED);
724 EditExperience(AGILITY, 125, ED << 7);
725 int Base = 10000;
726 if (IsPlayer()) {
727 switch (GetHungerState()) {
728 case SATIATED: Base = 11000; break;
729 case BLOATED: Base = 12500; break;
730 case OVER_FED: Base = 15000; break;
733 EditStamina(-Base / Max(GetAttribute(LEG_STRENGTH), 1), true);
734 } else {
735 int ED = GetSquareUnder()->GetEntryDifficulty();
736 EditAP(-GetMoveAPRequirement(ED));
737 EditNP(-12 * ED);
738 EditExperience(AGILITY, 75, ED << 7);
741 if (IsPlayer()) ShowNewPosInfo();
742 if (IsPlayer() && !game::IsInWilderness()) GetStackUnder()->SetSteppedOn(true);
743 if (!game::IsInWilderness()) SignalStepFrom(OldSquareUnder);
744 } else {
745 if (IsPlayer()) {
746 cchar *CrawlVerb = StateIsActivated(LEVITATION) ? "float" : "crawl";
747 ADD_MESSAGE("You try very hard to %s forward. But your load is too heavy.", CrawlVerb);
749 EditAP(-1000);
754 void character::GetAICommand () {
755 SeekLeader(GetLeader());
756 if (FollowLeader(GetLeader())) return;
757 if (CheckForEnemies(true, true, true)) return;
758 if (CheckForUsefulItemsOnGround()) return;
759 if (CheckForDoors()) return;
760 if (CheckSadism()) return;
761 if (MoveRandomly()) return;
762 EditAP(-1000);
766 truth character::MoveTowardsTarget (truth Run) {
767 v2 MoveTo[3];
768 v2 TPos;
769 v2 Pos = GetPos();
771 if (!Route.empty()) {
772 TPos = Route.back();
773 Route.pop_back();
774 } else TPos = GoingTo;
776 MoveTo[0] = v2(0, 0);
777 MoveTo[1] = v2(0, 0);
778 MoveTo[2] = v2(0, 0);
780 if (TPos.X < Pos.X) {
781 if (TPos.Y < Pos.Y) {
782 MoveTo[0] = v2(-1, -1);
783 MoveTo[1] = v2(-1, 0);
784 MoveTo[2] = v2( 0, -1);
785 } else if (TPos.Y == Pos.Y) {
786 MoveTo[0] = v2(-1, 0);
787 MoveTo[1] = v2(-1, -1);
788 MoveTo[2] = v2(-1, 1);
789 } else if (TPos.Y > Pos.Y) {
790 MoveTo[0] = v2(-1, 1);
791 MoveTo[1] = v2(-1, 0);
792 MoveTo[2] = v2( 0, 1);
794 } else if (TPos.X == Pos.X) {
795 if (TPos.Y < Pos.Y) {
796 MoveTo[0] = v2( 0, -1);
797 MoveTo[1] = v2(-1, -1);
798 MoveTo[2] = v2( 1, -1);
799 } else if (TPos.Y == Pos.Y) {
800 TerminateGoingTo();
801 return false;
802 } else if (TPos.Y > Pos.Y) {
803 MoveTo[0] = v2( 0, 1);
804 MoveTo[1] = v2(-1, 1);
805 MoveTo[2] = v2( 1, 1);
807 } else if (TPos.X > Pos.X) {
808 if (TPos.Y < Pos.Y) {
809 MoveTo[0] = v2(1, -1);
810 MoveTo[1] = v2(1, 0);
811 MoveTo[2] = v2(0, -1);
812 } else if (TPos.Y == Pos.Y) {
813 MoveTo[0] = v2(1, 0);
814 MoveTo[1] = v2(1, -1);
815 MoveTo[2] = v2(1, 1);
816 } else if (TPos.Y > Pos.Y) {
817 MoveTo[0] = v2(1, 1);
818 MoveTo[1] = v2(1, 0);
819 MoveTo[2] = v2(0, 1);
823 v2 ModifiedMoveTo = ApplyStateModification(MoveTo[0]);
825 if (TryMove(ModifiedMoveTo, true, Run)) return true;
827 int L = (Pos-TPos).GetManhattanLength();
829 if (RAND()&1) Swap(MoveTo[1], MoveTo[2]);
831 if (Pos.IsAdjacent(TPos)) {
832 TerminateGoingTo();
833 return false;
836 if ((Pos+MoveTo[1]-TPos).GetManhattanLength() <= L && TryMove(ApplyStateModification(MoveTo[1]), true, Run)) return true;
837 if ((Pos+MoveTo[2]-TPos).GetManhattanLength() <= L && TryMove(ApplyStateModification(MoveTo[2]), true, Run)) return true;
838 Illegal.insert(Pos+ModifiedMoveTo);
839 if (CreateRoute()) return true;
840 return false;
844 int character::CalculateNewSquaresUnder (lsquare **NewSquare, v2 Pos) const {
845 if (GetLevel()->IsValidPos(Pos)) {
846 *NewSquare = GetNearLSquare(Pos);
847 return 1;
849 return 0;
853 truth character::TryMove (v2 MoveVector, truth Important, truth Run) {
854 lsquare *MoveToSquare[MAX_SQUARES_UNDER];
855 character *Pet[MAX_SQUARES_UNDER];
856 character *Neutral[MAX_SQUARES_UNDER];
857 character *Hostile[MAX_SQUARES_UNDER];
858 v2 PetPos[MAX_SQUARES_UNDER];
859 v2 NeutralPos[MAX_SQUARES_UNDER];
860 v2 HostilePos[MAX_SQUARES_UNDER];
861 v2 MoveTo = GetPos()+MoveVector;
862 int Direction = game::GetDirectionForVector(MoveVector);
863 if (Direction == DIR_ERROR) ABORT("Direction fault.");
864 if (!game::IsInWilderness()) {
865 int Squares = CalculateNewSquaresUnder(MoveToSquare, MoveTo);
866 if (Squares) {
867 int Pets = 0;
868 int Neutrals = 0;
869 int Hostiles = 0;
870 for (int c = 0; c < Squares; ++c) {
871 character* Char = MoveToSquare[c]->GetCharacter();
872 if (Char && Char != this) {
873 v2 Pos = MoveToSquare[c]->GetPos();
874 if (IsAlly(Char)) {
875 Pet[Pets] = Char;
876 PetPos[Pets++] = Pos;
877 } else if (Char->GetRelation(this) != HOSTILE) {
878 Neutral[Neutrals] = Char;
879 NeutralPos[Neutrals++] = Pos;
880 } else {
881 Hostile[Hostiles] = Char;
882 HostilePos[Hostiles++] = Pos;
887 if (Hostiles == 1) return Hit(Hostile[0], HostilePos[0], Direction);
888 if (Hostiles) {
889 int Index = RAND() % Hostiles;
890 return Hit(Hostile[Index], HostilePos[Index], Direction);
893 if (Neutrals == 1) {
894 if (!IsPlayer() && !Pets && Important && CanMoveOn(MoveToSquare[0]))
895 return HandleCharacterBlockingTheWay(Neutral[0], NeutralPos[0], Direction);
896 else
897 return IsPlayer() && Hit(Neutral[0], NeutralPos[0], Direction);
898 } else if (Neutrals) {
899 if (IsPlayer()) {
900 int Index = RAND() % Neutrals;
901 return Hit(Neutral[Index], NeutralPos[Index], Direction);
903 return false;
906 if (!IsPlayer()) {
907 for (int c = 0; c < Squares; ++c) if (MoveToSquare[c]->IsScary(this)) return false;
910 if (Pets == 1) {
911 if (IsPlayer() && !ivanconfig::GetBeNice() && Pet[0]->IsMasochist() && HasSadistAttackMode() &&
912 game::TruthQuestion("Do you want to punish " + Pet[0]->GetObjectPronoun() + "? [y/N]"))
913 return Hit(Pet[0], PetPos[0], Direction, SADIST_HIT);
914 else
915 return (Important && (CanMoveOn(MoveToSquare[0]) ||
916 (IsPlayer() && game::GoThroughWallsCheatIsActive())) && Displace(Pet[0]));
917 } else if (Pets) return false;
919 if ((CanMove() && CanMoveOn(MoveToSquare[0])) || ((game::GoThroughWallsCheatIsActive() && IsPlayer()))) {
920 Move(MoveTo, false, Run);
921 if (IsEnabled() && GetPos() == GoingTo) TerminateGoingTo();
922 return true;
923 } else {
924 for (int c = 0; c < Squares; ++c) {
925 olterrain *Terrain = MoveToSquare[c]->GetOLTerrain();
926 if (Terrain && Terrain->CanBeOpened()) {
927 if (CanOpen()) {
928 if (Terrain->IsLocked()) {
929 if (IsPlayer()) {
930 /*k8*/
931 if (ivanconfig::GetKickDownDoors()) {
932 Kick(MoveToSquare[c], Direction);
933 return true;
935 /*k8*/
936 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 */
937 return false;
938 } else if (Important && CheckKick()) {
939 room *Room = MoveToSquare[c]->GetRoom();
940 if (!Room || Room->AllowKick(this, MoveToSquare[c])) {
941 int HP = Terrain->GetHP();
942 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s kicks %s.", CHAR_NAME(DEFINITE), Terrain->CHAR_NAME(DEFINITE));
943 Kick(MoveToSquare[c], Direction);
944 olterrain *NewTerrain = MoveToSquare[c]->GetOLTerrain();
945 if (NewTerrain == Terrain && Terrain->GetHP() == HP) { // BUG!
946 Illegal.insert(MoveTo);
947 CreateRoute();
949 return true;
952 } else { /* if (Terrain->IsLocked()) */
953 /*if(!IsPlayer() || game::TruthQuestion(CONST_S("Do you want to open ") + Terrain->GetName(DEFINITE) + "? [y/N]", false, game::GetMoveCommandKeyBetweenPoints(PLAYER->GetPos(), MoveToSquare[0]->GetPos()))) return MoveToSquare[c]->Open(this);*/
954 /* Non-players always try to open it */
955 if (!IsPlayer()) return MoveToSquare[c]->Open(this);
956 if (game::TruthQuestion(CONST_S("Do you want to ")+(ivanconfig::GetKickDownDoors()?"kick ":"open ")+
957 Terrain->GetName(DEFINITE)+"? [y/N]", false, game::GetMoveCommandKeyBetweenPoints(PLAYER->GetPos(),
958 MoveToSquare[0]->GetPos()))) {
959 if (ivanconfig::GetKickDownDoors()) {
960 Kick(MoveToSquare[c], Direction);
961 return true;
963 return MoveToSquare[c]->Open(this);
965 return false;
966 } /* if (Terrain->IsLocked()) */
967 } else { /* if (CanOpen()) */
968 if (IsPlayer()) {
969 ADD_MESSAGE("This monster type cannot open doors.");
970 return false;
972 if (Important) {
973 Illegal.insert(MoveTo);
974 return CreateRoute();
976 } /* if (CanOpen()) */
977 } /* if (Terrain && Terrain->CanBeOpened()) */
978 } /* for */
979 } /* if */
980 return false;
981 } else {
982 if (IsPlayer() && !IsStuck() && GetLevel()->IsOnGround() && game::TruthQuestion(CONST_S("Do you want to leave ")+game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex())+"? [y/N]")) {
983 if (HasPetrussNut() && !HasGoldenEagleShirt()) {
984 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!"));
985 game::GetCurrentArea()->SendNewDrawRequest();
986 game::DrawEverything();
987 ShowAdventureInfo();
988 festring Msg = CONST_S("killed Petrus and became the Avatar of Chaos");
989 PLAYER->AddScoreEntry(Msg, 3, false);
990 game::End(Msg);
991 return true;
993 if (game::TryTravel(WORLD_MAP, WORLD_MAP, game::GetCurrentDungeonIndex())) return true;
995 return false;
997 } else {
998 /** No multitile support */
999 if (CanMove() && GetArea()->IsValidPos(MoveTo) && (CanMoveOn(GetNearWSquare(MoveTo)) || game::GoThroughWallsCheatIsActive())) {
1000 if (!game::GoThroughWallsCheatIsActive()) {
1001 charactervector &V = game::GetWorldMap()->GetPlayerGroup();
1002 truth Discard = false;
1003 for (uInt c = 0; c < V.size(); ++c) {
1004 if (!V[c]->CanMoveOn(GetNearWSquare(MoveTo))) {
1005 if (!Discard) {
1006 ADD_MESSAGE("One or more of your team members cannot cross this terrain.");
1007 if (!game::TruthQuestion("Discard them? [y/N]")) return false;
1008 Discard = true;
1010 if (Discard) delete V[c];
1011 V.erase(V.begin() + c--);
1015 Move(MoveTo, false);
1016 return true;
1017 } else {
1018 return false;
1024 void character::CreateCorpse (lsquare *Square) {
1025 if (!BodyPartsDisappearWhenSevered() && !game::AllBodyPartsVanish()) {
1026 corpse *Corpse = corpse::Spawn(0, NO_MATERIALS);
1027 Corpse->SetDeceased(this);
1028 Square->AddItem(Corpse);
1029 Disable();
1030 } else {
1031 SendToHell();
1036 void character::Die (ccharacter *Killer, cfestring &Msg, uLong DeathFlags) {
1037 /* Note: This function musn't delete any objects, since one of these may be
1038 the one currently processed by pool::Be()! */
1039 if (!IsEnabled()) return;
1040 game::mActor = Killer;
1041 game::RunOnCharEvent(this, CONST_S("die"));
1042 game::mActor = 0;
1043 RemoveTraps();
1044 if (IsPlayer()) {
1045 ADD_MESSAGE("You die.");
1046 game::DrawEverything();
1047 if (game::TruthQuestion(CONST_S("Do you want to save screenshot? [y/n]"), REQUIRES_ANSWER)) {
1048 festring dir = inputfile::GetMyDir()+"/DeathShots";
1049 #ifndef WIN32
1050 mkdir(dir.CStr(), 0755);
1051 #else
1052 mkdir(dir.CStr());
1053 #endif
1054 festring timestr;
1055 time_t t = time(NULL);
1056 struct tm *ts = localtime(&t);
1057 if (ts) {
1058 timestr << (int)(ts->tm_year%100);
1059 int t = ts->tm_mon+1;
1060 if (t < 10) timestr << '0'; timestr << t;
1061 t = ts->tm_mday; if (t < 10) timestr << '0'; timestr << t;
1062 timestr << '_';
1063 t = ts->tm_hour; if (t < 10) timestr << '0'; timestr << t;
1064 t = ts->tm_min; if (t < 10) timestr << '0'; timestr << t;
1065 t = ts->tm_sec; if (t < 10) timestr << '0'; timestr << t;
1066 } else {
1067 timestr = "heh";
1069 #if defined(HAVE_IMLIB2) || defined(HAVE_LIBPNG)
1070 festring ext = ".png";
1071 #else
1072 festring ext = ".bmp";
1073 #endif
1074 festring fname = dir+"/deathshot_"+timestr;
1075 if (inputfile::fileExists(fname+ext)) {
1076 for (int f = 0; f < 1000; f++) {
1077 char buf[16];
1078 sprintf(buf, "%03d", f);
1079 festring fn = fname+buf;
1080 if (!inputfile::fileExists(fn+ext)) {
1081 fname = fn;
1082 break;
1086 fname << ext;
1087 fprintf(stderr, "deathshot: %s\n", fname.CStr());
1088 #if defined(HAVE_IMLIB2) || defined(HAVE_LIBPNG)
1089 DOUBLE_BUFFER->SavePNG(fname);
1090 #else
1091 DOUBLE_BUFFER->SaveBMP(fname);
1092 #endif
1094 if (game::WizardModeIsActive()) {
1095 game::DrawEverything();
1096 if (!game::TruthQuestion(CONST_S("Do you want to do this, cheater? [y/n]"), REQUIRES_ANSWER)) {
1097 RestoreBodyParts();
1098 ResetSpoiling();
1099 RestoreHP();
1100 RestoreStamina();
1101 ResetStates();
1102 SetNP(SATIATED_LEVEL);
1103 SendNewDrawRequest();
1104 return;
1107 } else if (CanBeSeenByPlayer() && !(DeathFlags & DISALLOW_MSG)) {
1108 ProcessAndAddMessage(GetDeathMessage());
1109 } else if (DeathFlags & FORCE_MSG) {
1110 ADD_MESSAGE("You sense the death of something.");
1113 if (!(DeathFlags & FORBID_REINCARNATION)) {
1114 if (StateIsActivated(LIFE_SAVED) && CanMoveOn(!game::IsInWilderness() ? GetSquareUnder() : PLAYER->GetSquareUnder())) {
1115 SaveLife();
1116 return;
1118 if (SpecialSaveLife()) return;
1119 } else if (StateIsActivated(LIFE_SAVED)) {
1120 RemoveLifeSavers();
1123 Flags |= C_IN_NO_MSG_MODE;
1124 character *Ghost = 0;
1125 if (IsPlayer()) {
1126 game::RemoveSaves();
1127 if (!game::IsInWilderness()) {
1128 Ghost = game::CreateGhost();
1129 Ghost->Disable();
1133 square *SquareUnder[MAX_SQUARES_UNDER];
1134 memset(SquareUnder, 0, sizeof(SquareUnder));
1135 Disable();
1136 if (IsPlayer() || !game::IsInWilderness()) {
1137 for (int c = 0; c < SquaresUnder; ++c) SquareUnder[c] = GetSquareUnder(c);
1138 Remove();
1139 } else {
1140 charactervector& V = game::GetWorldMap()->GetPlayerGroup();
1141 V.erase(std::find(V.begin(), V.end(), this));
1143 //lsquare **LSquareUnder = reinterpret_cast<lsquare **>(SquareUnder); /* warning; wtf? */
1144 lsquare *LSquareUnder[MAX_SQUARES_UNDER];
1145 memcpy(LSquareUnder, SquareUnder, sizeof(SquareUnder));
1147 if (!game::IsInWilderness()) {
1148 if (!StateIsActivated(POLYMORPHED)) {
1149 if (!IsPlayer() && !IsTemporary() && !Msg.IsEmpty()) game::SignalDeath(this, Killer, Msg);
1150 if (!(DeathFlags & DISALLOW_CORPSE)) CreateCorpse(LSquareUnder[0]); else SendToHell();
1151 } else {
1152 if (!IsPlayer() && !IsTemporary() && !Msg.IsEmpty()) game::SignalDeath(GetPolymorphBackup(), Killer, Msg);
1153 GetPolymorphBackup()->CreateCorpse(LSquareUnder[0]);
1154 GetPolymorphBackup()->Flags &= ~C_POLYMORPHED;
1155 SetPolymorphBackup(0);
1156 SendToHell();
1158 } else {
1159 if (!IsPlayer() && !IsTemporary() && !Msg.IsEmpty()) game::SignalDeath(this, Killer, Msg);
1160 SendToHell();
1163 if (IsPlayer()) {
1164 if (!game::IsInWilderness()) {
1165 for (int c = 0; c < GetSquaresUnder(); ++c) LSquareUnder[c]->SetTemporaryEmitation(GetEmitation());
1167 ShowAdventureInfo();
1168 if (!game::IsInWilderness()) {
1169 for(int c = 0; c < GetSquaresUnder(); ++c) LSquareUnder[c]->SetTemporaryEmitation(0);
1173 if (!game::IsInWilderness()) {
1174 if (GetSquaresUnder() == 1) {
1175 stack *StackUnder = LSquareUnder[0]->GetStack();
1176 GetStack()->MoveItemsTo(StackUnder);
1177 doforbodypartswithparam<stack*>()(this, &bodypart::DropEquipment, StackUnder);
1178 } else {
1179 while (GetStack()->GetItems()) GetStack()->GetBottom()->MoveTo(LSquareUnder[RAND_N(GetSquaresUnder())]->GetStack());
1180 for (int c = 0; c < BodyParts; ++c) {
1181 bodypart *BodyPart = GetBodyPart(c);
1182 if (BodyPart) BodyPart->DropEquipment(LSquareUnder[RAND_N(GetSquaresUnder())]->GetStack());
1187 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(0);
1189 Flags &= ~C_IN_NO_MSG_MODE;
1191 if (IsPlayer()) {
1192 AddScoreEntry(Msg);
1193 if (!game::IsInWilderness()) {
1194 Ghost->PutTo(LSquareUnder[0]->GetPos());
1195 Ghost->Enable();
1196 game::CreateBone();
1198 game::TextScreen(CONST_S("Unfortunately you died."), ZERO_V2, WHITE, true, true, &game::ShowDeathSmiley);
1199 game::End(Msg);
1204 void character::AddMissMessage (ccharacter *Enemy) const {
1205 festring Msg;
1206 if (Enemy->IsPlayer()) Msg = GetDescription(DEFINITE)+" misses you!";
1207 else if (IsPlayer()) Msg = CONST_S("You miss ")+Enemy->GetDescription(DEFINITE)+'!';
1208 else if (CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer()) Msg = GetDescription(DEFINITE)+" misses "+Enemy->GetDescription(DEFINITE)+'!';
1209 else return;
1210 ADD_MESSAGE("%s", Msg.CStr());
1214 void character::AddBlockMessage (ccharacter *Enemy, citem *Blocker, cfestring &HitNoun, truth Partial) const {
1215 festring Msg;
1216 festring BlockVerb = (Partial ? " to partially block the " : " to block the ")+HitNoun;
1217 if (IsPlayer()) {
1218 Msg << "You manage" << BlockVerb << " with your " << Blocker->GetName(UNARTICLED) << '!';
1219 } else if (Enemy->IsPlayer() || Enemy->CanBeSeenByPlayer()) {
1220 if (CanBeSeenByPlayer())
1221 Msg << GetName(DEFINITE) << " manages" << BlockVerb << " with " << GetPossessivePronoun() << ' ' << Blocker->GetName(UNARTICLED) << '!';
1222 else
1223 Msg << "Something manages" << BlockVerb << " with something!";
1224 } else {
1225 return;
1227 ADD_MESSAGE("%s", Msg.CStr());
1231 void character::AddPrimitiveHitMessage (ccharacter *Enemy, cfestring &FirstPersonHitVerb,
1232 cfestring &ThirdPersonHitVerb, int BodyPart) const
1234 festring Msg;
1235 festring BodyPartDescription;
1236 if (BodyPart && (Enemy->CanBeSeenByPlayer() || Enemy->IsPlayer()))
1237 BodyPartDescription << " in the " << Enemy->GetBodyPartName(BodyPart);
1238 if (Enemy->IsPlayer())
1239 Msg << GetDescription(DEFINITE) << ' ' << ThirdPersonHitVerb << " you" << BodyPartDescription << '!';
1240 else if (IsPlayer())
1241 Msg << "You " << FirstPersonHitVerb << ' ' << Enemy->GetDescription(DEFINITE) << BodyPartDescription << '!';
1242 else if (CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer())
1243 Msg << GetDescription(DEFINITE) << ' ' << ThirdPersonHitVerb << ' ' << Enemy->GetDescription(DEFINITE) + BodyPartDescription << '!';
1244 else
1245 return;
1246 ADD_MESSAGE("%s", Msg.CStr());
1250 cchar *const HitVerb[] = { "strike", "slash", "stab" };
1251 cchar *const HitVerb3rdPersonEnd[] = { "s", "es", "s" };
1254 void character::AddWeaponHitMessage (ccharacter *Enemy, citem *Weapon, int BodyPart, truth Critical) const {
1255 festring Msg;
1256 festring BodyPartDescription;
1258 if (BodyPart && (Enemy->CanBeSeenByPlayer() || Enemy->IsPlayer()))
1259 BodyPartDescription << " in the " << Enemy->GetBodyPartName(BodyPart);
1261 int FittingTypes = 0;
1262 int DamageFlags = Weapon->GetDamageFlags();
1263 int DamageType = 0;
1265 for (int c = 0; c < DAMAGE_TYPES; ++c) {
1266 if (1 << c & DamageFlags) {
1267 if (!FittingTypes || !RAND_N(FittingTypes+1)) DamageType = c;
1268 ++FittingTypes;
1272 if (!FittingTypes) ABORT("No damage flags specified for %s!", Weapon->CHAR_NAME(UNARTICLED));
1274 festring NewHitVerb = Critical ? " critically " : " ";
1275 NewHitVerb << HitVerb[DamageType];
1276 cchar *const E = HitVerb3rdPersonEnd[DamageType];
1278 if (Enemy->IsPlayer()) {
1279 Msg << GetDescription(DEFINITE) << NewHitVerb << E << " you" << BodyPartDescription;
1280 if (CanBeSeenByPlayer()) Msg << " with " << GetPossessivePronoun() << ' ' << Weapon->GetName(UNARTICLED);
1281 Msg << '!';
1282 } else if (IsPlayer()) {
1283 Msg << "You" << NewHitVerb << ' ' << Enemy->GetDescription(DEFINITE) << BodyPartDescription << '!';
1284 } else if(CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer()) {
1285 Msg << GetDescription(DEFINITE) << NewHitVerb << E << ' ' << Enemy->GetDescription(DEFINITE) << BodyPartDescription;
1286 if (CanBeSeenByPlayer()) Msg << " with " << GetPossessivePronoun() << ' ' << Weapon->GetName(UNARTICLED);
1287 Msg << '!';
1288 } else {
1289 return;
1291 ADD_MESSAGE("%s", Msg.CStr());
1295 item *character::GeneralFindItem (ItemCheckerCB chk) const {
1296 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) {
1297 item *it = *i;
1298 if (it && chk(it)) return it;
1300 return 0;
1304 static truth isElpuriHead (item *i) { return i->IsHeadOfElpuri(); }
1305 truth character::HasHeadOfElpuri () const {
1306 if (GeneralFindItem(::isElpuriHead)) return true;
1307 return combineequipmentpredicates()(this, &item::IsHeadOfElpuri, 1);
1311 static truth isPetrussNut (item *i) { return i->IsPetrussNut(); }
1312 truth character::HasPetrussNut () const {
1313 if (GeneralFindItem(::isPetrussNut)) return true;
1314 return combineequipmentpredicates()(this, &item::IsPetrussNut, 1);
1318 static truth isGoldenEagleShirt (item *i) { return i->IsGoldenEagleShirt(); }
1319 truth character::HasGoldenEagleShirt () const {
1320 if (GeneralFindItem(::isGoldenEagleShirt)) return true;
1321 return combineequipmentpredicates()(this, &item::IsGoldenEagleShirt, 1);
1325 int character::GeneralRemoveItem (ItemCheckerCB chk, truth allItems) {
1326 truth done;
1327 int cnt = 0;
1328 // inventory
1329 do {
1330 done = true;
1331 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) {
1332 item *Item = *i;
1333 if (Item && chk(Item)) {
1334 Item->RemoveFromSlot();
1335 Item->SendToHell();
1336 cnt++;
1337 if (!allItems) return cnt;
1338 done = false;
1339 break;
1342 } while (!done);
1343 // equipments
1344 do {
1345 done = true;
1346 for (int c = 0; c < GetEquipments(); ++c) {
1347 item *Item = GetEquipment(c);
1348 if (Item && chk(Item)) {
1349 Item->RemoveFromSlot();
1350 Item->SendToHell();
1351 cnt++;
1352 if (!allItems) return cnt;
1353 done = false;
1354 break;
1357 } while (!done);
1358 return cnt;
1362 static truth isEncryptedScroll (item *i) { return i->IsEncryptedScroll(); }
1363 truth character::RemoveEncryptedScroll () { return GeneralRemoveItem(::isEncryptedScroll) != 0; }
1366 static truth isMondedrPass (item *i) { return i->IsMondedrPass(); }
1367 truth character::RemoveMondedrPass () { return GeneralRemoveItem(::isMondedrPass) != 0; }
1370 static truth isRingOfThieves (item *i) { return i->IsRingOfThieves(); }
1371 truth character::RemoveRingOfThieves () { return GeneralRemoveItem(::isRingOfThieves) != 0; }
1374 truth character::ReadItem (item *ToBeRead) {
1375 if (!ToBeRead->CanBeRead(this)) {
1376 if (IsPlayer()) ADD_MESSAGE("You can't read this.");
1377 return false;
1379 if (!GetLSquareUnder()->IsDark() || game::GetSeeWholeMapCheatMode()) {
1380 if (StateIsActivated(CONFUSED) && !(RAND()&7)) {
1381 if (!ToBeRead->IsDestroyable(this)) {
1382 ADD_MESSAGE("You read some words of %s and understand exactly nothing.", ToBeRead->CHAR_NAME(DEFINITE));
1383 } else {
1384 ADD_MESSAGE("%s is very confusing. Or perhaps you are just too confused?", ToBeRead->CHAR_NAME(DEFINITE));
1385 ActivateRandomState(SRC_CONFUSE_READ, 1000+RAND()%1500);
1386 ToBeRead->RemoveFromSlot();
1387 ToBeRead->SendToHell();
1389 EditAP(-1000);
1390 return true;
1392 if (ToBeRead->Read(this)) {
1393 if (!game::WizardModeIsActive()) {
1394 /* This AP is used to take the stuff out of backpack */
1395 DexterityAction(5);
1397 return true;
1399 return false;
1401 if (IsPlayer()) ADD_MESSAGE("It's too dark here to read.");
1402 return false;
1406 void character::CalculateBurdenState () {
1407 int OldBurdenState = BurdenState;
1408 sLong SumOfMasses = GetCarriedWeight();
1409 sLong CarryingStrengthUnits = sLong(GetCarryingStrength())*2500;
1410 if (SumOfMasses > (CarryingStrengthUnits << 1) + CarryingStrengthUnits) BurdenState = OVER_LOADED;
1411 else if (SumOfMasses > CarryingStrengthUnits << 1) BurdenState = STRESSED;
1412 else if (SumOfMasses > CarryingStrengthUnits) BurdenState = BURDENED;
1413 else BurdenState = UNBURDENED;
1414 if (!IsInitializing() && BurdenState != OldBurdenState) CalculateBattleInfo();
1418 void character::Save (outputfile &SaveFile) const {
1419 SaveFile << (uShort)GetType();
1420 Stack->Save(SaveFile);
1421 SaveFile << ID;
1422 for (int c = 0; c < BASE_ATTRIBUTES; ++c) SaveFile << BaseExperience[c];
1424 SaveFile << ExpModifierMap;
1425 SaveFile << NP << AP << Stamina << GenerationDanger << ScienceTalks << CounterToMindWormHatch;
1426 SaveFile << TemporaryState << EquipmentState << Money << GoingTo << RegenerationCounter << Route << Illegal;
1427 SaveFile.Put(!!IsEnabled());
1428 SaveFile << HomeData << BlocksSinceLastTurn << CommandFlags;
1429 SaveFile << WarnFlags << (uShort)Flags;
1431 for (int c = 0; c < BodyParts; ++c) SaveFile << BodyPartSlot[c] << OriginalBodyPartID[c];
1433 SaveLinkedList(SaveFile, TrapData);
1434 SaveFile << Action;
1436 for (int c = 0; c < STATES; ++c) SaveFile << TemporaryStateCounter[c];
1438 if (GetTeam()) {
1439 SaveFile.Put(true);
1440 SaveFile << Team->GetID(); // uLong
1441 } else {
1442 SaveFile.Put(false);
1445 if (GetTeam() && GetTeam()->GetLeader() == this) SaveFile.Put(true); else SaveFile.Put(false);
1447 SaveFile << AssignedName << PolymorphBackup;
1449 for (int c = 0; c < AllowedWeaponSkillCategories; ++c) SaveFile << CWeaponSkill[c];
1451 SaveFile << (uShort)GetConfig();
1455 void character::Load (inputfile &SaveFile) {
1456 LoadSquaresUnder();
1457 Stack->Load(SaveFile);
1458 SaveFile >> ID;
1459 game::AddCharacterID(this, ID);
1461 for (int c = 0; c < BASE_ATTRIBUTES; ++c) SaveFile >> BaseExperience[c];
1463 SaveFile >> ExpModifierMap;
1464 SaveFile >> NP >> AP >> Stamina >> GenerationDanger >> ScienceTalks >> CounterToMindWormHatch;
1465 SaveFile >> TemporaryState >> EquipmentState >> Money >> GoingTo >> RegenerationCounter >> Route >> Illegal;
1467 if (!SaveFile.Get()) Disable();
1469 SaveFile >> HomeData >> BlocksSinceLastTurn >> CommandFlags;
1470 SaveFile >> WarnFlags;
1471 WarnFlags &= ~WARNED;
1472 Flags |= ReadType<uShort>(SaveFile) & ~ENTITY_FLAGS;
1474 for (int c = 0; c < BodyParts; ++c) {
1475 SaveFile >> BodyPartSlot[c] >> OriginalBodyPartID[c];
1476 item *BodyPart = *BodyPartSlot[c];
1477 if (BodyPart) BodyPart->Disable();
1480 LoadLinkedList(SaveFile, TrapData);
1481 SaveFile >> Action;
1483 if (Action) Action->SetActor(this);
1485 for (int c = 0; c < STATES; ++c) SaveFile >> TemporaryStateCounter[c];
1487 if (SaveFile.Get()) SetTeam(game::GetTeam(ReadType<uLong>(SaveFile)));
1489 if (SaveFile.Get()) GetTeam()->SetLeader(this);
1491 SaveFile >> AssignedName >> PolymorphBackup;
1493 for (int c = 0; c < AllowedWeaponSkillCategories; ++c) SaveFile >> CWeaponSkill[c];
1495 databasecreator<character>::InstallDataBase(this, ReadType<uShort>(SaveFile));
1497 if (IsEnabled() && !game::IsInWilderness()) {
1498 for (int c = 1; c < GetSquaresUnder(); ++c) GetSquareUnder(c)->SetCharacter(this);
1503 truth character::Engrave (cfestring &What) {
1504 GetLSquareUnder()->Engrave(What);
1505 return true;
1508 truth character::MoveRandomly () {
1509 if (!IsEnabled()) return false;
1510 for (int c = 0; c < 10; ++c) {
1511 v2 ToTry = game::GetMoveVector(RAND()&7);
1512 if (GetLevel()->IsValidPos(GetPos()+ToTry)) {
1513 lsquare *Square = GetNearLSquare(GetPos()+ToTry);
1514 if (!Square->IsDangerous(this) && !Square->IsScary(this) && TryMove(ToTry, false, false)) return true;
1517 return false;
1521 truth character::TestForPickup (item *ToBeTested) const {
1522 if (MakesBurdened(ToBeTested->GetWeight()+GetCarriedWeight())) return false;
1523 return true;
1527 void character::AddScoreEntry (cfestring &Description, double Multiplier, truth AddEndLevel) const {
1528 if (!game::WizardModeIsReallyActive()) {
1529 highscore HScore;
1530 if (!HScore.CheckVersion()) {
1531 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;
1532 HScore.Clear();
1534 festring Desc = game::GetPlayerName();
1535 Desc << ", " << Description;
1536 if (AddEndLevel) Desc << " in "+(game::IsInWilderness() ? "the world map" : game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex()));
1537 HScore.Add(sLong(game::GetScore()*Multiplier), Desc);
1538 HScore.Save();
1543 truth character::CheckDeath (cfestring &Msg, ccharacter *Murderer, uLong DeathFlags) {
1544 if (!IsEnabled()) return true;
1545 if (game::IsSumoWrestling() && IsDead()) {
1546 game::EndSumoWrestling(!!IsPlayer());
1547 return true;
1549 if (DeathFlags & FORCE_DEATH || IsDead()) {
1550 if (Murderer && Murderer->IsPlayer() && GetTeam()->GetKillEvilness()) game::DoEvilDeed(GetTeam()->GetKillEvilness());
1551 festring SpecifierMsg;
1552 int SpecifierParts = 0;
1553 if (GetPolymorphBackup()) {
1554 SpecifierMsg << " polymorphed into ";
1555 id::AddName(SpecifierMsg, INDEFINITE);
1556 ++SpecifierParts;
1558 if (!(DeathFlags & IGNORE_TRAPS) && IsStuck()) {
1559 if (SpecifierParts++) SpecifierMsg << " and";
1560 SpecifierMsg << " caught in " << GetTrapDescription();
1562 if (GetAction() && !(DeathFlags & IGNORE_UNCONSCIOUSNESS && GetAction()->IsUnconsciousness())) {
1563 festring ActionMsg = GetAction()->GetDeathExplanation();
1564 if (!ActionMsg.IsEmpty()) {
1565 if (SpecifierParts > 1) {
1566 SpecifierMsg = ActionMsg << ',' << SpecifierMsg;
1567 } else {
1568 if (SpecifierParts) SpecifierMsg << " and";
1569 SpecifierMsg << ActionMsg;
1571 ++SpecifierParts;
1574 festring NewMsg = Msg;
1575 if (Murderer == this) {
1576 SEARCH_N_REPLACE(NewMsg, "@bkp", CONST_S("by ") + GetPossessivePronoun(false) + " own");
1577 SEARCH_N_REPLACE(NewMsg, "@bk", CONST_S("by ") + GetObjectPronoun(false) + "self");
1578 SEARCH_N_REPLACE(NewMsg, "@k", GetObjectPronoun(false) + "self");
1579 } else {
1580 SEARCH_N_REPLACE(NewMsg, "@bkp", CONST_S("by ") + Murderer->GetName(INDEFINITE) + "'s");
1581 SEARCH_N_REPLACE(NewMsg, "@bk", CONST_S("by ") + Murderer->GetName(INDEFINITE));
1582 SEARCH_N_REPLACE(NewMsg, "@k", CONST_S("by ") + Murderer->GetName(INDEFINITE));
1584 if (SpecifierParts) NewMsg << " while" << SpecifierMsg;
1585 if (IsPlayer() && game::WizardModeIsActive()) ADD_MESSAGE("Death message: %s. Score: %d.", NewMsg.CStr(), game::GetScore());
1586 Die(Murderer, NewMsg, DeathFlags);
1587 return true;
1589 return false;
1593 truth character::CheckStarvationDeath (cfestring &Msg) {
1594 if (GetNP() < 1 && UsesNutrition()) return CheckDeath(Msg, 0, FORCE_DEATH);
1595 return false;
1599 void character::ThrowItem (int Direction, item *ToBeThrown) {
1600 if (Direction > 7) ABORT("Throw in TOO odd direction...");
1601 ToBeThrown->Fly(this, Direction, GetAttribute(ARM_STRENGTH));
1605 void character::HasBeenHitByItem (character *Thrower, item *Thingy, int Damage, double ToHitValue, int Direction) {
1606 if (IsPlayer()) ADD_MESSAGE("%s hits you.", Thingy->CHAR_NAME(DEFINITE));
1607 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s hits %s.", Thingy->CHAR_NAME(DEFINITE), CHAR_NAME(DEFINITE));
1608 int BodyPart = ChooseBodyPartToReceiveHit(ToHitValue, DodgeValue);
1609 int WeaponSkillHits = Thrower ? CalculateWeaponSkillHits(Thrower) : 0;
1610 int DoneDamage = ReceiveBodyPartDamage(Thrower, Damage, PHYSICAL_DAMAGE, BodyPart, Direction);
1611 truth Succeeded = (GetBodyPart(BodyPart) && HitEffect(Thrower, Thingy, Thingy->GetPos(), THROW_ATTACK, BodyPart, Direction, !DoneDamage)) || DoneDamage;
1612 if (Succeeded && Thrower) Thrower->WeaponSkillHit(Thingy, THROW_ATTACK, WeaponSkillHits);
1613 festring DeathMsg = CONST_S("killed by a flying ")+Thingy->GetName(UNARTICLED);
1614 if (CheckDeath(DeathMsg, Thrower)) return;
1615 if (Thrower) {
1616 if (Thrower->CanBeSeenByPlayer())
1617 DeActivateVoluntaryAction(CONST_S("The attack of ")+Thrower->GetName(DEFINITE)+CONST_S(" interrupts you."));
1618 else
1619 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
1620 } else {
1621 DeActivateVoluntaryAction(CONST_S("The hit interrupts you."));
1626 truth character::DodgesFlyingItem (item *Item, double ToHitValue) {
1627 return !Item->EffectIsGood() && RAND() % int(100+ToHitValue/DodgeValue*100) < 100;
1631 void character::GetPlayerCommand () {
1632 command *cmd;
1633 truth HasActed = false;
1634 while (!HasActed) {
1635 game::DrawEverything();
1636 if (game::GetDangerFound()) {
1637 if (game::GetDangerFound() > 500.) {
1638 if (game::GetCausePanicFlag()) {
1639 game::SetCausePanicFlag(false);
1640 BeginTemporaryState(PANIC, 500+RAND_N(500));
1642 game::AskForEscPress(CONST_S("You are horrified by your situation!"));
1643 } else if (ivanconfig::GetWarnAboutDanger()) {
1644 if (game::GetDangerFound() > 50.) game::AskForEscPress(CONST_S("You sense great danger!"));
1645 else game::AskForEscPress(CONST_S("You sense danger!"));
1647 game::SetDangerFound(0);
1649 game::SetIsInGetCommand(true);
1650 int Key = GET_KEY();
1651 game::SetIsInGetCommand(false);
1652 if (Key != '+' && Key != '-' && Key != 'M') msgsystem::ThyMessagesAreNowOld(); // gum
1653 truth ValidKeyPressed = false;
1654 int c;
1655 for (c = 0; c < DIRECTION_COMMAND_KEYS; ++c) {
1656 if (Key == game::GetMoveCommandKey(c)) {
1657 HasActed = TryMove(ApplyStateModification(game::GetMoveVector(c)), true, game::PlayerIsRunning());
1658 ValidKeyPressed = true;
1661 for (c = 1; (cmd = commandsystem::GetCommand(c)); ++c) {
1662 /* k8 */
1663 /* Numpad aliases for most commonly used commands */
1664 if (Key == KEY_DEL && cmd->GetLinkedFunction() == commandsystem::Eat) Key = cmd->GetKey();
1665 if (Key == KEY_INS && cmd->GetLinkedFunction() == commandsystem::PickUp) Key = cmd->GetKey();
1666 if (Key == KEY_PLUS && cmd->GetLinkedFunction() == commandsystem::EquipmentScreen) Key = cmd->GetKey();
1667 /* k8 */
1668 if (Key == cmd->GetKey()) {
1669 if (game::IsInWilderness() && !commandsystem::GetCommand(c)->IsUsableInWilderness())
1670 ADD_MESSAGE("This function cannot be used while in wilderness.");
1671 else if (!game::WizardModeIsActive() && commandsystem::GetCommand(c)->IsWizardModeFunction())
1672 ADD_MESSAGE("Activate wizardmode to use this function.");
1673 else
1674 HasActed = commandsystem::GetCommand(c)->GetLinkedFunction()(this);
1675 ValidKeyPressed = true;
1676 break;
1679 if (!ValidKeyPressed) ADD_MESSAGE("Unknown key. Press '?' for a list of commands.");
1681 game::IncreaseTurn();
1685 void character::Vomit (v2 Pos, int Amount, truth ShowMsg) {
1686 if (!CanVomit()) return;
1687 if (ShowMsg) {
1688 if (IsPlayer()) ADD_MESSAGE("You vomit.");
1689 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s vomits.", CHAR_NAME(DEFINITE));
1691 if (VomittingIsUnhealthy()) {
1692 EditExperience(ARM_STRENGTH, -75, 1 << 9);
1693 EditExperience(LEG_STRENGTH, -75, 1 << 9);
1695 if (IsPlayer()) {
1696 EditNP(-2500-RAND()%2501);
1697 CheckStarvationDeath(CONST_S("vomited himself to death"));
1699 if (StateIsActivated(PARASITIZED) && !(RAND() & 7)) {
1700 if (IsPlayer()) ADD_MESSAGE("You notice a dead broad tapeworm among your former stomach contents.");
1701 DeActivateTemporaryState(PARASITIZED);
1703 if (!game::IsInWilderness()) {
1704 GetNearLSquare(Pos)->ReceiveVomit(this, liquid::Spawn(GetVomitMaterial(), sLong(sqrt(GetBodyVolume())*Amount/1000)));
1709 truth character::Polymorph (character *NewForm, int Counter) {
1710 if (!IsPolymorphable() || (!IsPlayer() && game::IsInWilderness())) {
1711 delete NewForm;
1712 return false;
1714 RemoveTraps();
1715 if (GetAction()) GetAction()->Terminate(false);
1716 NewForm->SetAssignedName("");
1717 if (IsPlayer())
1718 ADD_MESSAGE("Your body glows in a crimson light. You transform into %s!", NewForm->CHAR_NAME(INDEFINITE));
1719 else if (CanBeSeenByPlayer())
1720 ADD_MESSAGE("%s glows in a crimson light and %s transforms into %s!", CHAR_NAME(DEFINITE), GetPersonalPronoun().CStr(), NewForm->CHAR_NAME(INDEFINITE));
1722 Flags |= C_IN_NO_MSG_MODE;
1723 NewForm->Flags |= C_IN_NO_MSG_MODE;
1724 NewForm->ChangeTeam(GetTeam());
1725 NewForm->GenerationDanger = GenerationDanger;
1726 NewForm->mOnEvents = this->mOnEvents;
1728 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(NewForm);
1730 v2 Pos = GetPos();
1731 Remove();
1732 NewForm->PutToOrNear(Pos);
1733 NewForm->SetAssignedName(GetAssignedName());
1734 NewForm->ActivateTemporaryState(POLYMORPHED);
1735 NewForm->SetTemporaryStateCounter(POLYMORPHED, Counter);
1737 if (TemporaryStateIsActivated(POLYMORPHED)) {
1738 NewForm->SetPolymorphBackup(GetPolymorphBackup());
1739 SetPolymorphBackup(0);
1740 SendToHell();
1741 } else {
1742 NewForm->SetPolymorphBackup(this);
1743 Flags |= C_POLYMORPHED;
1744 Disable();
1747 GetStack()->MoveItemsTo(NewForm->GetStack());
1748 NewForm->SetMoney(GetMoney());
1749 DonateEquipmentTo(NewForm);
1750 Flags &= ~C_IN_NO_MSG_MODE;
1751 NewForm->Flags &= ~C_IN_NO_MSG_MODE;
1752 NewForm->CalculateAll();
1754 if (IsPlayer()) {
1755 Flags &= ~C_PLAYER;
1756 game::SetPlayer(NewForm);
1757 game::SendLOSUpdateRequest();
1758 UpdateESPLOS();
1761 NewForm->TestWalkability();
1762 return true;
1766 void character::BeKicked (character *Kicker, item *Boot, bodypart *Leg, v2 HitPos, double KickDamage,
1767 double ToHitValue, int Success, int Direction, truth Critical, truth ForceHit)
1769 //FIXME: other args
1770 game::mActor = Kicker;
1771 game::RunOnCharEvent(this, CONST_S("before_be_kicked"));
1772 game::mActor = 0;
1773 switch (TakeHit(Kicker, Boot, Leg, HitPos, KickDamage, ToHitValue, Success, KICK_ATTACK, Direction, Critical, ForceHit)) {
1774 case HAS_HIT:
1775 case HAS_BLOCKED:
1776 case DID_NO_DAMAGE:
1777 if (IsEnabled() && !CheckBalance(KickDamage)) {
1778 if (IsPlayer()) ADD_MESSAGE("The kick throws you off balance.");
1779 else if (Kicker->IsPlayer()) ADD_MESSAGE("The kick throws %s off balance.", CHAR_DESCRIPTION(DEFINITE));
1780 v2 FallToPos = GetPos()+game::GetMoveVector(Direction);
1781 FallTo(Kicker, FallToPos);
1787 /* Return true if still in balance */
1788 truth character::CheckBalance (double KickDamage) {
1789 return !CanMove() || IsStuck() || !KickDamage || (!IsFlying() && KickDamage*5 < RAND()%GetSize());
1793 void character::FallTo (character *GuiltyGuy, v2 Where) {
1794 EditAP(-500);
1795 lsquare *MoveToSquare[MAX_SQUARES_UNDER];
1796 int Squares = CalculateNewSquaresUnder(MoveToSquare, Where);
1797 if (Squares) {
1798 truth NoRoom = false;
1799 for (int c = 0; c < Squares; ++c) {
1800 olterrain *Terrain = MoveToSquare[c]->GetOLTerrain();
1801 if (Terrain && !CanMoveOn(Terrain)) { NoRoom = true; break; }
1803 if (NoRoom) {
1804 if (HasHead()) {
1805 if (IsPlayer()) ADD_MESSAGE("You hit your head on the wall.");
1806 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s hits %s head on the wall.", CHAR_NAME(DEFINITE), GetPossessivePronoun().CStr());
1808 ReceiveDamage(GuiltyGuy, 1+RAND()%5, PHYSICAL_DAMAGE, HEAD);
1809 CheckDeath(CONST_S("killed by hitting a wall due to being kicked @bk"), GuiltyGuy);
1810 } else {
1811 if (IsFreeForMe(MoveToSquare[0])) Move(Where, true);
1812 // Place code that handles characters bouncing to each other here
1818 truth character::CheckCannibalism (cmaterial *What) const {
1819 return GetTorso()->GetMainMaterial()->IsSameAs(What);
1823 void character::StandIdleAI () {
1824 SeekLeader(GetLeader());
1825 if (CheckForEnemies(true, true, true)) return;
1826 if (CheckForUsefulItemsOnGround()) return;
1827 if (FollowLeader(GetLeader())) return;
1828 if (CheckForDoors()) return;
1829 if (MoveTowardsHomePos()) return;
1830 if (CheckSadism()) return;
1831 EditAP(-1000);
1835 truth character::LoseConsciousness (int Counter, truth HungerFaint) {
1836 if (!AllowUnconsciousness()) return false;
1837 action *Action = GetAction();
1838 if (Action) {
1839 if (HungerFaint && !Action->AllowUnconsciousness()) return false;
1840 if (Action->IsUnconsciousness()) {
1841 static_cast<unconsciousness *>(Action)->RaiseCounterTo(Counter);
1842 return true;
1844 Action->Terminate(false);
1846 if (IsPlayer()) ADD_MESSAGE("You lose consciousness.");
1847 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s loses consciousness.", CHAR_NAME(DEFINITE));
1848 unconsciousness *Unconsciousness = unconsciousness::Spawn(this);
1849 Unconsciousness->SetCounter(Counter);
1850 SetAction(Unconsciousness);
1851 return true;
1855 void character::DeActivateVoluntaryAction (cfestring &Reason) {
1856 if (GetAction() && GetAction()->IsVoluntary()) {
1857 if (IsPlayer()) {
1858 if (Reason.GetSize()) ADD_MESSAGE("%s", Reason.CStr());
1859 if (game::TruthQuestion(CONST_S("Continue ") + GetAction()->GetDescription()+"? [y/N]")) GetAction()->ActivateInDNDMode();
1860 else GetAction()->Terminate(false);
1862 else {
1863 GetAction()->Terminate(false);
1869 void character::ActionAutoTermination () {
1870 if (!GetAction() || !GetAction()->IsVoluntary() || GetAction()->InDNDMode()) return;
1871 v2 Pos = GetPos();
1872 for (int c = 0; c < game::GetTeams(); ++c) {
1873 if (GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE) {
1874 for (std::list<character *>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i) {
1875 character *ch = *i;
1876 if (ch->IsEnabled() && ch->CanBeSeenBy(this, false, true) && (ch->CanMove() || ch->GetPos().IsAdjacent(Pos)) && ch->CanAttack()) {
1877 if (IsPlayer()) {
1878 ADD_MESSAGE("%s seems to be hostile.", ch->CHAR_NAME(DEFINITE));
1879 if (game::TruthQuestion(CONST_S("Continue ")+GetAction()->GetDescription()+"? [y/N]")) GetAction()->ActivateInDNDMode();
1880 else GetAction()->Terminate(false);
1881 } else {
1882 GetAction()->Terminate(false);
1884 return;
1892 truth character::CheckForEnemies (truth CheckDoors, truth CheckGround, truth MayMoveRandomly, truth RunTowardsTarget) {
1893 if (!IsEnabled()) return false;
1894 truth HostileCharsNear = false;
1895 character *NearestChar = 0;
1896 sLong NearestDistance = 0x7FFFFFFF;
1897 v2 Pos = GetPos();
1898 for (int c = 0; c < game::GetTeams(); ++c) {
1899 if (GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE) {
1900 for (std::list<character*>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i) {
1901 character *ch = *i;
1902 if (ch->IsEnabled() && GetAttribute(WISDOM) < ch->GetAttackWisdomLimit()) {
1903 sLong ThisDistance = Max<sLong>(abs(ch->GetPos().X - Pos.X), abs(ch->GetPos().Y - Pos.Y));
1904 if (ThisDistance <= GetLOSRangeSquare()) HostileCharsNear = true;
1905 if ((ThisDistance < NearestDistance || (ThisDistance == NearestDistance && !(RAND() % 3))) &&
1906 ch->CanBeSeenBy(this, false, IsGoingSomeWhere()) &&
1907 (!IsGoingSomeWhere() || HasClearRouteTo(ch->GetPos()))) {
1908 NearestChar = ch;
1909 NearestDistance = ThisDistance;
1916 if (NearestChar) {
1917 if (GetAttribute(INTELLIGENCE) >= 10 || IsSpy()) game::CallForAttention(GetPos(), 100);
1918 if (SpecialEnemySightedReaction(NearestChar)) return true;
1919 if (IsExtraCoward() && !StateIsActivated(PANIC) && NearestChar->GetRelativeDanger(this) >= 0.5) {
1920 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s sees %s.", CHAR_NAME(DEFINITE), NearestChar->CHAR_DESCRIPTION(DEFINITE));
1921 BeginTemporaryState(PANIC, 500+RAND()%500);
1923 if (!IsRetreating()) {
1924 if (CheckGround && NearestDistance > 2 && CheckForUsefulItemsOnGround(false)) return true;
1925 SetGoingTo(NearestChar->GetPos());
1926 } else {
1927 SetGoingTo(Pos-((NearestChar->GetPos()-Pos)<<4));
1929 return MoveTowardsTarget(true);
1930 } else {
1931 character *Leader = GetLeader();
1932 if (Leader == this) Leader = 0;
1933 if (!Leader && IsGoingSomeWhere()) {
1934 if (!MoveTowardsTarget(RunTowardsTarget)) {
1935 TerminateGoingTo();
1936 return false;
1937 } else {
1938 if (!IsEnabled()) return true;
1939 if (GetPos() == GoingTo) TerminateGoingTo();
1940 return true;
1942 } else {
1943 if ((!Leader || (Leader && !IsGoingSomeWhere())) && HostileCharsNear) {
1944 if (CheckDoors && CheckForDoors()) return true;
1945 if (CheckGround && CheckForUsefulItemsOnGround()) return true;
1946 if (MayMoveRandomly && MoveRandomly()) return true; // one has heard that an enemy is near but doesn't know where
1948 return false;
1954 truth character::CheckForDoors () {
1955 if (!CanOpen() || !IsEnabled()) return false;
1956 for (int d = 0; d < GetNeighbourSquares(); ++d) {
1957 lsquare *Square = GetNeighbourLSquare(d);
1958 if (Square && Square->GetOLTerrain() && Square->GetOLTerrain()->Open(this)) return true;
1960 return false;
1964 truth character::CheckForUsefulItemsOnGround (truth CheckFood) {
1965 if (StateIsActivated(PANIC) || !IsEnabled()) return false;
1966 itemvector ItemVector;
1967 GetStackUnder()->FillItemVector(ItemVector);
1968 for (uInt c = 0; c < ItemVector.size(); ++c) {
1969 if (ItemVector[c]->CanBeSeenBy(this) && ItemVector[c]->IsPickable(this)) {
1970 if (!(CommandFlags & DONT_CHANGE_EQUIPMENT) && TryToEquip(ItemVector[c])) return true;
1971 if (CheckFood && UsesNutrition() && !CheckIfSatiated() && TryToConsume(ItemVector[c])) return true;
1972 if ((ItemVector[c])->IsThrowingWeapon() && IsRangedAttacker() && TryToAddToInventory(ItemVector[c])) return true;
1975 return false;
1979 truth character::TryToAddToInventory (item *Item) {
1980 if (!(GetBurdenState() > STRESSED) || !CanUseEquipment() || Item->GetSquaresUnder() != 1) return false;
1981 room *Room = GetRoom();
1982 if (!Room || Room->PickupItem(this, Item, 1)) {
1983 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s picks up %s from the ground.", CHAR_NAME(DEFINITE), Item->CHAR_NAME(INDEFINITE));
1984 Item->MoveTo(GetStack());
1985 DexterityAction(5);
1986 return true;
1988 return false;
1992 truth character::CheckThrowItemOpportunity () {
1993 if (!IsRangedAttacker() || !CanThrow() || !IsHumanoid() || !IsSmall() || !IsEnabled()) return false; // total gum
1994 // Steps:
1995 // (1) - Acquire target as nearest enemy
1996 // (2) - Check that this enemy is in range, and is in appropriate direction
1997 // No friendly fire!
1998 // (3) - check inventory for throwing weapon, select this weapon
1999 // (4) - throw item in direction where the enemy is
2001 //Check the visible area for hostiles
2002 int ThrowDirection = 0;
2003 int TargetFound = 0;
2004 v2 Pos = GetPos();
2005 v2 TestPos;
2006 int RangeMax = GetLOSRange();
2007 int CandidateDirections[7] = {0, 0, 0, 0, 0, 0, 0};
2008 int HostileFound = 0;
2009 item *ToBeThrown = 0;
2010 itemvector ItemVector;
2012 for (int r = 1; r <= RangeMax; ++r) {
2013 for (int dir = 0; dir < 8; ++dir) {
2014 level *Level = GetLevel();
2016 switch (dir) {
2017 case 0: TestPos = v2(Pos.X-r, Pos.Y-r); break;
2018 case 1: TestPos = v2(Pos.X, Pos.Y-r); break;
2019 case 2: TestPos = v2(Pos.X+r, Pos.Y-r); break;
2020 case 3: TestPos = v2(Pos.X-r, Pos.Y); break;
2021 case 4: TestPos = v2(Pos.X+r, Pos.Y); break;
2022 case 5: TestPos = v2(Pos.X-r, Pos.Y+r); break;
2023 case 6: TestPos = v2(Pos.X, Pos.Y+r); break;
2024 case 7: TestPos = v2(Pos.X+r, Pos.Y+r); break;
2026 if (Level->IsValidPos(TestPos)) {
2027 square *TestSquare = GetNearSquare(TestPos);
2028 character *Dude = TestSquare->GetCharacter();
2030 if (Dude && Dude->IsEnabled() && GetRelation(Dude) != HOSTILE && Dude->CanBeSeenBy(this, false, true)) {
2031 CandidateDirections[dir] = BLOCKED;
2033 if (Dude && Dude->IsEnabled() && GetRelation(Dude) == HOSTILE && Dude->CanBeSeenBy(this, false, true) && CandidateDirections[dir] != BLOCKED) {
2034 //then load this candidate position direction into the vector of possible throw directions
2035 CandidateDirections[dir] = SUCCESS;
2036 HostileFound = 1;
2042 if (HostileFound) {
2043 for (int dir = 0; dir < 8; ++dir) {
2044 if (CandidateDirections[dir] == SUCCESS && !TargetFound) {
2045 ThrowDirection = dir;
2046 TargetFound = 1;
2049 if (!TargetFound) return false;
2050 } else {
2051 return false;
2053 //check inventory for throwing weapon
2054 GetStack()->FillItemVector(ItemVector);
2055 for (uInt c = 0; c < ItemVector.size(); ++c) {
2056 if (ItemVector[c]->IsThrowingWeapon()) {
2057 ToBeThrown = ItemVector[c];
2058 break;
2061 if (!ToBeThrown) return false;
2062 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s throws %s.", CHAR_NAME(DEFINITE), ToBeThrown->CHAR_NAME(INDEFINITE));
2063 ThrowItem(ThrowDirection, ToBeThrown);
2064 EditExperience(ARM_STRENGTH, 75, 1 << 8);
2065 EditExperience(DEXTERITY, 75, 1 << 8);
2066 EditExperience(PERCEPTION, 75, 1 << 8);
2067 EditNP(-50);
2068 DexterityAction(5);
2069 TerminateGoingTo();
2070 return true;
2074 truth character::FollowLeader (character *Leader) {
2075 if (!Leader || Leader == this || !IsEnabled()) return false;
2076 if (CommandFlags & FOLLOW_LEADER && Leader->CanBeSeenBy(this) && Leader->SquareUnderCanBeSeenBy(this, true)) {
2077 v2 Distance = GetPos()-GoingTo;
2078 if (abs(Distance.X) <= 2 && abs(Distance.Y) <= 2) return false;
2079 return MoveTowardsTarget(false);
2081 if (IsGoingSomeWhere()) {
2082 if (!MoveTowardsTarget(true)) {
2083 TerminateGoingTo();
2084 return false;
2086 return true;
2088 return false;
2092 void character::SeekLeader (ccharacter *Leader) {
2093 if (Leader && Leader != this) {
2094 if (Leader->CanBeSeenBy(this) && (Leader->SquareUnderCanBeSeenBy(this, true) || !IsGoingSomeWhere())) {
2095 if (CommandFlags & FOLLOW_LEADER) SetGoingTo(Leader->GetPos());
2096 } else if (!IsGoingSomeWhere()) {
2097 team *Team = GetTeam();
2098 for (std::list<character *>::const_iterator i = Team->GetMember().begin(); i != Team->GetMember().end(); ++i) {
2099 character *ch = *i;
2100 if (ch->IsEnabled() && ch->GetID() != GetID() &&
2101 (CommandFlags & FOLLOW_LEADER) == (ch->CommandFlags & FOLLOW_LEADER) && ch->CanBeSeenBy(this)) {
2102 v2 Pos = ch->GetPos();
2103 v2 Distance = GetPos()-Pos;
2104 if (abs(Distance.X) > 2 && abs(Distance.Y) > 2) {
2105 SetGoingTo(Pos);
2106 break;
2115 int character::GetMoveEase () const {
2116 switch (BurdenState) {
2117 case OVER_LOADED:
2118 case STRESSED: return 50;
2119 case BURDENED: return 75;
2120 case UNBURDENED: return 100;
2122 return 666;
2126 int character::GetLOSRange () const {
2127 if (!game::IsInWilderness()) return GetAttribute(PERCEPTION)*GetLevel()->GetLOSModifier()/48;
2128 return 3;
2132 truth character::Displace (character *Who, truth Forced) {
2133 if (GetBurdenState() == OVER_LOADED) {
2134 if (IsPlayer()) {
2135 cchar *CrawlVerb = StateIsActivated(LEVITATION) ? "float" : "crawl";
2136 ADD_MESSAGE("You try very hard to %s forward. But your load is too heavy.", CrawlVerb);
2137 EditAP(-1000);
2138 return true;
2140 return false;
2143 double Danger = GetRelativeDanger(Who);
2144 int PriorityDifference = Limit(GetDisplacePriority()-Who->GetDisplacePriority(), -31, 31);
2146 if (IsPlayer()) ++PriorityDifference;
2147 else if (Who->IsPlayer()) --PriorityDifference;
2149 if (PriorityDifference >= 0) Danger *= 1 << PriorityDifference;
2150 else Danger /= 1 << -PriorityDifference;
2152 if (IsSmall() && Who->IsSmall() &&
2153 (Forced || Danger > 1.0 || !(Who->IsPlayer() || Who->IsBadPath(GetPos()))) &&
2154 !IsStuck() && !Who->IsStuck() && (!Who->GetAction() || Who->GetAction()->TryDisplace()) &&
2155 CanMove() && Who->CanMove() && Who->CanMoveOn(GetLSquareUnder())) {
2156 if (IsPlayer()) ADD_MESSAGE("You displace %s!", Who->CHAR_DESCRIPTION(DEFINITE));
2157 else if (Who->IsPlayer()) ADD_MESSAGE("%s displaces you!", CHAR_DESCRIPTION(DEFINITE));
2158 else if (CanBeSeenByPlayer() || Who->CanBeSeenByPlayer()) ADD_MESSAGE("%s displaces %s!", CHAR_DESCRIPTION(DEFINITE), Who->CHAR_DESCRIPTION(DEFINITE));
2159 lsquare *OldSquareUnder1[MAX_SQUARES_UNDER];
2160 lsquare *OldSquareUnder2[MAX_SQUARES_UNDER];
2161 for (int c = 0; c < GetSquaresUnder(); ++c) OldSquareUnder1[c] = GetLSquareUnder(c);
2162 for (int c = 0; c < Who->GetSquaresUnder(); ++c) OldSquareUnder2[c] = Who->GetLSquareUnder(c);
2163 v2 Pos = GetPos();
2164 v2 WhoPos = Who->GetPos();
2165 Remove();
2166 Who->Remove();
2167 PutTo(WhoPos);
2168 Who->PutTo(Pos);
2169 EditAP(-GetMoveAPRequirement(GetSquareUnder()->GetEntryDifficulty()) - 500);
2170 EditNP(-12*GetSquareUnder()->GetEntryDifficulty());
2171 EditExperience(AGILITY, 75, GetSquareUnder()->GetEntryDifficulty() << 7);
2172 if (IsPlayer()) ShowNewPosInfo();
2173 if (Who->IsPlayer()) Who->ShowNewPosInfo();
2174 SignalStepFrom(OldSquareUnder1);
2175 Who->SignalStepFrom(OldSquareUnder2);
2176 return true;
2177 } else {
2178 if (IsPlayer()) {
2179 ADD_MESSAGE("%s resists!", Who->CHAR_DESCRIPTION(DEFINITE));
2180 EditAP(-1000);
2181 return true;
2183 return false;
2188 void character::SetNP (sLong What) {
2189 int OldState = GetHungerState();
2190 NP = What;
2191 if (IsPlayer()) {
2192 int NewState = GetHungerState();
2193 if (NewState == STARVING && OldState > STARVING) DeActivateVoluntaryAction(CONST_S("You are getting really hungry."));
2194 else if (NewState == VERY_HUNGRY && OldState > VERY_HUNGRY) DeActivateVoluntaryAction(CONST_S("You are getting very hungry."));
2195 else if (NewState == HUNGRY && OldState > HUNGRY) DeActivateVoluntaryAction(CONST_S("You are getting hungry."));
2200 void character::ShowNewPosInfo () const {
2201 msgsystem::EnterBigMessageMode();
2202 v2 Pos = GetPos();
2204 if (ivanconfig::GetAutoCenterMap()) {
2205 game::UpdateCameraX();
2206 game::UpdateCameraY();
2207 } else {
2208 if (Pos.X < game::GetCamera().X+3 || Pos.X >= game::GetCamera().X+game::GetScreenXSize()-3) game::UpdateCameraX();
2209 if (Pos.Y < game::GetCamera().Y+3 || Pos.Y >= game::GetCamera().Y+game::GetScreenYSize()-3) game::UpdateCameraY();
2212 game::SendLOSUpdateRequest();
2213 game::DrawEverythingNoBlit();
2214 UpdateESPLOS();
2216 if (!game::IsInWilderness()) {
2217 if (GetLSquareUnder()->IsDark() && !game::GetSeeWholeMapCheatMode()) ADD_MESSAGE("It's dark in here!");
2219 GetLSquareUnder()->ShowSmokeMessage();
2220 itemvectorvector PileVector;
2221 GetStackUnder()->Pile(PileVector, this, CENTER);
2223 if (PileVector.size()) {
2224 truth Feel = !GetLSquareUnder()->IsTransparent() || GetLSquareUnder()->IsDark();
2225 if (PileVector.size() == 1) {
2226 if (Feel) ADD_MESSAGE("You feel %s lying here.", PileVector[0][0]->GetName(INDEFINITE, PileVector[0].size()).CStr());
2227 else ADD_MESSAGE("%s %s lying here.", PileVector[0][0]->GetName(INDEFINITE, PileVector[0].size()).CStr(), PileVector[0].size() == 1 ? "is" : "are");
2228 } else {
2229 int Items = 0;
2230 for (uInt c = 0; c < PileVector.size(); ++c) {
2231 if ((Items += PileVector[c].size()) > 3) break;
2233 if (Items > 3) {
2234 if (Feel) ADD_MESSAGE("You feel several items lying here.");
2235 else ADD_MESSAGE("Several items are lying here.");
2236 } else if (Items) {
2237 if (Feel) ADD_MESSAGE("You feel a few items lying here.");
2238 else ADD_MESSAGE("A few items are lying here.");
2243 festring SideItems;
2244 GetLSquareUnder()->GetSideItemDescription(SideItems);
2246 if (!SideItems.IsEmpty()) ADD_MESSAGE("There is %s.", SideItems.CStr());
2248 if (GetLSquareUnder()->HasEngravings()) {
2249 if (CanRead()) ADD_MESSAGE("Something has been engraved here: \"%s\"", GetLSquareUnder()->GetEngraved());
2250 else ADD_MESSAGE("Something has been engraved here.");
2254 msgsystem::LeaveBigMessageMode();
2258 void character::Hostility (character *Enemy) {
2259 if (Enemy == this || !Enemy || !Team || !Enemy->Team) return;
2260 if (Enemy->IsMasochist() && GetRelation(Enemy) == FRIEND) return;
2261 if (!IsAlly(Enemy)) {
2262 GetTeam()->Hostility(Enemy->GetTeam());
2263 } else if (IsPlayer() && !Enemy->IsPlayer()) {
2264 // I believe both may be players due to polymorph feature...
2265 if (Enemy->CanBeSeenByPlayer()) ADD_MESSAGE("%s becomes enraged.", Enemy->CHAR_NAME(DEFINITE));
2266 Enemy->ChangeTeam(game::GetTeam(BETRAYED_TEAM));
2271 stack *character::GetGiftStack () const {
2272 if (GetLSquareUnder()->GetRoomIndex() && !GetLSquareUnder()->GetRoom()->AllowDropGifts()) return GetStack();
2273 return GetStackUnder();
2277 truth character::MoveRandomlyInRoom () {
2278 for (int c = 0; c < 10; ++c) {
2279 v2 ToTry = game::GetMoveVector(RAND()&7);
2280 if (GetLevel()->IsValidPos(GetPos()+ToTry)) {
2281 lsquare *Square = GetNearLSquare(GetPos()+ToTry);
2282 if (!Square->IsDangerous(this) && !Square->IsScary(this) &&
2283 (!Square->GetOLTerrain() || !Square->GetOLTerrain()->IsDoor()) &&
2284 TryMove(ToTry, false, false)) return true;
2287 return false;
2291 truth character::IsPassableSquare (int x, int y) const {
2292 area *ca = GetSquareUnder()->GetArea();
2293 if (x < 0 || y < 0 || x >= ca->GetXSize() || y >= ca->GetYSize()) return false;
2294 lsquare *sq = static_cast<lsquare *>(ca->GetSquare(x, y));
2295 return sq && CanMoveOn(sq);
2299 truth character::IsInCorridor () const { return IsInCorridor(GetPos().X, GetPos().Y); }
2301 truth character::IsInCorridor (int x, int y) const {
2302 if (!IsPassableSquare(x, y-1) && !IsPassableSquare(x, y+1)) return true;
2303 if (!IsPassableSquare(x-1, y) && !IsPassableSquare(x+1, y)) return true;
2304 if (IsPassableSquare(x, y-1) && IsPassableSquare(x, y+1) &&
2305 (IsPassableSquare(x-1, y) || IsPassableSquare(x+1, y))) return false;
2306 if (IsPassableSquare(x-1, y) && IsPassableSquare(x+1, y) &&
2307 (IsPassableSquare(x, y-1) || IsPassableSquare(x, y+1))) return false;
2308 if (!IsPassableSquare(x-1, y-1) && !IsPassableSquare(x+1, y-1) &&
2309 !IsPassableSquare(x-1, y+1) && !IsPassableSquare(x+1, y+1)) return true;
2310 return false;
2314 truth character::IsInCorridor (const v2 dir) const {
2315 v2 nxy = GetPos()+dir;
2316 return IsInCorridor(nxy.X, nxy.Y);
2320 void character::GoOn (go *Go, truth FirstStep) {
2321 //fprintf(stderr, "corridor: %s\n", IsInCorridor() ? "tan" : "ona");
2322 if (FirstStep) Go->SetIsWalkingInOpen(!IsInCorridor());
2324 v2 MoveVector = ApplyStateModification(game::GetMoveVector(Go->GetDirection()));
2325 lsquare *MoveToSquare[MAX_SQUARES_UNDER];
2327 int Squares = CalculateNewSquaresUnder(MoveToSquare, GetPos()+MoveVector);
2328 if (!Squares || !CanMoveOn(MoveToSquare[0])) {
2329 Go->Terminate(false);
2330 return;
2333 if (!FirstStep && !Go->IsWalkingInOpen() && !IsInCorridor(MoveVector)) {
2334 Go->Terminate(false);
2335 return;
2338 uInt OldRoomIndex = GetLSquareUnder()->GetRoomIndex();
2339 uInt CurrentRoomIndex = MoveToSquare[0]->GetRoomIndex();
2341 if ((OldRoomIndex && (CurrentRoomIndex != OldRoomIndex)) && !FirstStep) {
2342 Go->Terminate(false);
2343 return;
2346 for (int c = 0; c < Squares; ++c) {
2347 if ((MoveToSquare[c]->GetCharacter() && GetTeam() != MoveToSquare[c]->GetCharacter()->GetTeam()) || MoveToSquare[c]->IsDangerous(this)) {
2348 Go->Terminate(false);
2349 return;
2354 0: up-left
2355 1: up
2356 2: up-right
2357 3: left
2358 4: right
2359 5: down-left
2360 6: down
2361 7: down-right
2362 8: stand still
2364 static const int revDir[8] = { 7, 6, 5, 4, 3, 3, 1, 0 };
2365 static const bool orthoDir[8] = { false, true, false, true, true, false, true, false };
2367 int OKDirectionsCounter = 0, gd = Go->GetDirection(), odc = 0;
2368 for (int d = 0; d < GetNeighbourSquares(); ++d) {
2369 lsquare *Square = GetNeighbourLSquare(d);
2370 if (Square && CanMoveOn(Square)) ++OKDirectionsCounter;
2373 int nDir = -1;
2374 if (orthoDir[gd] && !Go->IsWalkingInOpen()) {
2375 // check if there is dead end forward and we have only one turn
2376 v2 nxy = GetPos()+MoveVector;
2377 v2 nxyff = nxy+MoveVector;
2378 odc = 0;
2379 lsquare *sqf = static_cast<lsquare *>(GetSquareUnder()->GetArea()->GetSquare(nxy));
2380 lsquare *sqff = static_cast<lsquare *>(GetSquareUnder()->GetArea()->GetSquare(nxyff));
2381 if (sqf && CanMoveOn(sqf) && (!sqff || !CanMoveOn(sqff))) {
2382 // dead end found
2383 for (int d = 0; d < GetNeighbourSquares(); ++d) {
2384 if (d == gd || d == revDir[gd] || !orthoDir[d]) continue;
2385 v2 sqxy = nxy+game::GetMoveVector(d);
2386 lsquare *sq = static_cast<lsquare *>(GetSquareUnder()->GetArea()->GetSquare(sqxy));
2387 if (sq && CanMoveOn(sq)) {
2388 nDir = d;
2389 odc++;
2393 //fprintf(stderr, "*: %d; nDir: %d\n", odc, nDir);
2396 bool doStop = false;
2397 if (!Go->IsWalkingInOpen()) {
2398 // in the corridor
2399 //fprintf(stderr, "dc: %d\n", OKDirectionsCounter);
2400 if (Go->prevWasTurn()) {
2401 Go->SetPrevWasTurn(false);
2402 } else if (odc == 1) {
2403 // if we will step on something, do it
2404 for (int d = 0; d < GetNeighbourSquares(); ++d) {
2405 lsquare *Square = GetNeighbourLSquare(d);
2406 if (Square && Square->GetStack()->HasSomethingFunny(this, ivanconfig::GetStopOnCorpses(), ivanconfig::GetStopOnSeenItems())) {
2407 //newDir = -1;
2408 doStop = true;
2409 break;
2412 // follow the turn; 3: back, forward and turn
2413 if (!doStop) {
2414 int newDir = -1;
2415 switch (nDir) {
2416 case 1: // up
2417 if (gd == 3) newDir = 0;
2418 else if (gd == 4) newDir = 2;
2419 break;
2420 case 3: // left
2421 if (gd == 1) newDir = 0;
2422 else if (gd == 6) newDir = 5;
2423 break;
2424 case 4: // right
2425 if (gd == 1) newDir = 2;
2426 else if (gd == 6) newDir = 7;
2427 break;
2428 case 6: // down
2429 if (gd == 3) newDir = 5;
2430 else if (gd == 4) newDir = 7;
2431 break;
2433 //if (newDir < 0) ABORT("go error");
2434 if (newDir < 0) { Go->Terminate(false); return; }
2435 lsquare *Square = GetNeighbourLSquare(newDir);
2436 if (Square && CanMoveOn(Square)) {
2437 // fuckin' copypasta
2438 MoveVector = ApplyStateModification(game::GetMoveVector(newDir));
2439 int squares = CalculateNewSquaresUnder(MoveToSquare, GetPos()+MoveVector);
2440 if (squares) {
2441 for (int c = 0; c < squares; ++c) {
2442 if ((MoveToSquare[c]->GetCharacter() && GetTeam() != MoveToSquare[c]->GetCharacter()->GetTeam()) || MoveToSquare[c]->IsDangerous(this)) {
2443 newDir = -1;
2444 break;
2447 if (newDir != -1) {
2449 } else {
2450 newDir = -1;
2453 if (newDir < 0) { Go->Terminate(false); return; }
2454 newDir = nDir;
2455 //fprintf(stderr, "newDir: %d\n", newDir);
2456 Go->SetDirection(newDir);
2457 Go->SetPrevWasTurn(true);
2458 v2 nxyf = GetPos()+MoveVector+game::GetMoveVector(newDir);
2459 v2 nxyff = nxyf+game::GetMoveVector(newDir);
2460 lsquare *sqf = static_cast<lsquare *>(GetSquareUnder()->GetArea()->GetSquare(nxyf));
2461 lsquare *sqff = static_cast<lsquare *>(GetSquareUnder()->GetArea()->GetSquare(nxyff));
2462 if (sqf && CanMoveOn(sqf)) {
2463 Go->SetPrevWasTurn(sqff && CanMoveOn(sqff));
2466 } else if (!IsInCorridor()) {
2467 Go->Terminate(false);
2468 return;
2470 } else {
2471 Go->SetPrevWasTurn(false);
2472 //if (OKDirectionsCounter <= 2) Go->SetIsWalkingInOpen(false);
2473 Go->SetIsWalkingInOpen(!IsInCorridor());
2476 square *BeginSquare = GetSquareUnder();
2478 if (!doStop) {
2479 for (int c = 0; c < Squares; ++c) {
2480 if (MoveToSquare[c]->GetStack()->HasSomethingFunny(this, ivanconfig::GetStopOnCorpses(), ivanconfig::GetStopOnSeenItems())) {
2481 doStop = true;
2482 break;
2487 truth moveOk = TryMove(MoveVector, true, game::PlayerIsRunning());
2488 if (!moveOk || BeginSquare == GetSquareUnder() || (CurrentRoomIndex && (OldRoomIndex != CurrentRoomIndex))) {
2489 if (moveOk) {
2490 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
2491 game::DrawEverything();
2493 Go->Terminate(false);
2494 return;
2497 if (doStop/* || GetStackUnder()->HasSomethingFunny(this, ivanconfig::GetStopOnCorpses(), ivanconfig::GetStopOnSeenItems())*/) {
2498 Go->Terminate(false);
2500 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
2502 game::DrawEverything();
2506 void character::SetTeam (team *What) {
2507 /*k8 if(Team) int esko = esko = 2; */
2508 Team = What;
2509 SetTeamIterator(What->Add(this));
2513 void character::ChangeTeam (team *What) {
2514 if (Team) Team->Remove(GetTeamIterator());
2515 Team = What;
2516 SendNewDrawRequest();
2517 if (Team) SetTeamIterator(Team->Add(this));
2521 truth character::ChangeRandomAttribute (int HowMuch) {
2522 for (int c = 0; c < 50; ++c) {
2523 int AttribID = RAND()%ATTRIBUTES;
2524 if (EditAttribute(AttribID, HowMuch)) return true;
2526 return false;
2530 int character::RandomizeReply (sLong &Said, int Replies) {
2531 truth NotSaid = false;
2532 for (int c = 0; c < Replies; ++c) {
2533 if (!(Said & (1 << c))) {
2534 NotSaid = true;
2535 break;
2538 if (!NotSaid) Said = 0;
2539 sLong ToSay;
2540 while (Said & 1 << (ToSay = RAND() % Replies));
2541 Said |= 1 << ToSay;
2542 return ToSay;
2546 void character::DisplayInfo (festring &Msg) {
2547 if (IsPlayer()) {
2548 Msg << " You are " << GetStandVerb() << " here.";
2549 } else {
2550 Msg << ' ' << GetName(INDEFINITE).CapitalizeCopy() << " is " << GetStandVerb() << " here. " << GetPersonalPronoun().CapitalizeCopy();
2551 cchar *Separator1 = GetAction() ? "," : " and";
2552 cchar *Separator2 = " and";
2553 if (GetTeam() == PLAYER->GetTeam()) {
2554 Msg << " is tame";
2555 } else {
2556 int Relation = GetRelation(PLAYER);
2557 if (Relation == HOSTILE) Msg << " is hostile";
2558 else if (Relation == UNCARING) {
2559 Msg << " does not care about you";
2560 Separator1 = Separator2 = " and is";
2561 } else {
2562 Msg << " is friendly";
2565 if (StateIsActivated(PANIC)) {
2566 Msg << Separator1 << " panicked";
2567 Separator2 = " and";
2569 if (GetAction()) Msg << Separator2 << ' ' << GetAction()->GetDescription();
2570 Msg << '.';
2575 void character::TestWalkability () {
2576 if (!IsEnabled()) return;
2577 square *SquareUnder = !game::IsInWilderness() ? GetSquareUnder() : PLAYER->GetSquareUnder();
2578 if (SquareUnder->IsFatalToStay() && !CanMoveOn(SquareUnder)) {
2579 truth Alive = false;
2580 if (!game::IsInWilderness() || IsPlayer()) {
2581 for (int d = 0; d < GetNeighbourSquares(); ++d) {
2582 square *Square = GetNeighbourSquare(d);
2583 if (Square && CanMoveOn(Square) && IsFreeForMe(Square)) {
2584 if (IsPlayer()) ADD_MESSAGE("%s.", SquareUnder->SurviveMessage(this));
2585 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE), SquareUnder->MonsterSurviveMessage(this));
2586 Move(Square->GetPos(), true); // actually, this shouldn't be a teleport move
2587 SquareUnder->SurviveEffect(this);
2588 Alive = true;
2589 break;
2593 if (!Alive) {
2594 if (IsPlayer()) {
2595 Remove();
2596 SendToHell();
2597 festring DeathMsg = festring(SquareUnder->DeathMessage(this));
2598 game::AskForEscPress(DeathMsg+".");
2599 festring Msg = SquareUnder->ScoreEntry(this);
2600 PLAYER->AddScoreEntry(Msg);
2601 game::End(Msg);
2602 } else {
2603 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE), SquareUnder->MonsterDeathVerb(this));
2604 Die(0, SquareUnder->ScoreEntry(this), DISALLOW_MSG);
2611 int character::GetSize () const {
2612 return GetTorso()->GetSize();
2616 void character::SetMainMaterial (material *NewMaterial, int SpecialFlags) {
2617 NewMaterial->SetVolume(GetBodyPart(0)->GetMainMaterial()->GetVolume());
2618 GetBodyPart(0)->SetMainMaterial(NewMaterial, SpecialFlags);
2619 for (int c = 1; c < BodyParts; ++c) {
2620 NewMaterial = NewMaterial->SpawnMore(GetBodyPart(c)->GetMainMaterial()->GetVolume());
2621 GetBodyPart(c)->SetMainMaterial(NewMaterial, SpecialFlags);
2626 void character::ChangeMainMaterial (material *NewMaterial, int SpecialFlags) {
2627 NewMaterial->SetVolume(GetBodyPart(0)->GetMainMaterial()->GetVolume());
2628 GetBodyPart(0)->ChangeMainMaterial(NewMaterial, SpecialFlags);
2629 for (int c = 1; c < BodyParts; ++c) {
2630 NewMaterial = NewMaterial->SpawnMore(GetBodyPart(c)->GetMainMaterial()->GetVolume());
2631 GetBodyPart(c)->ChangeMainMaterial(NewMaterial, SpecialFlags);
2636 void character::SetSecondaryMaterial (material *, int) {
2637 ABORT("Illegal character::SetSecondaryMaterial call!");
2641 void character::ChangeSecondaryMaterial (material *, int) {
2642 ABORT("Illegal character::ChangeSecondaryMaterial call!");
2646 void character::TeleportRandomly (truth Intentional) {
2647 v2 TelePos = ERROR_V2;
2648 if (StateIsActivated(TELEPORT_CONTROL)) {
2649 if (IsPlayer()) {
2650 v2 Input = game::PositionQuestion(CONST_S("Where do you wish to teleport? [direction keys move cursor, space accepts]"), GetPos(), &game::TeleportHandler, 0, false);
2651 if (Input == ERROR_V2) Input = GetPos(); // esc pressed
2652 lsquare *Square = GetNearLSquare(Input);
2653 if (CanMoveOn(Square) || game::GoThroughWallsCheatIsActive()) {
2654 if (Square->GetPos() == GetPos()) {
2655 ADD_MESSAGE("You disappear and reappear.");
2656 return;
2658 if (IsFreeForMe(Square)) {
2659 if ((Input-GetPos()).GetLengthSquare() <= GetTeleportRangeSquare()) {
2660 EditExperience(INTELLIGENCE, 100, 1 << 10);
2661 TelePos = Input;
2662 } else {
2663 ADD_MESSAGE("You cannot concentrate yourself enough to control a teleport that far.");
2665 } else {
2666 character *C = Square->GetCharacter();
2667 if (C) ADD_MESSAGE("For a moment you feel very much like %s.", C->CHAR_NAME(INDEFINITE));
2668 else ADD_MESSAGE("You feel that something weird has happened, but can't really tell what exactly.");
2670 } else {
2671 ADD_MESSAGE("You feel like having been hit by something really hard from the inside.");
2673 } else if (!Intentional) {
2674 if (IsGoingSomeWhere() && GetLevel()->IsValidPos(GoingTo)) {
2675 v2 Where = GetLevel()->GetNearestFreeSquare(this, GoingTo);
2676 if (Where != ERROR_V2 && (Where-GetPos()).GetLengthSquare() <= GetTeleportRangeSquare()) {
2677 EditExperience(INTELLIGENCE, 100, 1 << 10);
2678 Where = TelePos;
2684 if (IsPlayer()) {
2685 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.");
2688 if (TelePos != ERROR_V2) Move(TelePos, true);
2689 else Move(GetLevel()->GetRandomSquare(this), true);
2691 if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE));
2693 if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
2697 void character::RestoreHP () {
2698 doforbodyparts()(this, &bodypart::FastRestoreHP);
2699 HP = MaxHP;
2703 void character::RestoreLivingHP () {
2704 HP = 0;
2705 for (int c = 0; c < BodyParts; ++c) {
2706 bodypart *BodyPart = GetBodyPart(c);
2707 if (BodyPart && BodyPart->CanRegenerate()) {
2708 BodyPart->FastRestoreHP();
2709 HP += BodyPart->GetHP();
2715 truth character::AllowDamageTypeBloodSpill (int Type) {
2716 switch (Type&0xFFF) {
2717 case PHYSICAL_DAMAGE:
2718 case SOUND:
2719 case ENERGY:
2720 return true;
2721 case ACID:
2722 case FIRE:
2723 case DRAIN:
2724 case POISON:
2725 case ELECTRICITY:
2726 case MUSTARD_GAS_DAMAGE:
2727 case PSI:
2728 return false;
2730 ABORT("Unknown blood effect destroyed the dungeon!");
2731 return false;
2735 /* Returns truly done damage */
2736 int character::ReceiveBodyPartDamage (character *Damager, int Damage, int Type, int BodyPartIndex,
2737 int Direction, truth PenetrateResistance, truth Critical, truth ShowNoDamageMsg, truth CaptureBodyPart)
2739 bodypart *BodyPart = GetBodyPart(BodyPartIndex);
2740 if (!Damager || Damager->AttackMayDamageArmor()) BodyPart->DamageArmor(Damager, Damage, Type);
2741 if (!PenetrateResistance) {
2742 Damage -= (BodyPart->GetTotalResistance(Type)>>1)+RAND()%((BodyPart->GetTotalResistance(Type)>>1)+1);
2744 if (int(Damage) < 1) {
2745 if (Critical) {
2746 Damage = 1;
2747 } else {
2748 if (ShowNoDamageMsg) {
2749 if (IsPlayer()) ADD_MESSAGE("You are not hurt.");
2750 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s is not hurt.", GetPersonalPronoun().CStr());
2752 return 0;
2756 if (Critical && AllowDamageTypeBloodSpill(Type) && !game::IsInWilderness()) {
2757 BodyPart->SpillBlood(2+(RAND()&1));
2758 for (int d = 0; d < GetNeighbourSquares(); ++d) {
2759 lsquare *Square = GetNeighbourLSquare(d);
2760 if (Square && Square->IsFlyable()) BodyPart->SpillBlood(1, Square->GetPos());
2764 if (BodyPart->ReceiveDamage(Damager, Damage, Type, Direction) && BodyPartCanBeSevered(BodyPartIndex)) {
2765 if (DamageTypeDestroysBodyPart(Type)) {
2766 if (IsPlayer()) ADD_MESSAGE("Your %s is destroyed!", BodyPart->GetBodyPartName().CStr());
2767 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s is destroyed!", GetPossessivePronoun().CStr(), BodyPart->GetBodyPartName().CStr());
2768 GetBodyPart(BodyPartIndex)->DropEquipment();
2769 item *Severed = SevereBodyPart(BodyPartIndex);
2770 if (Severed) Severed->DestroyBodyPart(!game::IsInWilderness() ? GetStackUnder() : GetStack());
2771 SendNewDrawRequest();
2772 if (IsPlayer()) game::AskForEscPress(CONST_S("Bodypart destroyed!"));
2773 } else {
2774 if (IsPlayer()) ADD_MESSAGE("Your %s is severed off!", BodyPart->GetBodyPartName().CStr());
2775 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s is severed off!", GetPossessivePronoun().CStr(), BodyPart->GetBodyPartName().CStr());
2776 item *Severed = SevereBodyPart(BodyPartIndex);
2777 SendNewDrawRequest();
2778 if (Severed) {
2779 if (CaptureBodyPart) {
2780 Damager->GetLSquareUnder()->AddItem(Severed);
2781 } else if (!game::IsInWilderness()) {
2782 /** No multi-tile humanoid support! */
2783 GetStackUnder()->AddItem(Severed);
2784 if (Direction != YOURSELF) Severed->Fly(0, Direction, Damage);
2785 } else {
2786 GetStack()->AddItem(Severed);
2788 Severed->DropEquipment();
2789 } else if (IsPlayer() || CanBeSeenByPlayer()) {
2790 ADD_MESSAGE("It vanishes.");
2792 if (IsPlayer()) game::AskForEscPress(CONST_S("Bodypart severed!"));
2794 if (CanPanicFromSeveredBodyPart() && RAND()%100 < GetPanicLevel() && !StateIsActivated(PANIC) && !IsDead()) {
2795 BeginTemporaryState(PANIC, 1000+RAND()%1001);
2797 SpecialBodyPartSeverReaction();
2800 if (!IsDead()) CheckPanic(500);
2802 return Damage;
2806 /* Returns 0 if bodypart disappears */
2807 item *character::SevereBodyPart (int BodyPartIndex, truth ForceDisappearance, stack *EquipmentDropStack) {
2808 bodypart *BodyPart = GetBodyPart(BodyPartIndex);
2809 if (StateIsActivated(LEPROSY)) BodyPart->GetMainMaterial()->SetIsInfectedByLeprosy(true);
2810 if (ForceDisappearance || BodyPartsDisappearWhenSevered() || StateIsActivated(POLYMORPHED) || game::AllBodyPartsVanish()) {
2811 BodyPart->DropEquipment(EquipmentDropStack);
2812 BodyPart->RemoveFromSlot();
2813 CalculateAttributeBonuses();
2814 CalculateBattleInfo();
2815 BodyPart->SendToHell();
2816 SignalPossibleTransparencyChange();
2817 RemoveTraps(BodyPartIndex);
2818 return 0;
2820 BodyPart->SetOwnerDescription("of " + GetName(INDEFINITE));
2821 BodyPart->SetIsUnique(LeftOversAreUnique());
2822 UpdateBodyPartPicture(BodyPartIndex, true);
2823 BodyPart->RemoveFromSlot();
2824 BodyPart->RandomizePosition();
2825 CalculateAttributeBonuses();
2826 CalculateBattleInfo();
2827 BodyPart->Enable();
2828 SignalPossibleTransparencyChange();
2829 RemoveTraps(BodyPartIndex);
2830 return BodyPart;
2834 /* The second int is actually TargetFlags, which is not used here, but seems to be used in humanoid::ReceiveDamage.
2835 * Returns true if the character really receives damage */
2836 truth character::ReceiveDamage (character *Damager, int Damage, int Type, int, int Direction,
2837 truth, truth PenetrateArmor, truth Critical, truth ShowMsg)
2839 truth Affected = ReceiveBodyPartDamage(Damager, Damage, Type, 0, Direction, PenetrateArmor, Critical, ShowMsg);
2840 if (DamageTypeAffectsInventory(Type)) {
2841 for (int c = 0; c < GetEquipments(); ++c) {
2842 item *Equipment = GetEquipment(c);
2843 if (Equipment) Equipment->ReceiveDamage(Damager, Damage, Type);
2845 GetStack()->ReceiveDamage(Damager, Damage, Type);
2847 return Affected;
2851 festring character::GetDescription (int Case) const {
2852 if (IsPlayer()) return CONST_S("you");
2853 if (CanBeSeenByPlayer()) return GetName(Case);
2854 return CONST_S("something");
2858 festring character::GetPersonalPronoun (truth PlayersView) const {
2859 if (IsPlayer() && PlayersView) return CONST_S("you");
2860 if (GetSex() == UNDEFINED || (PlayersView && !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("it");
2861 if (GetSex() == MALE) return CONST_S("he");
2862 return CONST_S("she");
2866 festring character::GetPossessivePronoun (truth PlayersView) const {
2867 if (IsPlayer() && PlayersView) return CONST_S("your");
2868 if (GetSex() == UNDEFINED || (PlayersView && !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("its");
2869 if (GetSex() == MALE) return CONST_S("his");
2870 return CONST_S("her");
2874 festring character::GetObjectPronoun (truth PlayersView) const {
2875 if (IsPlayer() && PlayersView) return CONST_S("you");
2876 if (GetSex() == UNDEFINED || (PlayersView && !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("it");
2877 if (GetSex() == MALE) return CONST_S("him");
2878 return CONST_S("her");
2882 void character::AddName (festring &String, int Case) const {
2883 if (AssignedName.IsEmpty()) {
2884 id::AddName(String, Case);
2885 } else if (!(Case & PLURAL)) {
2886 if (!ShowClassDescription()) {
2887 String << AssignedName;
2888 } else {
2889 String << AssignedName << ' ';
2890 id::AddName(String, (Case|ARTICLE_BIT)&~INDEFINE_BIT);
2892 } else {
2893 id::AddName(String, Case);
2894 String << " named " << AssignedName;
2899 int character::GetHungerState () const {
2900 if (!UsesNutrition()) return NOT_HUNGRY;
2901 if (GetNP() > OVER_FED_LEVEL) return OVER_FED;
2902 if (GetNP() > BLOATED_LEVEL) return BLOATED;
2903 if (GetNP() > SATIATED_LEVEL) return SATIATED;
2904 if (GetNP() > NOT_HUNGER_LEVEL) return NOT_HUNGRY;
2905 if (GetNP() > HUNGER_LEVEL) return HUNGRY;
2906 if (GetNP() > VERY_HUNGER_LEVEL) return VERY_HUNGRY;
2907 return STARVING;
2911 truth character::CanConsume (material *Material) const {
2912 return GetConsumeFlags() & Material->GetConsumeType();
2916 void character::SetTemporaryStateCounter (sLong State, int What) {
2917 for (int c = 0; c < STATES; ++c) {
2918 if ((1 << c) & State) TemporaryStateCounter[c] = What;
2923 void character::EditTemporaryStateCounter (sLong State, int What) {
2924 for (int c = 0; c < STATES; ++c) {
2925 if ((1 << c) & State) TemporaryStateCounter[c] += What;
2930 int character::GetTemporaryStateCounter (sLong State) const {
2931 for (int c = 0; c < STATES; ++c) {
2932 if ((1 << c) & State) return TemporaryStateCounter[c];
2934 ABORT("Illegal GetTemporaryStateCounter request!");
2935 return 0;
2939 truth character::CheckKick () const {
2940 if (!CanKick()) {
2941 if (IsPlayer()) ADD_MESSAGE("This race can't kick.");
2942 return false;
2944 return true;
2948 int character::GetResistance (int Type) const {
2949 switch (Type&0xFFF) {
2950 case PHYSICAL_DAMAGE:
2951 case SOUND:
2952 case DRAIN:
2953 case MUSTARD_GAS_DAMAGE:
2954 case PSI:
2955 return 0;
2956 case ENERGY: return GetEnergyResistance();
2957 case FIRE: return GetFireResistance();
2958 case POISON: return GetPoisonResistance();
2959 case ELECTRICITY: return GetElectricityResistance();
2960 case ACID: return GetAcidResistance();
2962 ABORT("Resistance lack detected!");
2963 return 0;
2967 void character::Regenerate () {
2968 if (HP == MaxHP) return;
2969 sLong RegenerationBonus = 0;
2970 truth NoHealableBodyParts = true;
2971 for (int c = 0; c < BodyParts; ++c) {
2972 bodypart *BodyPart = GetBodyPart(c);
2973 if (BodyPart && BodyPart->CanRegenerate()) {
2974 RegenerationBonus += BodyPart->GetMaxHP();
2975 if (NoHealableBodyParts && BodyPart->GetHP() < BodyPart->GetMaxHP()) NoHealableBodyParts = false;
2978 if (!RegenerationBonus || NoHealableBodyParts) return;
2979 RegenerationBonus *= (50+GetAttribute(ENDURANCE));
2981 if (Action && Action->IsRest()) {
2982 if (SquaresUnder == 1) RegenerationBonus *= GetSquareUnder()->GetRestModifier() << 1;
2983 else {
2984 int Lowest = GetSquareUnder(0)->GetRestModifier();
2985 for (int c = 1; c < GetSquaresUnder(); ++c) {
2986 int Mod = GetSquareUnder(c)->GetRestModifier();
2987 if (Mod < Lowest) Lowest = Mod;
2989 RegenerationBonus *= Lowest << 1;
2993 RegenerationCounter += RegenerationBonus;
2995 while (RegenerationCounter > 1250000) {
2996 bodypart *BodyPart = HealHitPoint();
2997 if (!BodyPart) break;
2998 EditNP(-Max(7500/MaxHP, 1));
2999 RegenerationCounter -= 1250000;
3000 int HP = BodyPart->GetHP();
3001 EditExperience(ENDURANCE, Min(1000*BodyPart->GetMaxHP()/(HP*HP), 300), 1000);
3006 void character::PrintInfo () const {
3007 felist Info(CONST_S("Information about ")+GetName(DEFINITE));
3008 for (int c = 0; c < GetEquipments(); ++c) {
3009 item *Equipment = GetEquipment(c);
3010 if ((EquipmentEasilyRecognized(c) || game::WizardModeIsActive()) && Equipment) {
3011 int ImageKey = game::AddToItemDrawVector(itemvector(1, Equipment));
3012 Info.AddEntry(festring(GetEquipmentName(c))+": "+Equipment->GetName(INDEFINITE), LIGHT_GRAY, 0, ImageKey, true);
3015 if (Info.IsEmpty()) {
3016 ADD_MESSAGE("There's nothing special to tell about %s.", CHAR_NAME(DEFINITE));
3017 } else {
3018 game::SetStandardListAttributes(Info);
3019 Info.SetEntryDrawer(game::ItemEntryDrawer);
3020 Info.Draw();
3022 game::ClearItemDrawVector();
3026 truth character::TryToRiseFromTheDead () {
3027 for (int c = 0; c < BodyParts; ++c) {
3028 bodypart *BodyPart = GetBodyPart(c);
3029 if (BodyPart) {
3030 BodyPart->ResetSpoiling();
3031 if (BodyPart->CanRegenerate() || BodyPart->GetHP() < 1) BodyPart->SetHP(1);
3034 ResetStates();
3035 return true;
3039 truth character::RaiseTheDead (character *) {
3040 truth Useful = false;
3041 for (int c = 0; c < BodyParts; ++c) {
3042 bodypart *BodyPart = GetBodyPart(c);
3043 if (!BodyPart && CanCreateBodyPart(c)) {
3044 CreateBodyPart(c)->SetHP(1);
3045 if (IsPlayer()) ADD_MESSAGE("Suddenly you grow a new %s.", GetBodyPartName(c).CStr());
3046 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE), GetBodyPartName(c).CStr());
3047 Useful = true;
3048 } else if (BodyPart && BodyPart->CanRegenerate() && BodyPart->GetHP() < 1) {
3049 BodyPart->SetHP(1);
3052 if (!Useful) {
3053 if (IsPlayer()) ADD_MESSAGE("You shudder.");
3054 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s shudders.", CHAR_NAME(DEFINITE));
3056 return Useful;
3060 void character::SetSize (int Size) {
3061 for (int c = 0; c < BodyParts; ++c) {
3062 bodypart *BodyPart = GetBodyPart(c);
3063 if (BodyPart) BodyPart->SetSize(GetBodyPartSize(c, Size));
3068 sLong character::GetBodyPartSize (int I, int TotalSize) const {
3069 if (I == TORSO_INDEX) return TotalSize;
3070 ABORT("Weird bodypart size request for a character!");
3071 return 0;
3075 sLong character::GetBodyPartVolume (int I) const {
3076 if (I == TORSO_INDEX) return GetTotalVolume();
3077 ABORT("Weird bodypart volume request for a character!");
3078 return 0;
3082 void character::CreateBodyParts (int SpecialFlags) {
3083 for (int c = 0; c < BodyParts; ++c) if (CanCreateBodyPart(c)) CreateBodyPart(c, SpecialFlags);
3087 void character::RestoreBodyParts () {
3088 for (int c = 0; c < BodyParts; ++c) if (!GetBodyPart(c) && CanCreateBodyPart(c)) CreateBodyPart(c);
3092 void character::UpdatePictures () {
3093 if (!PictureUpdatesAreForbidden()) for (int c = 0; c < BodyParts; ++c) UpdateBodyPartPicture(c, false);
3097 bodypart *character::MakeBodyPart (int I) const {
3098 if (I == TORSO_INDEX) return normaltorso::Spawn(0, NO_MATERIALS);
3099 ABORT("Weird bodypart to make for a character!");
3100 return 0;
3104 bodypart *character::CreateBodyPart (int I, int SpecialFlags) {
3105 bodypart *BodyPart = MakeBodyPart(I);
3106 material *Material = CreateBodyPartMaterial(I, GetBodyPartVolume(I));
3107 BodyPart->InitMaterials(Material, false);
3108 BodyPart->SetSize(GetBodyPartSize(I, GetTotalSize()));
3109 BodyPart->SetBloodMaterial(GetBloodMaterial());
3110 BodyPart->SetNormalMaterial(Material->GetConfig());
3111 SetBodyPart(I, BodyPart);
3112 BodyPart->InitSpecialAttributes();
3113 if (!(SpecialFlags & NO_PIC_UPDATE)) UpdateBodyPartPicture(I, false);
3114 if (!IsInitializing()) {
3115 CalculateBattleInfo();
3116 SendNewDrawRequest();
3117 SignalPossibleTransparencyChange();
3119 return BodyPart;
3123 v2 character::GetBodyPartBitmapPos (int I, truth) const {
3124 if (I == TORSO_INDEX) return GetTorsoBitmapPos();
3125 ABORT("Weird bodypart BitmapPos request for a character!");
3126 return v2();
3130 void character::UpdateBodyPartPicture (int I, truth Severed) {
3131 bodypart *BP = GetBodyPart(I);
3132 if (BP) {
3133 BP->SetBitmapPos(GetBodyPartBitmapPos(I, Severed));
3134 BP->GetMainMaterial()->SetSkinColor(GetBodyPartColorA(I, Severed));
3135 BP->GetMainMaterial()->SetSkinColorIsSparkling(GetBodyPartSparkleFlags(I) & SPARKLING_A);
3136 BP->SetMaterialColorB(GetBodyPartColorB(I, Severed));
3137 BP->SetMaterialColorC(GetBodyPartColorC(I, Severed));
3138 BP->SetMaterialColorD(GetBodyPartColorD(I, Severed));
3139 BP->SetSparkleFlags(GetBodyPartSparkleFlags(I));
3140 BP->SetSpecialFlags(GetSpecialBodyPartFlags(I));
3141 BP->SetWobbleData(GetBodyPartWobbleData(I));
3142 BP->UpdatePictures();
3147 void character::LoadDataBaseStats () {
3148 for (int c = 0; c < BASE_ATTRIBUTES; ++c) {
3149 BaseExperience[c] = DataBase->NaturalExperience[c];
3150 if (BaseExperience[c]) LimitRef(BaseExperience[c], MIN_EXP, MAX_EXP);
3152 SetMoney(GetDefaultMoney());
3153 const fearray<sLong> &Skills = GetKnownCWeaponSkills();
3154 if (Skills.Size) {
3155 const fearray<sLong> &Hits = GetCWeaponSkillHits();
3156 if (Hits.Size == 1) {
3157 for (uInt c = 0; c < Skills.Size; ++c) {
3158 if (Skills[c] < AllowedWeaponSkillCategories) CWeaponSkill[Skills[c]].AddHit(Hits[0]*100);
3160 } else if (Hits.Size == Skills.Size) {
3161 for (uInt c = 0; c < Skills.Size; ++c) {
3162 if (Skills[c] < AllowedWeaponSkillCategories) CWeaponSkill[Skills[c]].AddHit(Hits[c]*100);
3164 } else {
3165 ABORT("Illegal weapon skill hit array size detected!");
3171 character *characterprototype::SpawnAndLoad (inputfile &SaveFile) const {
3172 character *Char = Spawner(0, LOAD);
3173 Char->Load(SaveFile);
3174 Char->CalculateAll();
3175 return Char;
3179 void character::Initialize (int NewConfig, int SpecialFlags) {
3180 Flags |= C_INITIALIZING|C_IN_NO_MSG_MODE;
3181 CalculateBodyParts();
3182 CalculateAllowedWeaponSkillCategories();
3183 CalculateSquaresUnder();
3184 BodyPartSlot = new bodypartslot[BodyParts];
3185 OriginalBodyPartID = new std::list<uLong>[BodyParts];
3186 CWeaponSkill = new cweaponskill[AllowedWeaponSkillCategories];
3187 SquareUnder = new square*[SquaresUnder];
3189 if (SquaresUnder == 1) *SquareUnder = 0; else memset(SquareUnder, 0, SquaresUnder*sizeof(square *));
3191 for (int c = 0; c < BodyParts; ++c) BodyPartSlot[c].SetMaster(this);
3193 if (!(SpecialFlags & LOAD)) {
3194 ID = game::CreateNewCharacterID(this);
3195 databasecreator<character>::InstallDataBase(this, NewConfig);
3196 LoadDataBaseStats();
3197 TemporaryState |= GetClassStates();
3198 if (TemporaryState) {
3199 for (int c = 0; c < STATES; ++c) if (TemporaryState & (1 << c)) TemporaryStateCounter[c] = PERMANENT;
3202 CreateBodyParts(SpecialFlags | NO_PIC_UPDATE);
3203 InitSpecialAttributes();
3204 CommandFlags = GetDefaultCommandFlags();
3206 if (GetAttribute(INTELLIGENCE, false) < 8) CommandFlags &= ~DONT_CONSUME_ANYTHING_VALUABLE; // gum
3207 if (!GetDefaultName().IsEmpty()) SetAssignedName(GetDefaultName());
3210 if (!(SpecialFlags & LOAD)) PostConstruct();
3212 if (!(SpecialFlags & LOAD)) {
3213 if (!(SpecialFlags & NO_EQUIPMENT)) CreateInitialEquipment((SpecialFlags & NO_EQUIPMENT_PIC_UPDATE) >> 1);
3214 if (!(SpecialFlags & NO_PIC_UPDATE)) UpdatePictures();
3215 CalculateAll();
3216 RestoreHP();
3217 RestoreStamina();
3220 Flags &= ~(C_INITIALIZING|C_IN_NO_MSG_MODE);
3224 truth character::TeleportNear (character *Caller) {
3225 v2 Where = GetLevel()->GetNearestFreeSquare(this, Caller->GetPos());
3226 if (Where == ERROR_V2) return false;
3227 Move(Where, true);
3228 return true;
3232 void character::ReceiveHeal (sLong Amount) {
3233 int c;
3234 for (c = 0; c < Amount / 10; ++c) if (!HealHitPoint()) break;
3235 Amount -= c*10;
3236 if (RAND()%10 < Amount) HealHitPoint();
3237 if (Amount >= 250 || RAND()%250 < Amount) {
3238 bodypart *NewBodyPart = GenerateRandomBodyPart();
3239 if (!NewBodyPart) return;
3240 NewBodyPart->SetHP(1);
3241 if (IsPlayer()) ADD_MESSAGE("You grow a new %s.", NewBodyPart->GetBodyPartName().CStr());
3242 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE), NewBodyPart->GetBodyPartName().CStr());
3247 void character::AddHealingLiquidConsumeEndMessage () const {
3248 if (IsPlayer()) ADD_MESSAGE("You feel better.");
3249 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks healthier.", CHAR_NAME(DEFINITE));
3253 void character::ReceiveSchoolFood (sLong SizeOfEffect) {
3254 SizeOfEffect += RAND()%SizeOfEffect;
3255 if (SizeOfEffect >= 250) VomitAtRandomDirection(SizeOfEffect);
3256 if (!(RAND() % 3) && SizeOfEffect >= 500 && EditAttribute(ENDURANCE, SizeOfEffect/500)) {
3257 if (IsPlayer()) ADD_MESSAGE("You gain a little bit of toughness for surviving this stuff.");
3258 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks tougher.", CHAR_NAME(DEFINITE));
3260 BeginTemporaryState(POISONED, (SizeOfEffect>>1));
3264 void character::AddSchoolFoodConsumeEndMessage () const {
3265 if (IsPlayer()) ADD_MESSAGE("Yuck! This stuff tasted like vomit and old mousepads.");
3269 void character::AddSchoolFoodHitMessage () const {
3270 if (IsPlayer()) ADD_MESSAGE("Yuck! This stuff feels like vomit and old mousepads.");
3274 void character::ReceiveNutrition (sLong SizeOfEffect) {
3275 EditNP(SizeOfEffect);
3279 void character::ReceiveOmmelUrine (sLong Amount) {
3280 EditExperience(ARM_STRENGTH, 500, Amount<<4);
3281 EditExperience(LEG_STRENGTH, 500, Amount<<4);
3282 if (IsPlayer()) game::DoEvilDeed(Amount/25);
3286 void character::ReceiveOmmelCerumen (sLong Amount) {
3287 EditExperience(INTELLIGENCE, 500, Amount << 5);
3288 EditExperience(WISDOM, 500, Amount << 5);
3289 if (IsPlayer()) game::DoEvilDeed(Amount / 25);
3293 void character::ReceiveOmmelSweat (sLong Amount) {
3294 EditExperience(AGILITY, 500, Amount << 4);
3295 EditExperience(DEXTERITY, 500, Amount << 4);
3296 RestoreStamina();
3297 if (IsPlayer()) game::DoEvilDeed(Amount / 25);
3301 void character::ReceiveOmmelTears (sLong Amount) {
3302 EditExperience(PERCEPTION, 500, Amount << 4);
3303 EditExperience(CHARISMA, 500, Amount << 4);
3304 if (IsPlayer()) game::DoEvilDeed(Amount / 25);
3308 void character::ReceiveOmmelSnot (sLong Amount) {
3309 EditExperience(ENDURANCE, 500, Amount << 5);
3310 RestoreLivingHP();
3311 if (IsPlayer()) game::DoEvilDeed(Amount / 25);
3315 void character::ReceiveOmmelBone (sLong Amount) {
3316 EditExperience(ARM_STRENGTH, 500, Amount << 6);
3317 EditExperience(LEG_STRENGTH, 500, Amount << 6);
3318 EditExperience(DEXTERITY, 500, Amount << 6);
3319 EditExperience(AGILITY, 500, Amount << 6);
3320 EditExperience(ENDURANCE, 500, Amount << 6);
3321 EditExperience(PERCEPTION, 500, Amount << 6);
3322 EditExperience(INTELLIGENCE, 500, Amount << 6);
3323 EditExperience(WISDOM, 500, Amount << 6);
3324 EditExperience(CHARISMA, 500, Amount << 6);
3325 RestoreLivingHP();
3326 RestoreStamina();
3327 if (IsPlayer()) game::DoEvilDeed(Amount / 25);
3331 void character::AddOmmelConsumeEndMessage () const {
3332 if (IsPlayer()) ADD_MESSAGE("You feel a primitive force coursing through your veins.");
3333 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks more powerful.", CHAR_NAME(DEFINITE));
3337 void character::ReceivePepsi (sLong Amount) {
3338 ReceiveDamage(0, Amount / 100, POISON, TORSO);
3339 EditExperience(PERCEPTION, Amount, 1 << 14);
3340 if (CheckDeath(CONST_S("was poisoned by pepsi"), 0)) return;
3341 if (IsPlayer()) game::DoEvilDeed(Amount / 10);
3345 void character::AddPepsiConsumeEndMessage () const {
3346 if (IsPlayer()) ADD_MESSAGE("Urgh. You feel your guruism fading away.");
3347 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks very lame.", CHAR_NAME(DEFINITE));
3351 void character::ReceiveDarkness (sLong Amount) {
3352 EditExperience(INTELLIGENCE, -Amount / 5, 1 << 13);
3353 EditExperience(WISDOM, -Amount / 5, 1 << 13);
3354 EditExperience(CHARISMA, -Amount / 5, 1 << 13);
3355 if (IsPlayer()) game::DoEvilDeed(int(Amount / 50));
3359 void character::AddFrogFleshConsumeEndMessage () const {
3360 if (IsPlayer()) ADD_MESSAGE("Arg. You feel the fate of a navastater placed upon you...");
3361 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks like a navastater.", CHAR_NAME(DEFINITE));
3365 void character::ReceiveKoboldFlesh (sLong) {
3366 /* As it is commonly known, the possibility of fainting per 500 cubic
3367 centimeters of kobold flesh is exactly 5%. */
3368 if (!(RAND() % 20)) {
3369 if (IsPlayer()) ADD_MESSAGE("You lose control of your legs and fall down.");
3370 LoseConsciousness(250 + RAND_N(250));
3375 void character::AddKoboldFleshConsumeEndMessage () const {
3376 if (IsPlayer()) ADD_MESSAGE("This stuff tasted really funny.");
3380 void character::AddKoboldFleshHitMessage () const {
3381 if (IsPlayer()) ADD_MESSAGE("You feel very funny.");
3385 void character::AddBoneConsumeEndMessage () const {
3386 if (IsPlayer()) ADD_MESSAGE("You feel like a hippie.");
3387 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s barks happily.", CHAR_NAME(DEFINITE)); // this suspects that nobody except dogs can eat bones
3390 truth character::RawEditAttribute (double &Experience, int Amount) const {
3391 /* Check if the attribute is disabled for creature */
3392 if (!Experience) return false;
3393 if ((Amount < 0 && Experience < 2 * EXP_MULTIPLIER) || (Amount > 0 && Experience > 999 * EXP_MULTIPLIER)) return false;
3394 Experience += Amount * EXP_MULTIPLIER;
3395 LimitRef<double>(Experience, MIN_EXP, MAX_EXP);
3396 return true;
3400 void character::DrawPanel (truth AnimationDraw) const {
3401 if (AnimationDraw) { DrawStats(true); return; }
3402 igraph::BlitBackGround(v2(19 + (game::GetScreenXSize() << 4), 0), v2(RES.X - 19 - (game::GetScreenXSize() << 4), RES.Y));
3403 igraph::BlitBackGround(v2(16, 45 + (game::GetScreenYSize() << 4)), v2(game::GetScreenXSize() << 4, 9));
3404 FONT->Printf(DOUBLE_BUFFER, v2(16, 45 + (game::GetScreenYSize() << 4)), WHITE, "%s", GetPanelName().CStr());
3405 game::UpdateAttributeMemory();
3406 int PanelPosX = RES.X - 96;
3407 int PanelPosY = DrawStats(false);
3408 PrintAttribute("End", ENDURANCE, PanelPosX, PanelPosY++);
3409 PrintAttribute("Per", PERCEPTION, PanelPosX, PanelPosY++);
3410 PrintAttribute("Int", INTELLIGENCE, PanelPosX, PanelPosY++);
3411 PrintAttribute("Wis", WISDOM, PanelPosX, PanelPosY++);
3412 PrintAttribute("Wil", WILL_POWER, PanelPosX, PanelPosY++);
3413 PrintAttribute("Cha", CHARISMA, PanelPosX, PanelPosY++);
3414 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Siz %d", GetSize());
3415 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), IsInBadCondition() ? RED : WHITE, "HP %d/%d", GetHP(), GetMaxHP());
3416 ++PanelPosY;
3417 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Gold: %d", GetMoney());
3418 ++PanelPosY;
3420 if (game::IsInWilderness())
3421 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Worldmap");
3422 else
3423 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "%s", game::GetCurrentDungeon()->GetShortLevelDescription(game::GetCurrentLevelIndex()).CapitalizeCopy().CStr());
3425 ivantime Time;
3426 game::GetTime(Time);
3427 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Day %d", Time.Day);
3428 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Time %d:%s%d", Time.Hour, Time.Min < 10 ? "0" : "", Time.Min);
3429 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Turn %d", game::GetTurn());
3431 ++PanelPosY;
3433 if (GetAction())
3434 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "%s", festring(GetAction()->GetDescription()).CapitalizeCopy().CStr());
3436 for (int c = 0; c < STATES; ++c)
3437 if (!(StateData[c].Flags & SECRET) && StateIsActivated(1 << c) && (1 << c != HASTE || !StateIsActivated(SLOW)) && (1 << c != SLOW || !StateIsActivated(HASTE)))
3438 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), (1 << c) & EquipmentState || TemporaryStateCounter[c] == PERMANENT ? BLUE : WHITE, "%s", StateData[c].Description);
3440 /* Make this more elegant!!! */
3441 if (GetHungerState() == STARVING)
3442 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), RED, "Starving");
3443 else if (GetHungerState() == VERY_HUNGRY)
3444 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), BLUE, "Very hungry");
3445 else if (GetHungerState() == HUNGRY)
3446 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), BLUE, "Hungry");
3447 else if (GetHungerState() == SATIATED)
3448 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Satiated");
3449 else if (GetHungerState() == BLOATED)
3450 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Bloated");
3451 else if (GetHungerState() == OVER_FED)
3452 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Overfed!");
3454 switch (GetBurdenState()) {
3455 case OVER_LOADED:
3456 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), RED, "Overload!");
3457 break;
3458 case STRESSED:
3459 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), BLUE, "Stressed");
3460 break;
3461 case BURDENED:
3462 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), BLUE, "Burdened");
3463 break;
3466 switch (GetTirednessState()) {
3467 case FAINTING:
3468 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), RED, "Fainting");
3469 break;
3470 case EXHAUSTED:
3471 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Exhausted");
3472 break;
3475 if (game::PlayerIsRunning()) {
3476 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, GetRunDescriptionLine(0));
3477 cchar *SecondLine = GetRunDescriptionLine(1);
3478 if (strlen(SecondLine)) FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, SecondLine);
3483 void character::CalculateDodgeValue () {
3484 DodgeValue = 0.05 * GetMoveEase() * GetAttribute(AGILITY) / sqrt(GetSize());
3485 if (IsFlying()) DodgeValue *= 2;
3486 if (DodgeValue < 1) DodgeValue = 1;
3490 truth character::DamageTypeAffectsInventory (int Type) {
3491 switch (Type&0xFFF) {
3492 case SOUND:
3493 case ENERGY:
3494 case ACID:
3495 case FIRE:
3496 case ELECTRICITY:
3497 return true;
3498 case PHYSICAL_DAMAGE:
3499 case POISON:
3500 case DRAIN:
3501 case MUSTARD_GAS_DAMAGE:
3502 case PSI:
3503 return false;
3505 ABORT("Unknown reaping effect destroyed dungeon!");
3506 return false;
3510 int character::CheckForBlockWithArm (character *Enemy, item *Weapon, arm *Arm,
3511 double WeaponToHitValue, int Damage, int Success, int Type)
3513 int BlockStrength = Arm->GetBlockCapability();
3514 double BlockValue = Arm->GetBlockValue();
3515 if (BlockStrength && BlockValue) {
3516 item *Blocker = Arm->GetWielded();
3517 if (RAND() % int(100+WeaponToHitValue/BlockValue/(1<<BlocksSinceLastTurn)*(100+Success)) < 100) {
3518 int NewDamage = BlockStrength < Damage ? Damage-BlockStrength : 0;
3519 switch (Type) {
3520 case UNARMED_ATTACK: AddBlockMessage(Enemy, Blocker, Enemy->UnarmedHitNoun(), NewDamage); break;
3521 case WEAPON_ATTACK: AddBlockMessage(Enemy, Blocker, "attack", NewDamage); break;
3522 case KICK_ATTACK: AddBlockMessage(Enemy, Blocker, Enemy->KickNoun(), NewDamage); break;
3523 case BITE_ATTACK: AddBlockMessage(Enemy, Blocker, Enemy->BiteNoun(), NewDamage); break;
3525 sLong Weight = Blocker->GetWeight();
3526 sLong StrExp = Limit(15 * Weight / 200, 75, 300);
3527 sLong DexExp = Weight ? Limit(75000 / Weight, 75, 300) : 300;
3528 Arm->EditExperience(ARM_STRENGTH, StrExp, 1 << 8);
3529 Arm->EditExperience(DEXTERITY, DexExp, 1 << 8);
3530 EditStamina(-10000 / GetAttribute(ARM_STRENGTH), false);
3531 if (Arm->TwoHandWieldIsActive()) {
3532 arm *PairArm = Arm->GetPairArm();
3533 PairArm->EditExperience(ARM_STRENGTH, StrExp, 1 << 8);
3534 PairArm->EditExperience(DEXTERITY, DexExp, 1 << 8);
3536 Blocker->WeaponSkillHit(Enemy->CalculateWeaponSkillHits(this));
3537 Blocker->ReceiveDamage(this, Damage, PHYSICAL_DAMAGE);
3538 Blocker->BlockEffect(this, Enemy, Weapon, Type);
3539 if (Weapon) Weapon->ReceiveDamage(Enemy, Damage - NewDamage, PHYSICAL_DAMAGE);
3540 if (BlocksSinceLastTurn < 16) ++BlocksSinceLastTurn;
3541 return NewDamage;
3544 return Damage;
3548 sLong character::GetStateAPGain (sLong BaseAPGain) const {
3549 if (!StateIsActivated(HASTE) == !StateIsActivated(SLOW)) return BaseAPGain;
3550 if (StateIsActivated(HASTE)) return (BaseAPGain * 5) >> 2;
3551 return (BaseAPGain << 2) / 5;
3555 void character::SignalEquipmentAdd (int EquipmentIndex) {
3556 item *Equipment = GetEquipment(EquipmentIndex);
3557 if (Equipment->IsInCorrectSlot(EquipmentIndex)) {
3558 sLong AddedStates = Equipment->GetGearStates();
3559 if (AddedStates) {
3560 for (int c = 0; c < STATES; ++c) {
3561 if (AddedStates & (1 << c)) {
3562 if (!StateIsActivated(1 << c)) {
3563 if (!IsInNoMsgMode()) (this->*StateData[c].PrintBeginMessage)();
3564 EquipmentState |= 1 << c;
3565 if (StateData[c].BeginHandler) (this->*StateData[c].BeginHandler)();
3566 } else {
3567 EquipmentState |= 1 << c;
3573 if (!IsInitializing() && Equipment->IsInCorrectSlot(EquipmentIndex)) ApplyEquipmentAttributeBonuses(Equipment);
3577 void character::SignalEquipmentRemoval (int, citem *Item) {
3578 CalculateEquipmentState();
3579 if (CalculateAttributeBonuses()) CheckDeath(festring("lost ")+GetPossessivePronoun(false)+" vital "+Item->GetName(INDEFINITE));
3583 void character::CalculateEquipmentState () {
3584 sLong Back = EquipmentState;
3585 EquipmentState = 0;
3586 for (int c = 0; c < GetEquipments(); ++c) {
3587 item *Equipment = GetEquipment(c);
3588 if (Equipment && Equipment->IsInCorrectSlot(c)) EquipmentState |= Equipment->GetGearStates();
3590 for (int c = 0; c < STATES; ++c) {
3591 if (Back & (1 << c) && !StateIsActivated(1 << c)) {
3592 if (StateData[c].EndHandler) {
3593 (this->*StateData[c].EndHandler)();
3594 if (!IsEnabled()) return;
3596 if (!IsInNoMsgMode()) (this->*StateData[c].PrintEndMessage)();
3602 /* Counter = duration in ticks */
3603 void character::BeginTemporaryState (sLong State, int Counter) {
3604 if (!Counter) return;
3605 int Index;
3606 if (State == POLYMORPHED) ABORT("No Polymorphing with BeginTemporaryState!");
3607 for (Index = 0; Index < STATES; ++Index) if (1 << Index == State) break;
3608 if (Index == STATES) ABORT("BeginTemporaryState works only when State == 2 ^ n!");
3609 if (TemporaryStateIsActivated(State)) {
3610 int OldCounter = GetTemporaryStateCounter(State);
3611 if (OldCounter != PERMANENT) EditTemporaryStateCounter(State, Max(Counter, 50-OldCounter));
3612 } else if (StateData[Index].IsAllowed == 0 || (this->*StateData[Index].IsAllowed)()) {
3613 SetTemporaryStateCounter(State, Max(Counter, 50));
3614 if (!EquipmentStateIsActivated(State)) {
3615 if (!IsInNoMsgMode()) (this->*StateData[Index].PrintBeginMessage)();
3616 ActivateTemporaryState(State);
3617 if (StateData[Index].BeginHandler) (this->*StateData[Index].BeginHandler)();
3618 } else {
3619 ActivateTemporaryState(State);
3625 void character::HandleStates () {
3626 if (!TemporaryState && !EquipmentState) return;
3627 for (int c = 0; c < STATES; ++c) {
3628 if (TemporaryState & (1 << c) && TemporaryStateCounter[c] != PERMANENT) {
3629 if (!--TemporaryStateCounter[c]) {
3630 TemporaryState &= ~(1 << c);
3631 if (!(EquipmentState & (1 << c))) {
3632 if (StateData[c].EndHandler) {
3633 (this->*StateData[c].EndHandler)();
3634 if (!IsEnabled()) return;
3636 if (!TemporaryStateCounter[c]) (this->*StateData[c].PrintEndMessage)();
3640 if (StateIsActivated(1 << c)) {
3641 if (StateData[c].Handler) (this->*StateData[c].Handler)();
3643 if (!IsEnabled()) return;
3648 void character::PrintBeginPolymorphControlMessage () const {
3649 if (IsPlayer()) ADD_MESSAGE("You feel your mind has total control over your body.");
3653 void character::PrintEndPolymorphControlMessage () const {
3654 if (IsPlayer()) ADD_MESSAGE("You are somehow uncertain of your willpower.");
3658 void character::PrintBeginLifeSaveMessage () const {
3659 if (IsPlayer()) ADD_MESSAGE("You hear Hell's gates being locked just now.");
3663 void character::PrintEndLifeSaveMessage () const {
3664 if (IsPlayer()) ADD_MESSAGE("You feel the Afterlife is welcoming you once again.");
3668 void character::PrintBeginLycanthropyMessage () const {
3669 if (IsPlayer()) ADD_MESSAGE("You suddenly notice you've always loved full moons.");
3673 void character::PrintEndLycanthropyMessage () const {
3674 if (IsPlayer()) ADD_MESSAGE("You feel the wolf inside you has had enough of your bad habits.");
3678 void character::PrintBeginInvisibilityMessage () const {
3679 if ((PLAYER->StateIsActivated(INFRA_VISION) && IsWarm()) || (PLAYER->StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5)) {
3680 if (IsPlayer()) ADD_MESSAGE("You seem somehow transparent.");
3681 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s seems somehow transparent.", CHAR_NAME(DEFINITE));
3682 } else {
3683 if (IsPlayer()) ADD_MESSAGE("You fade away.");
3684 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s disappears!", CHAR_NAME(DEFINITE));
3689 void character::PrintEndInvisibilityMessage () const {
3690 if ((PLAYER->StateIsActivated(INFRA_VISION) && IsWarm()) || (PLAYER->StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5)) {
3691 if (IsPlayer()) ADD_MESSAGE("Your notice your transparency has ended.");
3692 else if (CanBeSeenByPlayer()) ADD_MESSAGE("The appearance of %s seems far more solid now.", CHAR_NAME(INDEFINITE));
3693 } else {
3694 if (IsPlayer()) ADD_MESSAGE("You reappear.");
3695 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s appears from nowhere!", CHAR_NAME(INDEFINITE));
3700 void character::PrintBeginInfraVisionMessage () const {
3701 if (IsPlayer()) {
3702 if (StateIsActivated(INVISIBLE) && IsWarm() && !(StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5))
3703 ADD_MESSAGE("You reappear.");
3704 else
3705 ADD_MESSAGE("You feel your perception being magically altered.");
3710 void character::PrintEndInfraVisionMessage () const {
3711 if (IsPlayer()) {
3712 if (StateIsActivated(INVISIBLE) && IsWarm() && !(StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5))
3713 ADD_MESSAGE("You disappear.");
3714 else
3715 ADD_MESSAGE("You feel your perception returning to normal.");
3720 void character::PrintBeginESPMessage () const {
3721 if (IsPlayer()) ADD_MESSAGE("You suddenly feel like being only a tiny part of a great network of intelligent minds.");
3725 void character::PrintEndESPMessage () const {
3726 if (IsPlayer()) ADD_MESSAGE("You are filled with desire to be just yourself from now on.");
3730 void character::PrintBeginHasteMessage () const {
3731 if (IsPlayer()) ADD_MESSAGE("Time slows down to a crawl.");
3732 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks faster!", CHAR_NAME(DEFINITE));
3736 void character::PrintEndHasteMessage () const {
3737 if (IsPlayer()) ADD_MESSAGE("Everything seems to move much faster now.");
3738 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks slower!", CHAR_NAME(DEFINITE));
3742 void character::PrintBeginSlowMessage () const {
3743 if (IsPlayer()) ADD_MESSAGE("Everything seems to move much faster now.");
3744 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks slower!", CHAR_NAME(DEFINITE));
3748 void character::PrintEndSlowMessage () const {
3749 if (IsPlayer()) ADD_MESSAGE("Time slows down to a crawl.");
3750 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks faster!", CHAR_NAME(DEFINITE));
3754 void character::EndPolymorph () {
3755 ForceEndPolymorph();
3759 character *character::ForceEndPolymorph () {
3760 if (IsPlayer()) {
3761 ADD_MESSAGE("You return to your true form.");
3762 } else if (game::IsInWilderness()) {
3763 ActivateTemporaryState(POLYMORPHED);
3764 SetTemporaryStateCounter(POLYMORPHED, 10);
3765 return this; // fast gum solution, state ends when the player enters a dungeon
3767 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s returns to %s true form.", CHAR_NAME(DEFINITE), GetPossessivePronoun().CStr());
3768 RemoveTraps();
3769 if (GetAction()) GetAction()->Terminate(false);
3770 v2 Pos = GetPos();
3771 SendToHell();
3772 Remove();
3773 character *Char = GetPolymorphBackup();
3774 Flags |= C_IN_NO_MSG_MODE;
3775 Char->Flags |= C_IN_NO_MSG_MODE;
3776 Char->ChangeTeam(GetTeam());
3777 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(Char);
3778 SetPolymorphBackup(0);
3779 Char->PutToOrNear(Pos);
3780 Char->Enable();
3781 Char->Flags &= ~C_POLYMORPHED;
3782 GetStack()->MoveItemsTo(Char->GetStack());
3783 DonateEquipmentTo(Char);
3784 Char->SetMoney(GetMoney());
3785 Flags &= ~C_IN_NO_MSG_MODE;
3786 Char->Flags &= ~C_IN_NO_MSG_MODE;
3787 Char->CalculateAll();
3788 Char->SetAssignedName(GetAssignedName());
3789 if (IsPlayer()) {
3790 Flags &= ~C_PLAYER;
3791 game::SetPlayer(Char);
3792 game::SendLOSUpdateRequest();
3793 UpdateESPLOS();
3795 Char->TestWalkability();
3796 return Char;
3800 void character::LycanthropyHandler () {
3801 if (!(RAND() % 2000)) {
3802 if (StateIsActivated(POLYMORPH_CONTROL) && !game::TruthQuestion(CONST_S("Do you wish to change into a werewolf? [y/N]"))) return;
3803 Polymorph(werewolfwolf::Spawn(), 1000 + RAND() % 2000);
3808 void character::SaveLife () {
3809 if (TemporaryStateIsActivated(LIFE_SAVED)) {
3810 if (IsPlayer())
3811 ADD_MESSAGE("But wait! You glow briefly red and seem to be in a better shape!");
3812 else if (CanBeSeenByPlayer())
3813 ADD_MESSAGE("But wait, suddenly %s glows briefly red and seems to be in a better shape!", GetPersonalPronoun().CStr());
3814 DeActivateTemporaryState(LIFE_SAVED);
3815 } else {
3816 item *LifeSaver = 0;
3817 for (int c = 0; c < GetEquipments(); ++c) {
3818 item *Equipment = GetEquipment(c);
3819 if (Equipment && Equipment->IsInCorrectSlot(c) && Equipment->GetGearStates() & LIFE_SAVED) LifeSaver = Equipment;
3821 if (!LifeSaver) ABORT("The Universe can only kill you once!");
3822 if (IsPlayer())
3823 ADD_MESSAGE("But wait! Your %s glows briefly red and disappears and you seem to be in a better shape!", LifeSaver->CHAR_NAME(UNARTICLED));
3824 else if (CanBeSeenByPlayer())
3825 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());
3826 LifeSaver->RemoveFromSlot();
3827 LifeSaver->SendToHell();
3830 if (IsPlayer()) game::AskForEscPress(CONST_S("Life saved!"));
3832 RestoreBodyParts();
3833 ResetSpoiling();
3834 RestoreHP();
3835 RestoreStamina();
3836 ResetStates();
3838 if (GetNP() < SATIATED_LEVEL) SetNP(SATIATED_LEVEL);
3840 SendNewDrawRequest();
3842 if (GetAction()) GetAction()->Terminate(false);
3846 character *character::PolymorphRandomly (int MinDanger, int MaxDanger, int Time) {
3847 character *NewForm = 0;
3848 if (StateIsActivated(POLYMORPH_CONTROL)) {
3849 if (IsPlayer()) {
3850 if (!GetNewFormForPolymorphWithControl(NewForm)) return NewForm;
3852 else {
3853 NewForm = protosystem::CreateMonster(MinDanger * 10, MaxDanger * 10, NO_EQUIPMENT);
3855 } else {
3856 NewForm = protosystem::CreateMonster(MinDanger, MaxDanger, NO_EQUIPMENT);
3858 Polymorph(NewForm, Time);
3859 return NewForm;
3863 /* In reality, the reading takes Time / (Intelligence * 10) turns */
3864 void character::StartReading (item *Item, sLong Time) {
3865 study *Read = study::Spawn(this);
3866 Read->SetLiteratureID(Item->GetID());
3867 if (game::WizardModeIsActive()) Time = 1;
3868 Read->SetCounter(Time);
3869 SetAction(Read);
3870 if (IsPlayer()) ADD_MESSAGE("You start reading %s.", Item->CHAR_NAME(DEFINITE));
3871 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s starts reading %s.", CHAR_NAME(DEFINITE), Item->CHAR_NAME(DEFINITE));
3875 /* Call when one makes something with his/her/its hands.
3876 * Difficulty of 5 takes about one turn, so it's the most common to use. */
3877 void character::DexterityAction (int Difficulty) {
3878 EditAP(-20000 * Difficulty / APBonus(GetAttribute(DEXTERITY)));
3879 EditExperience(DEXTERITY, Difficulty * 15, 1 << 7);
3883 /* If Theoretically != false, range is not a factor. */
3884 truth character::CanBeSeenByPlayer (truth Theoretically, truth IgnoreESP) const {
3885 if (IsEnabled() && !game::IsGenerating() && (Theoretically || GetSquareUnder())) {
3886 truth MayBeESPSeen = PLAYER->IsEnabled() && !IgnoreESP && PLAYER->StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5;
3887 truth MayBeInfraSeen = PLAYER->IsEnabled() && PLAYER->StateIsActivated(INFRA_VISION) && IsWarm();
3888 truth Visible = !StateIsActivated(INVISIBLE) || MayBeESPSeen || MayBeInfraSeen;
3889 if (game::IsInWilderness()) return Visible;
3890 if (MayBeESPSeen && (Theoretically || GetDistanceSquareFrom(PLAYER) <= PLAYER->GetESPRangeSquare())) return true;
3891 if (!Visible) return false;
3892 return Theoretically || SquareUnderCanBeSeenByPlayer(MayBeInfraSeen);
3894 return false;
3898 truth character::CanBeSeenBy (ccharacter *Who, truth Theoretically, truth IgnoreESP) const {
3899 if (Who->IsPlayer()) return CanBeSeenByPlayer(Theoretically, IgnoreESP);
3900 if (IsEnabled() && !game::IsGenerating() && (Theoretically || GetSquareUnder())) {
3901 truth MayBeESPSeen = Who->IsEnabled() && !IgnoreESP && Who->StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5;
3902 truth MayBeInfraSeen = Who->IsEnabled() && Who->StateIsActivated(INFRA_VISION) && IsWarm();
3903 truth Visible = !StateIsActivated(INVISIBLE) || MayBeESPSeen || MayBeInfraSeen;
3904 if (game::IsInWilderness()) return Visible;
3905 if (MayBeESPSeen && (Theoretically || GetDistanceSquareFrom(Who) <= Who->GetESPRangeSquare())) return true;
3906 if (!Visible) return false;
3907 return Theoretically || SquareUnderCanBeSeenBy(Who, MayBeInfraSeen);
3909 return false;
3913 truth character::SquareUnderCanBeSeenByPlayer (truth IgnoreDarkness) const {
3914 if (!GetSquareUnder()) return false;
3915 int S1 = SquaresUnder, S2 = PLAYER->SquaresUnder;
3916 if (S1 == 1 && S2 == 1) {
3917 if (GetSquareUnder()->CanBeSeenByPlayer(IgnoreDarkness)) return true;
3918 if (IgnoreDarkness) {
3919 int LOSRangeSquare = PLAYER->GetLOSRangeSquare();
3920 if ((GetPos() - PLAYER->GetPos()).GetLengthSquare() <= LOSRangeSquare) {
3921 eyecontroller::Map = GetLevel()->GetMap();
3922 return mapmath<eyecontroller>::DoLine(PLAYER->GetPos().X, PLAYER->GetPos().Y, GetPos().X, GetPos().Y, SKIP_FIRST);
3925 return false;
3926 } else {
3927 for (int c1 = 0; c1 < S1; ++c1) {
3928 lsquare *Square = GetLSquareUnder(c1);
3929 if (Square->CanBeSeenByPlayer(IgnoreDarkness)) return true;
3930 else if (IgnoreDarkness) {
3931 v2 Pos = Square->GetPos();
3932 int LOSRangeSquare = PLAYER->GetLOSRangeSquare();
3933 for (int c2 = 0; c2 < S2; ++c2) {
3934 v2 PlayerPos = PLAYER->GetPos(c2);
3935 if ((Pos-PlayerPos).GetLengthSquare() <= LOSRangeSquare) {
3936 eyecontroller::Map = GetLevel()->GetMap();
3937 if (mapmath<eyecontroller>::DoLine(PlayerPos.X, PlayerPos.Y, Pos.X, Pos.Y, SKIP_FIRST)) return true;
3942 return false;
3947 truth character::SquareUnderCanBeSeenBy (ccharacter *Who, truth IgnoreDarkness) const {
3948 int S1 = SquaresUnder, S2 = Who->SquaresUnder;
3949 int LOSRangeSquare = Who->GetLOSRangeSquare();
3950 if (S1 == 1 && S2 == 1) return GetSquareUnder()->CanBeSeenFrom(Who->GetPos(), LOSRangeSquare, IgnoreDarkness);
3951 for (int c1 = 0; c1 < S1; ++c1) {
3952 lsquare *Square = GetLSquareUnder(c1);
3953 for (int c2 = 0; c2 < S2; ++c2) if (Square->CanBeSeenFrom(Who->GetPos(c2), LOSRangeSquare, IgnoreDarkness)) return true;
3955 return false;
3959 int character::GetDistanceSquareFrom (ccharacter *Who) const {
3960 int S1 = SquaresUnder, S2 = Who->SquaresUnder;
3961 if (S1 == 1 && S2 == 1) return (GetPos() - Who->GetPos()).GetLengthSquare();
3962 v2 MinDist(0x7FFF, 0x7FFF);
3963 int MinLength = 0xFFFF;
3964 for (int c1 = 0; c1 < S1; ++c1) {
3965 for (int c2 = 0; c2 < S2; ++c2) {
3966 v2 Dist = GetPos(c1)-Who->GetPos(c2);
3967 if (Dist.X < 0) Dist.X = -Dist.X;
3968 if (Dist.Y < 0) Dist.Y = -Dist.Y;
3969 if (Dist.X <= MinDist.X && Dist.Y <= MinDist.Y) {
3970 MinDist = Dist;
3971 MinLength = Dist.GetLengthSquare();
3972 } else if (Dist.X < MinDist.X || Dist.Y < MinDist.Y) {
3973 int Length = Dist.GetLengthSquare();
3974 if (Length < MinLength) {
3975 MinDist = Dist;
3976 MinLength = Length;
3981 return MinLength;
3985 void character::AttachBodyPart (bodypart *BodyPart) {
3986 SetBodyPart(BodyPart->GetBodyPartIndex(), BodyPart);
3987 if (!AllowSpoil()) BodyPart->ResetSpoiling();
3988 BodyPart->ResetPosition();
3989 BodyPart->UpdatePictures();
3990 CalculateAttributeBonuses();
3991 CalculateBattleInfo();
3992 SendNewDrawRequest();
3993 SignalPossibleTransparencyChange();
3997 /* Returns true if the character has all bodyparts, false if not. */
3998 truth character::HasAllBodyParts () const {
3999 for (int c = 0; c < BodyParts; ++c) if (!GetBodyPart(c) && CanCreateBodyPart(c)) return false;
4000 return true;
4004 bodypart *character::GenerateRandomBodyPart () {
4005 int NeededBodyPart[MAX_BODYPARTS];
4006 int Index = 0;
4007 for (int c = 0; c < BodyParts; ++c) if (!GetBodyPart(c) && CanCreateBodyPart(c)) NeededBodyPart[Index++] = c;
4008 return Index ? CreateBodyPart(NeededBodyPart[RAND() % Index]) : 0;
4012 /* Searches the character's Stack and if it find some bodyparts there that are the character's
4013 * old bodyparts returns a stackiterator to one of them (choosen in random).
4014 * If no fitting bodyparts are found the function returns 0 */
4015 bodypart *character::FindRandomOwnBodyPart (truth AllowNonLiving) const {
4016 itemvector LostAndFound;
4017 for (int c = 0; c < BodyParts; ++c) {
4018 if (!GetBodyPart(c)) {
4019 for (std::list<uLong>::iterator i = OriginalBodyPartID[c].begin(); i != OriginalBodyPartID[c].end(); ++i) {
4020 bodypart *Found = static_cast<bodypart *>(SearchForItem(*i));
4021 if (Found && (AllowNonLiving || Found->CanRegenerate())) LostAndFound.push_back(Found);
4025 if (LostAndFound.empty()) return 0;
4026 return static_cast<bodypart *>(LostAndFound[RAND() % LostAndFound.size()]);
4030 void character::PrintBeginPoisonedMessage () const {
4031 if (IsPlayer()) ADD_MESSAGE("You seem to be very ill.");
4032 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks very ill.", CHAR_NAME(DEFINITE));
4036 void character::PrintEndPoisonedMessage () const {
4037 if (IsPlayer()) ADD_MESSAGE("You feel better again.");
4038 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks better.", CHAR_NAME(DEFINITE));
4042 void character::PoisonedHandler () {
4043 if (!(RAND() % 100)) VomitAtRandomDirection(500 + RAND_N(250));
4044 int Damage = 0;
4045 for (int Used = 0; Used < GetTemporaryStateCounter(POISONED); Used += 100) if (!(RAND() % 100)) ++Damage;
4046 if (Damage) {
4047 ReceiveDamage(0, Damage, POISON, ALL, 8, false, false, false, false);
4048 CheckDeath(CONST_S("died of acute poisoning"), 0);
4053 truth character::IsWarm () const {
4054 return combinebodypartpredicates()(this, &bodypart::IsWarm, 1);
4058 void character::BeginInvisibility () {
4059 UpdatePictures();
4060 SendNewDrawRequest();
4061 SignalPossibleTransparencyChange();
4065 void character::BeginInfraVision () {
4066 if (IsPlayer()) GetArea()->SendNewDrawRequest();
4070 void character::BeginESP () {
4071 if (IsPlayer()) GetArea()->SendNewDrawRequest();
4075 void character::EndInvisibility () {
4076 UpdatePictures();
4077 SendNewDrawRequest();
4078 SignalPossibleTransparencyChange();
4082 void character::EndInfraVision () {
4083 if (IsPlayer() && IsEnabled()) GetArea()->SendNewDrawRequest();
4087 void character::EndESP () {
4088 if (IsPlayer() && IsEnabled()) GetArea()->SendNewDrawRequest();
4092 void character::Draw (blitdata &BlitData) const {
4093 col24 L = BlitData.Luminance;
4094 if (PLAYER->IsEnabled() &&
4095 ((PLAYER->StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5 &&
4096 (PLAYER->GetPos() - GetPos()).GetLengthSquare() <= PLAYER->GetESPRangeSquare()) ||
4097 (PLAYER->StateIsActivated(INFRA_VISION) && IsWarm())))
4098 BlitData.Luminance = ivanconfig::GetContrastLuminance();
4100 DrawBodyParts(BlitData);
4101 BlitData.Luminance = ivanconfig::GetContrastLuminance();
4102 BlitData.Src.Y = 16;
4103 cint SquareIndex = BlitData.CustomData & SQUARE_INDEX_MASK;
4105 if (GetTeam() == PLAYER->GetTeam() && !IsPlayer() && SquareIndex == GetTameSymbolSquareIndex()) {
4106 BlitData.Src.X = 32;
4107 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData);
4110 if (IsFlying() && SquareIndex == GetFlySymbolSquareIndex()) {
4111 BlitData.Src.X = 128;
4112 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData);
4115 if (IsSwimming() && SquareIndex == GetSwimmingSymbolSquareIndex()) {
4116 BlitData.Src.X = 240;
4117 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData);
4120 if (GetAction() && GetAction()->IsUnconsciousness() && SquareIndex == GetUnconsciousSymbolSquareIndex()) {
4121 BlitData.Src.X = 224;
4122 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData);
4125 BlitData.Src.X = BlitData.Src.Y = 0;
4126 BlitData.Luminance = L;
4130 void character::DrawBodyParts (blitdata &BlitData) const {
4131 GetTorso()->Draw(BlitData);
4135 void character::PrintBeginTeleportMessage () const {
4136 if (IsPlayer()) ADD_MESSAGE("You feel jumpy.");
4140 void character::PrintEndTeleportMessage () const {
4141 if (IsPlayer()) ADD_MESSAGE("You suddenly realize you've always preferred walking to jumping.");
4145 void character::TeleportHandler () {
4146 if (!(RAND() % 1500) && !game::IsInWilderness()) {
4147 if (IsPlayer()) ADD_MESSAGE("You feel an urgent spatial relocation is now appropriate.");
4148 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s disappears.", CHAR_NAME(DEFINITE));
4149 TeleportRandomly();
4154 void character::PrintBeginPolymorphMessage () const {
4155 if (IsPlayer()) ADD_MESSAGE("An unconfortable uncertainty of who you really are overwhelms you.");
4159 void character::PrintEndPolymorphMessage () const {
4160 if (IsPlayer()) ADD_MESSAGE("You feel you are you and no one else.");
4164 void character::PolymorphHandler () {
4165 if (!(RAND() % 1500)) PolymorphRandomly(1, 999999, 200 + RAND() % 800);
4168 void character::PrintBeginTeleportControlMessage () const {
4169 if (IsPlayer()) ADD_MESSAGE("You feel very controlled.");
4173 void character::PrintEndTeleportControlMessage () const {
4174 if (IsPlayer()) ADD_MESSAGE("You feel your control slipping.");
4178 void character::DisplayStethoscopeInfo (character *) const {
4179 felist Info(CONST_S("Information about ") + GetDescription(DEFINITE));
4180 AddSpecialStethoscopeInfo(Info);
4181 Info.AddEntry(CONST_S("Endurance: ") + GetAttribute(ENDURANCE), LIGHT_GRAY);
4182 Info.AddEntry(CONST_S("Perception: ") + GetAttribute(PERCEPTION), LIGHT_GRAY);
4183 Info.AddEntry(CONST_S("Intelligence: ") + GetAttribute(INTELLIGENCE), LIGHT_GRAY);
4184 Info.AddEntry(CONST_S("Wisdom: ") + GetAttribute(WISDOM), LIGHT_GRAY);
4185 //Info.AddEntry(CONST_S("Willpower: ") + GetAttribute(WILL_POWER), LIGHT_GRAY);
4186 Info.AddEntry(CONST_S("Charisma: ") + GetAttribute(CHARISMA), LIGHT_GRAY);
4187 Info.AddEntry(CONST_S("HP: ") + GetHP() + "/" + GetMaxHP(), IsInBadCondition() ? RED : LIGHT_GRAY);
4188 if (GetAction()) Info.AddEntry(festring(GetAction()->GetDescription()).CapitalizeCopy(), LIGHT_GRAY);
4189 for (int c = 0; c < STATES; ++c) {
4190 if (StateIsActivated(1 << c) && (1 << c != HASTE || !StateIsActivated(SLOW)) && (1 << c != SLOW || !StateIsActivated(HASTE)))
4191 Info.AddEntry(StateData[c].Description, LIGHT_GRAY);
4193 switch (GetTirednessState()) {
4194 case FAINTING: Info.AddEntry("Fainting", RED); break;
4195 case EXHAUSTED: Info.AddEntry("Exhausted", LIGHT_GRAY); break;
4197 game::SetStandardListAttributes(Info);
4198 Info.Draw();
4202 truth character::CanUseStethoscope (truth PrintReason) const {
4203 if (PrintReason) ADD_MESSAGE("This type of monster can't use a stethoscope.");
4204 return false;
4208 /* Effect used by at least Sophos.
4209 * NOTICE: Doesn't check for death! */
4210 void character::TeleportSomePartsAway (int NumberToTeleport) {
4211 for (int c = 0; c < NumberToTeleport; ++c) {
4212 int RandomBodyPart = GetRandomNonVitalBodyPart();
4213 if (RandomBodyPart == NONE_INDEX) {
4214 for (; c < NumberToTeleport; ++c) {
4215 GetTorso()->SetHP((GetTorso()->GetHP() << 2) / 5);
4216 sLong TorsosVolume = GetTorso()->GetMainMaterial()->GetVolume() / 10;
4217 if (!TorsosVolume) break;
4218 sLong Amount = (RAND() % TorsosVolume)+1;
4219 item *Lump = GetTorso()->GetMainMaterial()->CreateNaturalForm(Amount);
4220 GetTorso()->GetMainMaterial()->EditVolume(-Amount);
4221 Lump->MoveTo(GetNearLSquare(GetLevel()->GetRandomSquare())->GetStack());
4222 if (IsPlayer()) ADD_MESSAGE("Parts of you teleport away.");
4223 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Parts of %s teleport away.", CHAR_NAME(DEFINITE));
4225 } else {
4226 item *SeveredBodyPart = SevereBodyPart(RandomBodyPart);
4227 if (SeveredBodyPart) {
4228 GetNearLSquare(GetLevel()->GetRandomSquare())->AddItem(SeveredBodyPart);
4229 SeveredBodyPart->DropEquipment();
4230 if (IsPlayer()) ADD_MESSAGE("Your %s teleports away.", GetBodyPartName(RandomBodyPart).CStr());
4231 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s teleports away.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart).CStr());
4232 } else {
4233 if (IsPlayer()) ADD_MESSAGE("Your %s disappears.", GetBodyPartName(RandomBodyPart).CStr());
4234 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s disappears.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart).CStr());
4241 /* Returns an index of a random bodypart that is not vital. If no non-vital bodypart is found returns NONE_INDEX */
4242 int character::GetRandomNonVitalBodyPart () const {
4243 int OKBodyPart[MAX_BODYPARTS];
4244 int OKBodyParts = 0;
4245 for (int c = 0; c < BodyParts; ++c) if (GetBodyPart(c) && !BodyPartIsVital(c)) OKBodyPart[OKBodyParts++] = c;
4246 return OKBodyParts ? OKBodyPart[RAND() % OKBodyParts] : NONE_INDEX;
4250 void character::CalculateVolumeAndWeight () {
4251 Volume = Stack->GetVolume();
4252 Weight = Stack->GetWeight();
4253 BodyVolume = 0;
4254 CarriedWeight = Weight;
4255 for (int c = 0; c < BodyParts; ++c) {
4256 bodypart *BodyPart = GetBodyPart(c);
4257 if (BodyPart) {
4258 BodyVolume += BodyPart->GetBodyPartVolume();
4259 Volume += BodyPart->GetVolume();
4260 CarriedWeight += BodyPart->GetCarriedWeight();
4261 Weight += BodyPart->GetWeight();
4267 void character::SignalVolumeAndWeightChange () {
4268 if (!IsInitializing()) {
4269 CalculateVolumeAndWeight();
4270 if (IsEnabled()) CalculateBurdenState();
4271 if (MotherEntity) MotherEntity->SignalVolumeAndWeightChange();
4276 void character::SignalEmitationIncrease (col24 EmitationUpdate) {
4277 if (game::CompareLights(EmitationUpdate, Emitation) > 0) {
4278 game::CombineLights(Emitation, EmitationUpdate);
4279 if (MotherEntity) MotherEntity->SignalEmitationIncrease(EmitationUpdate);
4280 else if (SquareUnder[0] && !game::IsInWilderness()) {
4281 for(int c = 0; c < GetSquaresUnder(); ++c) GetLSquareUnder()->SignalEmitationIncrease(EmitationUpdate);
4287 void character::SignalEmitationDecrease (col24 EmitationUpdate) {
4288 if (game::CompareLights(EmitationUpdate, Emitation) >= 0 && Emitation) {
4289 col24 Backup = Emitation;
4290 CalculateEmitation();
4291 if (Backup != Emitation) {
4292 if (MotherEntity) MotherEntity->SignalEmitationDecrease(EmitationUpdate);
4293 else if (SquareUnder[0] && !game::IsInWilderness()) {
4294 for (int c = 0; c < GetSquaresUnder(); ++c) GetLSquareUnder(c)->SignalEmitationDecrease(EmitationUpdate);
4301 void character::CalculateEmitation () {
4302 Emitation = GetBaseEmitation();
4303 for (int c = 0; c < BodyParts; ++c) {
4304 bodypart *BodyPart = GetBodyPart(c);
4305 if (BodyPart) game::CombineLights(Emitation, BodyPart->GetEmitation());
4307 game::CombineLights(Emitation, Stack->GetEmitation());
4311 void character::CalculateAll () {
4312 Flags |= C_INITIALIZING;
4313 CalculateAttributeBonuses();
4314 CalculateVolumeAndWeight();
4315 CalculateEmitation();
4316 CalculateBodyPartMaxHPs(0);
4317 CalculateMaxStamina();
4318 CalculateBurdenState();
4319 CalculateBattleInfo();
4320 Flags &= ~C_INITIALIZING;
4324 void character::CalculateHP () {
4325 HP = sumbodypartproperties()(this, &bodypart::GetHP);
4329 void character::CalculateMaxHP () {
4330 MaxHP = sumbodypartproperties()(this, &bodypart::GetMaxHP);
4334 void character::CalculateBodyPartMaxHPs (uLong Flags) {
4335 doforbodypartswithparam<uLong>()(this, &bodypart::CalculateMaxHP, Flags);
4336 CalculateMaxHP();
4337 CalculateHP();
4341 truth character::EditAttribute (int Identifier, int Value) {
4342 if (Identifier == ENDURANCE && UseMaterialAttributes()) return false;
4343 if (RawEditAttribute(BaseExperience[Identifier], Value)) {
4344 if (!IsInitializing()) {
4345 if (Identifier == LEG_STRENGTH) CalculateBurdenState();
4346 else if (Identifier == ENDURANCE) CalculateBodyPartMaxHPs();
4347 else if (IsPlayer() && Identifier == PERCEPTION) game::SendLOSUpdateRequest();
4348 else if (IsPlayerKind() && (Identifier == INTELLIGENCE || Identifier == WISDOM || Identifier == CHARISMA)) UpdatePictures();
4349 CalculateBattleInfo();
4351 return true;
4353 return false;
4357 truth character::ActivateRandomState (int Flags, int Time, sLong Seed) {
4358 femath::SaveSeed();
4359 if (Seed) femath::SetSeed(Seed);
4360 sLong ToBeActivated = GetRandomState(Flags|DUR_TEMPORARY);
4361 femath::LoadSeed();
4362 if (!ToBeActivated) return false;
4363 BeginTemporaryState(ToBeActivated, Time);
4364 return true;
4368 truth character::GainRandomIntrinsic (int Flags) {
4369 sLong ToBeActivated = GetRandomState(Flags|DUR_PERMANENT);
4370 if (!ToBeActivated) return false;
4371 GainIntrinsic(ToBeActivated);
4372 return true;
4376 /* Returns 0 if state not found */
4377 sLong character::GetRandomState (int Flags) const {
4378 sLong OKStates[STATES];
4379 int NumberOfOKStates = 0;
4380 for (int c = 0; c < STATES; ++c) {
4381 if (StateData[c].Flags & Flags & DUR_FLAGS && StateData[c].Flags & Flags & SRC_FLAGS) OKStates[NumberOfOKStates++] = 1 << c;
4383 return NumberOfOKStates ? OKStates[RAND() % NumberOfOKStates] : 0;
4387 int characterprototype::CreateSpecialConfigurations (characterdatabase **TempConfig, int Configs, int Level) {
4388 if (Level == 0 && TempConfig[0]->CreateDivineConfigurations) {
4389 Configs = databasecreator<character>::CreateDivineConfigurations(this, TempConfig, Configs);
4391 if (Level == 1 && TempConfig[0]->CreateUndeadConfigurations) {
4392 for (int c = 1; c < protocontainer<character>::GetSize(); ++c) {
4393 const character::prototype *Proto = protocontainer<character>::GetProto(c);
4394 const character::database *const *CharacterConfigData = Proto->GetConfigData();
4395 if (!CharacterConfigData) ABORT("No database entry for character <%s>!", Proto->GetClassID());
4396 const character::database*const* End = CharacterConfigData + Proto->GetConfigSize();
4397 for (++CharacterConfigData; CharacterConfigData != End; ++CharacterConfigData) {
4398 const character::database *CharacterDataBase = *CharacterConfigData;
4399 if (CharacterDataBase->UndeadVersions) {
4400 character::database* ConfigDataBase = new character::database(**TempConfig);
4401 ConfigDataBase->InitDefaults(this, (c << 8) | CharacterDataBase->Config);
4402 ConfigDataBase->PostFix << "of ";
4403 if (CharacterDataBase->Adjective.GetSize()) {
4404 if (CharacterDataBase->UsesLongAdjectiveArticle) ConfigDataBase->PostFix << "an ";
4405 else ConfigDataBase->PostFix << "a ";
4406 ConfigDataBase->PostFix << CharacterDataBase->Adjective << ' ';
4407 } else {
4408 if (CharacterDataBase->UsesLongArticle) ConfigDataBase->PostFix << "an ";
4409 else ConfigDataBase->PostFix << "a ";
4411 ConfigDataBase->PostFix << CharacterDataBase->NameSingular;
4412 if (CharacterDataBase->PostFix.GetSize()) ConfigDataBase->PostFix << ' ' << CharacterDataBase->PostFix;
4413 int P1 = TempConfig[0]->UndeadAttributeModifier;
4414 int P2 = TempConfig[0]->UndeadVolumeModifier;
4415 int c2;
4416 for (c2 = 0; c2 < ATTRIBUTES; ++c2) ConfigDataBase->*ExpPtr[c2] = CharacterDataBase->*ExpPtr[c2] * P1 / 100;
4417 for (c2 = 0; c2 < EQUIPMENT_DATAS; ++c2) ConfigDataBase->*EquipmentDataPtr[c2] = contentscript<item>();
4418 ConfigDataBase->DefaultIntelligence = 5;
4419 ConfigDataBase->DefaultWisdom = 5;
4420 ConfigDataBase->DefaultCharisma = 5;
4421 ConfigDataBase->TotalSize = CharacterDataBase->TotalSize;
4422 ConfigDataBase->Sex = CharacterDataBase->Sex;
4423 ConfigDataBase->AttributeBonus = CharacterDataBase->AttributeBonus;
4424 ConfigDataBase->TotalVolume = CharacterDataBase->TotalVolume * P2 / 100;
4425 if (TempConfig[0]->UndeadCopyMaterials) {
4426 ConfigDataBase->HeadBitmapPos = CharacterDataBase->HeadBitmapPos;
4427 ConfigDataBase->HairColor = CharacterDataBase->HairColor;
4428 ConfigDataBase->EyeColor = CharacterDataBase->EyeColor;
4429 ConfigDataBase->CapColor = CharacterDataBase->CapColor;
4430 ConfigDataBase->FleshMaterial = CharacterDataBase->FleshMaterial;
4431 ConfigDataBase->BloodMaterial = CharacterDataBase->BloodMaterial;
4432 ConfigDataBase->VomitMaterial = CharacterDataBase->VomitMaterial;
4433 ConfigDataBase->SweatMaterial = CharacterDataBase->SweatMaterial;
4435 ConfigDataBase->KnownCWeaponSkills = CharacterDataBase->KnownCWeaponSkills;
4436 ConfigDataBase->CWeaponSkillHits = CharacterDataBase->CWeaponSkillHits;
4437 ConfigDataBase->PostProcess();
4438 TempConfig[Configs++] = ConfigDataBase;
4443 if (Level == 0 && TempConfig[0]->CreateGolemMaterialConfigurations) {
4444 for (int c = 1; c < protocontainer<material>::GetSize(); ++c) {
4445 const material::prototype* Proto = protocontainer<material>::GetProto(c);
4446 const material::database*const* MaterialConfigData = Proto->GetConfigData();
4447 const material::database*const* End = MaterialConfigData + Proto->GetConfigSize();
4448 for (++MaterialConfigData; MaterialConfigData != End; ++MaterialConfigData) {
4449 const material::database* MaterialDataBase = *MaterialConfigData;
4450 if (MaterialDataBase->CategoryFlags & IS_GOLEM_MATERIAL) {
4451 character::database* ConfigDataBase = new character::database(**TempConfig);
4452 ConfigDataBase->InitDefaults(this, MaterialDataBase->Config);
4453 ConfigDataBase->Adjective = MaterialDataBase->NameStem;
4454 ConfigDataBase->UsesLongAdjectiveArticle = MaterialDataBase->NameFlags & USE_AN;
4455 ConfigDataBase->AttachedGod = MaterialDataBase->AttachedGod;
4456 TempConfig[Configs++] = ConfigDataBase;
4461 return Configs;
4465 double character::GetTimeToDie (ccharacter *Enemy, int Damage, double ToHitValue, truth AttackIsBlockable, truth UseMaxHP) const {
4466 double DodgeValue = GetDodgeValue();
4467 if (!Enemy->CanBeSeenBy(this, true)) ToHitValue *= 2;
4468 if (!CanBeSeenBy(Enemy, true)) DodgeValue *= 2;
4469 double MinHits = 1000;
4470 truth First = true;
4471 for (int c = 0; c < BodyParts; ++c) {
4472 if (BodyPartIsVital(c) && GetBodyPart(c)) {
4473 double Hits = GetBodyPart(c)->GetTimeToDie(Damage, ToHitValue, DodgeValue, AttackIsBlockable, UseMaxHP);
4474 if (First) { MinHits = Hits; First = false; } else MinHits = 1 / (1 / MinHits + 1 / Hits);
4477 return MinHits;
4481 double character::GetRelativeDanger (ccharacter *Enemy, truth UseMaxHP) const {
4482 double Danger = Enemy->GetTimeToKill(this, UseMaxHP) / GetTimeToKill(Enemy, UseMaxHP);
4483 int EnemyAP = Enemy->GetMoveAPRequirement(1);
4484 int ThisAP = GetMoveAPRequirement(1);
4485 if (EnemyAP > ThisAP) Danger *= 1.25;
4486 else if (ThisAP > EnemyAP) Danger *= 0.80;
4487 if (!Enemy->CanBeSeenBy(this, true)) Danger *= Enemy->IsPlayer() ? 0.2 : 0.5;
4488 if (!CanBeSeenBy(Enemy, true)) Danger *= IsPlayer() ? 5. : 2.;
4489 if (GetAttribute(INTELLIGENCE) < 10 && !IsPlayer()) Danger *= 0.80;
4490 if (Enemy->GetAttribute(INTELLIGENCE) < 10 && !Enemy->IsPlayer()) Danger *= 1.25;
4491 return Limit(Danger, 0.001, 1000.0);
4495 festring character::GetBodyPartName (int I, truth Articled) const {
4496 if (I == TORSO_INDEX) return Articled ? CONST_S("a torso") : CONST_S("torso");
4497 ABORT("Illegal character bodypart name request!");
4498 return "";
4502 item *character::SearchForItem(uLong ID) const {
4503 item *Equipment = findequipment<uLong>()(this, &item::HasID, ID);
4504 if (Equipment) return Equipment;
4505 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) if (i->GetID() == ID) return *i;
4506 return 0;
4510 truth character::ContentsCanBeSeenBy (ccharacter *Viewer) const {
4511 return Viewer == this;
4515 truth character::HitEffect (character *Enemy, item* Weapon, v2 HitPos, int Type, int BodyPartIndex,
4516 int Direction, truth BlockedByArmour)
4518 if (Weapon) return Weapon->HitEffect(this, Enemy, HitPos, BodyPartIndex, Direction, BlockedByArmour);
4519 switch (Type) {
4520 case UNARMED_ATTACK: return Enemy->SpecialUnarmedEffect(this, HitPos, BodyPartIndex, Direction, BlockedByArmour);
4521 case KICK_ATTACK: return Enemy->SpecialKickEffect(this, HitPos, BodyPartIndex, Direction, BlockedByArmour);
4522 case BITE_ATTACK: return Enemy->SpecialBiteEffect(this, HitPos, BodyPartIndex, Direction, BlockedByArmour);
4524 return false;
4528 void character::WeaponSkillHit (item *Weapon, int Type, int Hits) {
4529 int Category;
4530 switch (Type) {
4531 case UNARMED_ATTACK: Category = UNARMED; break;
4532 case WEAPON_ATTACK: Weapon->WeaponSkillHit(Hits); return;
4533 case KICK_ATTACK: Category = KICK; break;
4534 case BITE_ATTACK: Category = BITE; break;
4535 case THROW_ATTACK:
4536 if (!IsHumanoid()) return;
4537 Category = Weapon->GetWeaponCategory();
4538 break;
4539 default:
4540 ABORT("Illegal Type %d passed to character::WeaponSkillHit()!", Type);
4541 return;
4543 if (GetCWeaponSkill(Category)->AddHit(Hits)) {
4544 CalculateBattleInfo();
4545 if (IsPlayer()) GetCWeaponSkill(Category)->AddLevelUpMessage(Category);
4550 /* Returns 0 if character cannot be duplicated */
4551 character *character::Duplicate (uLong Flags) {
4552 if (!(Flags & IGNORE_PROHIBITIONS) && !CanBeCloned()) return 0;
4553 character *Char = GetProtoType()->Clone(this);
4554 if (Flags & MIRROR_IMAGE) {
4555 DuplicateEquipment(Char, Flags & ~IGNORE_PROHIBITIONS);
4556 Char->SetLifeExpectancy(Flags >> LE_BASE_SHIFT & LE_BASE_RANGE, Flags >> LE_RAND_SHIFT & LE_RAND_RANGE);
4558 Char->CalculateAll();
4559 Char->CalculateEmitation();
4560 Char->UpdatePictures();
4561 Char->Flags &= ~(C_INITIALIZING|C_IN_NO_MSG_MODE);
4562 return Char;
4566 truth character::TryToEquip (item *Item) {
4567 if (!Item->AllowEquip() || !CanUseEquipment() || GetAttribute(WISDOM) >= Item->GetWearWisdomLimit() || Item->GetSquaresUnder() != 1)
4568 return false;
4569 for (int e = 0; e < GetEquipments(); ++e) {
4570 if (GetBodyPartOfEquipment(e) && EquipmentIsAllowed(e)) {
4571 sorter Sorter = EquipmentSorter(e);
4572 if ((Sorter == 0 || (Item->*Sorter)(this)) &&
4573 ((e != RIGHT_WIELDED_INDEX && e != LEFT_WIELDED_INDEX) ||
4574 Item->IsWeapon(this) || Item->IsShield(this)) && AllowEquipment(Item, e)) {
4575 item *OldEquipment = GetEquipment(e);
4576 if (BoundToUse(OldEquipment, e)) continue;
4577 lsquare *LSquareUnder = GetLSquareUnder();
4578 stack *StackUnder = LSquareUnder->GetStack();
4579 msgsystem::DisableMessages();
4580 Flags |= C_PICTURE_UPDATES_FORBIDDEN;
4581 LSquareUnder->Freeze();
4582 StackUnder->Freeze();
4583 double Danger = GetRelativeDanger(PLAYER);
4584 if (OldEquipment) OldEquipment->RemoveFromSlot();
4585 Item->RemoveFromSlot();
4586 SetEquipment(e, Item);
4587 double NewDanger = GetRelativeDanger(PLAYER);
4588 Item->RemoveFromSlot();
4589 StackUnder->AddItem(Item);
4590 if (OldEquipment) SetEquipment(e, OldEquipment);
4591 msgsystem::EnableMessages();
4592 Flags &= ~C_PICTURE_UPDATES_FORBIDDEN;
4593 LSquareUnder->UnFreeze();
4594 StackUnder->UnFreeze();
4595 if (OldEquipment) {
4596 if (NewDanger > Danger || BoundToUse(Item, e)) {
4597 room *Room = GetRoom();
4598 if (!Room || Room->PickupItem(this, Item, 1)) {
4599 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));
4600 if (Room) Room->DropItem(this, OldEquipment, 1);
4601 OldEquipment->MoveTo(StackUnder);
4602 Item->RemoveFromSlot();
4603 SetEquipment(e, Item);
4604 DexterityAction(5);
4605 return true;
4608 } else {
4609 if (NewDanger > Danger || (NewDanger == Danger && e != RIGHT_WIELDED_INDEX && e != LEFT_WIELDED_INDEX) || BoundToUse(Item, e)) {
4610 room *Room = GetRoom();
4611 if (!Room || Room->PickupItem(this, Item, 1)) {
4612 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s picks up and equips %s.", CHAR_NAME(DEFINITE), Item->CHAR_NAME(INDEFINITE));
4613 Item->RemoveFromSlot();
4614 SetEquipment(e, Item);
4615 DexterityAction(5);
4616 return true;
4623 return false;
4627 truth character::TryToConsume (item *Item) {
4628 return Item->CanBeEatenByAI(this) && ConsumeItem(Item, Item->GetConsumeMaterial(this)->GetConsumeVerb());
4632 void character::UpdateESPLOS () const {
4633 if (StateIsActivated(ESP) && !game::IsInWilderness()) {
4634 for (int c = 0; c < game::GetTeams(); ++c) {
4635 for (std::list<character *>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i) {
4636 const character *ch = *i;
4637 if (ch->IsEnabled()) ch->SendNewDrawRequest();
4644 int character::GetCWeaponSkillLevel (citem *Item) const {
4645 if (Item->GetWeaponCategory() < GetAllowedWeaponSkillCategories()) return GetCWeaponSkill(Item->GetWeaponCategory())->GetLevel();
4646 return 0;
4650 void character::PrintBeginPanicMessage () const {
4651 if (IsPlayer()) ADD_MESSAGE("You panic!");
4652 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s panics.", CHAR_NAME(DEFINITE));
4656 void character::PrintEndPanicMessage () const {
4657 if (IsPlayer()) ADD_MESSAGE("You finally calm down.");
4658 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s calms down.", CHAR_NAME(DEFINITE));
4662 void character::CheckPanic (int Ticks) {
4663 if (GetPanicLevel() > 1 && !StateIsActivated(PANIC) && GetHP() * 100 < RAND() % (GetPanicLevel() * GetMaxHP() << 1))
4664 BeginTemporaryState(PANIC, ((Ticks * 3) >> 2) + RAND() % ((Ticks >> 1) + 1)); // 25% randomness to ticks...
4668 /* returns 0 if fails else the newly created character */
4669 character *character::DuplicateToNearestSquare (character *Cloner, uLong Flags) {
4670 character *NewlyCreated = Duplicate(Flags);
4671 if (!NewlyCreated) return 0;
4672 if (Flags & CHANGE_TEAM && Cloner) NewlyCreated->ChangeTeam(Cloner->GetTeam());
4673 NewlyCreated->PutNear(GetPos());
4674 return NewlyCreated;
4678 void character::SignalSpoil (material *m) {
4679 if (GetMotherEntity()) GetMotherEntity()->SignalSpoil(m);
4680 else Disappear(0, "spoil", &item::IsVeryCloseToSpoiling);
4684 truth character::CanHeal () const {
4685 for (int c = 0; c < BodyParts; ++c) {
4686 bodypart *BodyPart = GetBodyPart(c);
4687 if (BodyPart && BodyPart->CanRegenerate() && BodyPart->GetHP() < BodyPart->GetMaxHP()) return true;
4689 return false;
4693 int character::GetRelation (ccharacter *Who) const {
4694 return GetTeam()->GetRelation(Who->GetTeam());
4698 truth (item::*AffectTest[BASE_ATTRIBUTES])() const = {
4699 &item::AffectsEndurance,
4700 &item::AffectsPerception,
4701 &item::AffectsIntelligence,
4702 &item::AffectsWisdom,
4703 &item::AffectsWillPower,
4704 &item::AffectsCharisma,
4705 &item::AffectsMana
4709 /* Returns nonzero if endurance has decreased and death may occur */
4710 truth character::CalculateAttributeBonuses () {
4711 doforbodyparts()(this, &bodypart::CalculateAttributeBonuses);
4712 int BackupBonus[BASE_ATTRIBUTES];
4713 int BackupCarryingBonus = CarryingBonus;
4714 CarryingBonus = 0;
4715 int c1;
4716 for (c1 = 0; c1 < BASE_ATTRIBUTES; ++c1) {
4717 BackupBonus[c1] = AttributeBonus[c1];
4718 AttributeBonus[c1] = 0;
4720 for (c1 = 0; c1 < GetEquipments(); ++c1) {
4721 item *Equipment = GetEquipment(c1);
4722 if (!Equipment || !Equipment->IsInCorrectSlot(c1)) continue;
4723 for (int c2 = 0; c2 < BASE_ATTRIBUTES; ++c2) {
4724 if ((Equipment->*AffectTest[c2])()) AttributeBonus[c2] += Equipment->GetEnchantment();
4726 if (Equipment->AffectsCarryingCapacity()) CarryingBonus += Equipment->GetCarryingBonus();
4729 ApplySpecialAttributeBonuses();
4731 if (IsPlayer() && !IsInitializing() && AttributeBonus[PERCEPTION] != BackupBonus[PERCEPTION]) game::SendLOSUpdateRequest();
4732 if (IsPlayer() && !IsInitializing() && AttributeBonus[INTELLIGENCE] != BackupBonus[INTELLIGENCE]) UpdateESPLOS();
4734 if (!IsInitializing() && CarryingBonus != BackupCarryingBonus) CalculateBurdenState();
4736 if (!IsInitializing() && AttributeBonus[ENDURANCE] != BackupBonus[ENDURANCE]) {
4737 CalculateBodyPartMaxHPs();
4738 CalculateMaxStamina();
4739 return AttributeBonus[ENDURANCE] < BackupBonus[ENDURANCE];
4742 return false;
4746 void character::ApplyEquipmentAttributeBonuses (item *Equipment) {
4747 if (Equipment->AffectsEndurance()) {
4748 AttributeBonus[ENDURANCE] += Equipment->GetEnchantment();
4749 CalculateBodyPartMaxHPs();
4750 CalculateMaxStamina();
4752 if (Equipment->AffectsPerception()) {
4753 AttributeBonus[PERCEPTION] += Equipment->GetEnchantment();
4754 if (IsPlayer()) game::SendLOSUpdateRequest();
4756 if (Equipment->AffectsIntelligence()) {
4757 AttributeBonus[INTELLIGENCE] += Equipment->GetEnchantment();
4758 if (IsPlayer()) UpdateESPLOS();
4760 if (Equipment->AffectsWisdom()) AttributeBonus[WISDOM] += Equipment->GetEnchantment();
4761 if (Equipment->AffectsWillPower()) AttributeBonus[WILL_POWER] += Equipment->GetEnchantment();
4762 if (Equipment->AffectsCharisma()) AttributeBonus[CHARISMA] += Equipment->GetEnchantment();
4763 if (Equipment->AffectsMana()) AttributeBonus[MANA] += Equipment->GetEnchantment();
4764 if (Equipment->AffectsCarryingCapacity()) {
4765 CarryingBonus += Equipment->GetCarryingBonus();
4766 CalculateBurdenState();
4771 void character::ReceiveAntidote (sLong Amount) {
4772 if (StateIsActivated(POISONED)) {
4773 if (GetTemporaryStateCounter(POISONED) > Amount) {
4774 EditTemporaryStateCounter(POISONED, -Amount);
4775 Amount = 0;
4776 } else {
4777 if (IsPlayer()) ADD_MESSAGE("Aaaah... You feel much better.");
4778 Amount -= GetTemporaryStateCounter(POISONED);
4779 DeActivateTemporaryState(POISONED);
4782 if ((Amount >= 100 || RAND_N(100) < Amount) && StateIsActivated(PARASITIZED)) {
4783 if (IsPlayer()) ADD_MESSAGE("Something in your belly didn't seem to like this stuff.");
4784 DeActivateTemporaryState(PARASITIZED);
4785 Amount -= Min(100, Amount);
4787 if ((Amount >= 100 || RAND_N(100) < Amount) && StateIsActivated(LEPROSY)) {
4788 if (IsPlayer()) ADD_MESSAGE("You are not falling to pieces anymore.");
4789 DeActivateTemporaryState(LEPROSY);
4790 Amount -= Min(100, Amount);
4795 void character::AddAntidoteConsumeEndMessage () const {
4796 if (StateIsActivated(POISONED)) {
4797 // true only if the antidote didn't cure the poison completely
4798 if (IsPlayer()) ADD_MESSAGE("Your body processes the poison in your veins with rapid speed.");
4803 truth character::IsDead () const {
4804 for (int c = 0; c < BodyParts; ++c) {
4805 bodypart *BodyPart = GetBodyPart(c);
4806 if (BodyPartIsVital(c) && (!BodyPart || BodyPart->GetHP() < 1)) return true;
4808 return false;
4812 void character::SignalSpoilLevelChange (material *m) {
4813 if (GetMotherEntity()) GetMotherEntity()->SignalSpoilLevelChange(m); else UpdatePictures();
4817 void character::AddOriginalBodyPartID (int I, uLong What) {
4818 if (std::find(OriginalBodyPartID[I].begin(), OriginalBodyPartID[I].end(), What) == OriginalBodyPartID[I].end()) {
4819 OriginalBodyPartID[I].push_back(What);
4820 if (OriginalBodyPartID[I].size() > 100) OriginalBodyPartID[I].erase(OriginalBodyPartID[I].begin());
4825 void character::AddToInventory (const fearray<contentscript<item> > &ItemArray, int SpecialFlags) {
4826 for (uInt c1 = 0; c1 < ItemArray.Size; ++c1) {
4827 if (ItemArray[c1].IsValid()) {
4828 const interval *TimesPtr = ItemArray[c1].GetTimes();
4829 int Times = TimesPtr ? TimesPtr->Randomize() : 1;
4830 for (int c2 = 0; c2 < Times; ++c2) {
4831 item *Item = ItemArray[c1].Instantiate(SpecialFlags);
4832 if (Item) {
4833 Stack->AddItem(Item);
4834 Item->SpecialGenerationHandler();
4842 truth character::HasHadBodyPart (citem *Item) const {
4843 for (int c = 0; c < BodyParts; ++c)
4844 if (std::find(OriginalBodyPartID[c].begin(), OriginalBodyPartID[c].end(), Item->GetID()) != OriginalBodyPartID[c].end())
4845 return true;
4846 return GetPolymorphBackup() && GetPolymorphBackup()->HasHadBodyPart(Item);
4850 festring &character::ProcessMessage (festring &Msg) const {
4851 SEARCH_N_REPLACE(Msg, "@nu", GetName(UNARTICLED));
4852 SEARCH_N_REPLACE(Msg, "@ni", GetName(INDEFINITE));
4853 SEARCH_N_REPLACE(Msg, "@nd", GetName(DEFINITE));
4854 SEARCH_N_REPLACE(Msg, "@du", GetDescription(UNARTICLED));
4855 SEARCH_N_REPLACE(Msg, "@di", GetDescription(INDEFINITE));
4856 SEARCH_N_REPLACE(Msg, "@dd", GetDescription(DEFINITE));
4857 SEARCH_N_REPLACE(Msg, "@pp", GetPersonalPronoun());
4858 SEARCH_N_REPLACE(Msg, "@sp", GetPossessivePronoun());
4859 SEARCH_N_REPLACE(Msg, "@op", GetObjectPronoun());
4860 SEARCH_N_REPLACE(Msg, "@Nu", GetName(UNARTICLED).CapitalizeCopy());
4861 SEARCH_N_REPLACE(Msg, "@Ni", GetName(INDEFINITE).CapitalizeCopy());
4862 SEARCH_N_REPLACE(Msg, "@Nd", GetName(DEFINITE).CapitalizeCopy());
4863 SEARCH_N_REPLACE(Msg, "@Du", GetDescription(UNARTICLED).CapitalizeCopy());
4864 SEARCH_N_REPLACE(Msg, "@Di", GetDescription(INDEFINITE).CapitalizeCopy());
4865 SEARCH_N_REPLACE(Msg, "@Dd", GetDescription(DEFINITE).CapitalizeCopy());
4866 SEARCH_N_REPLACE(Msg, "@Pp", GetPersonalPronoun().CapitalizeCopy());
4867 SEARCH_N_REPLACE(Msg, "@Sp", GetPossessivePronoun().CapitalizeCopy());
4868 SEARCH_N_REPLACE(Msg, "@Op", GetObjectPronoun().CapitalizeCopy());
4869 SEARCH_N_REPLACE(Msg, "@Gd", GetMasterGod()->GetName());
4870 return Msg;
4874 void character::ProcessAndAddMessage (festring Msg) const {
4875 ADD_MESSAGE("%s", ProcessMessage(Msg).CStr());
4879 void character::BeTalkedTo () {
4880 static sLong Said;
4881 if (GetRelation(PLAYER) == HOSTILE)
4882 ProcessAndAddMessage(GetHostileReplies()[RandomizeReply(Said, GetHostileReplies().Size)]);
4883 else
4884 ProcessAndAddMessage(GetFriendlyReplies()[RandomizeReply(Said, GetFriendlyReplies().Size)]);
4888 truth character::CheckZap () {
4889 if (!CanZap()) {
4890 ADD_MESSAGE("This monster type can't zap.");
4891 return false;
4893 return true;
4897 void character::DamageAllItems (character *Damager, int Damage, int Type) {
4898 GetStack()->ReceiveDamage(Damager, Damage, Type);
4899 for (int c = 0; c < GetEquipments(); ++c) {
4900 item *Equipment = GetEquipment(c);
4901 if (Equipment) Equipment->ReceiveDamage(Damager, Damage, Type);
4906 truth character::Equips (citem *Item) const {
4907 return combineequipmentpredicateswithparam<uLong>()(this, &item::HasID, Item->GetID(), 1);
4911 void character::PrintBeginConfuseMessage () const {
4912 if (IsPlayer()) ADD_MESSAGE("You feel quite happy.");
4916 void character::PrintEndConfuseMessage () const {
4917 if (IsPlayer()) ADD_MESSAGE("The world is boring again.");
4921 v2 character::ApplyStateModification (v2 TryDirection) const {
4922 if (!StateIsActivated(CONFUSED) || RAND() & 15 || game::IsInWilderness()) return TryDirection;
4923 v2 To = GetLevel()->GetFreeAdjacentSquare(this, GetPos(), true);
4924 if (To == ERROR_V2) return TryDirection;
4925 To -= GetPos();
4926 if (To != TryDirection && IsPlayer()) ADD_MESSAGE("Whoa! You somehow don't manage to walk straight.");
4927 return To;
4931 void character::AddConfuseHitMessage () const {
4932 if (IsPlayer()) ADD_MESSAGE("This stuff is confusing.");
4936 item *character::SelectFromPossessions (cfestring &Topic, sorter Sorter) {
4937 itemvector ReturnVector;
4938 SelectFromPossessions(ReturnVector, Topic, NO_MULTI_SELECT, Sorter);
4939 return !ReturnVector.empty() ? ReturnVector[0] : 0;
4943 truth character::SelectFromPossessions (itemvector &ReturnVector, cfestring &Topic, int Flags, sorter Sorter) {
4944 felist List(Topic);
4945 truth InventoryPossible = GetStack()->SortedItems(this, Sorter);
4946 if (InventoryPossible) List.AddEntry(CONST_S("choose from inventory"), LIGHT_GRAY, 20, game::AddToItemDrawVector(itemvector()));
4947 truth Any = false;
4948 itemvector Item;
4949 festring Entry;
4950 int c;
4951 for (c = 0; c < BodyParts; ++c) {
4952 bodypart *BodyPart = GetBodyPart(c);
4953 if (BodyPart && (Sorter == 0 || (BodyPart->*Sorter)(this))) {
4954 Item.push_back(BodyPart);
4955 Entry.Empty();
4956 BodyPart->AddName(Entry, UNARTICLED);
4957 int ImageKey = game::AddToItemDrawVector(itemvector(1, BodyPart));
4958 List.AddEntry(Entry, LIGHT_GRAY, 20, ImageKey, true);
4959 Any = true;
4962 for (c = 0; c < GetEquipments(); ++c) {
4963 item *Equipment = GetEquipment(c);
4964 if (Equipment && (Sorter == 0 || (Equipment->*Sorter)(this))) {
4965 Item.push_back(Equipment);
4966 Entry = GetEquipmentName(c);
4967 Entry << ':';
4968 Entry.Resize(20);
4969 Equipment->AddInventoryEntry(this, Entry, 1, true);
4970 AddSpecialEquipmentInfo(Entry, c);
4971 int ImageKey = game::AddToItemDrawVector(itemvector(1, Equipment));
4972 List.AddEntry(Entry, LIGHT_GRAY, 20, ImageKey, true);
4973 Any = true;
4976 if (Any) {
4977 game::SetStandardListAttributes(List);
4978 List.SetFlags(SELECTABLE|DRAW_BACKGROUND_AFTERWARDS);
4979 List.SetEntryDrawer(game::ItemEntryDrawer);
4980 game::DrawEverythingNoBlit();
4981 int Chosen = List.Draw();
4982 game::ClearItemDrawVector();
4983 if (Chosen != ESCAPED) {
4984 if ((InventoryPossible && !Chosen) || Chosen & FELIST_ERROR_BIT) {
4985 GetStack()->DrawContents(ReturnVector, this, Topic, Flags, Sorter);
4986 } else {
4987 ReturnVector.push_back(Item[InventoryPossible ? Chosen - 1 : Chosen]);
4988 if (Flags & SELECT_PAIR && ReturnVector[0]->HandleInPairs()) {
4989 item *PairEquipment = GetPairEquipment(ReturnVector[0]->GetEquipmentIndex());
4990 if (PairEquipment && PairEquipment->CanBePiledWith(ReturnVector[0], this)) ReturnVector.push_back(PairEquipment);
4994 } else {
4995 if (!GetStack()->SortedItems(this, Sorter)) return false;
4996 game::ClearItemDrawVector();
4997 GetStack()->DrawContents(ReturnVector, this, Topic, Flags, Sorter);
4999 return true;
5003 truth character::EquipsSomething (sorter Sorter) {
5004 for (int c = 0; c < GetEquipments(); ++c) {
5005 item *Equipment = GetEquipment(c);
5006 if (Equipment && (Sorter == 0 || (Equipment->*Sorter)(this))) return true;
5008 return false;
5012 material *character::CreateBodyPartMaterial (int, sLong Volume) const {
5013 return MAKE_MATERIAL(GetFleshMaterial(), Volume);
5017 truth character::CheckTalk () {
5018 if (!CanTalk()) {
5019 ADD_MESSAGE("This monster does not know the art of talking.");
5020 return false;
5022 return true;
5026 truth character::MoveTowardsHomePos () {
5027 if (HomeDataIsValid() && IsEnabled()) {
5028 SetGoingTo(HomeData->Pos);
5029 return MoveTowardsTarget(false) || (!GetPos().IsAdjacent(HomeData->Pos) && MoveRandomly());
5031 return false;
5035 truth character::TryToChangeEquipment (stack *MainStack, stack *SecStack, int Chosen) {
5036 if (!GetBodyPartOfEquipment(Chosen)) {
5037 ADD_MESSAGE("Bodypart missing!");
5038 return false;
5040 item *OldEquipment = GetEquipment(Chosen);
5041 if (!IsPlayer() && BoundToUse(OldEquipment, Chosen)) {
5042 ADD_MESSAGE("%s refuses to unequip %s.", CHAR_DESCRIPTION(DEFINITE), OldEquipment->CHAR_NAME(DEFINITE));
5043 return false;
5045 if (OldEquipment) OldEquipment->MoveTo(MainStack);
5046 sorter Sorter = EquipmentSorter(Chosen);
5047 if (!MainStack->SortedItems(this, Sorter) && (!SecStack || !SecStack->SortedItems(this, Sorter))) {
5048 ADD_MESSAGE("You haven't got any item that could be used for this purpose.");
5049 return false;
5051 game::DrawEverythingNoBlit();
5052 itemvector ItemVector;
5053 int Return = MainStack->DrawContents(ItemVector, SecStack, this,
5054 CONST_S("Choose ") + GetEquipmentName(Chosen) + ':',
5055 SecStack ? CONST_S("Items in your inventory") : CONST_S(""),
5056 SecStack ? festring(CONST_S("Items in ") + GetPossessivePronoun() + " inventory") : CONST_S(""),
5057 SecStack ? festring(GetDescription(DEFINITE) + " is " + GetVerbalBurdenState()) : CONST_S(""),
5058 GetVerbalBurdenStateColor(),
5059 NONE_AS_CHOICE|NO_MULTI_SELECT,
5060 Sorter);
5061 if (Return == ESCAPED) {
5062 if (OldEquipment) {
5063 OldEquipment->RemoveFromSlot();
5064 SetEquipment(Chosen, OldEquipment);
5066 return false;
5068 item *Item = ItemVector.empty() ? 0 : ItemVector[0];
5069 if (Item) {
5070 if (!IsPlayer() && !AllowEquipment(Item, Chosen)) {
5071 ADD_MESSAGE("%s refuses to equip %s.", CHAR_DESCRIPTION(DEFINITE), Item->CHAR_NAME(DEFINITE));
5072 return false;
5074 Item->RemoveFromSlot();
5075 SetEquipment(Chosen, Item);
5076 if (CheckIfEquipmentIsNotUsable(Chosen)) Item->MoveTo(MainStack); // small bug?
5078 return Item != OldEquipment;
5082 void character::PrintBeginParasitizedMessage () const {
5083 if (IsPlayer()) ADD_MESSAGE("You feel you are no longer alone.");
5087 void character::PrintEndParasitizedMessage () const {
5088 if (IsPlayer()) ADD_MESSAGE("A feeling of sLong welcome emptiness overwhelms you.");
5092 void character::ParasitizedHandler () {
5093 EditNP(-5);
5094 if (!(RAND() % 250)) {
5095 if (IsPlayer()) ADD_MESSAGE("Ugh. You feel something violently carving its way through your intestines.");
5096 ReceiveDamage(0, 1, POISON, TORSO, 8, false, false, false, false);
5097 CheckDeath(CONST_S("killed by a vile parasite"), 0);
5102 truth character::CanFollow () const {
5103 return CanMove() && !StateIsActivated(PANIC) && !IsStuck();
5107 festring character::GetKillName () const {
5108 if (!GetPolymorphBackup()) return GetName(INDEFINITE);
5109 festring KillName;
5110 GetPolymorphBackup()->AddName(KillName, INDEFINITE);
5111 KillName << " polymorphed into ";
5112 id::AddName(KillName, INDEFINITE);
5113 return KillName;
5117 festring character::GetPanelName () const {
5118 festring Name;
5119 Name << AssignedName << " the " << game::GetVerbalPlayerAlignment() << ' ';
5120 id::AddName(Name, UNARTICLED);
5121 return Name;
5125 sLong character::GetMoveAPRequirement (int Difficulty) const {
5126 return (!StateIsActivated(PANIC) ? 10000000 : 8000000) * Difficulty / (APBonus(GetAttribute(AGILITY)) * GetMoveEase());
5130 bodypart *character::HealHitPoint() {
5131 int NeedHeal = 0, NeedHealIndex[MAX_BODYPARTS];
5132 for (int c = 0; c < BodyParts; ++c) {
5133 bodypart *BodyPart = GetBodyPart(c);
5134 if (BodyPart && BodyPart->CanRegenerate() && BodyPart->GetHP() < BodyPart->GetMaxHP()) NeedHealIndex[NeedHeal++] = c;
5136 if (NeedHeal) {
5137 bodypart *BodyPart = GetBodyPart(NeedHealIndex[RAND() % NeedHeal]);
5138 BodyPart->IncreaseHP();
5139 ++HP;
5140 return BodyPart;
5142 return 0;
5146 void character::CreateHomeData () {
5147 HomeData = new homedata;
5148 lsquare *Square = GetLSquareUnder();
5149 HomeData->Pos = Square->GetPos();
5150 HomeData->Dungeon = Square->GetDungeonIndex();
5151 HomeData->Level = Square->GetLevelIndex();
5152 HomeData->Room = Square->GetRoomIndex();
5156 room *character::GetHomeRoom() const {
5157 if (HomeDataIsValid() && HomeData->Room) return GetLevel()->GetRoom(HomeData->Room);
5158 return 0;
5162 void character::RemoveHomeData () {
5163 delete HomeData;
5164 HomeData = 0;
5168 void character::AddESPConsumeMessage () const {
5169 if (IsPlayer()) ADD_MESSAGE("You feel a strange mental activity.");
5173 void character::SetBodyPart (int I, bodypart *What) {
5174 BodyPartSlot[I].PutInItem(What);
5175 if (What) {
5176 What->SignalPossibleUsabilityChange();
5177 What->Disable();
5178 AddOriginalBodyPartID(I, What->GetID());
5179 if (What->GetMainMaterial()->IsInfectedByLeprosy()) GainIntrinsic(LEPROSY);
5180 else if (StateIsActivated(LEPROSY)) What->GetMainMaterial()->SetIsInfectedByLeprosy(true);
5185 truth character::ConsumeItem (item *Item, cfestring &ConsumeVerb) {
5186 if (IsPlayer() && HasHadBodyPart(Item) && !game::TruthQuestion(CONST_S("Are you sure? You may be able to put it back... [y/N]")))
5187 return false;
5188 if (Item->IsOnGround() && GetRoom() && !GetRoom()->ConsumeItem(this, Item, 1))
5189 return false;
5190 if (IsPlayer()) ADD_MESSAGE("You begin %s %s.", ConsumeVerb.CStr(), Item->CHAR_NAME(DEFINITE));
5191 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s begins %s %s.", CHAR_NAME(DEFINITE), ConsumeVerb.CStr(), Item->CHAR_NAME(DEFINITE));
5192 consume *Consume = consume::Spawn(this);
5193 Consume->SetDescription(ConsumeVerb);
5194 Consume->SetConsumingID(Item->GetID());
5195 SetAction(Consume);
5196 DexterityAction(5);
5197 return true;
5201 truth character::CheckThrow () const {
5202 if (!CanThrow()) {
5203 ADD_MESSAGE("This monster type cannot throw.");
5204 return false;
5206 return true;
5210 void character::GetHitByExplosion (const explosion *Explosion, int Damage) {
5211 int DamageDirection = GetPos() == Explosion->Pos ? RANDOM_DIR : game::CalculateRoughDirection(GetPos() - Explosion->Pos);
5212 if (!IsPet() && Explosion->Terrorist && Explosion->Terrorist->IsPet()) Explosion->Terrorist->Hostility(this);
5213 GetTorso()->SpillBlood((8 - Explosion->Size + RAND() % (8 - Explosion->Size)) >> 1);
5214 v2 SpillPos = GetPos() + game::GetMoveVector(DamageDirection);
5215 if (GetArea()->IsValidPos(SpillPos)) GetTorso()->SpillBlood((8-Explosion->Size+RAND()%(8-Explosion->Size))>>1, SpillPos);
5216 if (IsPlayer()) ADD_MESSAGE("You are hit by the explosion!");
5217 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s is hit by the explosion.", CHAR_NAME(DEFINITE));
5218 truth WasUnconscious = GetAction() && GetAction()->IsUnconsciousness();
5219 ReceiveDamage(Explosion->Terrorist, Damage >> 1, FIRE, ALL, DamageDirection, true, false, false, false);
5220 if (IsEnabled()) {
5221 ReceiveDamage(Explosion->Terrorist, Damage >> 1, PHYSICAL_DAMAGE, ALL, DamageDirection, true, false, false, false);
5222 CheckDeath(Explosion->DeathMsg, Explosion->Terrorist, !WasUnconscious ? IGNORE_UNCONSCIOUSNESS : 0);
5227 void character::SortAllItems (const sortdata &SortData) {
5228 GetStack()->SortAllItems(SortData);
5229 doforequipmentswithparam<const sortdata&>()(this, &item::SortAllItems, SortData);
5233 void character::PrintBeginSearchingMessage () const {
5234 if (IsPlayer()) ADD_MESSAGE("You feel you can now notice even the very smallest details around you.");
5238 void character::PrintEndSearchingMessage () const {
5239 if (IsPlayer()) ADD_MESSAGE("You feel less perceptive.");
5243 void character::SearchingHandler () {
5244 if (!game::IsInWilderness()) Search(15);
5248 void character::Search (int Perception) {
5249 for (int d = 0; d < GetExtendedNeighbourSquares(); ++d) {
5250 lsquare *LSquare = GetNeighbourLSquare(d);
5251 if (LSquare) LSquare->GetStack()->Search(this, Min(Perception, 200));
5256 // surprisingly returns 0 if fails
5257 character *character::GetRandomNeighbour (int RelationFlags) const {
5258 character *Chars[MAX_NEIGHBOUR_SQUARES];
5259 int Index = 0;
5260 for (int d = 0; d < GetNeighbourSquares(); ++d) {
5261 lsquare *LSquare = GetNeighbourLSquare(d);
5262 if (LSquare) {
5263 character *Char = LSquare->GetCharacter();
5264 if (Char && (GetRelation(Char) & RelationFlags)) Chars[Index++] = Char;
5267 return Index ? Chars[RAND() % Index] : 0;
5271 void character::ResetStates () {
5272 for (int c = 0; c < STATES; ++c) {
5273 if (1 << c != POLYMORPHED && TemporaryStateIsActivated(1 << c) && TemporaryStateCounter[c] != PERMANENT) {
5274 TemporaryState &= ~(1 << c);
5275 if (StateData[c].EndHandler) {
5276 (this->*StateData[c].EndHandler)();
5277 if (!IsEnabled())return;
5284 void characterdatabase::InitDefaults (const characterprototype *NewProtoType, int NewConfig) {
5285 IsAbstract = false;
5286 ProtoType = NewProtoType;
5287 Config = NewConfig;
5288 Alias.Clear();
5292 void character::PrintBeginGasImmunityMessage () const {
5293 if (IsPlayer()) ADD_MESSAGE("All smells fade away.");
5297 void character::PrintEndGasImmunityMessage () const {
5298 if (IsPlayer()) ADD_MESSAGE("Yuck! The world smells bad again.");
5302 void character::ShowAdventureInfo () const {
5303 static const char *lists[4][4] = {
5304 { "Show massacre history",
5305 "Show inventory",
5306 "Show message history",
5307 NULL },
5308 { "Show inventory",
5309 "Show message history",
5310 NULL,
5311 NULL },
5312 { "Show message history",
5313 NULL,
5314 NULL,
5315 NULL },
5316 { "Show massacre history",
5317 "Show message history",
5318 NULL,
5319 NULL }
5321 // massacre, inventory, messages
5322 static const int nums[4][3] = {
5323 { 0, 1, 2},
5324 {-1, 0, 1},
5325 {-1,-1, 0},
5326 { 0,-1, 0}
5328 int idx = 0;
5329 if (GetStack()->GetItems()) {
5330 idx = game::MassacreListsEmpty() ? 1 : 0;
5331 } else {
5332 idx = game::MassacreListsEmpty() ? 2 : 3;
5334 int sel = -1;
5335 for (;;) {
5336 sel = game::ListSelectorArray(sel, CONST_S("Do you want to see some funny history?"), lists[idx]);
5337 if (sel < 0) break;
5338 if (sel == nums[idx][0] && !game::MassacreListsEmpty()) game::DisplayMassacreLists();
5339 if (sel == nums[idx][1] && GetStack()->GetItems()) {
5340 GetStack()->DrawContents(this, CONST_S("Your inventory"), NO_SELECT);
5341 for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) i->DrawContents(this);
5342 doforequipmentswithparam<ccharacter *>()(this, &item::DrawContents, this);
5344 if (sel == nums[idx][2]) msgsystem::DrawMessageHistory();
5349 truth character::EditAllAttributes (int Amount) {
5350 if (!Amount) return true;
5351 int c;
5352 truth MayEditMore = false;
5353 for (c = 0; c < BodyParts; ++c) {
5354 bodypart *BodyPart = GetBodyPart(c);
5355 if (BodyPart && BodyPart->EditAllAttributes(Amount)) MayEditMore = true;
5357 for (c = 0; c < BASE_ATTRIBUTES; ++c) {
5358 if (BaseExperience[c]) {
5359 BaseExperience[c] += Amount * EXP_MULTIPLIER;
5360 LimitRef(BaseExperience[c], MIN_EXP, MAX_EXP);
5361 if ((Amount < 0 && BaseExperience[c] != MIN_EXP) || (Amount > 0 && BaseExperience[c] != MAX_EXP)) MayEditMore = true;
5364 CalculateAll();
5365 RestoreHP();
5366 RestoreStamina();
5367 if (IsPlayer()) {
5368 game::SendLOSUpdateRequest();
5369 UpdateESPLOS();
5371 if (IsPlayerKind()) UpdatePictures();
5372 return MayEditMore;
5376 #ifdef WIZARD
5377 void character::AddAttributeInfo (festring &Entry) const {
5378 Entry.Resize(57);
5379 Entry << GetAttribute(ENDURANCE);
5380 Entry.Resize(60);
5381 Entry << GetAttribute(PERCEPTION);
5382 Entry.Resize(63);
5383 Entry << GetAttribute(INTELLIGENCE);
5384 Entry.Resize(66);
5385 Entry << GetAttribute(WISDOM);
5386 Entry.Resize(69);
5387 Entry << GetAttribute(CHARISMA);
5388 Entry.Resize(72);
5389 Entry << GetAttribute(MANA);
5393 void character::AddDefenceInfo (felist &List) const {
5394 festring Entry;
5395 for (int c = 0; c < BodyParts; ++c) {
5396 bodypart *BodyPart = GetBodyPart(c);
5397 if (BodyPart) {
5398 Entry = CONST_S(" ");
5399 BodyPart->AddName(Entry, UNARTICLED);
5400 Entry.Resize(60);
5401 Entry << BodyPart->GetMaxHP();
5402 Entry.Resize(70);
5403 Entry << BodyPart->GetTotalResistance(PHYSICAL_DAMAGE);
5404 List.AddEntry(Entry, LIGHT_GRAY);
5410 void character::DetachBodyPart () {
5411 ADD_MESSAGE("You haven't got any extra bodyparts.");
5413 #endif
5416 void character::ReceiveHolyBanana (sLong Amount) {
5417 Amount <<= 1;
5418 EditExperience(ARM_STRENGTH, Amount, 1 << 13);
5419 EditExperience(LEG_STRENGTH, Amount, 1 << 13);
5420 EditExperience(DEXTERITY, Amount, 1 << 13);
5421 EditExperience(AGILITY, Amount, 1 << 13);
5422 EditExperience(ENDURANCE, Amount, 1 << 13);
5423 EditExperience(PERCEPTION, Amount, 1 << 13);
5424 EditExperience(INTELLIGENCE, Amount, 1 << 13);
5425 EditExperience(WISDOM, Amount, 1 << 13);
5426 EditExperience(CHARISMA, Amount, 1 << 13);
5427 RestoreLivingHP();
5431 void character::AddHolyBananaConsumeEndMessage () const {
5432 if (IsPlayer()) ADD_MESSAGE("You feel a mysterious strengthening fire coursing through your body.");
5433 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s is surrounded by a swirling fire aura.", CHAR_NAME(DEFINITE));
5437 truth character::PreProcessForBone () {
5438 if (IsPet() && IsEnabled()) {
5439 Die(0, CONST_S(""), FORBID_REINCARNATION);
5440 return true;
5442 if (GetAction()) GetAction()->Terminate(false);
5443 if (TemporaryStateIsActivated(POLYMORPHED)) {
5444 character *PolymorphBackup = GetPolymorphBackup();
5445 EndPolymorph();
5446 PolymorphBackup->PreProcessForBone();
5447 return true;
5449 if (MustBeRemovedFromBone()) return false;
5450 if (IsUnique() && !CanBeGenerated()) game::SignalQuestMonsterFound();
5451 RestoreLivingHP();
5452 ResetStates();
5453 RemoveTraps();
5454 GetStack()->PreProcessForBone();
5455 doforequipments()(this, &item::PreProcessForBone);
5456 doforbodyparts()(this, &bodypart::PreProcessForBone);
5457 game::RemoveCharacterID(ID);
5458 ID = -ID;
5459 game::AddCharacterID(this, ID);
5460 return true;
5464 truth character::PostProcessForBone (double &DangerSum, int& Enemies) {
5465 if (PostProcessForBone()) {
5466 if (GetRelation(PLAYER) == HOSTILE) {
5467 double Danger = GetRelativeDanger(PLAYER, true);
5468 if (Danger > 99.0) game::SetTooGreatDangerFound(true);
5469 else if (!IsUnique() && !IgnoreDanger()) {
5470 DangerSum += Danger;
5471 ++Enemies;
5474 return true;
5476 return false;
5480 truth character::PostProcessForBone () {
5481 uLong NewID = game::CreateNewCharacterID(this);
5482 game::GetBoneCharacterIDMap().insert(std::make_pair(-ID, NewID));
5483 game::RemoveCharacterID(ID);
5484 ID = NewID;
5485 if (IsUnique() && CanBeGenerated()) {
5486 if (DataBase->Flags & HAS_BEEN_GENERATED) return false;
5487 SignalGeneration();
5489 GetStack()->PostProcessForBone();
5490 doforequipments()(this, &item::PostProcessForBone);
5491 doforbodyparts()(this, &bodypart::PostProcessForBone);
5492 return true;
5496 void character::FinalProcessForBone () {
5497 Flags &= ~C_PLAYER;
5498 GetStack()->FinalProcessForBone();
5499 doforequipments()(this, &item::FinalProcessForBone);
5500 int c;
5501 for (c = 0; c < BodyParts; ++c) {
5502 for (std::list<uLong>::iterator i = OriginalBodyPartID[c].begin(); i != OriginalBodyPartID[c].end();) {
5503 boneidmap::iterator BI = game::GetBoneItemIDMap().find(*i);
5504 if (BI == game::GetBoneItemIDMap().end()) {
5505 std::list<uLong>::iterator Dirt = i++;
5506 OriginalBodyPartID[c].erase(Dirt);
5507 } else {
5508 *i = BI->second;
5509 ++i;
5516 void character::SetSoulID (uLong What) {
5517 if (GetPolymorphBackup()) GetPolymorphBackup()->SetSoulID(What);
5521 truth character::SearchForItem (citem *Item) const {
5522 if (combineequipmentpredicateswithparam<uLong>()(this, &item::HasID, Item->GetID(), 1)) return true;
5523 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) if (*i == Item) return true;
5524 return false;
5528 item *character::SearchForItem (const sweaponskill *SWeaponSkill) const {
5529 for (int c = 0; c < GetEquipments(); ++c) {
5530 item *Equipment = GetEquipment(c);
5531 if (Equipment && SWeaponSkill->IsSkillOf(Equipment)) return Equipment;
5533 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) if (SWeaponSkill->IsSkillOf(*i)) return *i;
5534 return 0;
5538 void character::PutNear (v2 Pos) {
5539 v2 NewPos = game::GetCurrentLevel()->GetNearestFreeSquare(this, Pos, false);
5540 if (NewPos == ERROR_V2) {
5541 do { NewPos = game::GetCurrentLevel()->GetRandomSquare(this); } while(NewPos == Pos);
5543 PutTo(NewPos);
5547 void character::PutToOrNear (v2 Pos) {
5548 if (game::IsInWilderness() || (CanMoveOn(game::GetCurrentLevel()->GetLSquare(Pos)) && IsFreeForMe(game::GetCurrentLevel()->GetLSquare(Pos))))
5549 PutTo(Pos);
5550 else
5551 PutNear(Pos);
5555 void character::PutTo (v2 Pos) {
5556 SquareUnder[0] = game::GetCurrentArea()->GetSquare(Pos);
5557 SquareUnder[0]->AddCharacter(this);
5561 void character::Remove () {
5562 SquareUnder[0]->RemoveCharacter();
5563 SquareUnder[0] = 0;
5567 void character::SendNewDrawRequest () const {
5568 for (int c = 0; c < SquaresUnder; ++c) {
5569 square *Square = GetSquareUnder(c);
5570 if (Square) Square->SendNewDrawRequest();
5575 truth character::IsOver (v2 Pos) const {
5576 for (int c = 0; c < SquaresUnder; ++c) {
5577 square *Square = GetSquareUnder(c);
5578 if (Square && Square->GetPos() == Pos) return true;
5580 return false;
5584 truth character::CanTheoreticallyMoveOn (const lsquare *LSquare) const { return GetMoveType() & LSquare->GetTheoreticalWalkability(); }
5585 truth character::CanMoveOn (const lsquare *LSquare) const { return GetMoveType() & LSquare->GetWalkability(); }
5586 truth character::CanMoveOn (const square *Square) const { return GetMoveType() & Square->GetSquareWalkability(); }
5587 truth character::CanMoveOn (const olterrain *OLTerrain) const { return GetMoveType() & OLTerrain->GetWalkability(); }
5588 truth character::CanMoveOn (const oterrain *OTerrain) const { return GetMoveType() & OTerrain->GetWalkability(); }
5589 truth character::IsFreeForMe(square *Square) const { return !Square->GetCharacter() || Square->GetCharacter() == this; }
5590 void character::LoadSquaresUnder () { SquareUnder[0] = game::GetSquareInLoad(); }
5592 truth character::AttackAdjacentEnemyAI () {
5593 if (!IsEnabled()) return false;
5594 character *Char[MAX_NEIGHBOUR_SQUARES];
5595 v2 Pos[MAX_NEIGHBOUR_SQUARES];
5596 int Dir[MAX_NEIGHBOUR_SQUARES];
5597 int Index = 0;
5598 for (int d = 0; d < GetNeighbourSquares(); ++d) {
5599 square *Square = GetNeighbourSquare(d);
5600 if (Square) {
5601 character *Enemy = Square->GetCharacter();
5602 if (Enemy && (GetRelation(Enemy) == HOSTILE || StateIsActivated(CONFUSED))) {
5603 Dir[Index] = d;
5604 Pos[Index] = Square->GetPos();
5605 Char[Index++] = Enemy;
5609 if (Index) {
5610 int ChosenIndex = RAND() % Index;
5611 Hit(Char[ChosenIndex], Pos[ChosenIndex], Dir[ChosenIndex]);
5612 return true;
5614 return false;
5618 void character::SignalStepFrom (lsquare **OldSquareUnder) {
5619 int c;
5620 lsquare *NewSquareUnder[MAX_SQUARES_UNDER];
5621 for (c = 0; c < GetSquaresUnder(); ++c) NewSquareUnder[c] = GetLSquareUnder(c);
5622 for (c = 0; c < GetSquaresUnder(); ++c) {
5623 if (IsEnabled() && GetLSquareUnder(c) == NewSquareUnder[c]) NewSquareUnder[c]->StepOn(this, OldSquareUnder);
5628 int character::GetSumOfAttributes () const {
5629 return GetAttribute(ENDURANCE) + GetAttribute(PERCEPTION) + GetAttribute(INTELLIGENCE) + GetAttribute(WISDOM) + GetAttribute(CHARISMA) + GetAttribute(ARM_STRENGTH) + GetAttribute(AGILITY);
5633 void character::IntelligenceAction (int Difficulty) {
5634 EditAP(-20000 * Difficulty / APBonus(GetAttribute(INTELLIGENCE)));
5635 EditExperience(INTELLIGENCE, Difficulty * 15, 1 << 7);
5639 struct walkabilitycontroller {
5640 static truth Handler (int x, int y) {
5641 return x >= 0 && y >= 0 && x < LevelXSize && y < LevelYSize && Map[x][y]->GetTheoreticalWalkability() & MoveType;
5643 static lsquare ***Map;
5644 static int LevelXSize, LevelYSize;
5645 static int MoveType;
5649 lsquare ***walkabilitycontroller::Map;
5650 int walkabilitycontroller::LevelXSize, walkabilitycontroller::LevelYSize;
5651 int walkabilitycontroller::MoveType;
5654 truth character::CreateRoute () {
5655 Route.clear();
5656 if (GetAttribute(INTELLIGENCE) >= 10 && !StateIsActivated(CONFUSED)) {
5657 v2 Pos = GetPos();
5658 walkabilitycontroller::Map = GetLevel()->GetMap();
5659 walkabilitycontroller::LevelXSize = GetLevel()->GetXSize();
5660 walkabilitycontroller::LevelYSize = GetLevel()->GetYSize();
5661 walkabilitycontroller::MoveType = GetMoveType();
5662 node *Node;
5663 for (int c = 0; c < game::GetTeams(); ++c)
5664 for (std::list<character *>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i) {
5665 character *Char = *i;
5666 if (Char->IsEnabled() && !Char->Route.empty() && (Char->GetMoveType()&GetMoveType()) == Char->GetMoveType()) {
5667 v2 CharGoingTo = Char->Route[0];
5668 v2 iPos = Char->Route.back();
5669 if ((GoingTo-CharGoingTo).GetLengthSquare() <= 100 && (Pos - iPos).GetLengthSquare() <= 100 &&
5670 mapmath<walkabilitycontroller>::DoLine(CharGoingTo.X, CharGoingTo.Y, GoingTo.X, GoingTo.Y, SKIP_FIRST) &&
5671 mapmath<walkabilitycontroller>::DoLine(Pos.X, Pos.Y, iPos.X, iPos.Y, SKIP_FIRST)) {
5672 if (!Illegal.empty() && Illegal.find(Char->Route.back()) != Illegal.end()) continue;
5673 Node = GetLevel()->FindRoute(CharGoingTo, GoingTo, Illegal, GetMoveType());
5674 if (Node) { while(Node->Last) { Route.push_back(Node->Pos); Node = Node->Last; } }
5675 else { Route.clear(); continue; }
5676 Route.insert(Route.end(), Char->Route.begin(), Char->Route.end());
5677 Node = GetLevel()->FindRoute(Pos, iPos, Illegal, GetMoveType());
5678 if (Node) { while (Node->Last) { Route.push_back(Node->Pos); Node = Node->Last; } }
5679 else { Route.clear(); continue; }
5680 IntelligenceAction(1);
5681 return true;
5685 Node = GetLevel()->FindRoute(Pos, GoingTo, Illegal, GetMoveType());
5686 if (Node) { while(Node->Last) { Route.push_back(Node->Pos); Node = Node->Last; } }
5687 else TerminateGoingTo();
5688 IntelligenceAction(5);
5689 return true;
5691 return false;
5695 void character::SetGoingTo (v2 What) {
5696 if (GoingTo != What) {
5697 GoingTo = What;
5698 Route.clear();
5699 Illegal.clear();
5704 void character::TerminateGoingTo () {
5705 GoingTo = ERROR_V2;
5706 Route.clear();
5707 Illegal.clear();
5711 truth character::CheckForFood (int Radius) {
5712 if (StateIsActivated(PANIC) || !UsesNutrition() || !IsEnabled()) return false;
5713 v2 Pos = GetPos();
5714 int x, y;
5715 for (int r = 1; r <= Radius; ++r) {
5716 x = Pos.X-r;
5717 if (x >= 0) {
5718 for (y = Pos.Y-r; y <= Pos.Y+r; ++y) if (CheckForFoodInSquare(v2(x, y))) return true;
5720 x = Pos.X+r;
5721 if (x < GetLevel()->GetXSize()) {
5722 for (y = Pos.Y-r; y <= Pos.Y+r; ++y) if (CheckForFoodInSquare(v2(x, y))) return true;
5724 y = Pos.Y-r;
5725 if (y >= 0) {
5726 for (x = Pos.X-r; x <= Pos.X+r; ++x) if (CheckForFoodInSquare(v2(x, y))) return true;
5728 y = Pos.Y+r;
5729 if (y < GetLevel()->GetYSize()) {
5730 for (x = Pos.X-r; x <= Pos.X+r; ++x) if (CheckForFoodInSquare(v2(x, y))) return true;
5733 return false;
5737 truth character::CheckForFoodInSquare (v2 Pos) {
5738 level *Level = GetLevel();
5739 if (Level->IsValidPos(Pos)) {
5740 lsquare *Square = Level->GetLSquare(Pos);
5741 stack *Stack = Square->GetStack();
5742 if (Stack->GetItems()) {
5743 for (stackiterator i = Stack->GetBottom(); i.HasItem(); ++i) {
5744 if (i->IsPickable(this) && i->CanBeSeenBy(this) && i->CanBeEatenByAI(this) && (!Square->GetRoomIndex() || Square->GetRoom()->AllowFoodSearch())) {
5745 SetGoingTo(Pos);
5746 return MoveTowardsTarget(false);
5751 return false;
5755 void character::SetConfig (int NewConfig, int SpecialFlags) {
5756 databasecreator<character>::InstallDataBase(this, NewConfig);
5757 CalculateAll();
5758 CheckIfSeen();
5759 if (!(SpecialFlags & NO_PIC_UPDATE)) UpdatePictures();
5763 truth character::IsOver (citem *Item) const {
5764 for (int c1 = 0; c1 < Item->GetSquaresUnder(); ++c1)
5765 for (int c2 = 0; c2 < SquaresUnder; ++c2)
5766 if (Item->GetPos(c1) == GetPos(c2)) return true;
5767 return false;
5771 truth character::CheckConsume (cfestring &Verb) const {
5772 if (!UsesNutrition()) {
5773 if (IsPlayer()) ADD_MESSAGE("In this form you can't and don't need to %s.", Verb.CStr());
5774 return false;
5776 return true;
5780 void character::PutTo (lsquare *To) {
5781 PutTo(To->GetPos());
5785 double character::RandomizeBabyExperience (double SumE) {
5786 if (!SumE) return 0;
5787 double E = (SumE / 4) - (SumE / 32) + (double(RAND()) / MAX_RAND) * (SumE / 16 + 1);
5788 return Limit(E, MIN_EXP, MAX_EXP);
5792 liquid *character::CreateBlood (sLong Volume) const {
5793 return liquid::Spawn(GetBloodMaterial(), Volume);
5797 void character::SpillFluid (character *Spiller, liquid *Liquid, int SquareIndex) {
5798 sLong ReserveVolume = Liquid->GetVolume() >> 1;
5799 Liquid->EditVolume(-ReserveVolume);
5800 GetStack()->SpillFluid(Spiller, Liquid, sLong(Liquid->GetVolume() * sqrt(double(GetStack()->GetVolume()) / GetVolume())));
5801 Liquid->EditVolume(ReserveVolume);
5802 int c;
5803 sLong Modifier[MAX_BODYPARTS], ModifierSum = 0;
5804 for (c = 0; c < BodyParts; ++c) {
5805 if (GetBodyPart(c)) {
5806 Modifier[c] = sLong(sqrt(GetBodyPart(c)->GetVolume()));
5807 if (Modifier[c]) Modifier[c] *= 1 + (RAND() & 3);
5808 ModifierSum += Modifier[c];
5809 } else {
5810 Modifier[c] = 0;
5813 for (c = 1; c < GetBodyParts(); ++c) {
5814 if (GetBodyPart(c) && IsEnabled())
5815 GetBodyPart(c)->SpillFluid(Spiller, Liquid->SpawnMoreLiquid(Liquid->GetVolume() * Modifier[c] / ModifierSum), SquareIndex);
5817 if (IsEnabled()) {
5818 Liquid->SetVolume(Liquid->GetVolume() * Modifier[TORSO_INDEX] / ModifierSum);
5819 GetTorso()->SpillFluid(Spiller, Liquid, SquareIndex);
5824 void character::StayOn (liquid *Liquid) {
5825 Liquid->TouchEffect(this, TORSO_INDEX);
5829 truth character::IsAlly (ccharacter *Char) const {
5830 return Char->GetTeam()->GetID() == GetTeam()->GetID();
5834 void character::ResetSpoiling () {
5835 doforbodyparts()(this, &bodypart::ResetSpoiling);
5839 item *character::SearchForItem (ccharacter *Char, sorter Sorter) const {
5840 item *Equipment = findequipment<ccharacter *>()(this, Sorter, Char);
5841 if (Equipment) return Equipment;
5842 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) if (((*i)->*Sorter)(Char)) return *i;
5843 return 0;
5847 truth character::DetectMaterial (cmaterial *Material) const {
5848 return GetStack()->DetectMaterial(Material) ||
5849 combinebodypartpredicateswithparam<cmaterial*>()(this, &bodypart::DetectMaterial, Material, 1) ||
5850 combineequipmentpredicateswithparam<cmaterial*>()(this, &item::DetectMaterial, Material, 1);
5854 truth character::DamageTypeDestroysBodyPart (int Type) {
5855 return (Type&0xFFF) != PHYSICAL_DAMAGE;
5859 truth character::CheckIfTooScaredToHit (ccharacter *Enemy) const {
5860 if (IsPlayer() && StateIsActivated(PANIC)) {
5861 for (int d = 0; d < GetNeighbourSquares(); ++d) {
5862 square *Square = GetNeighbourSquare(d);
5863 if (Square) {
5864 if(CanMoveOn(Square) && (!Square->GetCharacter() || Square->GetCharacter()->IsPet())) {
5865 ADD_MESSAGE("You are too scared to attack %s.", Enemy->CHAR_DESCRIPTION(DEFINITE));
5866 return true;
5871 return false;
5875 void character::PrintBeginLevitationMessage () const {
5876 if (!IsFlying()) {
5877 if (IsPlayer()) ADD_MESSAGE("You rise into the air like a small hot-air balloon.");
5878 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s begins to float.", CHAR_NAME(DEFINITE));
5883 void character::PrintEndLevitationMessage () const {
5884 if (!IsFlying()) {
5885 if (IsPlayer()) ADD_MESSAGE("You descend gently onto the ground.");
5886 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s drops onto the ground.", CHAR_NAME(DEFINITE));
5891 truth character::IsLimbIndex (int I) {
5892 switch (I) {
5893 case RIGHT_ARM_INDEX:
5894 case LEFT_ARM_INDEX:
5895 case RIGHT_LEG_INDEX:
5896 case LEFT_LEG_INDEX:
5897 return true;
5899 return false;
5903 void character::EditExperience (int Identifier, double Value, double Speed) {
5904 if (!AllowExperience() || (Identifier == ENDURANCE && UseMaterialAttributes())) return;
5905 int Change = RawEditExperience(BaseExperience[Identifier], GetNaturalExperience(Identifier), Value, Speed);
5906 if (!Change) return;
5907 cchar *PlayerMsg = 0, *NPCMsg = 0;
5908 switch (Identifier) {
5909 case ENDURANCE:
5910 if (Change > 0) {
5911 PlayerMsg = "You feel tougher than anything!";
5912 if (IsPet()) NPCMsg = "Suddenly %s looks tougher.";
5913 } else {
5914 PlayerMsg = "You feel less healthy.";
5915 if (IsPet()) NPCMsg = "Suddenly %s looks less healthy.";
5917 CalculateBodyPartMaxHPs();
5918 CalculateMaxStamina();
5919 break;
5920 case PERCEPTION:
5921 if (IsPlayer()) {
5922 if (Change > 0) {
5923 PlayerMsg = "You now see the world in much better detail than before.";
5924 } else {
5925 PlayerMsg = "You feel very guru.";
5926 game::GetGod(VALPURUS)->AdjustRelation(100);
5928 game::SendLOSUpdateRequest();
5930 break;
5931 case INTELLIGENCE:
5932 if (IsPlayer()) {
5933 if (Change > 0) PlayerMsg = "Suddenly the inner structure of the Multiverse around you looks quite simple.";
5934 else PlayerMsg = "It surely is hard to think today.";
5935 UpdateESPLOS();
5937 if (IsPlayerKind()) UpdatePictures();
5938 break;
5939 case WISDOM:
5940 if (IsPlayer()) {
5941 if (Change > 0) PlayerMsg = "You feel your life experience increasing all the time.";
5942 else PlayerMsg = "You feel like having done something unwise.";
5944 if (IsPlayerKind()) UpdatePictures();
5945 break;
5946 case CHARISMA:
5947 if (Change > 0) {
5948 PlayerMsg = "You feel very confident of your social skills.";
5949 if (IsPet()) {
5950 if (GetAttribute(CHARISMA) <= 15) NPCMsg = "%s looks less ugly.";
5951 else NPCMsg = "%s looks more attractive.";
5953 } else {
5954 PlayerMsg = "You feel somehow disliked.";
5955 if (IsPet()) {
5956 if (GetAttribute(CHARISMA) < 15) NPCMsg = "%s looks more ugly.";
5957 else NPCMsg = "%s looks less attractive.";
5960 if (IsPlayerKind()) UpdatePictures();
5961 break;
5962 case MANA:
5963 if (Change > 0) {
5964 PlayerMsg = "You feel magical forces coursing through your body!";
5965 NPCMsg = "You notice an odd glow around %s.";
5966 } else {
5967 PlayerMsg = "You feel your magical abilities withering slowly.";
5968 NPCMsg = "You notice strange vibrations in the air around %s. But they disappear rapidly.";
5970 break;
5973 if (IsPlayer()) ADD_MESSAGE(PlayerMsg);
5974 else if (NPCMsg && CanBeSeenByPlayer()) ADD_MESSAGE(NPCMsg, CHAR_NAME(DEFINITE));
5976 CalculateBattleInfo();
5980 int character::RawEditExperience (double &Exp, double NaturalExp, double Value, double Speed) const {
5981 double OldExp = Exp;
5982 if (Speed < 0) {
5983 Speed = -Speed;
5984 Value = -Value;
5986 if(!OldExp || !Value || (Value > 0 && OldExp >= NaturalExp * (100 + Value) / 100) ||
5987 (Value < 0 && OldExp <= NaturalExp * (100 + Value) / 100)) return 0;
5988 if (!IsPlayer()) Speed *= 1.5;
5989 Exp += (NaturalExp * (100 + Value) - 100 * OldExp) * Speed * EXP_DIVISOR;
5990 LimitRef(Exp, MIN_EXP, MAX_EXP);
5991 int NewA = int(Exp * EXP_DIVISOR);
5992 int OldA = int(OldExp * EXP_DIVISOR);
5993 int Delta = NewA - OldA;
5994 if (Delta > 0) Exp = Max(Exp, (NewA + 0.05) * EXP_MULTIPLIER);
5995 else if (Delta < 0) Exp = Min(Exp, (NewA + 0.95) * EXP_MULTIPLIER);
5996 LimitRef(Exp, MIN_EXP, MAX_EXP);
5997 return Delta;
6001 int character::GetAttribute (int Identifier, truth AllowBonus) const {
6002 int A = int(BaseExperience[Identifier] * EXP_DIVISOR);
6003 if (AllowBonus && Identifier == INTELLIGENCE && BrainsHurt()) return Max((A + AttributeBonus[INTELLIGENCE]) / 3, 1);
6004 return A && AllowBonus ? Max(A + AttributeBonus[Identifier], 1) : A;
6008 void characterdatabase::PostProcess () {
6009 double AM = (100 + AttributeBonus) * EXP_MULTIPLIER / 100;
6010 for (int c = 0; c < ATTRIBUTES; ++c) NaturalExperience[c] = this->*ExpPtr[c] * AM;
6014 void character::EditDealExperience (sLong Price) {
6015 EditExperience(CHARISMA, sqrt(Price) / 5, 1 << 9);
6019 void character::PrintBeginLeprosyMessage () const {
6020 if (IsPlayer()) ADD_MESSAGE("You feel you're falling in pieces.");
6024 void character::PrintEndLeprosyMessage () const {
6025 if (IsPlayer()) ADD_MESSAGE("You feel your limbs are stuck in place tightly."); // CHANGE OR DIE
6029 void character::TryToInfectWithLeprosy (ccharacter *Infector) {
6030 if (!IsImmuneToLeprosy() &&
6031 ((GetRelation(Infector) == HOSTILE && !RAND_N(50 * GetAttribute(ENDURANCE))) ||
6032 !RAND_N(500 * GetAttribute(ENDURANCE)))) GainIntrinsic(LEPROSY);
6036 void character::SignalGeneration () {
6037 const_cast<database *>(DataBase)->Flags |= HAS_BEEN_GENERATED;
6041 void character::CheckIfSeen () {
6042 if (IsPlayer() || CanBeSeenByPlayer()) SignalSeen();
6046 void character::SignalSeen () {
6047 if (!(WarnFlags & WARNED) && GetRelation(PLAYER) == HOSTILE) {
6048 double Danger = GetRelativeDanger(PLAYER);
6049 if (Danger > 5.0) {
6050 game::SetDangerFound(Max(game::GetDangerFound(), Danger));
6051 if (Danger > 500.0 && !(WarnFlags & HAS_CAUSED_PANIC)) {
6052 WarnFlags |= HAS_CAUSED_PANIC;
6053 game::SetCausePanicFlag(true);
6055 WarnFlags |= WARNED;
6058 const_cast<database *>(DataBase)->Flags |= HAS_BEEN_SEEN;
6062 int character::GetPolymorphIntelligenceRequirement () const {
6063 if (DataBase->PolymorphIntelligenceRequirement == DEPENDS_ON_ATTRIBUTES) return Max(GetAttributeAverage() - 5, 0);
6064 return DataBase->PolymorphIntelligenceRequirement;
6068 void character::RemoveAllItems () {
6069 GetStack()->Clean();
6070 for (int c = 0; c < GetEquipments(); ++c) {
6071 item *Equipment = GetEquipment(c);
6072 if (Equipment) {
6073 Equipment->RemoveFromSlot();
6074 Equipment->SendToHell();
6080 int character::CalculateWeaponSkillHits (ccharacter *Enemy) const {
6081 if (Enemy->IsPlayer()) {
6082 configid ConfigID(GetType(), GetConfig());
6083 const dangerid& DangerID = game::GetDangerMap().find(ConfigID)->second;
6084 return Min(int(DangerID.EquippedDanger * 2000), 1000);
6086 return Min(int(GetRelativeDanger(Enemy, true) * 2000), 1000);
6090 truth character::CanUseEquipment (int I) const {
6091 return CanUseEquipment() && I < GetEquipments() && GetBodyPartOfEquipment(I) && EquipmentIsAllowed(I);
6095 /* Target mustn't have any equipment */
6096 void character::DonateEquipmentTo (character *Character) {
6097 if (IsPlayer()) {
6098 uLong *EquipmentMemory = game::GetEquipmentMemory();
6099 for (int c = 0; c < MAX_EQUIPMENT_SLOTS; ++c) {
6100 item *Item = GetEquipment(c);
6101 if (Item) {
6102 if (Character->CanUseEquipment(c)) {
6103 Item->RemoveFromSlot();
6104 Character->SetEquipment(c, Item);
6105 } else {
6106 EquipmentMemory[c] = Item->GetID();
6107 Item->MoveTo(Character->GetStack());
6109 } else if (CanUseEquipment(c)) {
6110 EquipmentMemory[c] = 0;
6111 } else if (EquipmentMemory[c] && Character->CanUseEquipment(c)) {
6112 for (stackiterator i = Character->GetStack()->GetBottom(); i.HasItem(); ++i) {
6113 if (i->GetID() == EquipmentMemory[c]) {
6114 item *Item = *i;
6115 Item->RemoveFromSlot();
6116 Character->SetEquipment(c, Item);
6117 break;
6120 EquipmentMemory[c] = 0;
6123 } else {
6124 for (int c = 0; c < GetEquipments(); ++c) {
6125 item *Item = GetEquipment(c);
6126 if (Item) {
6127 if (Character->CanUseEquipment(c)) {
6128 Item->RemoveFromSlot();
6129 Character->SetEquipment(c, Item);
6130 } else {
6131 Item->MoveTo(Character->GetStackUnder());
6139 void character::ReceivePeaSoup (sLong) {
6140 lsquare *Square = GetLSquareUnder();
6141 if (Square->IsFlyable()) Square->AddSmoke(gas::Spawn(FART, 250));
6145 void character::AddPeaSoupConsumeEndMessage () const {
6146 if (IsPlayer()) {
6147 if (CanHear()) ADD_MESSAGE("Mmmh! The soup is very tasty. You hear a small puff.");
6148 else ADD_MESSAGE("Mmmh! The soup is very tasty.");
6149 } else if (CanBeSeenByPlayer() && PLAYER->CanHear()) {
6150 // change someday
6151 ADD_MESSAGE("You hear a small puff.");
6156 void character::CalculateMaxStamina () {
6157 MaxStamina = TorsoIsAlive() ? GetAttribute(ENDURANCE) * 10000 : 0;
6161 void character::EditStamina (int Amount, truth CanCauseUnconsciousness) {
6162 if (!TorsoIsAlive()) return;
6163 int UnconsciousnessStamina = MaxStamina >> 3;
6164 if (!CanCauseUnconsciousness && Amount < 0) {
6165 if (Stamina > UnconsciousnessStamina) {
6166 Stamina += Amount;
6167 if (Stamina < UnconsciousnessStamina) Stamina = UnconsciousnessStamina;
6169 return;
6171 int OldStamina = Stamina;
6172 Stamina += Amount;
6173 if (Stamina > MaxStamina) {
6174 Stamina = MaxStamina;
6175 } else if (Stamina < 0) {
6176 Stamina = 0;
6177 LoseConsciousness(250 + RAND_N(250));
6178 } else if (IsPlayer()) {
6179 if (OldStamina >= MaxStamina >> 2 && Stamina < MaxStamina >> 2) {
6180 ADD_MESSAGE("You are getting a little tired.");
6181 } else if(OldStamina >= UnconsciousnessStamina && Stamina < UnconsciousnessStamina) {
6182 ADD_MESSAGE("You are seriously out of breath!");
6183 game::SetPlayerIsRunning(false);
6186 if (IsPlayer() && StateIsActivated(PANIC) && GetTirednessState() != FAINTING) game::SetPlayerIsRunning(true);
6190 void character::RegenerateStamina () {
6191 if (GetTirednessState() != UNTIRED) {
6192 EditExperience(ENDURANCE, 50, 1);
6193 if (Sweats() && TorsoIsAlive() && !RAND_N(30) && !game::IsInWilderness()) {
6194 sLong Volume = sLong(0.05 * sqrt(GetBodyVolume()));
6195 if (GetTirednessState() == FAINTING) Volume <<= 1;
6196 for (int c = 0; c < SquaresUnder; ++c) GetLSquareUnder(c)->SpillFluid(0, CreateSweat(Volume), false, false);
6199 int Bonus = 1;
6200 if (Action) {
6201 if (Action->IsRest()) {
6202 if (SquaresUnder == 1) {
6203 Bonus = GetSquareUnder()->GetRestModifier() << 1;
6204 } else {
6205 int Lowest = GetSquareUnder(0)->GetRestModifier();
6206 for (int c = 1; c < GetSquaresUnder(); ++c) {
6207 int Mod = GetSquareUnder(c)->GetRestModifier();
6208 if (Mod < Lowest) Lowest = Mod;
6210 Bonus = Lowest << 1;
6212 } else if (Action->IsUnconsciousness()) Bonus = 2;
6214 int Plus1 = 100;
6215 switch (GetBurdenState()) {
6216 case OVER_LOADED: Plus1 = 25; break;
6217 case STRESSED: Plus1 = 50; break;
6218 case BURDENED: Plus1 = 75; break;
6220 int Plus2 = 100;
6221 if (IsPlayer()) {
6222 switch (GetHungerState()) {
6223 case STARVING: Plus2 = 25; break;
6224 case VERY_HUNGRY: Plus2 = 50; break;
6225 case HUNGRY: Plus2 = 75; break;
6228 Stamina += Plus1 * Plus2 * Bonus / 1000;
6229 if (Stamina > MaxStamina) Stamina = MaxStamina;
6230 if (IsPlayer() && StateIsActivated(PANIC) && GetTirednessState() != FAINTING) game::SetPlayerIsRunning(true);
6234 void character::BeginPanic () {
6235 if (IsPlayer() && GetTirednessState() != FAINTING) game::SetPlayerIsRunning(true);
6236 DeActivateVoluntaryAction();
6240 void character::EndPanic () {
6241 if (IsPlayer()) game::SetPlayerIsRunning(false);
6245 int character::GetTirednessState () const {
6246 if (Stamina >= MaxStamina >> 2) return UNTIRED;
6247 if (Stamina >= MaxStamina >> 3) return EXHAUSTED;
6248 return FAINTING;
6252 void character::ReceiveBlackUnicorn (sLong Amount) {
6253 if (!(RAND() % 160)) game::DoEvilDeed(Amount / 50);
6254 BeginTemporaryState(TELEPORT, Amount / 100);
6255 for (int c = 0; c < STATES; ++c) {
6256 if (StateData[c].Flags & DUR_TEMPORARY) {
6257 BeginTemporaryState(1 << c, Amount / 100);
6258 if (!IsEnabled()) return;
6259 } else if (StateData[c].Flags & DUR_PERMANENT) {
6260 GainIntrinsic(1 << c);
6261 if (!IsEnabled()) return;
6267 void character::ReceiveGrayUnicorn (sLong Amount) {
6268 if (!(RAND() % 80)) game::DoEvilDeed(Amount / 50);
6269 BeginTemporaryState(TELEPORT, Amount / 100);
6270 for (int c = 0; c < STATES; ++c) {
6271 if (1 << c != TELEPORT) {
6272 DecreaseStateCounter(1 << c, -Amount / 100);
6273 if (!IsEnabled()) return;
6279 void character::ReceiveWhiteUnicorn (sLong Amount) {
6280 if (!(RAND() % 40)) game::DoEvilDeed(Amount / 50);
6281 BeginTemporaryState(TELEPORT, Amount / 100);
6282 DecreaseStateCounter(LYCANTHROPY, -Amount / 100);
6283 DecreaseStateCounter(POISONED, -Amount / 100);
6284 DecreaseStateCounter(PARASITIZED, -Amount / 100);
6285 DecreaseStateCounter(LEPROSY, -Amount / 100);
6289 /* Counter should be negative. Removes intrinsics. */
6290 void character::DecreaseStateCounter (sLong State, int Counter) {
6291 int Index;
6292 for (Index = 0; Index < STATES; ++Index) if (1 << Index == State) break;
6293 if (Index == STATES) ABORT("DecreaseTemporaryStateCounter works only when State == 2 ^ n!");
6294 if (TemporaryState & State) {
6295 if (TemporaryStateCounter[Index] == PERMANENT || (TemporaryStateCounter[Index] += Counter) <= 0) {
6296 TemporaryState &= ~State;
6297 if (!(EquipmentState & State)) {
6298 if (StateData[Index].EndHandler) {
6299 (this->*StateData[Index].EndHandler)();
6300 if (!IsEnabled()) return;
6302 (this->*StateData[Index].PrintEndMessage)();
6309 truth character::IsImmuneToLeprosy () const {
6310 return DataBase->IsImmuneToLeprosy || UseMaterialAttributes();
6314 void character::LeprosyHandler () {
6315 EditExperience(ARM_STRENGTH, -25, 1 << 1);
6316 EditExperience(LEG_STRENGTH, -25, 1 << 1);
6317 EditExperience(DEXTERITY, -25, 1 << 1);
6318 EditExperience(AGILITY, -25, 1 << 1);
6319 EditExperience(ENDURANCE, -25, 1 << 1);
6320 EditExperience(CHARISMA, -25, 1 << 1);
6321 CheckDeath(CONST_S("killed by leprosy"));
6325 bodypart *character::SearchForOriginalBodyPart (int I) const {
6326 for (stackiterator i1 = GetStackUnder()->GetBottom(); i1.HasItem(); ++i1) {
6327 for (std::list<uLong>::iterator i2 = OriginalBodyPartID[I].begin(); i2 != OriginalBodyPartID[I].end(); ++i2)
6328 if (i1->GetID() == *i2) return static_cast<bodypart*>(*i1);
6330 return 0;
6334 void character::SetLifeExpectancy (int Base, int RandPlus) {
6335 int c;
6336 for (c = 0; c < BodyParts; ++c) {
6337 bodypart *BodyPart = GetBodyPart(c);
6338 if (BodyPart) BodyPart->SetLifeExpectancy(Base, RandPlus);
6340 for (c = 0; c < GetEquipments(); ++c) {
6341 item *Equipment = GetEquipment(c);
6342 if (Equipment) Equipment->SetLifeExpectancy(Base, RandPlus);
6347 /* Receiver should be a fresh duplicate of this */
6348 void character::DuplicateEquipment (character *Receiver, uLong Flags) {
6349 for (int c = 0; c < GetEquipments(); ++c) {
6350 item *Equipment = GetEquipment(c);
6351 if (Equipment) {
6352 item *Duplicate = Equipment->Duplicate(Flags);
6353 Receiver->SetEquipment(c, Duplicate);
6359 void character::Disappear (corpse *Corpse, cchar *Verb, truth (item::*ClosePredicate)() const) {
6360 truth TorsoDisappeared = false;
6361 truth CanBeSeen = Corpse ? Corpse->CanBeSeenByPlayer() : IsPlayer() || CanBeSeenByPlayer();
6362 int c;
6363 if ((GetTorso()->*ClosePredicate)()) {
6364 if (CanBeSeen) {
6365 if (Corpse) ADD_MESSAGE("%s %ss.", Corpse->CHAR_NAME(DEFINITE), Verb);
6366 else if (IsPlayer()) ADD_MESSAGE("You %s.", Verb);
6367 else ADD_MESSAGE("%s %ss.", CHAR_NAME(DEFINITE), Verb);
6369 TorsoDisappeared = true;
6370 for (c = 0; c < GetEquipments(); ++c) {
6371 item *Equipment = GetEquipment(c);
6372 if (Equipment && (Equipment->*ClosePredicate)()) {
6373 Equipment->RemoveFromSlot();
6374 Equipment->SendToHell();
6377 itemvector ItemVector;
6378 GetStack()->FillItemVector(ItemVector);
6379 for (uInt c = 0; c < ItemVector.size(); ++c) {
6380 if (ItemVector[c] && (ItemVector[c]->*ClosePredicate)()) {
6381 ItemVector[c]->RemoveFromSlot();
6382 ItemVector[c]->SendToHell();
6386 for (c = 1; c < GetBodyParts(); ++c) {
6387 bodypart *BodyPart = GetBodyPart(c);
6388 if (BodyPart) {
6389 if ((BodyPart->*ClosePredicate)()) {
6390 if (!TorsoDisappeared && CanBeSeen) {
6391 if(IsPlayer()) ADD_MESSAGE("Your %s %ss.", GetBodyPartName(c).CStr(), Verb);
6392 else ADD_MESSAGE("The %s of %s %ss.", GetBodyPartName(c).CStr(), CHAR_NAME(DEFINITE), Verb);
6394 BodyPart->DropEquipment();
6395 item *BodyPart = SevereBodyPart(c);
6396 if (BodyPart) BodyPart->SendToHell();
6397 } else if (TorsoDisappeared) {
6398 BodyPart->DropEquipment();
6399 item *BodyPart = SevereBodyPart(c);
6400 if (BodyPart) {
6401 if (Corpse) Corpse->GetSlot()->AddFriendItem(BodyPart);
6402 else if (!game::IsInWilderness()) GetStackUnder()->AddItem(BodyPart);
6403 else BodyPart->SendToHell();
6408 if (TorsoDisappeared) {
6409 if (Corpse) {
6410 Corpse->RemoveFromSlot();
6411 Corpse->SendToHell();
6412 } else {
6413 CheckDeath(festring(Verb) + "ed", 0, FORCE_DEATH|DISALLOW_CORPSE|DISALLOW_MSG);
6415 } else {
6416 CheckDeath(festring(Verb) + "ed", 0, DISALLOW_MSG);
6421 void character::SignalDisappearance () {
6422 if (GetMotherEntity()) GetMotherEntity()->SignalDisappearance();
6423 else Disappear(0, "disappear", &item::IsVeryCloseToDisappearance);
6427 truth character::HornOfFearWorks () const {
6428 return CanHear() && GetPanicLevel() > RAND() % 33;
6432 void character::BeginLeprosy () {
6433 doforbodypartswithparam<truth>()(this, &bodypart::SetIsInfectedByLeprosy, true);
6437 void character::EndLeprosy () {
6438 doforbodypartswithparam<truth>()(this, &bodypart::SetIsInfectedByLeprosy, false);
6442 truth character::IsSameAs (ccharacter *What) const {
6443 return What->GetType() == GetType() && What->GetConfig() == GetConfig();
6447 uLong character::GetCommandFlags () const {
6448 return !StateIsActivated(PANIC) ? CommandFlags : CommandFlags|FLEE_FROM_ENEMIES;
6452 uLong character::GetConstantCommandFlags () const {
6453 return !StateIsActivated(PANIC) ? DataBase->ConstantCommandFlags : DataBase->ConstantCommandFlags|FLEE_FROM_ENEMIES;
6457 uLong character::GetPossibleCommandFlags () const {
6458 int Int = GetAttribute(INTELLIGENCE);
6459 uLong Flags = ALL_COMMAND_FLAGS;
6460 if (!CanMove() || Int < 4) Flags &= ~FOLLOW_LEADER;
6461 if (!CanMove() || Int < 6) Flags &= ~FLEE_FROM_ENEMIES;
6462 if (!CanUseEquipment() || Int < 8) Flags &= ~DONT_CHANGE_EQUIPMENT;
6463 if (!UsesNutrition() || Int < 8) Flags &= ~DONT_CONSUME_ANYTHING_VALUABLE;
6464 return Flags;
6468 truth character::IsRetreating () const {
6469 return StateIsActivated(PANIC) || (CommandFlags & FLEE_FROM_ENEMIES && IsPet());
6473 truth character::ChatMenu () {
6474 if (GetAction() && !GetAction()->CanBeTalkedTo()) {
6475 ADD_MESSAGE("%s is silent.", CHAR_DESCRIPTION(DEFINITE));
6476 PLAYER->EditAP(-200);
6477 return true;
6479 uLong ManagementFlags = GetManagementFlags();
6480 if (ManagementFlags == CHAT_IDLY || !IsPet()) return ChatIdly();
6481 static cchar *const ChatMenuEntry[CHAT_MENU_ENTRIES] = {
6482 "Change equipment",
6483 "Take items",
6484 "Give items",
6485 "Issue commands",
6486 "Chat idly",
6488 static const petmanagementfunction PMF[CHAT_MENU_ENTRIES] = {
6489 &character::ChangePetEquipment,
6490 &character::TakePetItems,
6491 &character::GivePetItems,
6492 &character::IssuePetCommands,
6493 &character::ChatIdly
6495 felist List(CONST_S("Choose action:"));
6496 game::SetStandardListAttributes(List);
6497 List.AddFlags(SELECTABLE);
6498 int c, i;
6499 for (c = 0; c < CHAT_MENU_ENTRIES; ++c) if (1 << c & ManagementFlags) List.AddEntry(ChatMenuEntry[c], LIGHT_GRAY);
6500 int Chosen = List.Draw();
6501 if (Chosen & FELIST_ERROR_BIT) return false;
6502 for (c = 0, i = 0; c < CHAT_MENU_ENTRIES; ++c) {
6503 if (1 << c & ManagementFlags && i++ == Chosen) return (this->*PMF[c])();
6505 return false; // dummy
6509 truth character::ChangePetEquipment () {
6510 if (EquipmentScreen(PLAYER->GetStack(), GetStack())) {
6511 DexterityAction(3);
6512 return true;
6514 return false;
6518 truth character::TakePetItems () {
6519 truth Success = false;
6520 stack::SetSelected(0);
6521 for (;;) {
6522 itemvector ToTake;
6523 game::DrawEverythingNoBlit();
6524 GetStack()->DrawContents(
6525 ToTake,
6527 PLAYER,
6528 CONST_S("What do you want to take from ") + CHAR_DESCRIPTION(DEFINITE) + '?',
6529 CONST_S(""),
6530 CONST_S(""),
6531 GetDescription(DEFINITE) + " is " + GetVerbalBurdenState(),
6532 GetVerbalBurdenStateColor(),
6533 REMEMBER_SELECTED);
6534 if (ToTake.empty()) break;
6535 for (uInt c = 0; c < ToTake.size(); ++c) ToTake[c]->MoveTo(PLAYER->GetStack());
6536 ADD_MESSAGE("You take %s.", ToTake[0]->GetName(DEFINITE, ToTake.size()).CStr());
6537 Success = true;
6539 if (Success) {
6540 DexterityAction(2);
6541 PLAYER->DexterityAction(2);
6543 return Success;
6547 truth character::GivePetItems () {
6548 truth Success = false;
6549 stack::SetSelected(0);
6550 for (;;) {
6551 itemvector ToGive;
6552 game::DrawEverythingNoBlit();
6553 PLAYER->GetStack()->DrawContents(
6554 ToGive,
6556 this,
6557 CONST_S("What do you want to give to ") + CHAR_DESCRIPTION(DEFINITE) + '?',
6558 CONST_S(""),
6559 CONST_S(""),
6560 GetDescription(DEFINITE) + " is " + GetVerbalBurdenState(),
6561 GetVerbalBurdenStateColor(),
6562 REMEMBER_SELECTED);
6563 if (ToGive.empty()) break;
6564 for (uInt c = 0; c < ToGive.size(); ++c) ToGive[c]->MoveTo(GetStack());
6565 ADD_MESSAGE("You give %s to %s.", ToGive[0]->GetName(DEFINITE, ToGive.size()).CStr(), CHAR_DESCRIPTION(DEFINITE));
6566 Success = true;
6568 if (Success) {
6569 DexterityAction(2);
6570 PLAYER->DexterityAction(2);
6572 return Success;
6576 truth character::IssuePetCommands () {
6577 if (!IsConscious()) {
6578 ADD_MESSAGE("%s is unconscious.", CHAR_DESCRIPTION(DEFINITE));
6579 return false;
6581 uLong PossibleC = GetPossibleCommandFlags();
6582 if (!PossibleC) {
6583 ADD_MESSAGE("%s cannot be commanded.", CHAR_DESCRIPTION(DEFINITE));
6584 return false;
6586 uLong OldC = GetCommandFlags();
6587 uLong NewC = OldC, VaryFlags = 0;
6588 game::CommandScreen(CONST_S("Issue commands to ")+GetDescription(DEFINITE), PossibleC, GetConstantCommandFlags(), VaryFlags, NewC);
6589 if (NewC == OldC) return false;
6590 SetCommandFlags(NewC);
6591 PLAYER->EditAP(-500);
6592 PLAYER->EditExperience(CHARISMA, 25, 1 << 7);
6593 return true;
6597 truth character::ChatIdly () {
6598 if (!TryToTalkAboutScience()) {
6599 BeTalkedTo();
6600 PLAYER->EditExperience(CHARISMA, 75, 1 << 7);
6602 PLAYER->EditAP(-1000);
6603 return true;
6607 truth character::EquipmentScreen (stack *MainStack, stack *SecStack) {
6608 if (!CanUseEquipment()) {
6609 ADD_MESSAGE("%s cannot use equipment.", CHAR_DESCRIPTION(DEFINITE));
6610 return false;
6612 int Chosen = 0;
6613 truth EquipmentChanged = false;
6614 felist List(CONST_S("Equipment menu [ESC exits]"));
6615 festring Entry;
6616 for (;;) {
6617 List.Empty();
6618 List.EmptyDescription();
6619 if (!IsPlayer()) {
6620 List.AddDescription(CONST_S(""));
6621 List.AddDescription(festring(GetDescription(DEFINITE) + " is " + GetVerbalBurdenState()).CapitalizeCopy(), GetVerbalBurdenStateColor());
6623 for (int c = 0; c < GetEquipments(); ++c) {
6624 Entry = GetEquipmentName(c);
6625 Entry << ':';
6626 Entry.Resize(20);
6627 item *Equipment = GetEquipment(c);
6628 if (Equipment) {
6629 Equipment->AddInventoryEntry(this, Entry, 1, true);
6630 AddSpecialEquipmentInfo(Entry, c);
6631 int ImageKey = game::AddToItemDrawVector(itemvector(1, Equipment));
6632 List.AddEntry(Entry, LIGHT_GRAY, 20, ImageKey, true);
6633 } else {
6634 Entry << (GetBodyPartOfEquipment(c) ? "-" : "can't use");
6635 List.AddEntry(Entry, LIGHT_GRAY, 20, game::AddToItemDrawVector(itemvector()));
6638 game::DrawEverythingNoBlit();
6639 game::SetStandardListAttributes(List);
6640 List.SetFlags(SELECTABLE|DRAW_BACKGROUND_AFTERWARDS);
6641 List.SetEntryDrawer(game::ItemEntryDrawer);
6642 Chosen = List.Draw();
6643 game::ClearItemDrawVector();
6644 if (Chosen >= GetEquipments()) break;
6645 EquipmentChanged = TryToChangeEquipment(MainStack, SecStack, Chosen);
6647 if (EquipmentChanged) DexterityAction(5);
6648 return EquipmentChanged;
6652 uLong character::GetManagementFlags () const {
6653 uLong Flags = ALL_MANAGEMENT_FLAGS;
6654 if (!CanUseEquipment() || !AllowPlayerToChangeEquipment()) Flags &= ~CHANGE_EQUIPMENT;
6655 if (!GetStack()->GetItems()) Flags &= ~TAKE_ITEMS;
6656 if (!WillCarryItems()) Flags &= ~GIVE_ITEMS;
6657 if (!GetPossibleCommandFlags()) Flags &= ~ISSUE_COMMANDS;
6658 return Flags;
6662 cchar *VerbalBurdenState[] = { "overloaded", "stressed", "burdened", "unburdened" };
6663 col16 VerbalBurdenStateColor[] = { RED, BLUE, BLUE, WHITE };
6665 cchar *character::GetVerbalBurdenState () const { return VerbalBurdenState[BurdenState]; }
6666 col16 character::GetVerbalBurdenStateColor () const { return VerbalBurdenStateColor[BurdenState]; }
6667 int character::GetAttributeAverage () const { return GetSumOfAttributes()/7; }
6669 cfestring &character::GetStandVerb() const {
6670 if (ForceCustomStandVerb()) return DataBase->StandVerb;
6671 static festring Hovering = "hovering";
6672 static festring Swimming = "swimming";
6673 if (StateIsActivated(LEVITATION)) return Hovering;
6674 if (IsSwimming()) return Swimming;
6675 return DataBase->StandVerb;
6679 truth character::CheckApply () const {
6680 if (!CanApply()) {
6681 ADD_MESSAGE("This monster type cannot apply.");
6682 return false;
6684 return true;
6688 void character::EndLevitation () {
6689 if (!IsFlying() && GetSquareUnder()) {
6690 if (!game::IsInWilderness()) SignalStepFrom(0);
6691 if (game::IsInWilderness() || !GetLSquareUnder()->IsFreezed()) TestWalkability();
6696 truth character::CanMove () const {
6697 return !IsRooted() || StateIsActivated(LEVITATION);
6701 void character::CalculateEnchantments () {
6702 doforequipments()(this, &item::CalculateEnchantment);
6703 GetStack()->CalculateEnchantments();
6707 truth character::GetNewFormForPolymorphWithControl (character *&NewForm) {
6708 festring Topic, Temp;
6709 NewForm = 0;
6710 while (!NewForm) {
6711 festring Temp = game::DefaultQuestion(CONST_S("What do you want to become? [press '?' for a list]"), game::GetDefaultPolymorphTo(), &game::PolymorphControlKeyHandler);
6712 NewForm = protosystem::CreateMonster(Temp);
6713 if (NewForm) {
6714 if (NewForm->IsSameAs(this)) {
6715 delete NewForm;
6716 ADD_MESSAGE("You choose not to polymorph.");
6717 NewForm = this;
6718 return false;
6720 if (PolymorphBackup && NewForm->IsSameAs(PolymorphBackup)) {
6721 delete NewForm;
6722 NewForm = ForceEndPolymorph();
6723 return false;
6725 if (NewForm->GetPolymorphIntelligenceRequirement() > GetAttribute(INTELLIGENCE) && !game::WizardModeIsActive()) {
6726 ADD_MESSAGE("You feel your mind isn't yet powerful enough to call forth the form of %s.", NewForm->CHAR_NAME(INDEFINITE));
6727 delete NewForm;
6728 NewForm = 0;
6729 } else {
6730 NewForm->RemoveAllItems();
6734 return true;
6738 liquid *character::CreateSweat(sLong Volume) const {
6739 return liquid::Spawn(GetSweatMaterial(), Volume);
6743 truth character::TeleportRandomItem (truth TryToHinderVisibility) {
6744 if (IsImmuneToItemTeleport()) return false;
6745 itemvector ItemVector;
6746 std::vector<sLong> PossibilityVector;
6747 int TotalPossibility = 0;
6748 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) {
6749 ItemVector.push_back(*i);
6750 int Possibility = i->GetTeleportPriority();
6751 if (TryToHinderVisibility) Possibility += i->GetHinderVisibilityBonus(this);
6752 PossibilityVector.push_back(Possibility);
6753 TotalPossibility += Possibility;
6755 for (int c = 0; c < GetEquipments(); ++c) {
6756 item *Equipment = GetEquipment(c);
6757 if (Equipment) {
6758 ItemVector.push_back(Equipment);
6759 int Possibility = Equipment->GetTeleportPriority();
6760 if (TryToHinderVisibility) Possibility += Equipment->GetHinderVisibilityBonus(this);
6761 PossibilityVector.push_back(Possibility <<= 1);
6762 TotalPossibility += Possibility;
6765 if (!TotalPossibility) return false;
6766 int Chosen = femath::WeightedRand(PossibilityVector, TotalPossibility);
6767 item *Item = ItemVector[Chosen];
6768 truth Equipped = PLAYER->Equips(Item);
6769 truth Seen = Item->CanBeSeenByPlayer();
6770 Item->RemoveFromSlot();
6771 if (Seen) ADD_MESSAGE("%s disappears.", Item->CHAR_NAME(DEFINITE));
6772 if (Equipped) game::AskForEscPress(CONST_S("Equipment lost!"));
6773 v2 Pos = GetPos();
6774 int Range = Item->GetEmitation() && TryToHinderVisibility ? 25 : 5;
6775 rect Border(Pos + v2(-Range, -Range), Pos + v2(Range, Range));
6776 Pos = GetLevel()->GetRandomSquare(this, 0, &Border);
6777 if (Pos == ERROR_V2) Pos = GetLevel()->GetRandomSquare();
6778 GetNearLSquare(Pos)->GetStack()->AddItem(Item);
6779 if (Item->CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", Item->CHAR_NAME(INDEFINITE));
6780 return true;
6784 truth character::HasClearRouteTo (v2 Pos) const {
6785 pathcontroller::Map = GetLevel()->GetMap();
6786 pathcontroller::Character = this;
6787 v2 ThisPos = GetPos();
6788 return mapmath<pathcontroller>::DoLine(ThisPos.X, ThisPos.Y, Pos.X, Pos.Y, SKIP_FIRST);
6792 truth character::IsTransparent () const {
6793 return !IsEnormous() || GetTorso()->GetMainMaterial()->IsTransparent() || StateIsActivated(INVISIBLE);
6797 void character::SignalPossibleTransparencyChange () {
6798 if (!game::IsInWilderness()) {
6799 for (int c = 0; c < SquaresUnder; ++c) {
6800 lsquare *Square = GetLSquareUnder(c);
6801 if (Square) Square->SignalPossibleTransparencyChange();
6807 int character::GetCursorData () const {
6808 int Bad = 0;
6809 int Color = game::PlayerIsRunning() ? BLUE_CURSOR : DARK_CURSOR;
6810 for (int c = 0; c < BodyParts; ++c) {
6811 bodypart *BodyPart = GetBodyPart(c);
6812 if (BodyPart && BodyPart->IsUsable()) {
6813 int ConditionColorIndex = BodyPart->GetConditionColorIndex();
6814 if ((BodyPartIsVital(c) && !ConditionColorIndex) || (ConditionColorIndex <= 1 && ++Bad == 2)) return Color|CURSOR_FLASH;
6815 } else if (++Bad == 2) return Color|CURSOR_FLASH;
6817 Color = game::PlayerIsRunning() ? YELLOW_CURSOR : RED_CURSOR;
6818 return Bad ? Color|CURSOR_FLASH : Color;
6822 void character::TryToName () {
6823 if (!IsPet()) ADD_MESSAGE("%s refuses to let YOU decide what %s's called.", CHAR_NAME(DEFINITE), CHAR_PERSONAL_PRONOUN);
6824 else if (IsPlayer()) ADD_MESSAGE("You can't rename yourself.");
6825 else if (!IsNameable()) ADD_MESSAGE("%s refuses to be called anything else but %s.", CHAR_NAME(DEFINITE), CHAR_NAME(DEFINITE));
6826 else {
6827 festring Topic = CONST_S("What name will you give to ")+GetName(DEFINITE)+'?';
6828 festring Name = game::StringQuestion(Topic, WHITE, 0, 80, true);
6829 if (Name.GetSize()) SetAssignedName(Name);
6834 double character::GetSituationDanger (ccharacter *Enemy, v2 ThisPos, v2 EnemyPos, truth SeesEnemy) const {
6835 double Danger;
6836 if (IgnoreDanger() && !IsPlayer()) {
6837 if (Enemy->IgnoreDanger() && !Enemy->IsPlayer()) {
6838 Danger = double(GetHP())*GetHPRequirementForGeneration()/(Enemy->GetHP()*Enemy->GetHPRequirementForGeneration());
6840 else {
6841 Danger = 0.25*GetHPRequirementForGeneration()/Enemy->GetHP();
6843 } else if (Enemy->IgnoreDanger() && !Enemy->IsPlayer()) {
6844 Danger = 4.0*GetHP()/Enemy->GetHPRequirementForGeneration();
6845 } else {
6846 Danger = GetRelativeDanger(Enemy);
6848 Danger *= 3.0/((EnemyPos-ThisPos).GetManhattanLength()+2);
6849 if (!SeesEnemy) Danger *= 0.2;
6850 if (StateIsActivated(PANIC)) Danger *= 0.2;
6851 Danger *= double(GetHP())*Enemy->GetMaxHP()/(Enemy->GetHP()*GetMaxHP());
6852 return Danger;
6856 void character::ModifySituationDanger (double &Danger) const {
6857 switch (GetTirednessState()) {
6858 case FAINTING: Danger *= 1.5;
6859 case EXHAUSTED: Danger *= 1.25;
6861 for (int c = 0; c < STATES; ++c) {
6862 if (StateIsActivated(1 << c) && StateData[c].SituationDangerModifier != 0) (this->*StateData[c].SituationDangerModifier)(Danger);
6867 void character::LycanthropySituationDangerModifier (double &Danger) const {
6868 character *Wolf = werewolfwolf::Spawn();
6869 double DangerToWolf = GetRelativeDanger(Wolf);
6870 Danger *= pow(DangerToWolf, 0.1);
6871 delete Wolf;
6875 void character::PoisonedSituationDangerModifier (double &Danger) const {
6876 int C = GetTemporaryStateCounter(POISONED);
6877 Danger *= (1+(C*C)/(GetHP()*10000.0*(GetGlobalResistance(POISON)+1)));
6881 void character::PolymorphingSituationDangerModifier (double &Danger) const {
6882 if (!StateIsActivated(POLYMORPH_CONTROL)) Danger *= 1.5;
6886 void character::PanicSituationDangerModifier (double &Danger) const {
6887 Danger *= 1.5;
6891 void character::ConfusedSituationDangerModifier (double &Danger) const {
6892 Danger *= 1.5;
6896 void character::ParasitizedSituationDangerModifier (double &Danger) const {
6897 Danger *= 1.25;
6901 void character::LeprosySituationDangerModifier (double &Danger) const {
6902 Danger *= 1.5;
6906 void character::AddRandomScienceName (festring &String) const {
6907 festring Science = GetScienceTalkName().GetRandomElement().CStr();
6908 if (Science[0] == '!') {
6909 String << Science.CStr()+1;
6910 return;
6912 festring Attribute = GetScienceTalkAdjectiveAttribute().GetRandomElement();
6913 festring Prefix;
6914 truth NoAttrib = Attribute.IsEmpty(), NoSecondAdjective = false;
6915 if (!Attribute.IsEmpty() && Attribute[0] == '!') {
6916 NoSecondAdjective = true;
6917 Attribute.Erase(0, 1);
6919 if (!Science.Find("the ")) {
6920 Science.Erase(0, 4);
6921 if (!Attribute.Find("the ", 0, 4)) Attribute << " the"; else Attribute.Insert(0, "the ", 4);
6923 if (islower(Science[0]) && Science.Find(' ') == festring::NPos && Science.Find('-') == festring::NPos &&
6924 Science.Find("phobia") == festring::NPos) {
6925 Prefix = GetScienceTalkPrefix().GetRandomElement();
6926 if (!Prefix.IsEmpty() && Science.Find(Prefix) != festring::NPos) Prefix.Empty();
6928 int L = Prefix.GetSize();
6929 if (L && Prefix[L-1] == Science[0]) Science.Erase(0, 1);
6930 if (!NoAttrib && !NoSecondAdjective == !RAND_GOOD(3)) {
6931 int S1 = NoSecondAdjective ? 0 : GetScienceTalkAdjectiveAttribute().Size;
6932 int S2 = GetScienceTalkSubstantiveAttribute().Size;
6933 festring OtherAttribute;
6934 int Chosen = RAND_GOOD(S1+S2);
6935 if (Chosen < S1) OtherAttribute = GetScienceTalkAdjectiveAttribute()[Chosen];
6936 else OtherAttribute = GetScienceTalkSubstantiveAttribute()[Chosen - S1];
6937 if (!OtherAttribute.IsEmpty() && OtherAttribute.Find("the ", 0, 4) && Attribute.Find(OtherAttribute) == festring::NPos) {
6938 String << Attribute << ' ' << OtherAttribute << ' ' << Prefix << Science;
6939 return;
6942 String << Attribute;
6943 if (!NoAttrib) String << ' ';
6944 String << Prefix << Science;
6948 truth character::TryToTalkAboutScience () {
6949 if (GetRelation(PLAYER) == HOSTILE ||
6950 GetScienceTalkPossibility() <= RAND_GOOD(100) ||
6951 PLAYER->GetAttribute(INTELLIGENCE) < GetScienceTalkIntelligenceRequirement() ||
6952 PLAYER->GetAttribute(WISDOM) < GetScienceTalkWisdomRequirement() ||
6953 PLAYER->GetAttribute(CHARISMA) < GetScienceTalkCharismaRequirement())
6954 return false;
6955 festring Science;
6956 if (RAND_GOOD(3)) {
6957 AddRandomScienceName(Science);
6958 } else {
6959 festring S1, S2;
6960 AddRandomScienceName(S1);
6961 AddRandomScienceName(S2);
6962 if (S1.Find(S2) == festring::NPos && S2.Find(S1) == festring::NPos) {
6963 switch (RAND_GOOD(3)) {
6964 case 0: Science = "the relation of "; break;
6965 case 1: Science = "the differences of "; break;
6966 case 2: Science = "the similarities of "; break;
6968 Science << S1 << " and " << S2;
6970 else {
6971 AddRandomScienceName(Science);
6974 switch ((RAND() + GET_TICK()) % 10) {
6975 case 0:
6976 ADD_MESSAGE("You have a rather pleasant chat about %s with %s.", Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
6977 break;
6978 case 1:
6979 ADD_MESSAGE("%s explains a few of %s opinions regarding %s to you.", CHAR_DESCRIPTION(DEFINITE), CHAR_POSSESSIVE_PRONOUN, Science.CStr());
6980 break;
6981 case 2:
6982 ADD_MESSAGE("%s reveals a number of %s insightful views of %s to you.", CHAR_DESCRIPTION(DEFINITE), CHAR_POSSESSIVE_PRONOUN, Science.CStr());
6983 break;
6984 case 3:
6985 ADD_MESSAGE("You exhange some information pertaining to %s with %s.", Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
6986 break;
6987 case 4:
6988 ADD_MESSAGE("You engage in a pretty intriguing conversation about %s with %s.", Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
6989 break;
6990 case 5:
6991 ADD_MESSAGE("You discuss at length about %s with %s.", Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
6992 break;
6993 case 6:
6994 ADD_MESSAGE("You have a somewhat boring talk concerning %s with %s.", Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
6995 break;
6996 case 7:
6997 ADD_MESSAGE("You are drawn into a heated argument regarding %s with %s.", Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
6998 break;
6999 case 8:
7000 ADD_MESSAGE("%s delivers a sLong monologue concerning eg. %s.", CHAR_DESCRIPTION(DEFINITE), Science.CStr());
7001 break;
7002 case 9:
7003 ADD_MESSAGE("You dive into a brief but thought-provoking debate over %s with %s", Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
7004 break;
7006 PLAYER->EditExperience(INTELLIGENCE, 1000, 50. * GetScienceTalkIntelligenceModifier() / ++ScienceTalks);
7007 PLAYER->EditExperience(WISDOM, 1000, 50. * GetScienceTalkWisdomModifier() / ++ScienceTalks);
7008 PLAYER->EditExperience(CHARISMA, 1000, 50. * GetScienceTalkCharismaModifier() / ++ScienceTalks);
7009 return true;
7013 truth character::IsUsingWeaponOfCategory (int Category) const {
7014 return
7015 ((GetMainWielded() && GetMainWielded()->GetWeaponCategory() == Category) ||
7016 (GetSecondaryWielded() && GetSecondaryWielded()->GetWeaponCategory() == Category));
7020 truth character::TryToUnStickTraps (v2 Dir) {
7021 if (!TrapData) return true;
7022 std::vector<trapdata> TrapVector;
7023 for (const trapdata *T = TrapData; T; T = T->Next) TrapVector.push_back(*TrapData);
7024 for (uInt c = 0; c < TrapVector.size(); ++c) {
7025 if (IsEnabled()) {
7026 entity *Trap = game::SearchTrap(TrapVector[c].TrapID);
7027 /*k8:??? if(!Trap->Exists()) int esko = esko = 2; */
7028 if (!Trap->Exists()) continue; /*k8: ??? added by me; what this means? */
7029 if (Trap->GetVictimID() == GetID() && Trap->TryToUnStick(this, Dir)) break;
7032 return !TrapData && IsEnabled();
7036 struct trapidcomparer {
7037 trapidcomparer (uLong ID) : ID(ID) {}
7038 truth operator () (const trapdata *T) const { return T->TrapID == ID; }
7039 uLong ID;
7043 void character::RemoveTrap (uLong ID) {
7044 trapdata *&T = ListFind(TrapData, trapidcomparer(ID));
7045 T = T->Next;
7046 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange);
7050 void character::AddTrap (uLong ID, uLong BodyParts) {
7051 trapdata *&T = ListFind(TrapData, trapidcomparer(ID));
7052 if (T) T->BodyParts |= BodyParts;
7053 else T = new trapdata(ID, GetID(), BodyParts);
7054 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange);
7058 truth character::IsStuckToTrap (uLong ID) const {
7059 for (const trapdata *T = TrapData; T; T = T->Next) if (T->TrapID == ID) return true;
7060 return false;
7064 void character::RemoveTraps () {
7065 for (trapdata *T = TrapData; T;) {
7066 entity *Trap = game::SearchTrap(T->TrapID);
7067 if (Trap) Trap->UnStick();
7068 trapdata *ToDel = T;
7069 T = T->Next;
7070 delete ToDel;
7072 TrapData = 0;
7073 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange);
7077 void character::RemoveTraps (int BodyPartIndex) {
7078 uLong Flag = 1 << BodyPartIndex;
7079 for (trapdata **T = &TrapData; *T;) {
7080 if ((*T)->BodyParts & Flag) {
7081 entity *Trap = game::SearchTrap((*T)->TrapID);
7082 if (!((*T)->BodyParts &= ~Flag)) {
7083 if (Trap) Trap->UnStick();
7084 trapdata *ToDel = *T;
7085 *T = (*T)->Next;
7086 delete ToDel;
7087 } else {
7088 if (Trap) Trap->UnStick(BodyPartIndex);
7089 T = &(*T)->Next;
7092 else {
7093 T = &(*T)->Next;
7096 if (GetBodyPart(BodyPartIndex)) GetBodyPart(BodyPartIndex)->SignalPossibleUsabilityChange();
7100 festring character::GetTrapDescription () const {
7101 festring Desc;
7102 std::pair<entity *, int> TrapStack[3];
7103 int Index = 0;
7104 for (const trapdata *T = TrapData; T; T = T->Next) {
7105 if (Index < 3) {
7106 entity *Trap = game::SearchTrap(T->TrapID);
7107 if (Trap) {
7108 int c;
7109 for (c = 0; c < Index; ++c) if (TrapStack[c].first->GetTrapType() == Trap->GetTrapType()) ++TrapStack[c].second;
7110 if (c == Index) TrapStack[Index++] = std::make_pair(Trap, 1);
7112 } else {
7113 ++Index;
7114 break;
7117 if (Index <= 3) {
7118 TrapStack[0].first->AddTrapName(Desc, TrapStack[0].second);
7119 if (Index == 2) {
7120 Desc << " and ";
7121 TrapStack[1].first->AddTrapName(Desc, TrapStack[1].second);
7122 } else if (Index == 3) {
7123 Desc << ", ";
7124 TrapStack[1].first->AddTrapName(Desc, TrapStack[1].second);
7125 Desc << " and ";
7126 TrapStack[2].first->AddTrapName(Desc, TrapStack[2].second);
7128 } else {
7129 Desc << "lots of traps";
7131 return Desc;
7135 int character::RandomizeHurtBodyPart (uLong BodyParts) const {
7136 int BodyPartIndex[MAX_BODYPARTS];
7137 int Index = 0;
7138 for (int c = 0; c < GetBodyParts(); ++c) {
7139 if (1 << c & BodyParts) {
7140 /*k8: ??? if(!GetBodyPart(c)) int esko = esko = 2; */
7141 if (!GetBodyPart(c)) continue;
7142 BodyPartIndex[Index++] = c;
7144 /*k8: ??? if(!Index) int esko = esko = 2;*/
7146 if (!Index) abort();
7147 return BodyPartIndex[RAND_N(Index)];
7151 truth character::BodyPartIsStuck (int I) const {
7152 for (const trapdata *T = TrapData; T; T = T->Next) if (1 << I & T->BodyParts) return true;
7153 return false;
7157 void character::PrintAttribute (cchar *Desc, int I, int PanelPosX, int PanelPosY) const {
7158 int Attribute = GetAttribute(I);
7159 int NoBonusAttribute = GetAttribute(I, false);
7160 col16 C = game::GetAttributeColor(I);
7161 festring String = Desc;
7162 String.Resize(5);
7163 String << Attribute;
7164 String.Resize(8);
7165 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY * 10), C, String.CStr());
7166 if (Attribute != NoBonusAttribute) {
7167 int Where = PanelPosX + ((String.GetSize() + 1) << 3);
7168 FONT->Printf(DOUBLE_BUFFER, v2(Where, PanelPosY * 10), LIGHT_GRAY, "%d", NoBonusAttribute);
7173 truth character::AllowUnconsciousness () const {
7174 return DataBase->AllowUnconsciousness && TorsoIsAlive();
7178 truth character::CanPanic () const {
7179 return !Action || !Action->IsUnconsciousness();
7183 int character::GetRandomBodyPart (uLong Possible) const {
7184 int OKBodyPart[MAX_BODYPARTS];
7185 int OKBodyParts = 0;
7186 for (int c = 0; c < BodyParts; ++c) if (1 << c & Possible && GetBodyPart(c)) OKBodyPart[OKBodyParts++] = c;
7187 return OKBodyParts ? OKBodyPart[RAND_N(OKBodyParts)] : NONE_INDEX;
7191 void character::EditNP (sLong What) {
7192 int OldState = GetHungerState();
7193 NP += What;
7194 int NewState = GetHungerState();
7195 if (OldState > VERY_HUNGRY && NewState == VERY_HUNGRY) DeActivateVoluntaryAction(CONST_S("You are getting really hungry."));
7196 if (OldState > STARVING && NewState == STARVING) DeActivateVoluntaryAction(CONST_S("You are getting extremely hungry."));
7200 truth character::IsSwimming () const {
7201 return !IsFlying() && GetSquareUnder() && GetSquareUnder()->GetSquareWalkability() & SWIM;
7205 void character::AddBlackUnicornConsumeEndMessage () const {
7206 if (IsPlayer()) ADD_MESSAGE("You feel dirty and loathsome.");
7210 void character::AddGrayUnicornConsumeEndMessage () const {
7211 if (IsPlayer()) ADD_MESSAGE("You feel neutralized.");
7215 void character::AddWhiteUnicornConsumeEndMessage () const {
7216 if (IsPlayer()) ADD_MESSAGE("You feel purified.");
7220 void character::AddOmmelBoneConsumeEndMessage () const {
7221 if (IsPlayer()) ADD_MESSAGE("You feel the power of all your canine ancestors combining in your body.");
7222 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s looks extremely ferocious. You shudder.", CHAR_NAME(DEFINITE));
7226 int character::GetBodyPartSparkleFlags (int) const {
7227 return
7228 ((GetNaturalSparkleFlags() & SKIN_COLOR ? SPARKLING_A : 0) |
7229 (GetNaturalSparkleFlags() & TORSO_MAIN_COLOR ? SPARKLING_B : 0) |
7230 (GetNaturalSparkleFlags() & TORSO_SPECIAL_COLOR ? SPARKLING_D : 0));
7234 truth character::IsAnimated () const {
7235 return combinebodypartpredicates()(this, &bodypart::IsAnimated, 1);
7239 double character::GetNaturalExperience (int Identifier) const {
7240 return DataBase->NaturalExperience[Identifier];
7244 truth character::HasBodyPart (sorter Sorter) const {
7245 if (Sorter == 0) return true;
7246 return combinebodypartpredicateswithparam<ccharacter*>()(this, Sorter, this, 1);
7250 truth character::PossessesItem (sorter Sorter) const {
7251 if (Sorter == 0) return true;
7252 return
7253 (GetStack()->SortedItems(this, Sorter) ||
7254 combinebodypartpredicateswithparam<ccharacter*>()(this, Sorter, this, 1) ||
7255 combineequipmentpredicateswithparam<ccharacter*>()(this, Sorter, this, 1));
7259 /* 0 <= I <= 1 */
7260 cchar *character::GetRunDescriptionLine (int I) const {
7261 if (!GetRunDescriptionLineOne().IsEmpty()) return !I ? GetRunDescriptionLineOne().CStr() : GetRunDescriptionLineTwo().CStr();
7262 if (IsFlying()) return !I ? "Flying" : "very fast";
7263 if (IsSwimming()) return !I ? "Swimming" : "very fast";
7264 return !I ? "Running" : "";
7268 void character::VomitAtRandomDirection (int Amount) {
7269 if (game::IsInWilderness()) return;
7270 /* Lacks support of multitile monsters */
7271 v2 Possible[9];
7272 int Index = 0;
7273 for (int d = 0; d < 9; ++d) {
7274 lsquare *Square = GetLSquareUnder()->GetNeighbourLSquare(d);
7275 if (Square && !Square->VomitingIsDangerous(this)) Possible[Index++] = Square->GetPos();
7277 if (Index) Vomit(Possible[RAND_N(Index)], Amount);
7278 else Vomit(GetPos(), Amount);
7282 void character::RemoveLifeSavers () {
7283 for (int c = 0; c < GetEquipments(); ++c) {
7284 item *Equipment = GetEquipment(c);
7285 if (Equipment && Equipment->IsInCorrectSlot(c) && Equipment->GetGearStates() & LIFE_SAVED) {
7286 Equipment->SendToHell();
7287 Equipment->RemoveFromSlot();
7293 ccharacter *character::FindCarrier () const {
7294 return this; //check
7298 void character::PrintBeginHiccupsMessage () const {
7299 if (IsPlayer()) ADD_MESSAGE("Your diaphragm is spasming vehemently.");
7303 void character::PrintEndHiccupsMessage () const {
7304 if (IsPlayer()) ADD_MESSAGE("You feel your annoying hiccoughs have finally subsided.");
7308 void character::HiccupsHandler () {
7310 if (!(RAND() % 2000)) {
7311 if (IsPlayer()) ADD_MESSAGE("");
7312 else if (CanBeSeenByPlayer()) ADD_MESSAGE("");
7313 else if ((PLAYER->GetPos()-GetPos()).GetLengthSquare() <= 400) ADD_MESSAGE("");
7314 game::CallForAttention(GetPos(), 400);
7320 void character::HiccupsSituationDangerModifier (double &Danger) const {
7321 Danger *= 1.25;
7325 bool character::IsConscious () const {
7326 return !Action || !Action->IsUnconsciousness();
7330 wsquare *character::GetNearWSquare (v2 Pos) const {
7331 return static_cast<wsquare *>(GetSquareUnder()->GetArea()->GetSquare(Pos));
7335 wsquare *character::GetNearWSquare (int x, int y) const {
7336 return static_cast<wsquare *>(GetSquareUnder()->GetArea()->GetSquare(x, y));
7340 void character::ForcePutNear (v2 Pos) {
7341 /* GUM SOLUTION!!! */
7342 v2 NewPos = game::GetCurrentLevel()->GetNearestFreeSquare(PLAYER, Pos, false);
7343 if (NewPos == ERROR_V2) do { NewPos = game::GetCurrentLevel()->GetRandomSquare(this); } while(NewPos == Pos);
7344 PutTo(NewPos);
7348 void character::ReceiveMustardGas (int BodyPart, sLong Volume) {
7349 if (Volume) GetBodyPart(BodyPart)->AddFluid(liquid::Spawn(MUSTARD_GAS_LIQUID, Volume), CONST_S("skin"), 0, true);
7353 void character::ReceiveMustardGasLiquid (int BodyPartIndex, sLong Modifier) {
7354 bodypart *BodyPart = GetBodyPart(BodyPartIndex);
7355 if (BodyPart->GetMainMaterial()->GetInteractionFlags() & IS_AFFECTED_BY_MUSTARD_GAS) {
7356 sLong Tries = Modifier;
7357 Modifier -= Tries; //opt%?
7358 int Damage = 0;
7359 for (sLong c = 0; c < Tries; ++c) if (!(RAND() % 100)) ++Damage;
7360 if (Modifier && !(RAND() % 1000 / Modifier)) ++Damage;
7361 if (Damage) {
7362 uLong Minute = game::GetTotalMinutes();
7363 if (GetLastAcidMsgMin() != Minute && (CanBeSeenByPlayer() || IsPlayer())) {
7364 SetLastAcidMsgMin(Minute);
7365 if (IsPlayer()) ADD_MESSAGE("Mustard gas dissolves the skin of your %s.", BodyPart->GetBodyPartName().CStr());
7366 else ADD_MESSAGE("Mustard gas dissolves %s.", CHAR_NAME(DEFINITE));
7368 ReceiveBodyPartDamage(0, Damage, MUSTARD_GAS_DAMAGE, BodyPartIndex, YOURSELF, false, false, false);
7369 CheckDeath(CONST_S("killed by a fatal exposure to mustard gas"));
7375 truth character::IsBadPath (v2 Pos) const {
7376 if (!IsGoingSomeWhere()) return false;
7377 v2 TPos = !Route.empty() ? Route.back() : GoingTo;
7378 return ((TPos - Pos).GetManhattanLength() > (TPos - GetPos()).GetManhattanLength());
7382 double &character::GetExpModifierRef (expid E) {
7383 return ExpModifierMap.insert(std::make_pair(E, 1.)).first->second;
7387 /* Should probably do more. Now only makes Player forget gods */
7388 truth character::ForgetRandomThing () {
7389 if (IsPlayer()) {
7390 /* hopefully this code isn't some where else */
7391 std::vector<god *> Known;
7392 for (int c = 1; c <= GODS; ++c) if (game::GetGod(c)->IsKnown()) Known.push_back(game::GetGod(c));
7393 if (Known.empty()) return false;
7394 int RandomGod = RAND_N(Known.size());
7395 Known.at(RAND_N(Known.size()))->SetIsKnown(false);
7396 ADD_MESSAGE("You forget how to pray to %s.",
7397 Known.at(RandomGod)->GetName());
7398 return true;
7400 return false;
7404 int character::CheckForBlock (character *Enemy, item *Weapon, double ToHitValue, int Damage, int Success, int Type) {
7405 return Damage;
7409 void character::ApplyAllGodsKnownBonus () {
7410 stack *AddPlace = GetStackUnder();
7411 if (game::IsInWilderness()) AddPlace = GetStack(); else AddPlace = GetStackUnder();
7412 pantheonbook *NewBook = pantheonbook::Spawn();
7413 AddPlace->AddItem(NewBook);
7414 ADD_MESSAGE("\"MORTAL! BEHOLD THE HOLY SAGA\"");
7415 ADD_MESSAGE("%s materializes near your feet.", NewBook->CHAR_NAME(INDEFINITE));
7419 void character::ReceiveSirenSong (character *Siren) {
7420 if (Siren->GetTeam() == GetTeam()) return;
7421 if (!RAND_N(4)) {
7422 if (IsPlayer()) ADD_MESSAGE("The beautiful melody of %s makes you feel sleepy.", Siren->CHAR_NAME(DEFINITE));
7423 else if (CanBeSeenByPlayer()) ADD_MESSAGE("The beautiful melody of %s makes %s look sleepy.", Siren->CHAR_NAME(DEFINITE), CHAR_NAME(DEFINITE)); /*k8*/
7424 Stamina -= (1 + RAND_N(4)) * 10000;
7425 return;
7427 if (!IsPlayer() && IsCharmable() && !RAND_N(5)) {
7428 ChangeTeam(Siren->GetTeam());
7429 ADD_MESSAGE("%s seems to be totally brainwashed by %s melodies.", CHAR_NAME(DEFINITE), Siren->CHAR_NAME(DEFINITE));
7430 return;
7432 if (!RAND_N(4)) {
7433 item *What = GiveMostExpensiveItem(Siren);
7434 if (What) {
7435 if (IsPlayer()) {
7436 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);
7437 } else {
7438 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);
7440 } else {
7441 if (IsPlayer()) ADD_MESSAGE("You would like to give something to %s.", Siren->CHAR_NAME(DEFINITE));
7443 return;
7448 // return 0, if no item found
7449 item *character::FindMostExpensiveItem () const {
7450 int MaxPrice = -1;
7451 item *MostExpensive = 0;
7452 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) {
7453 if ((*i)->GetPrice() > MaxPrice) {
7454 MaxPrice = (*i)->GetPrice();
7455 MostExpensive = (*i);
7458 for (int c = 0; c < GetEquipments(); ++c) {
7459 item *Equipment = GetEquipment(c);
7460 if (Equipment && Equipment->GetPrice() > MaxPrice) {
7461 MaxPrice = Equipment->GetPrice();
7462 MostExpensive = Equipment;
7465 return MostExpensive;
7469 // returns 0 if no items available
7470 item *character::GiveMostExpensiveItem(character *ToWhom) {
7471 item *ToGive = FindMostExpensiveItem();
7472 if (!ToGive) return 0;
7473 truth Equipped = PLAYER->Equips(ToGive);
7474 ToGive->RemoveFromSlot();
7475 if (Equipped) game::AskForEscPress(CONST_S("Equipment lost!"));
7476 ToWhom->ReceiveItemAsPresent(ToGive);
7477 EditAP(-1000);
7478 return ToGive;
7482 void character::ReceiveItemAsPresent (item *Present) {
7483 if (TestForPickup(Present)) GetStack()->AddItem(Present); else GetStackUnder()->AddItem(Present);
7487 /* returns 0 if no enemies in sight */
7488 character *character::GetNearestEnemy () const {
7489 character *NearestEnemy = 0;
7490 sLong NearestEnemyDistance = 0x7FFFFFFF;
7491 v2 Pos = GetPos();
7492 for (int c = 0; c < game::GetTeams(); ++c) {
7493 if (GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE) {
7494 for (std::list<character*>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i) {
7495 if ((*i)->IsEnabled()) {
7496 sLong ThisDistance = Max<sLong>(abs((*i)->GetPos().X - Pos.X), abs((*i)->GetPos().Y - Pos.Y));
7497 if ((ThisDistance < NearestEnemyDistance || (ThisDistance == NearestEnemyDistance && !(RAND() % 3))) && (*i)->CanBeSeenBy(this)) {
7498 NearestEnemy = *i;
7499 NearestEnemyDistance = ThisDistance;
7505 return NearestEnemy;
7509 truth character::MindWormCanPenetrateSkull (mindworm *) const {
7510 return false;
7514 truth character::CanTameWithDulcis (const character *Tamer) const {
7515 int TamingDifficulty = GetTamingDifficulty();
7516 if (TamingDifficulty == NO_TAMING) return false;
7517 if (GetAttachedGod() == DULCIS) return true;
7518 int Modifier = Tamer->GetAttribute(WISDOM) + Tamer->GetAttribute(CHARISMA);
7519 if (Tamer->IsPlayer()) Modifier += game::GetGod(DULCIS)->GetRelation() / 20;
7520 else if (Tamer->GetAttachedGod() == DULCIS) Modifier += 50;
7521 if (TamingDifficulty == 0) {
7522 if (!IgnoreDanger()) TamingDifficulty = int(10 * GetRelativeDanger(Tamer));
7523 else TamingDifficulty = 10 * GetHPRequirementForGeneration()/Max(Tamer->GetHP(), 1);
7525 return Modifier >= TamingDifficulty * 3;
7529 truth character::CanTameWithLyre (const character *Tamer) const {
7530 int TamingDifficulty = GetTamingDifficulty();
7531 if (TamingDifficulty == NO_TAMING) return false;
7532 if (TamingDifficulty == 0) {
7533 if (!IgnoreDanger()) TamingDifficulty = int(10 * GetRelativeDanger(Tamer));
7534 else TamingDifficulty = 10*GetHPRequirementForGeneration()/Max(Tamer->GetHP(), 1);
7536 return Tamer->GetAttribute(CHARISMA) >= TamingDifficulty;
7540 truth character::CanTameWithScroll (const character *Tamer) const {
7541 int TamingDifficulty = GetTamingDifficulty();
7542 return
7543 (TamingDifficulty != NO_TAMING &&
7544 (TamingDifficulty == 0 ||
7545 Tamer->GetAttribute(INTELLIGENCE) * 4 + Tamer->GetAttribute(CHARISMA) >= TamingDifficulty * 5));
7549 truth character::CheckSadism () {
7550 if (!IsSadist() || !HasSadistAttackMode() || !IsSmall()) return false; // gum
7551 if (!RAND_N(10)) {
7552 for (int d = 0; d < 8; ++d) {
7553 square *Square = GetNeighbourSquare(d);
7554 if (Square) {
7555 character *Char = Square->GetCharacter();
7556 if (Char && Char->IsMasochist() && GetRelation(Char) == FRIEND &&
7557 Char->GetHP() * 3 >= Char->GetMaxHP() * 2 && Hit(Char, Square->GetPos(), d, SADIST_HIT)) {
7558 TerminateGoingTo();
7559 return true;
7564 return false;
7568 truth character::CheckForBeverage () {
7569 if (StateIsActivated(PANIC) || !IsEnabled() || !UsesNutrition() || CheckIfSatiated()) return false;
7570 itemvector ItemVector;
7571 GetStack()->FillItemVector(ItemVector);
7572 for (uInt c = 0; c < ItemVector.size(); ++c) if (ItemVector[c]->IsBeverage(this) && TryToConsume(ItemVector[c])) return true;
7573 return false;
7577 void character::Haste () {
7578 doforbodyparts()(this, &bodypart::Haste);
7579 doforequipments()(this, &item::Haste);
7580 BeginTemporaryState(HASTE, 500 + RAND() % 1000);
7584 void character::Slow () {
7585 doforbodyparts()(this, &bodypart::Slow);
7586 doforequipments()(this, &item::Slow);
7587 //BeginTemporaryState(HASTE, 500 + RAND() % 1000); // this seems to be a bug
7588 BeginTemporaryState(SLOW, 500 + RAND() % 1000);