fix for https://github.com/Attnam/ivan/issues/119
[k8-i-v-a-n.git] / src / game / char.cpp
blob0f4ae7211c78451ce84e8109951b0af4310e4059
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
253 }, {
254 "Vampirism",
255 DUR_FLAGS, //perhaps no fountain, no secret and no confuse read either: SECRET|SRC_FOUNTAIN|SRC_CONFUSE_READ|
256 &character::PrintBeginVampirismMessage,
257 &character::PrintEndVampirismMessage,
260 &character::VampirismHandler,
262 &character::VampirismSituationDangerModifier
263 }, {
264 "Detecting",
265 SECRET|(RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL)),
266 &character::PrintBeginDetectMessage,
267 &character::PrintEndDetectMessage,
270 &character::DetectHandler,
277 characterprototype::characterprototype (const characterprototype *Base, characterspawner Spawner,
278 charactercloner Cloner, cchar *ClassID)
279 : Base(Base),
280 Spawner(Spawner),
281 Cloner(Cloner),
282 ClassID(ClassID)
284 Index = protocontainer<character>::Add(this);
288 std::list<character *>::iterator character::GetTeamIterator () { return TeamIterator; }
289 void character::SetTeamIterator (std::list<character *>::iterator What) { TeamIterator = What; }
290 void character::CreateInitialEquipment (int SpecialFlags) { AddToInventory(DataBase->Inventory, SpecialFlags); }
291 void character::EditAP (sLong What) { AP = Limit<sLong>(AP+What, -12000, 1200); }
292 int character::GetRandomStepperBodyPart () const { return TORSO_INDEX; }
293 void character::GainIntrinsic (sLong What) { BeginTemporaryState(What, PERMANENT); }
294 truth character::IsUsingArms () const { return GetAttackStyle() & USE_ARMS; }
295 truth character::IsUsingLegs () const { return GetAttackStyle() & USE_LEGS; }
296 truth character::IsUsingHead () const { return GetAttackStyle() & USE_HEAD; }
297 void character::CalculateAllowedWeaponSkillCategories () { AllowedWeaponSkillCategories = MARTIAL_SKILL_CATEGORIES; }
298 festring character::GetBeVerb () const { return IsPlayer() ? CONST_S("are") : CONST_S("is"); }
299 void character::SetEndurance (int What) { BaseExperience[ENDURANCE] = What * EXP_MULTIPLIER; }
300 void character::SetPerception (int What) { BaseExperience[PERCEPTION] = What * EXP_MULTIPLIER; }
301 void character::SetIntelligence (int What) { BaseExperience[INTELLIGENCE] = What * EXP_MULTIPLIER; }
302 void character::SetWisdom (int What) { BaseExperience[WISDOM] = What * EXP_MULTIPLIER; }
303 void character::SetWillPower (int What) { BaseExperience[WILL_POWER] = What * EXP_MULTIPLIER; }
304 void character::SetCharisma (int What) { BaseExperience[CHARISMA] = What * EXP_MULTIPLIER; }
305 void character::SetMana (int What) { BaseExperience[MANA] = What * EXP_MULTIPLIER; }
306 truth character::IsOnGround () const { return MotherEntity && MotherEntity->IsOnGround(); }
307 truth character::LeftOversAreUnique () const { return GetArticleMode() || AssignedName.GetSize(); }
308 truth character::HomeDataIsValid () const { return (HomeData && HomeData->Level == GetLSquareUnder()->GetLevelIndex() && HomeData->Dungeon == GetLSquareUnder()->GetDungeonIndex()); }
309 void character::SetHomePos (v2 Pos) { HomeData->Pos = Pos; }
310 cchar *character::FirstPersonUnarmedHitVerb () const { return "hit"; }
311 cchar *character::FirstPersonCriticalUnarmedHitVerb () const { return "critically hit"; }
312 cchar *character::ThirdPersonUnarmedHitVerb () const { return "hits"; }
313 cchar *character::ThirdPersonCriticalUnarmedHitVerb () const { return "critically hits"; }
314 cchar *character::FirstPersonKickVerb () const { return "kick"; }
315 cchar *character::FirstPersonCriticalKickVerb () const { return "critically kick"; }
316 cchar *character::ThirdPersonKickVerb () const { return "kicks"; }
317 cchar *character::ThirdPersonCriticalKickVerb () const { return "critically kicks"; }
318 cchar *character::FirstPersonBiteVerb () const { return "bite"; }
319 cchar *character::FirstPersonCriticalBiteVerb () const { return "critically bite"; }
320 cchar *character::ThirdPersonBiteVerb () const { return "bites"; }
321 cchar *character::ThirdPersonCriticalBiteVerb () const { return "critically bites"; }
322 cchar *character::UnarmedHitNoun () const { return "attack"; }
323 cchar *character::KickNoun () const { return "kick"; }
324 cchar *character::BiteNoun () const { return "attack"; }
325 cchar *character::GetEquipmentName (int) const { return ""; }
326 const std::list<feuLong> &character::GetOriginalBodyPartID (int I) const { return OriginalBodyPartID[I]; }
327 square *character::GetNeighbourSquare (int I) const { return GetSquareUnder()->GetNeighbourSquare(I); }
328 lsquare *character::GetNeighbourLSquare (int I) const { return static_cast<lsquare *>(GetSquareUnder())->GetNeighbourLSquare(I); }
329 wsquare *character::GetNeighbourWSquare (int I) const { return static_cast<wsquare *>(GetSquareUnder())->GetNeighbourWSquare(I); }
330 god *character::GetMasterGod () const { return game::GetGod(GetConfig()); }
331 col16 character::GetBodyPartColorA (int, truth) const { return GetSkinColor(); }
332 col16 character::GetBodyPartColorB (int, truth) const { return GetTorsoMainColor(); }
333 col16 character::GetBodyPartColorC (int, truth) const { return GetBeltColor(); } // sorry...
334 col16 character::GetBodyPartColorD (int, truth) const { return GetTorsoSpecialColor(); }
335 int character::GetRandomApplyBodyPart () const { return TORSO_INDEX; }
336 truth character::MustBeRemovedFromBone () const { return IsUnique() && !CanBeGenerated(); }
337 truth character::IsPet () const { return GetTeam()->GetID() == PLAYER_TEAM; }
338 character* character::GetLeader () const { return GetTeam()->GetLeader(); }
339 int character::GetMoveType () const { return (!StateIsActivated(LEVITATION) ? DataBase->MoveType : DataBase->MoveType | FLY); }
340 festring character::GetZombieDescription () const { return " of "+GetName(INDEFINITE); }
341 truth character::BodyPartCanBeSevered (int I) const { return I; }
342 truth character::HasBeenSeen () const { return DataBase->Flags & HAS_BEEN_SEEN; }
343 truth character::IsTemporary () const { return GetTorso()->GetLifeExpectancy(); }
344 cchar *character::GetNormalDeathMessage () const { return "killed @k"; }
347 int characterdatabase::*ExpPtr[ATTRIBUTES] = {
348 &characterdatabase::DefaultEndurance,
349 &characterdatabase::DefaultPerception,
350 &characterdatabase::DefaultIntelligence,
351 &characterdatabase::DefaultWisdom,
352 &characterdatabase::DefaultWillPower,
353 &characterdatabase::DefaultCharisma,
354 &characterdatabase::DefaultMana,
355 &characterdatabase::DefaultArmStrength,
356 &characterdatabase::DefaultLegStrength,
357 &characterdatabase::DefaultDexterity,
358 &characterdatabase::DefaultAgility
362 contentscript<item> characterdatabase::*EquipmentDataPtr[EQUIPMENT_DATAS] = {
363 &characterdatabase::Helmet,
364 &characterdatabase::Amulet,
365 &characterdatabase::Cloak,
366 &characterdatabase::BodyArmor,
367 &characterdatabase::Belt,
368 &characterdatabase::RightWielded,
369 &characterdatabase::LeftWielded,
370 &characterdatabase::RightRing,
371 &characterdatabase::LeftRing,
372 &characterdatabase::RightGauntlet,
373 &characterdatabase::LeftGauntlet,
374 &characterdatabase::RightBoot,
375 &characterdatabase::LeftBoot
379 character::character (ccharacter &Char) :
380 entity(Char), id(Char), NP(Char.NP), AP(Char.AP),
381 TemporaryState(Char.TemporaryState&~POLYMORPHED),
382 Team(Char.Team), GoingTo(ERROR_V2), Money(0),
383 AssignedName(Char.AssignedName), Action(0),
384 DataBase(Char.DataBase), MotherEntity(0),
385 PolymorphBackup(0), EquipmentState(0), SquareUnder(0),
386 AllowedWeaponSkillCategories(Char.AllowedWeaponSkillCategories),
387 BodyParts(Char.BodyParts),
388 RegenerationCounter(Char.RegenerationCounter),
389 SquaresUnder(Char.SquaresUnder), LastAcidMsgMin(0),
390 Stamina(Char.Stamina), MaxStamina(Char.MaxStamina),
391 BlocksSinceLastTurn(0), GenerationDanger(Char.GenerationDanger),
392 CommandFlags(Char.CommandFlags), WarnFlags(0),
393 ScienceTalks(Char.ScienceTalks), TrapData(0), CounterToMindWormHatch(0)
395 int c;
396 Flags &= ~C_PLAYER;
397 Flags |= C_INITIALIZING|C_IN_NO_MSG_MODE;
398 Stack = new stack(0, this, HIDDEN);
399 for (c = 0; c < STATES; ++c) TemporaryStateCounter[c] = Char.TemporaryStateCounter[c];
400 if (Team) TeamIterator = Team->Add(this);
401 for (c = 0; c < BASE_ATTRIBUTES; ++c) BaseExperience[c] = Char.BaseExperience[c];
402 BodyPartSlot = new bodypartslot[BodyParts];
403 OriginalBodyPartID = new std::list<feuLong>[BodyParts];
404 CWeaponSkill = new cweaponskill[AllowedWeaponSkillCategories];
405 SquareUnder = new square *[SquaresUnder];
406 if (SquaresUnder == 1) *SquareUnder = 0; else memset(SquareUnder, 0, SquaresUnder*sizeof(square *));
407 for (c = 0; c < BodyParts; ++c) {
408 BodyPartSlot[c].SetMaster(this);
409 bodypart *CharBodyPart = Char.GetBodyPart(c);
410 OriginalBodyPartID[c] = Char.OriginalBodyPartID[c];
411 if (CharBodyPart) {
412 bodypart *BodyPart = static_cast<bodypart *>(CharBodyPart->Duplicate());
413 SetBodyPart(c, BodyPart);
414 BodyPart->CalculateEmitation();
417 for (c = 0; c < AllowedWeaponSkillCategories; ++c) CWeaponSkill[c] = Char.CWeaponSkill[c];
418 HomeData = Char.HomeData ? new homedata(*Char.HomeData) : 0;
419 ID = game::CreateNewCharacterID(this);
423 character::character () :
424 entity(HAS_BE), NP(50000), AP(0), TemporaryState(0), Team(0),
425 GoingTo(ERROR_V2), Money(0), Action(0), MotherEntity(0),
426 PolymorphBackup(0), EquipmentState(0), SquareUnder(0),
427 RegenerationCounter(0), HomeData(0), LastAcidMsgMin(0),
428 BlocksSinceLastTurn(0), GenerationDanger(DEFAULT_GENERATION_DANGER),
429 WarnFlags(0), ScienceTalks(0), TrapData(0), CounterToMindWormHatch(0)
431 Stack = new stack(0, this, HIDDEN);
435 character::~character () {
436 if (Action) delete Action;
437 if (Team) Team->Remove(GetTeamIterator());
438 delete Stack;
439 for (int c = 0; c < BodyParts; ++c) delete GetBodyPart(c);
440 delete [] BodyPartSlot;
441 delete [] OriginalBodyPartID;
442 delete PolymorphBackup;
443 delete [] SquareUnder;
444 delete [] CWeaponSkill;
445 delete HomeData;
446 for (trapdata *T = TrapData; T;) {
447 trapdata *ToDel = T;
448 T = T->Next;
449 delete ToDel;
451 game::RemoveCharacterID(ID);
455 void character::Hunger () {
456 switch (GetBurdenState()) {
457 case OVER_LOADED:
458 case STRESSED:
459 EditNP(-8);
460 EditExperience(LEG_STRENGTH, 150, 1 << 2);
461 EditExperience(AGILITY, -50, 1 << 2);
462 break;
463 case BURDENED:
464 EditNP(-2);
465 EditExperience(LEG_STRENGTH, 75, 1 << 1);
466 EditExperience(AGILITY, -25, 1 << 1);
467 break;
468 case UNBURDENED:
469 EditNP(-1);
470 break;
473 switch (GetHungerState()) {
474 case STARVING:
475 EditExperience(ARM_STRENGTH, -75, 1 << 3);
476 EditExperience(LEG_STRENGTH, -75, 1 << 3);
477 break;
478 case VERY_HUNGRY:
479 EditExperience(ARM_STRENGTH, -50, 1 << 2);
480 EditExperience(LEG_STRENGTH, -50, 1 << 2);
481 break;
482 case HUNGRY:
483 EditExperience(ARM_STRENGTH, -25, 1 << 1);
484 EditExperience(LEG_STRENGTH, -25, 1 << 1);
485 break;
486 case SATIATED:
487 EditExperience(AGILITY, -25, 1 << 1);
488 break;
489 case BLOATED:
490 EditExperience(AGILITY, -50, 1 << 2);
491 break;
492 case OVER_FED:
493 EditExperience(AGILITY, -75, 1 << 3);
494 break;
496 CheckStarvationDeath(CONST_S("starved to death"));
500 int character::TakeHit (character *Enemy, item *Weapon, bodypart *EnemyBodyPart, v2 HitPos, double Damage,
501 double ToHitValue, int Success, int Type, int GivenDir, truth Critical, truth ForceHit)
503 //FIXME: args
504 game::ClearEventData();
505 game::mActor = Enemy;
506 game::mResult = DID_NO_DAMAGE;
507 if (game::RunOnCharEvent(this, CONST_S("take_hit"))) { game::ClearEventData(); return game::mResult; }
508 game::ClearEventData();
509 int Dir = Type == BITE_ATTACK ? YOURSELF : GivenDir;
510 double DodgeValue = GetDodgeValue();
511 if (!Enemy->IsPlayer() && GetAttackWisdomLimit() != NO_LIMIT) Enemy->EditExperience(WISDOM, 75, 1 << 13);
512 if (!Enemy->CanBeSeenBy(this)) ToHitValue *= 2;
513 if (!CanBeSeenBy(Enemy)) DodgeValue *= 2;
514 if (Enemy->StateIsActivated(CONFUSED)) ToHitValue *= 0.75;
516 switch (Enemy->GetTirednessState()) {
517 case FAINTING:
518 ToHitValue *= 0.50;
519 case EXHAUSTED:
520 ToHitValue *= 0.75;
522 switch (GetTirednessState()) {
523 case FAINTING:
524 DodgeValue *= 0.50;
525 case EXHAUSTED:
526 DodgeValue *= 0.75;
529 if (!ForceHit) {
530 if (!IsRetreating()) SetGoingTo(Enemy->GetPos());
531 else SetGoingTo(GetPos()-((Enemy->GetPos()-GetPos())<<4));
532 if (!Enemy->IsRetreating()) Enemy->SetGoingTo(GetPos());
533 else Enemy->SetGoingTo(Enemy->GetPos()-((GetPos()-Enemy->GetPos())<<4));
536 /* Effectively, the average chance to hit is 100% / (DV/THV + 1). */
537 if (RAND() % int(100+ToHitValue/DodgeValue*(100+Success)) < 100 && !Critical && !ForceHit) {
538 Enemy->AddMissMessage(this);
539 EditExperience(AGILITY, 150, 1 << 7);
540 EditExperience(PERCEPTION, 75, 1 << 7);
541 if (Enemy->CanBeSeenByPlayer())
542 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy->GetName(DEFINITE)+CONST_S(" interrupts you."));
543 else
544 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
545 return HAS_DODGED;
548 int TrueDamage = int(Damage*(100+Success)/100)+(RAND()%3 ? 1 : 0);
549 if (Critical) {
550 TrueDamage += TrueDamage >> 1;
551 ++TrueDamage;
554 int BodyPart = ChooseBodyPartToReceiveHit(ToHitValue, DodgeValue);
555 if (Critical) {
556 switch (Type) {
557 case UNARMED_ATTACK:
558 Enemy->AddPrimitiveHitMessage(this, Enemy->FirstPersonCriticalUnarmedHitVerb(), Enemy->ThirdPersonCriticalUnarmedHitVerb(), BodyPart);
559 break;
560 case WEAPON_ATTACK:
561 Enemy->AddWeaponHitMessage(this, Weapon, BodyPart, true);
562 break;
563 case KICK_ATTACK:
564 Enemy->AddPrimitiveHitMessage(this, Enemy->FirstPersonCriticalKickVerb(), Enemy->ThirdPersonCriticalKickVerb(), BodyPart);
565 break;
566 case BITE_ATTACK:
567 Enemy->AddPrimitiveHitMessage(this, Enemy->FirstPersonCriticalBiteVerb(), Enemy->ThirdPersonCriticalBiteVerb(), BodyPart);
568 break;
570 } else {
571 switch (Type) {
572 case UNARMED_ATTACK:
573 Enemy->AddPrimitiveHitMessage(this, Enemy->FirstPersonUnarmedHitVerb(), Enemy->ThirdPersonUnarmedHitVerb(), BodyPart);
574 break;
575 case WEAPON_ATTACK:
576 Enemy->AddWeaponHitMessage(this, Weapon, BodyPart, false);
577 break;
578 case KICK_ATTACK:
579 Enemy->AddPrimitiveHitMessage(this, Enemy->FirstPersonKickVerb(), Enemy->ThirdPersonKickVerb(), BodyPart);
580 break;
581 case BITE_ATTACK:
582 Enemy->AddPrimitiveHitMessage(this, Enemy->FirstPersonBiteVerb(), Enemy->ThirdPersonBiteVerb(), BodyPart);
583 break;
587 if (!Critical && TrueDamage && Enemy->AttackIsBlockable(Type)) {
588 TrueDamage = CheckForBlock(Enemy, Weapon, ToHitValue, TrueDamage, Success, Type);
589 if (!TrueDamage || (Weapon && !Weapon->Exists())) {
590 if (Enemy->CanBeSeenByPlayer())
591 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy->GetName(DEFINITE)+CONST_S(" interrupts you."));
592 else
593 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
594 return HAS_BLOCKED;
598 int WeaponSkillHits = CalculateWeaponSkillHits(Enemy);
599 int DoneDamage = ReceiveBodyPartDamage(Enemy, TrueDamage, PHYSICAL_DAMAGE, BodyPart, Dir, false, Critical, true, Type == BITE_ATTACK && Enemy->BiteCapturesBodyPart());
600 truth Succeeded = (GetBodyPart(BodyPart) && HitEffect(Enemy, Weapon, HitPos, Type, BodyPart, Dir, !DoneDamage)) || DoneDamage;
601 if (Succeeded) Enemy->WeaponSkillHit(Weapon, Type, WeaponSkillHits);
603 if (Weapon) {
604 if (Weapon->Exists() && DoneDamage < TrueDamage) Weapon->ReceiveDamage(Enemy, TrueDamage-DoneDamage, PHYSICAL_DAMAGE);
605 if (Weapon->Exists() && DoneDamage && SpillsBlood() && GetBodyPart(BodyPart) &&
606 (GetBodyPart(BodyPart)->IsAlive() || GetBodyPart(BodyPart)->GetMainMaterial()->IsLiquid()))
607 Weapon->SpillFluid(0, CreateBlood(15+RAND()%15));
610 if (Enemy->AttackIsBlockable(Type)) SpecialBodyDefenceEffect(Enemy, EnemyBodyPart, Type);
612 if (!Succeeded) {
613 if (Enemy->CanBeSeenByPlayer())
614 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy->GetName(DEFINITE)+CONST_S(" interrupts you."));
615 else
616 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
618 return DID_NO_DAMAGE;
621 if (CheckDeath(GetNormalDeathMessage(), Enemy, Enemy->IsPlayer() ? FORCE_MSG : 0)) return HAS_DIED;
623 if (Enemy->CanBeSeenByPlayer())
624 DeActivateVoluntaryAction(CONST_S("The attack of ")+Enemy->GetName(DEFINITE)+CONST_S(" interrupts you."));
625 else
626 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
628 return HAS_HIT;
632 struct svpriorityelement {
633 svpriorityelement (int BodyPart, int StrengthValue) : BodyPart(BodyPart), StrengthValue(StrengthValue) {}
634 bool operator < (const svpriorityelement &AnotherPair) const { return StrengthValue > AnotherPair.StrengthValue; }
635 int BodyPart;
636 int StrengthValue;
640 int character::ChooseBodyPartToReceiveHit (double ToHitValue, double DodgeValue) {
641 if (BodyParts == 1) return 0;
642 std::priority_queue<svpriorityelement> SVQueue;
643 for (int c = 0; c < BodyParts; ++c) {
644 bodypart *BodyPart = GetBodyPart(c);
645 if (BodyPart && (BodyPart->GetHP() != 1 || BodyPart->CanBeSevered(PHYSICAL_DAMAGE)))
646 SVQueue.push(svpriorityelement(c, ModifyBodyPartHitPreference(c, BodyPart->GetStrengthValue()+BodyPart->GetHP())));
648 while (SVQueue.size()) {
649 svpriorityelement E = SVQueue.top();
650 int ToHitPercentage = int(GLOBAL_WEAK_BODYPART_HIT_MODIFIER*ToHitValue*GetBodyPart(E.BodyPart)->GetBodyPartVolume()/(DodgeValue*GetBodyVolume()));
651 ToHitPercentage = ModifyBodyPartToHitChance(E.BodyPart, ToHitPercentage);
652 if (ToHitPercentage < 1) ToHitPercentage = 1;
653 else if (ToHitPercentage > 95) ToHitPercentage = 95;
654 if (ToHitPercentage > RAND()%100) return E.BodyPart;
655 SVQueue.pop();
657 return 0;
661 void character::Be () {
662 if (game::ForceJumpToPlayerBe()) {
663 if (!IsPlayer()) return;
664 game::SetForceJumpToPlayerBe(false);
665 } else {
666 truth ForceBe = HP != MaxHP || AllowSpoil();
667 for (int c = 0; c < BodyParts; ++c) {
668 bodypart *BodyPart = GetBodyPart(c);
669 if (BodyPart && (ForceBe || BodyPart->NeedsBe())) BodyPart->Be();
671 HandleStates();
672 if (!IsEnabled()) return;
673 if (GetTeam() == PLAYER->GetTeam()) {
674 for (int c = 0; c < AllowedWeaponSkillCategories; ++c) {
675 if (CWeaponSkill[c].Tick() && IsPlayer()) CWeaponSkill[c].AddLevelDownMessage(c);
677 SWeaponSkillTick();
679 if (IsPlayer()) {
680 if (GetHungerState() == STARVING && !(RAND()%50)) LoseConsciousness(250+RAND_N(250), true);
681 if (!Action || Action->AllowFoodConsumption()) Hunger();
683 if (Stamina != MaxStamina) RegenerateStamina();
684 if (HP != MaxHP) Regenerate();
685 if (Action && AP >= 1000) ActionAutoTermination();
686 if (Action && AP >= 1000) {
687 Action->Handle();
688 if (!IsEnabled()) return;
689 } else {
690 EditAP(GetStateAPGain(100));
693 if (AP >= 1000) {
694 SpecialTurnHandler();
695 BlocksSinceLastTurn = 0;
696 if (IsPlayer()) {
697 static int Timer = 0;
699 if (ivanconfig::GetAutoSaveInterval() && !GetAction() && ++Timer >= ivanconfig::GetAutoSaveInterval()) {
700 //fprintf(stderr, "autosaving..."); fflush(stderr);
701 game::Save(game::GetAutoSaveFileName());
702 //fprintf(stderr, "done\n"); fflush(stderr);
703 Timer = 0;
705 game::CalculateNextDanger();
706 if (!StateIsActivated(POLYMORPHED)) game::UpdatePlayerAttributeAverage();
707 if (!game::IsInWilderness()) Search(GetAttribute(PERCEPTION));
708 if (!Action) {
709 GetPlayerCommand();
710 } else {
711 if (Action->ShowEnvironment()) {
712 static int Counter = 0;
713 if (++Counter == 10) {
714 game::DrawEverything();
715 Counter = 0;
718 msgsystem::ThyMessagesAreNowOld();
719 if (Action->IsVoluntary() && READ_KEY()) Action->Terminate(false);
721 } else {
722 if (!Action && !game::IsInWilderness()) GetAICommand();
728 void character::Move (v2 MoveTo, truth TeleportMove, truth Run) {
729 if (!IsEnabled()) return;
730 /* Test whether the player is stuck to something */
731 if (!TeleportMove && !TryToUnStickTraps(MoveTo-GetPos())) return;
732 if (Run && !IsPlayer() && TorsoIsAlive() &&
733 (Stamina <= 10000/Max(GetAttribute(LEG_STRENGTH), 1) || (!StateIsActivated(PANIC) && Stamina < MaxStamina>>2))) {
734 Run = false;
736 RemoveTraps();
737 if (GetBurdenState() != OVER_LOADED || TeleportMove) {
738 lsquare *OldSquareUnder[MAX_SQUARES_UNDER];
740 if (!game::IsInWilderness()) {
741 if (IsPlayer()) {
742 // idiotic code!
743 area *ca = GetSquareUnder()->GetArea();
745 for (int f = 0; f < 8; ++f) {
746 v2 np = GetPos()+game::GetMoveVector(f);
748 if (np.X >= 0 && np.Y >= 0 && np.X < ca->GetXSize() && np.Y < ca->GetYSize()) {
749 lsquare *sq = static_cast<lsquare *>(ca->GetSquare(np.X, np.Y));
751 sq->SetGoSeen(true);
756 for (int c = 0; c < GetSquaresUnder(); ++c) OldSquareUnder[c] = GetLSquareUnder(c);
758 Remove();
759 PutTo(MoveTo);
760 if (!TeleportMove) {
761 /* Multitiled creatures should behave differently, maybe? */
762 if (Run) {
763 int ED = GetSquareUnder()->GetEntryDifficulty(), Base = 10000;
765 EditAP(-GetMoveAPRequirement(ED)>>1);
766 EditNP(-24*ED);
767 EditExperience(AGILITY, 125, ED<<7);
768 if (IsPlayer()) {
769 switch (GetHungerState()) {
770 case SATIATED: Base = 11000; break;
771 case BLOATED: Base = 12500; break;
772 case OVER_FED: Base = 15000; break;
775 EditStamina(-Base/Max(GetAttribute(LEG_STRENGTH), 1), true);
776 } else {
777 int ED = GetSquareUnder()->GetEntryDifficulty();
779 EditAP(-GetMoveAPRequirement(ED));
780 EditNP(-12*ED);
781 EditExperience(AGILITY, 75, ED<<7);
784 if (IsPlayer()) ShowNewPosInfo();
785 if (IsPlayer() && !game::IsInWilderness()) GetStackUnder()->SetSteppedOn(true);
786 if (!game::IsInWilderness()) SignalStepFrom(OldSquareUnder);
787 } else {
788 if (IsPlayer()) {
789 cchar *CrawlVerb = StateIsActivated(LEVITATION) ? "float" : "crawl";
791 ADD_MESSAGE("You try very hard to %s forward. But your load is too heavy.", CrawlVerb);
793 EditAP(-1000);
798 void character::GetAICommand () {
799 SeekLeader(GetLeader());
800 if (FollowLeader(GetLeader())) return;
801 if (CheckForEnemies(true, true, true)) return;
802 if (CheckForUsefulItemsOnGround()) return;
803 if (CheckForDoors()) return;
804 if (CheckSadism()) return;
805 if (MoveRandomly()) return;
806 EditAP(-1000);
810 truth character::MoveTowardsTarget (truth Run) {
811 v2 MoveTo[3];
812 v2 TPos;
813 v2 Pos = GetPos();
815 if (!Route.empty()) {
816 TPos = Route.back();
817 Route.pop_back();
818 } else TPos = GoingTo;
820 MoveTo[0] = v2(0, 0);
821 MoveTo[1] = v2(0, 0);
822 MoveTo[2] = v2(0, 0);
824 if (TPos.X < Pos.X) {
825 if (TPos.Y < Pos.Y) {
826 MoveTo[0] = v2(-1, -1);
827 MoveTo[1] = v2(-1, 0);
828 MoveTo[2] = v2( 0, -1);
829 } else if (TPos.Y == Pos.Y) {
830 MoveTo[0] = v2(-1, 0);
831 MoveTo[1] = v2(-1, -1);
832 MoveTo[2] = v2(-1, 1);
833 } else if (TPos.Y > Pos.Y) {
834 MoveTo[0] = v2(-1, 1);
835 MoveTo[1] = v2(-1, 0);
836 MoveTo[2] = v2( 0, 1);
838 } else if (TPos.X == Pos.X) {
839 if (TPos.Y < Pos.Y) {
840 MoveTo[0] = v2( 0, -1);
841 MoveTo[1] = v2(-1, -1);
842 MoveTo[2] = v2( 1, -1);
843 } else if (TPos.Y == Pos.Y) {
844 TerminateGoingTo();
845 return false;
846 } else if (TPos.Y > Pos.Y) {
847 MoveTo[0] = v2( 0, 1);
848 MoveTo[1] = v2(-1, 1);
849 MoveTo[2] = v2( 1, 1);
851 } else if (TPos.X > Pos.X) {
852 if (TPos.Y < Pos.Y) {
853 MoveTo[0] = v2(1, -1);
854 MoveTo[1] = v2(1, 0);
855 MoveTo[2] = v2(0, -1);
856 } else if (TPos.Y == Pos.Y) {
857 MoveTo[0] = v2(1, 0);
858 MoveTo[1] = v2(1, -1);
859 MoveTo[2] = v2(1, 1);
860 } else if (TPos.Y > Pos.Y) {
861 MoveTo[0] = v2(1, 1);
862 MoveTo[1] = v2(1, 0);
863 MoveTo[2] = v2(0, 1);
867 v2 ModifiedMoveTo = ApplyStateModification(MoveTo[0]);
869 if (TryMove(ModifiedMoveTo, true, Run)) return true;
871 int L = (Pos-TPos).GetManhattanLength();
873 if (RAND()&1) Swap(MoveTo[1], MoveTo[2]);
875 if (Pos.IsAdjacent(TPos)) {
876 TerminateGoingTo();
877 return false;
880 if ((Pos+MoveTo[1]-TPos).GetManhattanLength() <= L && TryMove(ApplyStateModification(MoveTo[1]), true, Run)) return true;
881 if ((Pos+MoveTo[2]-TPos).GetManhattanLength() <= L && TryMove(ApplyStateModification(MoveTo[2]), true, Run)) return true;
882 Illegal.insert(Pos+ModifiedMoveTo);
883 if (CreateRoute()) return true;
884 return false;
888 int character::CalculateNewSquaresUnder (lsquare **NewSquare, v2 Pos) const {
889 if (GetLevel()->IsValidPos(Pos)) {
890 *NewSquare = GetNearLSquare(Pos);
891 return 1;
893 return 0;
897 truth character::TryMove (v2 MoveVector, truth Important, truth Run) {
898 lsquare *MoveToSquare[MAX_SQUARES_UNDER];
899 character *Pet[MAX_SQUARES_UNDER];
900 character *Neutral[MAX_SQUARES_UNDER];
901 character *Hostile[MAX_SQUARES_UNDER];
902 v2 PetPos[MAX_SQUARES_UNDER];
903 v2 NeutralPos[MAX_SQUARES_UNDER];
904 v2 HostilePos[MAX_SQUARES_UNDER];
905 v2 MoveTo = GetPos()+MoveVector;
906 int Direction = game::GetDirectionForVector(MoveVector);
907 if (Direction == DIR_ERROR) ABORT("Direction fault.");
908 if (!game::IsInWilderness()) {
909 int Squares = CalculateNewSquaresUnder(MoveToSquare, MoveTo);
910 if (Squares) {
911 int Pets = 0;
912 int Neutrals = 0;
913 int Hostiles = 0;
914 for (int c = 0; c < Squares; ++c) {
915 character* Char = MoveToSquare[c]->GetCharacter();
916 if (Char && Char != this) {
917 v2 Pos = MoveToSquare[c]->GetPos();
918 if (IsAlly(Char)) {
919 Pet[Pets] = Char;
920 PetPos[Pets++] = Pos;
921 } else if (Char->GetRelation(this) != HOSTILE) {
922 Neutral[Neutrals] = Char;
923 NeutralPos[Neutrals++] = Pos;
924 } else {
925 Hostile[Hostiles] = Char;
926 HostilePos[Hostiles++] = Pos;
931 if (Hostiles == 1) return Hit(Hostile[0], HostilePos[0], Direction);
932 if (Hostiles) {
933 int Index = RAND() % Hostiles;
934 return Hit(Hostile[Index], HostilePos[Index], Direction);
937 if (Neutrals == 1) {
938 if (!IsPlayer() && !Pets && Important && CanMoveOn(MoveToSquare[0]))
939 return HandleCharacterBlockingTheWay(Neutral[0], NeutralPos[0], Direction);
940 else
941 return IsPlayer() && Hit(Neutral[0], NeutralPos[0], Direction);
942 } else if (Neutrals) {
943 if (IsPlayer()) {
944 int Index = RAND() % Neutrals;
945 return Hit(Neutral[Index], NeutralPos[Index], Direction);
947 return false;
950 if (!IsPlayer()) {
951 for (int c = 0; c < Squares; ++c) if (MoveToSquare[c]->IsScary(this)) return false;
954 if (Pets == 1) {
955 if (IsPlayer() && !ivanconfig::GetBeNice() && Pet[0]->IsMasochist() && HasSadistAttackMode() &&
956 game::TruthQuestion("Do you want to punish " + Pet[0]->GetObjectPronoun() + "? [y/N]"))
957 return Hit(Pet[0], PetPos[0], Direction, SADIST_HIT);
958 else
959 return (Important && (CanMoveOn(MoveToSquare[0]) ||
960 (IsPlayer() && game::GoThroughWallsCheatIsActive())) && Displace(Pet[0]));
961 } else if (Pets) return false;
963 if ((CanMove() && CanMoveOn(MoveToSquare[0])) || ((game::GoThroughWallsCheatIsActive() && IsPlayer()))) {
964 Move(MoveTo, false, Run);
965 if (IsEnabled() && GetPos() == GoingTo) TerminateGoingTo();
966 return true;
967 } else {
968 for (int c = 0; c < Squares; ++c) {
969 olterrain *Terrain = MoveToSquare[c]->GetOLTerrain();
970 if (Terrain && Terrain->CanBeOpened()) {
971 if (CanOpen()) {
972 if (Terrain->IsLocked()) {
973 if (IsPlayer()) {
974 /*k8*/
975 if (ivanconfig::GetKickDownDoors()) {
976 if (game::TruthQuestion(CONST_S("Locked! Do you want to kick ")+Terrain->GetName(DEFINITE)+"? [Y/n]", true, game::GetMoveCommandKeyBetweenPoints(PLAYER->GetPos(), MoveToSquare[0]->GetPos()))) {
977 Kick(MoveToSquare[c], Direction);
978 return true;
979 } else {
980 return false;
983 /*k8*/
984 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 */
985 return false;
986 } else if (Important && CheckKick()) {
987 room *Room = MoveToSquare[c]->GetRoom();
988 if (!Room || Room->AllowKick(this, MoveToSquare[c])) {
989 int HP = Terrain->GetHP();
990 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s kicks %s.", CHAR_NAME(DEFINITE), Terrain->CHAR_NAME(DEFINITE));
991 Kick(MoveToSquare[c], Direction);
992 olterrain *NewTerrain = MoveToSquare[c]->GetOLTerrain();
993 if (NewTerrain == Terrain && Terrain->GetHP() == HP) { // BUG!
994 Illegal.insert(MoveTo);
995 CreateRoute();
997 return true;
1000 } else { /* if (Terrain->IsLocked()) */
1001 /*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);*/
1002 /* Non-players always try to open it */
1003 if (!IsPlayer()) return MoveToSquare[c]->Open(this);
1004 if (game::TruthQuestion(CONST_S("Do you want to ")+(ivanconfig::GetKickDownDoors()?"kick ":"open ")+
1005 Terrain->GetName(DEFINITE)+"? [y/N]", false, game::GetMoveCommandKeyBetweenPoints(PLAYER->GetPos(),
1006 MoveToSquare[0]->GetPos()))) {
1007 if (ivanconfig::GetKickDownDoors()) {
1008 Kick(MoveToSquare[c], Direction);
1009 return true;
1011 return MoveToSquare[c]->Open(this);
1013 return false;
1014 } /* if (Terrain->IsLocked()) */
1015 } else { /* if (CanOpen()) */
1016 if (IsPlayer()) {
1017 ADD_MESSAGE("This monster type cannot open doors.");
1018 return false;
1020 if (Important) {
1021 Illegal.insert(MoveTo);
1022 return CreateRoute();
1024 } /* if (CanOpen()) */
1025 } /* if (Terrain && Terrain->CanBeOpened()) */
1026 } /* for */
1027 } /* if */
1028 return false;
1029 } else {
1030 if (IsPlayer() && !IsStuck() && GetLevel()->IsOnGround() && game::TruthQuestion(CONST_S("Do you want to leave ")+game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex())+"? [y/N]")) {
1031 if (HasPetrussNut() && !HasGoldenEagleShirt()) {
1032 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!"));
1033 game::GetCurrentArea()->SendNewDrawRequest();
1034 game::DrawEverything();
1035 ShowAdventureInfo();
1036 festring Msg = CONST_S("killed Petrus and became the Avatar of Chaos");
1037 PLAYER->AddScoreEntry(Msg, 3, false);
1038 game::End(Msg);
1039 return true;
1041 if (game::TryTravel(WORLD_MAP, WORLD_MAP, game::GetCurrentDungeonIndex())) return true;
1043 return false;
1045 } else {
1046 /** No multitile support */
1047 if (CanMove() && GetArea()->IsValidPos(MoveTo) && (CanMoveOn(GetNearWSquare(MoveTo)) || game::GoThroughWallsCheatIsActive())) {
1048 if (!game::GoThroughWallsCheatIsActive()) {
1049 charactervector &V = game::GetWorldMap()->GetPlayerGroup();
1050 truth Discard = false;
1051 for (uInt c = 0; c < V.size(); ++c) {
1052 if (!V[c]->CanMoveOn(GetNearWSquare(MoveTo))) {
1053 if (!Discard) {
1054 ADD_MESSAGE("One or more of your team members cannot cross this terrain.");
1055 if (!game::TruthQuestion("Discard them? [y/N]")) return false;
1056 Discard = true;
1058 if (Discard) delete V[c];
1059 V.erase(V.begin() + c--);
1063 Move(MoveTo, false);
1064 return true;
1065 } else {
1066 return false;
1072 void character::CreateCorpse (lsquare *Square) {
1073 if (!BodyPartsDisappearWhenSevered() && !game::AllBodyPartsVanish()) {
1074 corpse *Corpse = corpse::Spawn(0, NO_MATERIALS);
1075 Corpse->SetDeceased(this);
1076 Square->AddItem(Corpse);
1077 Disable();
1078 } else {
1079 SendToHell();
1084 void character::Die (ccharacter *Killer, cfestring &Msg, feuLong DeathFlags) {
1085 /* Note: This function musn't delete any objects, since one of these may be
1086 the one currently processed by pool::Be()! */
1087 if (!IsEnabled()) return;
1088 game::ClearEventData();
1089 game::mActor = Killer;
1090 if (game::RunOnCharEvent(this, CONST_S("die"))) { game::ClearEventData(); RemoveTraps(); return; }
1091 game::ClearEventData();
1092 RemoveTraps();
1093 if (IsPlayer()) {
1094 ADD_MESSAGE("You die.");
1095 game::DrawEverything();
1096 if (game::TruthQuestion(CONST_S("Do you want to save screenshot? [y/n]"), REQUIRES_ANSWER)) {
1097 festring dir;
1098 #ifdef LOCAL_SAVES
1099 dir << ivanconfig::GetMyDir() << "/save";
1100 mkdir(dir.CStr(), 0755);
1101 #else
1102 dir << getenv("HOME") << "/.ivan-save";
1103 mkdir(dir.CStr(), 0755);
1104 #endif
1105 dir << "/deathshots";
1106 mkdir(dir.CStr(), 0755);
1107 festring timestr;
1108 time_t t = time(NULL);
1109 struct tm *ts = localtime(&t);
1110 if (ts) {
1111 timestr << (int)(ts->tm_year%100);
1112 int t = ts->tm_mon+1;
1113 if (t < 10) timestr << '0'; timestr << t;
1114 t = ts->tm_mday; if (t < 10) timestr << '0'; timestr << t;
1115 timestr << '_';
1116 t = ts->tm_hour; if (t < 10) timestr << '0'; timestr << t;
1117 t = ts->tm_min; if (t < 10) timestr << '0'; timestr << t;
1118 t = ts->tm_sec; if (t < 10) timestr << '0'; timestr << t;
1119 } else {
1120 timestr = "heh";
1122 #if defined(HAVE_IMLIB2) || defined(HAVE_LIBPNG)
1123 festring ext = ".png";
1124 #else
1125 festring ext = ".bmp";
1126 #endif
1127 festring fname = dir+"/deathshot_"+timestr;
1128 if (inputfile::fileExists(fname+ext)) {
1129 for (int f = 0; f < 1000; f++) {
1130 char buf[16];
1131 sprintf(buf, "%03d", f);
1132 festring fn = fname+buf;
1133 if (!inputfile::fileExists(fn+ext)) {
1134 fname = fn;
1135 break;
1139 fname << ext;
1140 fprintf(stderr, "deathshot: %s\n", fname.CStr());
1141 #if defined(HAVE_IMLIB2) || defined(HAVE_LIBPNG)
1142 DOUBLE_BUFFER->SavePNG(fname);
1143 #else
1144 DOUBLE_BUFFER->SaveBMP(fname);
1145 #endif
1147 if (game::WizardModeIsActive()) {
1148 game::DrawEverything();
1149 if (!game::TruthQuestion(CONST_S("Do you want to do this, cheater? [y/n]"), REQUIRES_ANSWER)) {
1150 RestoreBodyParts();
1151 ResetSpoiling();
1152 RestoreHP();
1153 RestoreStamina();
1154 ResetStates();
1155 SetNP(SATIATED_LEVEL);
1156 SendNewDrawRequest();
1157 return;
1160 } else if (CanBeSeenByPlayer() && !(DeathFlags & DISALLOW_MSG)) {
1161 ProcessAndAddMessage(GetDeathMessage());
1162 } else if (DeathFlags & FORCE_MSG) {
1163 ADD_MESSAGE("You sense the death of something.");
1166 if (!(DeathFlags & FORBID_REINCARNATION)) {
1167 if (StateIsActivated(LIFE_SAVED) && CanMoveOn(!game::IsInWilderness() ? GetSquareUnder() : PLAYER->GetSquareUnder())) {
1168 SaveLife();
1169 return;
1171 if (SpecialSaveLife()) return;
1172 } else if (StateIsActivated(LIFE_SAVED)) {
1173 RemoveLifeSavers();
1176 Flags |= C_IN_NO_MSG_MODE;
1177 character *Ghost = 0;
1178 if (IsPlayer()) {
1179 game::RemoveSaves();
1180 if (!game::IsInWilderness()) {
1181 Ghost = game::CreateGhost();
1182 Ghost->Disable();
1186 square *SquareUnder[MAX_SQUARES_UNDER];
1187 memset(SquareUnder, 0, sizeof(SquareUnder));
1188 Disable();
1189 if (IsPlayer() || !game::IsInWilderness()) {
1190 for (int c = 0; c < SquaresUnder; ++c) SquareUnder[c] = GetSquareUnder(c);
1191 Remove();
1192 } else {
1193 charactervector& V = game::GetWorldMap()->GetPlayerGroup();
1194 V.erase(std::find(V.begin(), V.end(), this));
1196 //lsquare **LSquareUnder = reinterpret_cast<lsquare **>(SquareUnder); /* warning; wtf? */
1197 lsquare *LSquareUnder[MAX_SQUARES_UNDER];
1198 memmove(LSquareUnder, SquareUnder, sizeof(SquareUnder));
1200 if (!game::IsInWilderness()) {
1201 if (!StateIsActivated(POLYMORPHED)) {
1202 if (!IsPlayer() && !IsTemporary() && !Msg.IsEmpty()) game::SignalDeath(this, Killer, Msg);
1203 if (!(DeathFlags & DISALLOW_CORPSE)) CreateCorpse(LSquareUnder[0]); else SendToHell();
1204 } else {
1205 if (!IsPlayer() && !IsTemporary() && !Msg.IsEmpty()) game::SignalDeath(GetPolymorphBackup(), Killer, Msg);
1206 GetPolymorphBackup()->CreateCorpse(LSquareUnder[0]);
1207 GetPolymorphBackup()->Flags &= ~C_POLYMORPHED;
1208 SetPolymorphBackup(0);
1209 SendToHell();
1211 } else {
1212 if (!IsPlayer() && !IsTemporary() && !Msg.IsEmpty()) game::SignalDeath(this, Killer, Msg);
1213 SendToHell();
1216 if (IsPlayer()) {
1217 if (!game::IsInWilderness()) {
1218 for (int c = 0; c < GetSquaresUnder(); ++c) LSquareUnder[c]->SetTemporaryEmitation(GetEmitation());
1220 ShowAdventureInfo();
1221 if (!game::IsInWilderness()) {
1222 for(int c = 0; c < GetSquaresUnder(); ++c) LSquareUnder[c]->SetTemporaryEmitation(0);
1226 if (!game::IsInWilderness()) {
1227 if (GetSquaresUnder() == 1) {
1228 stack *StackUnder = LSquareUnder[0]->GetStack();
1229 GetStack()->MoveItemsTo(StackUnder);
1230 doforbodypartswithparam<stack*>()(this, &bodypart::DropEquipment, StackUnder);
1231 } else {
1232 while (GetStack()->GetItems()) GetStack()->GetBottom()->MoveTo(LSquareUnder[RAND_N(GetSquaresUnder())]->GetStack());
1233 for (int c = 0; c < BodyParts; ++c) {
1234 bodypart *BodyPart = GetBodyPart(c);
1235 if (BodyPart) BodyPart->DropEquipment(LSquareUnder[RAND_N(GetSquaresUnder())]->GetStack());
1240 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(0);
1242 Flags &= ~C_IN_NO_MSG_MODE;
1244 if (IsPlayer()) {
1245 AddScoreEntry(Msg);
1246 if (!game::IsInWilderness()) {
1247 Ghost->PutTo(LSquareUnder[0]->GetPos());
1248 Ghost->Enable();
1249 game::CreateBone();
1251 game::TextScreen(CONST_S("Unfortunately you died."), ZERO_V2, WHITE, true, true, &game::ShowDeathSmiley);
1252 game::End(Msg);
1257 void character::AddMissMessage (ccharacter *Enemy) const {
1258 festring Msg;
1259 if (Enemy->IsPlayer()) Msg = GetDescription(DEFINITE)+" misses you!";
1260 else if (IsPlayer()) Msg = CONST_S("You miss ")+Enemy->GetDescription(DEFINITE)+'!';
1261 else if (CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer()) Msg = GetDescription(DEFINITE)+" misses "+Enemy->GetDescription(DEFINITE)+'!';
1262 else return;
1263 ADD_MESSAGE("%s", Msg.CStr());
1267 void character::AddBlockMessage (ccharacter *Enemy, citem *Blocker, cfestring &HitNoun, truth Partial) const {
1268 festring Msg;
1269 festring BlockVerb = (Partial ? " to partially block the " : " to block the ")+HitNoun;
1270 if (IsPlayer()) {
1271 Msg << "You manage" << BlockVerb << " with your " << Blocker->GetName(UNARTICLED) << '!';
1272 } else if (Enemy->IsPlayer() || Enemy->CanBeSeenByPlayer()) {
1273 if (CanBeSeenByPlayer())
1274 Msg << GetName(DEFINITE) << " manages" << BlockVerb << " with " << GetPossessivePronoun() << ' ' << Blocker->GetName(UNARTICLED) << '!';
1275 else
1276 Msg << "Something manages" << BlockVerb << " with something!";
1277 } else {
1278 return;
1280 ADD_MESSAGE("%s", Msg.CStr());
1284 void character::AddPrimitiveHitMessage (ccharacter *Enemy, cfestring &FirstPersonHitVerb,
1285 cfestring &ThirdPersonHitVerb, int BodyPart) const
1287 festring Msg;
1288 festring BodyPartDescription;
1289 if (BodyPart && (Enemy->CanBeSeenByPlayer() || Enemy->IsPlayer()))
1290 BodyPartDescription << " in the " << Enemy->GetBodyPartName(BodyPart);
1291 if (Enemy->IsPlayer())
1292 Msg << GetDescription(DEFINITE) << ' ' << ThirdPersonHitVerb << " you" << BodyPartDescription << '!';
1293 else if (IsPlayer())
1294 Msg << "You " << FirstPersonHitVerb << ' ' << Enemy->GetDescription(DEFINITE) << BodyPartDescription << '!';
1295 else if (CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer())
1296 Msg << GetDescription(DEFINITE) << ' ' << ThirdPersonHitVerb << ' ' << Enemy->GetDescription(DEFINITE) + BodyPartDescription << '!';
1297 else
1298 return;
1299 ADD_MESSAGE("%s", Msg.CStr());
1303 cchar *const HitVerb[] = { "strike", "slash", "stab" };
1304 cchar *const HitVerb3rdPersonEnd[] = { "s", "es", "s" };
1307 void character::AddWeaponHitMessage (ccharacter *Enemy, citem *Weapon, int BodyPart, truth Critical) const {
1308 festring Msg;
1309 festring BodyPartDescription;
1311 if (BodyPart && (Enemy->CanBeSeenByPlayer() || Enemy->IsPlayer()))
1312 BodyPartDescription << " in the " << Enemy->GetBodyPartName(BodyPart);
1314 int FittingTypes = 0;
1315 int DamageFlags = Weapon->GetDamageFlags();
1316 int DamageType = 0;
1318 for (int c = 0; c < DAMAGE_TYPES; ++c) {
1319 if (1 << c & DamageFlags) {
1320 if (!FittingTypes || !RAND_N(FittingTypes+1)) DamageType = c;
1321 ++FittingTypes;
1325 if (!FittingTypes) ABORT("No damage flags specified for %s!", Weapon->CHAR_NAME(UNARTICLED));
1327 festring NewHitVerb = Critical ? " critically " : " ";
1328 NewHitVerb << HitVerb[DamageType];
1329 cchar *const E = HitVerb3rdPersonEnd[DamageType];
1331 if (Enemy->IsPlayer()) {
1332 Msg << GetDescription(DEFINITE) << NewHitVerb << E << " you" << BodyPartDescription;
1333 if (CanBeSeenByPlayer()) Msg << " with " << GetPossessivePronoun() << ' ' << Weapon->GetName(UNARTICLED);
1334 Msg << '!';
1335 } else if (IsPlayer()) {
1336 Msg << "You" << NewHitVerb << ' ' << Enemy->GetDescription(DEFINITE) << BodyPartDescription << '!';
1337 } else if(CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer()) {
1338 Msg << GetDescription(DEFINITE) << NewHitVerb << E << ' ' << Enemy->GetDescription(DEFINITE) << BodyPartDescription;
1339 if (CanBeSeenByPlayer()) Msg << " with " << GetPossessivePronoun() << ' ' << Weapon->GetName(UNARTICLED);
1340 Msg << '!';
1341 } else {
1342 return;
1344 ADD_MESSAGE("%s", Msg.CStr());
1348 item *character::GeneralFindItem (ItemCheckerCB chk) const {
1349 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) {
1350 item *it = *i;
1351 if (it && chk(it)) return it;
1353 return 0;
1357 static truth isElpuriHead (item *i) { return i->IsHeadOfElpuri(); }
1358 truth character::HasHeadOfElpuri () const {
1359 if (GeneralFindItem(::isElpuriHead)) return true;
1360 return combineequipmentpredicates()(this, &item::IsHeadOfElpuri, 1);
1364 static truth isPetrussNut (item *i) { return i->IsPetrussNut(); }
1365 truth character::HasPetrussNut () const {
1366 if (GeneralFindItem(::isPetrussNut)) return true;
1367 return combineequipmentpredicates()(this, &item::IsPetrussNut, 1);
1371 static truth isGoldenEagleShirt (item *i) { return i->IsGoldenEagleShirt(); }
1372 truth character::HasGoldenEagleShirt () const {
1373 if (GeneralFindItem(::isGoldenEagleShirt)) return true;
1374 return combineequipmentpredicates()(this, &item::IsGoldenEagleShirt, 1);
1378 truth character::HasOmmelBlood () const {
1379 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
1380 if (i->IsKleinBottle() && i->GetSecondaryMaterial() && i->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD) return true;
1382 for (int c = 0; c < GetEquipments(); ++c) {
1383 item *Item = GetEquipment(c);
1385 if (Item && Item->IsKleinBottle() && Item->GetSecondaryMaterial() && Item->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD) return true;
1387 return false; //combineequipmentpredicates()(this, &item::IsKleinBottle, 1);
1391 truth character::HasCurdledBlood () const {
1392 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
1393 if (i->IsKleinBottle() && i->GetSecondaryMaterial() && i->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD) return true;
1395 for (int c = 0; c < GetEquipments(); ++c) {
1396 item *Item = GetEquipment(c);
1398 if (Item && Item->IsKleinBottle() && Item->GetSecondaryMaterial() && Item->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD) return true;
1400 return false; //combineequipmentpredicates()(this, &item::IsKleinBottle, 1);
1404 truth character::CurdleOmmelBlood () const {
1405 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) {
1406 if (i->IsKleinBottle() && i->GetSecondaryMaterial() && i->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD) {
1407 i->ChangeSecondaryMaterial(MAKE_MATERIAL(CURDLED_OMMEL_BLOOD));
1408 return true;
1412 for (int c = 0; c < GetEquipments(); ++c) {
1413 item *Item = GetEquipment(c);
1415 if (Item && Item->IsKleinBottle() && Item->GetSecondaryMaterial() && Item->GetSecondaryMaterial()->GetConfig() == OMMEL_BLOOD) {
1416 Item->ChangeSecondaryMaterial(MAKE_MATERIAL(CURDLED_OMMEL_BLOOD));
1417 return true;
1420 return false; //combineequipmentpredicates()(this, &item::IsKleinBottle, 1);
1424 truth character::RemoveCurdledOmmelBlood () {
1425 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) {
1426 if (i->IsKleinBottle() && i->GetSecondaryMaterial() && i->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD) {
1427 (*i)->RemoveFromSlot();
1428 (*i)->SendToHell();
1429 return true;
1433 for (int c = 0; c < GetEquipments(); ++c) {
1434 item *Item = GetEquipment(c);
1436 if (Item && Item->IsKleinBottle() && Item->GetSecondaryMaterial() && Item->GetSecondaryMaterial()->GetConfig() == CURDLED_OMMEL_BLOOD) {
1437 Item->RemoveFromSlot();
1438 Item->SendToHell();
1439 return true;
1442 return false;
1446 int character::GeneralRemoveItem (ItemCheckerCB chk, truth allItems) {
1447 truth done;
1448 int cnt = 0;
1449 // inventory
1450 do {
1451 done = true;
1452 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) {
1453 item *Item = *i;
1454 if (Item && chk(Item)) {
1455 Item->RemoveFromSlot();
1456 Item->SendToHell();
1457 cnt++;
1458 if (!allItems) return cnt;
1459 done = false;
1460 break;
1463 } while (!done);
1464 // equipments
1465 do {
1466 done = true;
1467 for (int c = 0; c < GetEquipments(); ++c) {
1468 item *Item = GetEquipment(c);
1469 if (Item && chk(Item)) {
1470 Item->RemoveFromSlot();
1471 Item->SendToHell();
1472 cnt++;
1473 if (!allItems) return cnt;
1474 done = false;
1475 break;
1478 } while (!done);
1479 return cnt;
1483 static truth isEncryptedScroll (item *i) { return i->IsEncryptedScroll(); }
1484 truth character::RemoveEncryptedScroll () { return GeneralRemoveItem(::isEncryptedScroll) != 0; }
1487 static truth isMondedrPass (item *i) { return i->IsMondedrPass(); }
1488 truth character::RemoveMondedrPass () { return GeneralRemoveItem(::isMondedrPass) != 0; }
1491 static truth isRingOfThieves (item *i) { return i->IsRingOfThieves(); }
1492 truth character::RemoveRingOfThieves () { return GeneralRemoveItem(::isRingOfThieves) != 0; }
1495 truth character::ReadItem (item *ToBeRead) {
1496 if (!ToBeRead->CanBeRead(this)) {
1497 if (IsPlayer()) ADD_MESSAGE("You can't read this.");
1498 return false;
1500 if (!GetLSquareUnder()->IsDark() || game::GetSeeWholeMapCheatMode()) {
1501 if (StateIsActivated(CONFUSED) && !(RAND()&7)) {
1502 if (!ToBeRead->IsDestroyable(this)) {
1503 ADD_MESSAGE("You read some words of %s and understand exactly nothing.", ToBeRead->CHAR_NAME(DEFINITE));
1504 } else {
1505 ADD_MESSAGE("%s is very confusing. Or perhaps you are just too confused?", ToBeRead->CHAR_NAME(DEFINITE));
1506 ActivateRandomState(SRC_CONFUSE_READ, 1000+RAND()%1500);
1507 ToBeRead->RemoveFromSlot();
1508 ToBeRead->SendToHell();
1510 EditAP(-1000);
1511 return true;
1513 if (ToBeRead->Read(this)) {
1514 if (!game::WizardModeIsActive()) {
1515 /* This AP is used to take the stuff out of backpack */
1516 DexterityAction(5);
1518 return true;
1520 return false;
1522 if (IsPlayer()) ADD_MESSAGE("It's too dark here to read.");
1523 return false;
1527 void character::CalculateBurdenState () {
1528 int OldBurdenState = BurdenState;
1529 sLong SumOfMasses = GetCarriedWeight();
1530 sLong CarryingStrengthUnits = sLong(GetCarryingStrength())*2500;
1531 if (SumOfMasses > (CarryingStrengthUnits << 1) + CarryingStrengthUnits) BurdenState = OVER_LOADED;
1532 else if (SumOfMasses > CarryingStrengthUnits << 1) BurdenState = STRESSED;
1533 else if (SumOfMasses > CarryingStrengthUnits) BurdenState = BURDENED;
1534 else BurdenState = UNBURDENED;
1535 if (!IsInitializing() && BurdenState != OldBurdenState) CalculateBattleInfo();
1539 void character::Save (outputfile &SaveFile) const {
1540 SaveFile << (uShort)GetType();
1541 Stack->Save(SaveFile);
1542 SaveFile << ID;
1543 for (int c = 0; c < BASE_ATTRIBUTES; ++c) SaveFile << BaseExperience[c];
1545 SaveFile << ExpModifierMap;
1546 SaveFile << NP << AP << Stamina << GenerationDanger << ScienceTalks << CounterToMindWormHatch;
1547 SaveFile << TemporaryState << EquipmentState << Money << GoingTo << RegenerationCounter << Route << Illegal;
1548 SaveFile << CurrentSweatMaterial;
1549 SaveFile.Put(!!IsEnabled());
1550 SaveFile << HomeData << BlocksSinceLastTurn << CommandFlags;
1551 SaveFile << WarnFlags << (uShort)Flags;
1553 for (int c = 0; c < BodyParts; ++c) SaveFile << BodyPartSlot[c] << OriginalBodyPartID[c];
1555 SaveLinkedList(SaveFile, TrapData);
1556 SaveFile << Action;
1558 for (int c = 0; c < STATES; ++c) SaveFile << TemporaryStateCounter[c];
1560 if (GetTeam()) {
1561 SaveFile.Put(true);
1562 SaveFile << Team->GetID(); // feuLong
1563 } else {
1564 SaveFile.Put(false);
1567 if (GetTeam() && GetTeam()->GetLeader() == this) SaveFile.Put(true); else SaveFile.Put(false);
1569 SaveFile << AssignedName << PolymorphBackup;
1571 for (int c = 0; c < AllowedWeaponSkillCategories; ++c) SaveFile << CWeaponSkill[c];
1573 SaveFile << (uShort)GetConfig();
1577 void character::Load (inputfile &SaveFile) {
1578 LoadSquaresUnder();
1579 Stack->Load(SaveFile);
1580 SaveFile >> ID;
1581 game::AddCharacterID(this, ID);
1583 for (int c = 0; c < BASE_ATTRIBUTES; ++c) SaveFile >> BaseExperience[c];
1585 SaveFile >> ExpModifierMap;
1586 SaveFile >> NP >> AP >> Stamina >> GenerationDanger >> ScienceTalks >> CounterToMindWormHatch;
1587 SaveFile >> TemporaryState >> EquipmentState >> Money >> GoingTo >> RegenerationCounter >> Route >> Illegal;
1588 SaveFile >> CurrentSweatMaterial;
1590 if (!SaveFile.Get()) Disable();
1592 SaveFile >> HomeData >> BlocksSinceLastTurn >> CommandFlags;
1593 SaveFile >> WarnFlags;
1594 WarnFlags &= ~WARNED;
1595 Flags |= ReadType(uShort, SaveFile) & ~ENTITY_FLAGS;
1597 for (int c = 0; c < BodyParts; ++c) {
1598 SaveFile >> BodyPartSlot[c] >> OriginalBodyPartID[c];
1599 item *BodyPart = *BodyPartSlot[c];
1600 if (BodyPart) BodyPart->Disable();
1603 LoadLinkedList(SaveFile, TrapData);
1604 SaveFile >> Action;
1606 if (Action) Action->SetActor(this);
1608 for (int c = 0; c < STATES; ++c) SaveFile >> TemporaryStateCounter[c];
1610 if (SaveFile.Get()) SetTeam(game::GetTeam(ReadType(feuLong, SaveFile)));
1612 if (SaveFile.Get()) GetTeam()->SetLeader(this);
1614 SaveFile >> AssignedName >> PolymorphBackup;
1616 for (int c = 0; c < AllowedWeaponSkillCategories; ++c) SaveFile >> CWeaponSkill[c];
1618 databasecreator<character>::InstallDataBase(this, ReadType(uShort, SaveFile));
1620 if (IsEnabled() && !game::IsInWilderness()) {
1621 for (int c = 1; c < GetSquaresUnder(); ++c) GetSquareUnder(c)->SetCharacter(this);
1625 const fearray<festring> &lt = GetLevelTags();
1626 if (lt.Size > 1) {
1627 fprintf(stderr, "====\n");
1628 for (uInt f = 0; f < lt.Size; ++f) fprintf(stderr, " %u: [%s]\n", f, lt[f].CStr());
1634 truth character::Engrave (cfestring &What) {
1635 GetLSquareUnder()->Engrave(What);
1636 return true;
1639 truth character::MoveRandomly () {
1640 if (!IsEnabled()) return false;
1641 for (int c = 0; c < 10; ++c) {
1642 v2 ToTry = game::GetMoveVector(RAND()&7);
1643 if (GetLevel()->IsValidPos(GetPos()+ToTry)) {
1644 lsquare *Square = GetNearLSquare(GetPos()+ToTry);
1645 if (!Square->IsDangerous(this) && !Square->IsScary(this) && TryMove(ToTry, false, false)) return true;
1648 return false;
1652 truth character::TestForPickup (item *ToBeTested) const {
1653 if (MakesBurdened(ToBeTested->GetWeight()+GetCarriedWeight())) return false;
1654 return true;
1658 void character::AddScoreEntry (cfestring &Description, double Multiplier, truth AddEndLevel) const {
1659 if (!game::WizardModeIsReallyActive()) {
1660 highscore HScore;
1661 if (!HScore.CheckVersion()) {
1662 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;
1663 HScore.Clear();
1665 festring Desc = game::GetPlayerName();
1666 Desc << ", " << Description;
1667 if (AddEndLevel) Desc << " in "+(game::IsInWilderness() ? "the world map" : game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex()));
1668 HScore.Add(sLong(game::GetScore()*Multiplier), Desc);
1669 HScore.Save();
1674 truth character::CheckDeath (cfestring &Msg, ccharacter *Murderer, feuLong DeathFlags) {
1675 if (!IsEnabled()) return true;
1676 if (game::IsSumoWrestling() && IsDead()) {
1677 game::EndSumoWrestling(!!IsPlayer());
1678 return true;
1680 if (DeathFlags & FORCE_DEATH || IsDead()) {
1681 if (Murderer && Murderer->IsPlayer() && GetTeam()->GetKillEvilness()) game::DoEvilDeed(GetTeam()->GetKillEvilness());
1682 festring SpecifierMsg;
1683 int SpecifierParts = 0;
1684 if (GetPolymorphBackup()) {
1685 SpecifierMsg << " polymorphed into ";
1686 id::AddName(SpecifierMsg, INDEFINITE);
1687 ++SpecifierParts;
1689 if (!(DeathFlags & IGNORE_TRAPS) && IsStuck()) {
1690 if (SpecifierParts++) SpecifierMsg << " and";
1691 SpecifierMsg << " caught in " << GetTrapDescription();
1693 if (GetAction() && !(DeathFlags & IGNORE_UNCONSCIOUSNESS && GetAction()->IsUnconsciousness())) {
1694 festring ActionMsg = GetAction()->GetDeathExplanation();
1695 if (!ActionMsg.IsEmpty()) {
1696 if (SpecifierParts > 1) {
1697 SpecifierMsg = ActionMsg << ',' << SpecifierMsg;
1698 } else {
1699 if (SpecifierParts) SpecifierMsg << " and";
1700 SpecifierMsg << ActionMsg;
1702 ++SpecifierParts;
1705 festring NewMsg = Msg;
1706 if (Murderer == this) {
1707 SEARCH_N_REPLACE(NewMsg, "@bkp", CONST_S("by ") + GetPossessivePronoun(false) + " own");
1708 SEARCH_N_REPLACE(NewMsg, "@bk", CONST_S("by ") + GetObjectPronoun(false) + "self");
1709 SEARCH_N_REPLACE(NewMsg, "@k", GetObjectPronoun(false) + "self");
1710 } else {
1711 SEARCH_N_REPLACE(NewMsg, "@bkp", CONST_S("by ") + Murderer->GetName(INDEFINITE) + "'s");
1712 SEARCH_N_REPLACE(NewMsg, "@bk", CONST_S("by ") + Murderer->GetName(INDEFINITE));
1713 SEARCH_N_REPLACE(NewMsg, "@k", CONST_S("by ") + Murderer->GetName(INDEFINITE));
1715 if (SpecifierParts) NewMsg << " while" << SpecifierMsg;
1716 if (IsPlayer() && game::WizardModeIsActive()) ADD_MESSAGE("Death message: %s. Score: %d.", NewMsg.CStr(), game::GetScore());
1717 Die(Murderer, NewMsg, DeathFlags);
1718 return true;
1720 return false;
1724 truth character::CheckStarvationDeath (cfestring &Msg) {
1725 if (GetNP() < 1 && UsesNutrition()) return CheckDeath(Msg, 0, FORCE_DEATH);
1726 return false;
1730 void character::ThrowItem (int Direction, item *ToBeThrown) {
1731 if (Direction > 7) ABORT("Throw in TOO odd direction...");
1732 ToBeThrown->Fly(this, Direction, GetAttribute(ARM_STRENGTH));
1736 void character::HasBeenHitByItem (character *Thrower, item *Thingy, int Damage, double ToHitValue, int Direction) {
1737 if (IsPlayer()) ADD_MESSAGE("%s hits you.", Thingy->CHAR_NAME(DEFINITE));
1738 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s hits %s.", Thingy->CHAR_NAME(DEFINITE), CHAR_NAME(DEFINITE));
1739 int BodyPart = ChooseBodyPartToReceiveHit(ToHitValue, DodgeValue);
1740 int WeaponSkillHits = Thrower ? CalculateWeaponSkillHits(Thrower) : 0;
1741 int DoneDamage = ReceiveBodyPartDamage(Thrower, Damage, PHYSICAL_DAMAGE, BodyPart, Direction);
1742 truth Succeeded = (GetBodyPart(BodyPart) && HitEffect(Thrower, Thingy, Thingy->GetPos(), THROW_ATTACK, BodyPart, Direction, !DoneDamage)) || DoneDamage;
1743 if (Succeeded && Thrower) Thrower->WeaponSkillHit(Thingy, THROW_ATTACK, WeaponSkillHits);
1744 festring DeathMsg = CONST_S("killed by a flying ")+Thingy->GetName(UNARTICLED);
1745 if (CheckDeath(DeathMsg, Thrower)) return;
1746 if (Thrower) {
1747 if (Thrower->CanBeSeenByPlayer())
1748 DeActivateVoluntaryAction(CONST_S("The attack of ")+Thrower->GetName(DEFINITE)+CONST_S(" interrupts you."));
1749 else
1750 DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
1751 } else {
1752 DeActivateVoluntaryAction(CONST_S("The hit interrupts you."));
1757 truth character::DodgesFlyingItem (item *Item, double ToHitValue) {
1758 return !Item->EffectIsGood() && RAND() % int(100+ToHitValue/DodgeValue*100) < 100;
1762 void character::GetPlayerCommand () {
1763 command *cmd;
1764 truth HasActed = false;
1765 while (!HasActed) {
1766 game::DrawEverything();
1767 if (game::GetDangerFound()) {
1768 if (game::GetDangerFound() > 500.) {
1769 if (game::GetCausePanicFlag()) {
1770 game::SetCausePanicFlag(false);
1771 BeginTemporaryState(PANIC, 500+RAND_N(500));
1773 game::AskForEscPress(CONST_S("You are horrified by your situation!"));
1774 } else if (ivanconfig::GetWarnAboutDanger()) {
1775 if (game::GetDangerFound() > 50.) game::AskForEscPress(CONST_S("You sense great danger!"));
1776 else game::AskForEscPress(CONST_S("You sense danger!"));
1778 game::SetDangerFound(0);
1780 game::SetIsInGetCommand(true);
1781 int Key = GET_KEY();
1782 game::SetIsInGetCommand(false);
1783 if (Key != '+' && Key != '-' && Key != 'M') msgsystem::ThyMessagesAreNowOld(); // gum
1784 truth ValidKeyPressed = false;
1786 for (int c = 0; c < DIRECTION_COMMAND_KEYS; ++c) {
1787 if (Key == game::GetMoveCommandKey(c)) {
1788 HasActed = TryMove(ApplyStateModification(game::GetMoveVector(c)), true, game::PlayerIsRunning());
1789 ValidKeyPressed = true;
1793 if (!ValidKeyPressed) {
1794 for (int c = 0; (cmd = commandsystem::GetCommand(c)); ++c) {
1795 /* k8 */
1796 /* Numpad aliases for most commonly used commands */
1797 if (Key == KEY_DEL && cmd->GetName() == "Eat") Key = cmd->GetKey();
1798 if (Key == KEY_INS && cmd->GetName() == "PickUp") Key = cmd->GetKey();
1799 if (Key == KEY_PLUS && cmd->GetName() == "EquipmentScreen") Key = cmd->GetKey();
1800 /* k8 */
1801 if (Key == cmd->GetKey()) {
1802 if (game::IsInWilderness() && !commandsystem::GetCommand(c)->IsUsableInWilderness()) {
1803 ADD_MESSAGE("This function cannot be used while in wilderness.");
1804 } else if (!game::WizardModeIsActive() && commandsystem::GetCommand(c)->IsWizardModeFunction()) {
1805 ADD_MESSAGE("Activate wizardmode to use this function.");
1806 } else {
1807 HasActed = commandsystem::GetCommand(c)->GetLinkedFunction()(this);
1809 ValidKeyPressed = true;
1810 break;
1814 if (!ValidKeyPressed) ADD_MESSAGE("Unknown key. Press '?' for a list of commands.");
1816 game::IncreaseTurn();
1820 void character::Vomit (v2 Pos, int Amount, truth ShowMsg) {
1821 if (!CanVomit()) return;
1822 if (ShowMsg) {
1823 if (IsPlayer()) ADD_MESSAGE("You vomit.");
1824 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s vomits.", CHAR_NAME(DEFINITE));
1826 if (VomittingIsUnhealthy()) {
1827 EditExperience(ARM_STRENGTH, -75, 1 << 9);
1828 EditExperience(LEG_STRENGTH, -75, 1 << 9);
1830 if (IsPlayer()) {
1831 EditNP(-2500-RAND()%2501);
1832 CheckStarvationDeath(CONST_S("vomited himself to death"));
1834 if (StateIsActivated(PARASITIZED) && !(RAND() & 7)) {
1835 if (IsPlayer()) ADD_MESSAGE("You notice a dead broad tapeworm among your former stomach contents.");
1836 DeActivateTemporaryState(PARASITIZED);
1838 if (!game::IsInWilderness()) {
1839 GetNearLSquare(Pos)->ReceiveVomit(this, liquid::Spawn(GetVomitMaterial(), sLong(sqrt(GetBodyVolume())*Amount/1000)));
1844 truth character::Polymorph (character *NewForm, int Counter) {
1845 if (!IsPolymorphable() || (!IsPlayer() && game::IsInWilderness())) {
1846 delete NewForm;
1847 return false;
1849 RemoveTraps();
1850 if (GetAction()) GetAction()->Terminate(false);
1851 NewForm->SetAssignedName("");
1852 if (IsPlayer())
1853 ADD_MESSAGE("Your body glows in a crimson light. You transform into %s!", NewForm->CHAR_NAME(INDEFINITE));
1854 else if (CanBeSeenByPlayer())
1855 ADD_MESSAGE("%s glows in a crimson light and %s transforms into %s!", CHAR_NAME(DEFINITE), GetPersonalPronoun().CStr(), NewForm->CHAR_NAME(INDEFINITE));
1857 Flags |= C_IN_NO_MSG_MODE;
1858 NewForm->Flags |= C_IN_NO_MSG_MODE;
1859 NewForm->ChangeTeam(GetTeam());
1860 NewForm->GenerationDanger = GenerationDanger;
1861 NewForm->mOnEvents = this->mOnEvents;
1863 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(NewForm);
1865 v2 Pos = GetPos();
1866 Remove();
1867 NewForm->PutToOrNear(Pos);
1868 NewForm->SetAssignedName(GetAssignedName());
1869 NewForm->ActivateTemporaryState(POLYMORPHED);
1870 NewForm->SetTemporaryStateCounter(POLYMORPHED, Counter);
1872 if (TemporaryStateIsActivated(POLYMORPHED)) {
1873 NewForm->SetPolymorphBackup(GetPolymorphBackup());
1874 SetPolymorphBackup(0);
1875 SendToHell();
1876 } else {
1877 NewForm->SetPolymorphBackup(this);
1878 Flags |= C_POLYMORPHED;
1879 Disable();
1882 GetStack()->MoveItemsTo(NewForm->GetStack());
1883 NewForm->SetMoney(GetMoney());
1884 DonateEquipmentTo(NewForm);
1885 Flags &= ~C_IN_NO_MSG_MODE;
1886 NewForm->Flags &= ~C_IN_NO_MSG_MODE;
1887 NewForm->CalculateAll();
1889 if (IsPlayer()) {
1890 Flags &= ~C_PLAYER;
1891 game::SetPlayer(NewForm);
1892 game::SendLOSUpdateRequest();
1893 UpdateESPLOS();
1896 NewForm->TestWalkability();
1897 return true;
1901 void character::BeKicked (character *Kicker, item *Boot, bodypart *Leg, v2 HitPos, double KickDamage,
1902 double ToHitValue, int Success, int Direction, truth Critical, truth ForceHit)
1904 //FIXME: other args
1905 game::ClearEventData();
1906 game::mActor = Kicker;
1907 if (game::RunOnCharEvent(this, CONST_S("before_be_kicked"))) { game::ClearEventData(); return; }
1908 game::ClearEventData();
1909 game::mActor = 0;
1910 switch (TakeHit(Kicker, Boot, Leg, HitPos, KickDamage, ToHitValue, Success, KICK_ATTACK, Direction, Critical, ForceHit)) {
1911 case HAS_HIT:
1912 case HAS_BLOCKED:
1913 case DID_NO_DAMAGE:
1914 if (IsEnabled() && !CheckBalance(KickDamage)) {
1915 if (IsPlayer()) ADD_MESSAGE("The kick throws you off balance.");
1916 else if (Kicker->IsPlayer()) ADD_MESSAGE("The kick throws %s off balance.", CHAR_DESCRIPTION(DEFINITE));
1917 v2 FallToPos = GetPos()+game::GetMoveVector(Direction);
1918 FallTo(Kicker, FallToPos);
1924 /* Return true if still in balance */
1925 truth character::CheckBalance (double KickDamage) {
1926 return !CanMove() || IsStuck() || !KickDamage || (!IsFlying() && KickDamage*5 < RAND()%GetSize());
1930 void character::FallTo (character *GuiltyGuy, v2 Where) {
1931 EditAP(-500);
1932 lsquare *MoveToSquare[MAX_SQUARES_UNDER];
1933 int Squares = CalculateNewSquaresUnder(MoveToSquare, Where);
1934 if (Squares) {
1935 truth NoRoom = false;
1936 for (int c = 0; c < Squares; ++c) {
1937 olterrain *Terrain = MoveToSquare[c]->GetOLTerrain();
1938 if (Terrain && !CanMoveOn(Terrain)) { NoRoom = true; break; }
1940 if (NoRoom) {
1941 if (HasHead()) {
1942 if (IsPlayer()) ADD_MESSAGE("You hit your head on the wall.");
1943 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s hits %s head on the wall.", CHAR_NAME(DEFINITE), GetPossessivePronoun().CStr());
1945 ReceiveDamage(GuiltyGuy, 1+RAND()%5, PHYSICAL_DAMAGE, HEAD);
1946 CheckDeath(CONST_S("killed by hitting a wall due to being kicked @bk"), GuiltyGuy);
1947 } else {
1948 if (IsFreeForMe(MoveToSquare[0])) Move(Where, true);
1949 // Place code that handles characters bouncing to each other here
1955 truth character::CheckCannibalism (cmaterial *What) const {
1956 return GetTorso()->GetMainMaterial()->IsSameAs(What);
1960 void character::StandIdleAI () {
1961 SeekLeader(GetLeader());
1962 if (CheckForEnemies(true, true, true)) return;
1963 if (CheckForUsefulItemsOnGround()) return;
1964 if (FollowLeader(GetLeader())) return;
1965 if (CheckForDoors()) return;
1966 if (MoveTowardsHomePos()) return;
1967 if (CheckSadism()) return;
1968 EditAP(-1000);
1972 truth character::LoseConsciousness (int Counter, truth HungerFaint) {
1973 if (!AllowUnconsciousness()) return false;
1974 action *Action = GetAction();
1975 if (Action) {
1976 if (HungerFaint && !Action->AllowUnconsciousness()) return false;
1977 if (Action->IsUnconsciousness()) {
1978 static_cast<unconsciousness *>(Action)->RaiseCounterTo(Counter);
1979 return true;
1981 Action->Terminate(false);
1983 if (IsPlayer()) ADD_MESSAGE("You lose consciousness.");
1984 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s loses consciousness.", CHAR_NAME(DEFINITE));
1985 unconsciousness *Unconsciousness = unconsciousness::Spawn(this);
1986 Unconsciousness->SetCounter(Counter);
1987 SetAction(Unconsciousness);
1988 return true;
1992 void character::DeActivateVoluntaryAction (cfestring &Reason) {
1993 if (GetAction() && GetAction()->IsVoluntary()) {
1994 if (IsPlayer()) {
1995 if (Reason.GetSize()) ADD_MESSAGE("%s", Reason.CStr());
1996 if (game::TruthQuestion(CONST_S("Continue ") + GetAction()->GetDescription()+"? [y/N]")) GetAction()->ActivateInDNDMode();
1997 else GetAction()->Terminate(false);
1999 else {
2000 GetAction()->Terminate(false);
2006 void character::ActionAutoTermination () {
2007 if (!GetAction() || !GetAction()->IsVoluntary() || GetAction()->InDNDMode()) return;
2008 v2 Pos = GetPos();
2009 for (int c = 0; c < game::GetTeams(); ++c) {
2010 if (GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE) {
2011 for (std::list<character *>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i) {
2012 character *ch = *i;
2013 if (ch->IsEnabled() && ch->CanBeSeenBy(this, false, true) && (ch->CanMove() || ch->GetPos().IsAdjacent(Pos)) && ch->CanAttack()) {
2014 if (IsPlayer()) {
2015 ADD_MESSAGE("%s seems to be hostile.", ch->CHAR_NAME(DEFINITE));
2016 if (game::TruthQuestion(CONST_S("Continue ")+GetAction()->GetDescription()+"? [y/N]")) GetAction()->ActivateInDNDMode();
2017 else GetAction()->Terminate(false);
2018 } else {
2019 GetAction()->Terminate(false);
2021 return;
2029 truth character::CheckForEnemies (truth CheckDoors, truth CheckGround, truth MayMoveRandomly, truth RunTowardsTarget) {
2030 if (!IsEnabled()) return false;
2031 truth HostileCharsNear = false;
2032 character *NearestChar = 0;
2033 sLong NearestDistance = 0x7FFFFFFF;
2034 v2 Pos = GetPos();
2035 for (int c = 0; c < game::GetTeams(); ++c) {
2036 if (GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE) {
2037 for (std::list<character*>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i) {
2038 character *ch = *i;
2039 if (ch->IsEnabled() && GetAttribute(WISDOM) < ch->GetAttackWisdomLimit()) {
2040 sLong ThisDistance = Max<sLong>(abs(ch->GetPos().X - Pos.X), abs(ch->GetPos().Y - Pos.Y));
2041 if (ThisDistance <= GetLOSRangeSquare()) HostileCharsNear = true;
2042 if ((ThisDistance < NearestDistance || (ThisDistance == NearestDistance && !(RAND() % 3))) &&
2043 ch->CanBeSeenBy(this, false, IsGoingSomeWhere()) &&
2044 (!IsGoingSomeWhere() || HasClearRouteTo(ch->GetPos()))) {
2045 NearestChar = ch;
2046 NearestDistance = ThisDistance;
2053 if (NearestChar) {
2054 if (GetAttribute(INTELLIGENCE) >= 10 || IsSpy()) game::CallForAttention(GetPos(), 100);
2055 if (SpecialEnemySightedReaction(NearestChar)) return true;
2056 if (IsExtraCoward() && !StateIsActivated(PANIC) && NearestChar->GetRelativeDanger(this) >= 0.5) {
2057 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s sees %s.", CHAR_NAME(DEFINITE), NearestChar->CHAR_DESCRIPTION(DEFINITE));
2058 BeginTemporaryState(PANIC, 500+RAND()%500);
2060 if (!IsRetreating()) {
2061 if (CheckGround && NearestDistance > 2 && CheckForUsefulItemsOnGround(false)) return true;
2062 SetGoingTo(NearestChar->GetPos());
2063 } else {
2064 SetGoingTo(Pos-((NearestChar->GetPos()-Pos)<<4));
2066 return MoveTowardsTarget(true);
2067 } else {
2068 character *Leader = GetLeader();
2069 if (Leader == this) Leader = 0;
2070 if (!Leader && IsGoingSomeWhere()) {
2071 if (!MoveTowardsTarget(RunTowardsTarget)) {
2072 TerminateGoingTo();
2073 return false;
2074 } else {
2075 if (!IsEnabled()) return true;
2076 if (GetPos() == GoingTo) TerminateGoingTo();
2077 return true;
2079 } else {
2080 if ((!Leader || (Leader && !IsGoingSomeWhere())) && HostileCharsNear) {
2081 if (CheckDoors && CheckForDoors()) return true;
2082 if (CheckGround && CheckForUsefulItemsOnGround()) return true;
2083 if (MayMoveRandomly && MoveRandomly()) return true; // one has heard that an enemy is near but doesn't know where
2085 return false;
2091 truth character::CheckForDoors () {
2092 if (!CanOpen() || !IsEnabled()) return false;
2093 for (int d = 0; d < GetNeighbourSquares(); ++d) {
2094 lsquare *Square = GetNeighbourLSquare(d);
2095 if (Square && Square->GetOLTerrain() && Square->GetOLTerrain()->Open(this)) return true;
2097 return false;
2101 truth character::CheckForUsefulItemsOnGround (truth CheckFood) {
2102 if (StateIsActivated(PANIC) || !IsEnabled()) return false;
2103 itemvector ItemVector;
2104 GetStackUnder()->FillItemVector(ItemVector);
2105 for (uInt c = 0; c < ItemVector.size(); ++c) {
2106 if (ItemVector[c]->CanBeSeenBy(this) && ItemVector[c]->IsPickable(this)) {
2107 if (!(CommandFlags & DONT_CHANGE_EQUIPMENT) && TryToEquip(ItemVector[c])) return true;
2108 if (CheckFood && UsesNutrition() && !CheckIfSatiated() && TryToConsume(ItemVector[c])) return true;
2109 if (IsRangedAttacker() && (ItemVector[c])->GetThrowItemTypes() && TryToAddToInventory(ItemVector[c])) return true;
2112 return false;
2116 truth character::TryToAddToInventory (item *Item) {
2117 if (!(GetBurdenState() > STRESSED) || !CanUseEquipment() || Item->GetSquaresUnder() != 1) return false;
2118 room *Room = GetRoom();
2119 if (!Room || Room->PickupItem(this, Item, 1)) {
2120 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s picks up %s from the ground.", CHAR_NAME(DEFINITE), Item->CHAR_NAME(INDEFINITE));
2121 Item->MoveTo(GetStack());
2122 DexterityAction(5);
2123 return true;
2125 return false;
2129 truth character::CheckInventoryForItemToThrow (item *ToBeChecked) {
2130 return (ToBeChecked->GetThrowItemTypes() & GetWhatThrowItemTypesToThrow()) ? true : false; //hehe
2134 truth character::CheckThrowItemOpportunity () {
2135 if (!IsRangedAttacker() || !CanThrow() || !IsHumanoid() || !IsSmall() || !IsEnabled()) return false; // total gum
2136 //fprintf(stderr, "character::CheckThrowItemOpportunity...\n");
2137 // Steps:
2138 // (1) - Acquire target as nearest enemy
2139 // (2) - Check that this enemy is in range, and is in appropriate direction; no friendly fire!
2140 // (3) - check inventory for throwing weapon, select this weapon
2141 // (4) - throw item in direction where the enemy is
2143 //Check the visible area for hostiles
2144 int ThrowDirection = 0;
2145 int TargetFound = 0;
2146 v2 Pos = GetPos();
2147 v2 TestPos;
2148 int RangeMax = GetLOSRange();
2149 int CandidateDirections[7] = {0, 0, 0, 0, 0, 0, 0};
2150 int HostileFound = 0;
2151 item *ToBeThrown = 0;
2152 level *Level = GetLevel();
2154 for (int r = 1; r <= RangeMax; ++r) {
2155 for (int dir = 0; dir < 8; ++dir) {
2157 switch (dir) {
2158 case 0: TestPos = v2(Pos.X-r, Pos.Y-r); break;
2159 case 1: TestPos = v2(Pos.X, Pos.Y-r); break;
2160 case 2: TestPos = v2(Pos.X+r, Pos.Y-r); break;
2161 case 3: TestPos = v2(Pos.X-r, Pos.Y); break;
2162 case 4: TestPos = v2(Pos.X+r, Pos.Y); break;
2163 case 5: TestPos = v2(Pos.X-r, Pos.Y+r); break;
2164 case 6: TestPos = v2(Pos.X, Pos.Y+r); break;
2165 case 7: TestPos = v2(Pos.X+r, Pos.Y+r); break;
2167 if (Level->IsValidPos(TestPos)) {
2168 square *TestSquare = GetNearSquare(TestPos);
2169 character *Dude = TestSquare->GetCharacter();
2171 if (Dude && Dude->IsEnabled() && Dude->CanBeSeenBy(this, false, true)) {
2172 if (GetRelation(Dude) != HOSTILE) CandidateDirections[dir] = BLOCKED;
2173 else if (GetRelation(Dude) == HOSTILE && CandidateDirections[dir] != BLOCKED) {
2174 //then load this candidate position direction into the vector of possible throw directions
2175 CandidateDirections[dir] = SUCCESS;
2176 HostileFound = 1;
2183 if (HostileFound) {
2184 for (int dir = 0; dir < 8; ++dir) {
2185 if (CandidateDirections[dir] == SUCCESS && !TargetFound) {
2186 ThrowDirection = dir;
2187 TargetFound = 1;
2188 break;
2191 if (!TargetFound) return false;
2192 } else {
2193 return false;
2195 //fprintf(stderr, "throw: has target.\n");
2196 // check inventory for throwing weapon
2197 itemvector ItemVector;
2198 GetStack()->FillItemVector(ItemVector);
2199 for (uInt c = 0; c < ItemVector.size(); ++c) {
2200 if (ItemVector[c]->IsThrowingWeapon()) {
2201 ToBeThrown = ItemVector[c];
2202 break;
2205 if (!ToBeThrown) return false;
2206 //fprintf(stderr, "throw: has throwing weapon.\n");
2207 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s throws %s.", CHAR_NAME(DEFINITE), ToBeThrown->CHAR_NAME(INDEFINITE));
2208 ThrowItem(ThrowDirection, ToBeThrown);
2209 EditExperience(ARM_STRENGTH, 75, 1<<8);
2210 EditExperience(DEXTERITY, 75, 1<<8);
2211 EditExperience(PERCEPTION, 75, 1<<8);
2212 EditNP(-50);
2213 DexterityAction(5);
2214 TerminateGoingTo();
2215 return true;
2219 truth character::CheckAIZapOpportunity () {
2220 if (/*!IsRangedAttacker() || */ !CanZap() || !IsHumanoid() || !IsSmall() || !IsEnabled()) return false; // total gum
2221 // Steps:
2222 // (1) - Acquire target as nearest enemy
2223 // (2) - Check that this enemy is in range, and is in appropriate direction; no friendly fire!
2224 // (3) - check inventory for zappable item
2225 // (4) - zap item in direction where the enemy is
2226 //Check the rest of the visible area for hostiles
2227 v2 Pos = GetPos();
2228 v2 TestPos;
2229 int SensibleRange = 5;
2230 int RangeMax = GetLOSRange();
2231 if (RangeMax < SensibleRange) SensibleRange = RangeMax;
2232 int CandidateDirections[7] = {0, 0, 0, 0, 0, 0, 0};
2233 int HostileFound = 0;
2234 int ZapDirection = 0;
2235 int TargetFound = 0;
2236 item *ToBeZapped = 0;
2237 level *Level = GetLevel();
2239 for (int r = 2; r <= SensibleRange; ++r) {
2240 for (int dir = 0; dir < 8; ++dir) {
2241 switch (dir) {
2242 case 0: TestPos = v2(Pos.X-r, Pos.Y-r); break;
2243 case 1: TestPos = v2(Pos.X, Pos.Y-r); break;
2244 case 2: TestPos = v2(Pos.X+r, Pos.Y-r); break;
2245 case 3: TestPos = v2(Pos.X-r, Pos.Y); break;
2246 case 4: TestPos = v2(Pos.X+r, Pos.Y); break;
2247 case 5: TestPos = v2(Pos.X-r, Pos.Y+r); break;
2248 case 6: TestPos = v2(Pos.X, Pos.Y+r); break;
2249 case 7: TestPos = v2(Pos.X+r, Pos.Y+r); break;
2251 if (Level->IsValidPos(TestPos)) {
2252 square *TestSquare = GetNearSquare(TestPos);
2253 character *Dude = TestSquare->GetCharacter();
2255 if (Dude && Dude->IsEnabled() && Dude->CanBeSeenBy(this, false, true)) {
2256 if (GetRelation(Dude) != HOSTILE) CandidateDirections[dir] = BLOCKED;
2257 else if (GetRelation(Dude) == HOSTILE && CandidateDirections[dir] != BLOCKED) {
2258 //then load this candidate position direction into the vector of possible zap directions
2259 CandidateDirections[dir] = SUCCESS;
2260 HostileFound = 1;
2267 if (HostileFound) {
2268 for (int dir = 0; dir < 8; ++dir) {
2269 if (CandidateDirections[dir] == SUCCESS && !TargetFound) {
2270 ZapDirection = dir;
2271 TargetFound = 1;
2272 break;
2275 if (!TargetFound) return false;
2276 } else {
2277 return false;
2279 // check inventory for zappable item
2280 itemvector ItemVector;
2281 GetStack()->FillItemVector(ItemVector);
2282 for (unsigned int c = 0; c < ItemVector.size(); ++c) {
2283 if (ItemVector[c]->GetMinCharges() > 0 && ItemVector[c]->GetPrice()) {
2284 // bald-faced gum solution for choosing zappables that have shots left.
2285 // MinCharges needs to be replaced. Empty wands have zero price!
2286 ToBeZapped = ItemVector[c];
2287 break;
2290 if (!ToBeZapped) return false;
2291 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s zaps %s.", CHAR_NAME(DEFINITE), ToBeZapped->CHAR_NAME(INDEFINITE));
2292 if (ToBeZapped->Zap(this, GetPos(), ZapDirection)) {
2293 EditAP(-100000/APBonus(GetAttribute(PERCEPTION)));
2294 return true;
2295 } else {
2296 return false;
2298 TerminateGoingTo();
2299 return true;
2303 truth character::FollowLeader (character *Leader) {
2304 if (!Leader || Leader == this || !IsEnabled()) return false;
2305 if (CommandFlags & FOLLOW_LEADER && Leader->CanBeSeenBy(this) && Leader->SquareUnderCanBeSeenBy(this, true)) {
2306 v2 Distance = GetPos()-GoingTo;
2307 if (abs(Distance.X) <= 2 && abs(Distance.Y) <= 2) return false;
2308 return MoveTowardsTarget(false);
2310 if (IsGoingSomeWhere()) {
2311 if (!MoveTowardsTarget(true)) {
2312 TerminateGoingTo();
2313 return false;
2315 return true;
2317 return false;
2321 void character::SeekLeader (ccharacter *Leader) {
2322 if (Leader && Leader != this) {
2323 if (Leader->CanBeSeenBy(this) && (Leader->SquareUnderCanBeSeenBy(this, true) || !IsGoingSomeWhere())) {
2324 if (CommandFlags & FOLLOW_LEADER) SetGoingTo(Leader->GetPos());
2325 } else if (!IsGoingSomeWhere()) {
2326 team *Team = GetTeam();
2327 for (std::list<character *>::const_iterator i = Team->GetMember().begin(); i != Team->GetMember().end(); ++i) {
2328 character *ch = *i;
2329 if (ch->IsEnabled() && ch->GetID() != GetID() &&
2330 (CommandFlags & FOLLOW_LEADER) == (ch->CommandFlags & FOLLOW_LEADER) && ch->CanBeSeenBy(this)) {
2331 v2 Pos = ch->GetPos();
2332 v2 Distance = GetPos()-Pos;
2333 if (abs(Distance.X) > 2 && abs(Distance.Y) > 2) {
2334 SetGoingTo(Pos);
2335 break;
2344 int character::GetMoveEase () const {
2345 switch (BurdenState) {
2346 case OVER_LOADED:
2347 case STRESSED: return 50;
2348 case BURDENED: return 75;
2349 case UNBURDENED: return 100;
2351 return 666;
2355 int character::GetLOSRange () const {
2356 if (!game::IsInWilderness()) return GetAttribute(PERCEPTION)*GetLevel()->GetLOSModifier()/48;
2357 return 3;
2361 truth character::Displace (character *Who, truth Forced) {
2362 if (GetBurdenState() == OVER_LOADED) {
2363 if (IsPlayer()) {
2364 cchar *CrawlVerb = StateIsActivated(LEVITATION) ? "float" : "crawl";
2365 ADD_MESSAGE("You try very hard to %s forward. But your load is too heavy.", CrawlVerb);
2366 EditAP(-1000);
2367 return true;
2369 return false;
2372 double Danger = GetRelativeDanger(Who);
2373 int PriorityDifference = Limit(GetDisplacePriority()-Who->GetDisplacePriority(), -31, 31);
2375 if (IsPlayer()) ++PriorityDifference;
2376 else if (Who->IsPlayer()) --PriorityDifference;
2378 if (PriorityDifference >= 0) Danger *= 1 << PriorityDifference;
2379 else Danger /= 1 << -PriorityDifference;
2381 if (IsSmall() && Who->IsSmall() &&
2382 (Forced || Danger > 1.0 || !(Who->IsPlayer() || Who->IsBadPath(GetPos()))) &&
2383 !IsStuck() && !Who->IsStuck() && (!Who->GetAction() || Who->GetAction()->TryDisplace()) &&
2384 CanMove() && Who->CanMove() && Who->CanMoveOn(GetLSquareUnder())) {
2385 if (IsPlayer()) ADD_MESSAGE("You displace %s!", Who->CHAR_DESCRIPTION(DEFINITE));
2386 else if (Who->IsPlayer()) ADD_MESSAGE("%s displaces you!", CHAR_DESCRIPTION(DEFINITE));
2387 else if (CanBeSeenByPlayer() || Who->CanBeSeenByPlayer()) ADD_MESSAGE("%s displaces %s!", CHAR_DESCRIPTION(DEFINITE), Who->CHAR_DESCRIPTION(DEFINITE));
2388 lsquare *OldSquareUnder1[MAX_SQUARES_UNDER];
2389 lsquare *OldSquareUnder2[MAX_SQUARES_UNDER];
2390 for (int c = 0; c < GetSquaresUnder(); ++c) OldSquareUnder1[c] = GetLSquareUnder(c);
2391 for (int c = 0; c < Who->GetSquaresUnder(); ++c) OldSquareUnder2[c] = Who->GetLSquareUnder(c);
2392 v2 Pos = GetPos();
2393 v2 WhoPos = Who->GetPos();
2394 Remove();
2395 Who->Remove();
2396 PutTo(WhoPos);
2397 Who->PutTo(Pos);
2398 EditAP(-GetMoveAPRequirement(GetSquareUnder()->GetEntryDifficulty()) - 500);
2399 EditNP(-12*GetSquareUnder()->GetEntryDifficulty());
2400 EditExperience(AGILITY, 75, GetSquareUnder()->GetEntryDifficulty() << 7);
2401 if (IsPlayer()) ShowNewPosInfo();
2402 if (Who->IsPlayer()) Who->ShowNewPosInfo();
2403 SignalStepFrom(OldSquareUnder1);
2404 Who->SignalStepFrom(OldSquareUnder2);
2405 return true;
2406 } else {
2407 if (IsPlayer()) {
2408 ADD_MESSAGE("%s resists!", Who->CHAR_DESCRIPTION(DEFINITE));
2409 EditAP(-1000);
2410 return true;
2412 return false;
2417 void character::SetNP (sLong What) {
2418 int OldState = GetHungerState();
2419 NP = What;
2420 if (IsPlayer()) {
2421 int NewState = GetHungerState();
2422 if (NewState == STARVING && OldState > STARVING) DeActivateVoluntaryAction(CONST_S("You are getting really hungry."));
2423 else if (NewState == VERY_HUNGRY && OldState > VERY_HUNGRY) DeActivateVoluntaryAction(CONST_S("You are getting very hungry."));
2424 else if (NewState == HUNGRY && OldState > HUNGRY) DeActivateVoluntaryAction(CONST_S("You are getting hungry."));
2429 void character::ShowNewPosInfo () const {
2430 msgsystem::EnterBigMessageMode();
2431 v2 Pos = GetPos();
2433 if (ivanconfig::GetAutoCenterMap()) {
2434 game::UpdateCameraX();
2435 game::UpdateCameraY();
2436 } else {
2437 if (Pos.X < game::GetCamera().X+3 || Pos.X >= game::GetCamera().X+game::GetScreenXSize()-3) game::UpdateCameraX();
2438 if (Pos.Y < game::GetCamera().Y+3 || Pos.Y >= game::GetCamera().Y+game::GetScreenYSize()-3) game::UpdateCameraY();
2441 game::SendLOSUpdateRequest();
2442 game::DrawEverythingNoBlit();
2443 UpdateESPLOS();
2445 if (!game::IsInWilderness()) {
2446 if (GetLSquareUnder()->IsDark() && !game::GetSeeWholeMapCheatMode()) ADD_MESSAGE("It's dark in here!");
2448 GetLSquareUnder()->ShowSmokeMessage();
2449 itemvectorvector PileVector;
2450 GetStackUnder()->Pile(PileVector, this, CENTER);
2452 if (PileVector.size()) {
2453 truth Feel = !GetLSquareUnder()->IsTransparent() || GetLSquareUnder()->IsDark();
2455 if (PileVector.size() == 1) {
2456 if (Feel) {
2457 ADD_MESSAGE("You feel %s lying here.", PileVector[0][0]->GetName(INDEFINITE, PileVector[0].size()).CStr());
2458 } else {
2459 if (ivanconfig::GetShowFullItemDesc() && PileVector[0][0]->AllowDetailedDescription()) {
2460 festring text;
2462 PileVector[0][0]->AddInventoryEntry(PLAYER, text, PileVector[0].size(), true);
2463 //fprintf(stderr, "invdsc : [%s]\n", text.CStr());
2464 ADD_MESSAGE("%s %s lying here.", text.CStr(), PileVector[0].size() == 1 ? "is" : "are");
2465 } else {
2466 ADD_MESSAGE("%s %s lying here.", PileVector[0][0]->GetName(INDEFINITE, PileVector[0].size()).CStr(), PileVector[0].size() == 1 ? "is" : "are");
2469 fprintf(stderr, "description: [%s]\n", PileVector[0][0]->GetDescription(INDEFINITE).CStr());
2470 fprintf(stderr, "strength : [%s]\n", PileVector[0][0]->GetStrengthValueDescription());
2471 fprintf(stderr, "basetohit : [%s]\n", PileVector[0][0]->GetBaseToHitValueDescription());
2472 fprintf(stderr, "baseblock : [%s]\n", PileVector[0][0]->GetBaseBlockValueDescription());
2473 fprintf(stderr, "extdsc : [%s]\n", PileVector[0][0]->GetExtendedDescription().CStr());
2476 } else {
2477 int Items = 0;
2478 for (uInt c = 0; c < PileVector.size(); ++c) {
2479 if ((Items += PileVector[c].size()) > 3) break;
2481 if (Items > 3) {
2482 if (Feel) ADD_MESSAGE("You feel several items lying here.");
2483 else ADD_MESSAGE("Several items are lying here.");
2484 } else if (Items) {
2485 if (Feel) ADD_MESSAGE("You feel a few items lying here.");
2486 else ADD_MESSAGE("A few items are lying here.");
2491 festring SideItems;
2492 GetLSquareUnder()->GetSideItemDescription(SideItems);
2494 if (!SideItems.IsEmpty()) ADD_MESSAGE("There is %s.", SideItems.CStr());
2496 if (GetLSquareUnder()->HasEngravings()) {
2497 if (CanRead()) ADD_MESSAGE("Something has been engraved here: \"%s\"", GetLSquareUnder()->GetEngraved());
2498 else ADD_MESSAGE("Something has been engraved here.");
2502 msgsystem::LeaveBigMessageMode();
2506 void character::Hostility (character *Enemy) {
2507 if (Enemy == this || !Enemy || !Team || !Enemy->Team) return;
2508 if (Enemy->IsMasochist() && GetRelation(Enemy) == FRIEND) return;
2509 if (!IsAlly(Enemy)) {
2510 GetTeam()->Hostility(Enemy->GetTeam());
2511 } else if (IsPlayer() && !Enemy->IsPlayer()) {
2512 // I believe both may be players due to polymorph feature...
2513 if (Enemy->CanBeSeenByPlayer()) ADD_MESSAGE("%s becomes enraged.", Enemy->CHAR_NAME(DEFINITE));
2514 Enemy->ChangeTeam(game::GetTeam(BETRAYED_TEAM));
2519 stack *character::GetGiftStack () const {
2520 if (GetLSquareUnder()->GetRoomIndex() && !GetLSquareUnder()->GetRoom()->AllowDropGifts()) return GetStack();
2521 return GetStackUnder();
2525 truth character::MoveRandomlyInRoom () {
2526 for (int c = 0; c < 10; ++c) {
2527 v2 ToTry = game::GetMoveVector(RAND()&7);
2528 if (GetLevel()->IsValidPos(GetPos()+ToTry)) {
2529 lsquare *Square = GetNearLSquare(GetPos()+ToTry);
2530 if (!Square->IsDangerous(this) && !Square->IsScary(this) &&
2531 (!Square->GetOLTerrain() || !Square->GetOLTerrain()->IsDoor()) &&
2532 TryMove(ToTry, false, false)) return true;
2535 return false;
2539 //#define dirlogf(...) do { fprintf(stderr, __VA_ARGS__); } while (0)
2540 #define dirlogf(...) ((void)0)
2544 * 0: up-left
2545 * 1: up
2546 * 2: up-right
2547 * 3: left
2548 * 4: right
2549 * 5: down-left
2550 * 6: down
2551 * 7: down-right
2552 * 8: stand still
2554 enum {
2555 MDIR_UP_LEFT,
2556 MDIR_UP,
2557 MDIR_UP_RIGHT,
2558 MDIR_LEFT,
2559 MDIR_RIGHT,
2560 MDIR_DOWN_LEFT,
2561 MDIR_DOWN,
2562 MDIR_DOWN_RIGHT,
2563 MDIR_STAND
2567 static const int revDir[8] = { MDIR_DOWN_RIGHT, MDIR_DOWN, MDIR_DOWN_LEFT, MDIR_RIGHT, MDIR_LEFT, MDIR_UP_RIGHT, MDIR_UP, MDIR_UP_LEFT };
2568 static const bool orthoDir[8] = { false, true, false, true, true, false, true, false };
2571 // only for ortho moveDir
2572 static inline truth IsDirExcluded (int moveDir, int dir) {
2573 if (moveDir == dir) return true;
2574 switch (moveDir) {
2575 case MDIR_UP: return (dir == MDIR_UP_LEFT || dir == MDIR_UP_RIGHT);
2576 case MDIR_LEFT: return (dir == MDIR_UP_LEFT || dir == MDIR_DOWN_LEFT);
2577 case MDIR_RIGHT: return (dir == MDIR_UP_RIGHT || dir == MDIR_DOWN_RIGHT);
2578 case MDIR_DOWN: return (dir == MDIR_DOWN_LEFT || dir == MDIR_DOWN_RIGHT);
2580 return false;
2584 truth character::IsPassableSquare (int x, int y) const {
2585 if (x >= 0 && y >= 0) {
2586 area *ca = GetSquareUnder()->GetArea();
2587 lsquare *sq;
2589 if (x >= ca->GetXSize() || y >= ca->GetYSize()) return false;
2590 sq = static_cast<lsquare *>(ca->GetSquare(x, y));
2591 return sq && CanMoveOn(sq);
2593 return false;
2597 void character::CountPossibleMoveDirs (cv2 pos, int *odirs, int *ndirs, int exclideDir) const {
2598 if (odirs) *odirs = 0;
2599 if (ndirs) *ndirs = 0;
2600 for (int f = 0; f < 8; ++f) {
2601 if (!IsDirExcluded(exclideDir, f)) {
2602 if (IsPassableSquare(pos+game::GetMoveVector(f))) {
2603 if (orthoDir[f]) {
2604 if (odirs) ++(*odirs);
2605 } else {
2606 if (ndirs) ++(*ndirs);
2615 * in corridor (for orto-dirs):
2616 * count dirs excluding ortho-dir we going:
2617 * if there is one or less ortho-dirs and one or less non-ortho-dirs, we are in corridor
2619 // only for ortho-dirs
2620 truth character::IsInCorridor (int x, int y, int moveDir) const {
2621 int od = 0, nd = 0;
2623 dirlogf("IsInCorridor(%d,%d,%d)\n", x, y, moveDir);
2624 // reverse moveDir
2625 moveDir = (moveDir >= 0 && moveDir < MDIR_STAND ? revDir[moveDir] : -1);
2626 dirlogf(" reversedDir: %d\n", moveDir);
2627 CountPossibleMoveDirs(v2(x, y), &od, &nd, moveDir);
2628 dirlogf(" possibleDirs: (%d:%d)\n", od, nd);
2629 dirlogf(" IsInCorridor: %s\n", ((od <= 1 && nd <= 1) ? "yes" : "no"));
2630 return (od <= 1 && nd <= 1);
2634 cv2 character::GetDiagonalForDirs (int moveDir, int newDir) const {
2635 switch (moveDir) {
2636 case MDIR_UP:
2637 switch (newDir) {
2638 case MDIR_LEFT: return game::GetMoveVector(MDIR_UP_LEFT);
2639 case MDIR_RIGHT: return game::GetMoveVector(MDIR_UP_RIGHT);
2641 break;
2642 case MDIR_DOWN:
2643 switch (newDir) {
2644 case MDIR_LEFT: return game::GetMoveVector(MDIR_DOWN_LEFT);
2645 case MDIR_RIGHT: return game::GetMoveVector(MDIR_DOWN_RIGHT);
2647 break;
2648 case MDIR_LEFT:
2649 switch (newDir) {
2650 case MDIR_UP: return game::GetMoveVector(MDIR_UP_LEFT);
2651 case MDIR_DOWN: return game::GetMoveVector(MDIR_DOWN_LEFT);
2653 break;
2654 case MDIR_RIGHT:
2655 switch (newDir) {
2656 case MDIR_UP: return game::GetMoveVector(MDIR_UP_RIGHT);
2657 case MDIR_DOWN: return game::GetMoveVector(MDIR_DOWN_RIGHT);
2659 break;
2661 ABORT("wtf in character::GetDiagonalForDirs()");
2665 truth character::IsInTunnelDeadEnd () const {
2666 int od, nd;
2668 CountPossibleMoveDirs(GetPos(), &od, &nd, -1);
2669 return (od <= 1 && nd == 0);
2674 * try to walk in the given dir
2675 * can do two steps without a turn and still in corridor?
2676 * yes:
2677 * just go
2678 * no:
2679 * go in non-ortho dir, set prevdir to last ortho-dir from corridor tracing
2681 // only for ortho-dirs; assume that the char is in corridor
2682 int character::CheckCorridorMove (v2 &moveVector, cv2 pos, int moveDir, truth *markAsTurn) const {
2683 v2 ps1(pos+(moveVector = game::GetMoveVector(moveDir)));
2685 if (markAsTurn) *markAsTurn = true;
2687 if (IsPassableSquare(ps1)) {
2688 // we can do first step in the given dir
2689 // check if we will be in corridor after it
2690 dirlogf("CheckCorridorMove: can do first step\n");
2691 if (IsInCorridor(ps1, moveDir)) {
2692 // check second step
2693 v2 ps2(ps1+moveVector);
2695 dirlogf("CheckCorridorMove: still in corridor after the first step\n");
2696 if (IsPassableSquare(ps2)) {
2697 // can do second step
2698 dirlogf("CheckCorridorMove: can do second step\n");
2699 return moveDir;
2700 } else {
2701 // can't do second step; but we still in corridor, so we should make a turn
2702 int newDir = -1; // direction to turn
2704 for (int f = 0; f < MDIR_STAND; ++f) {
2705 if (f != moveDir && orthoDir[f] && f != revDir[moveDir] && IsPassableSquare(ps1+game::GetMoveVector(f))) {
2706 newDir = f;
2707 break;
2710 dirlogf("CheckCorridorMove: can't do second step; moveDir=%d; newDir=%d\n", moveDir, newDir);
2711 if (newDir < 0) {
2712 // dead end, will stop
2713 //ABORT("wtd in character::CheckCorridorMove()");
2714 return moveDir;
2716 // we should do diagonal move
2717 moveVector = GetDiagonalForDirs(moveDir, newDir);
2718 // if this is 'one-tile-turn', we should not change the direction to newDir
2719 if (IsPassableSquare(ps1+game::GetMoveVector(newDir)+game::GetMoveVector(moveDir))) {
2720 // yes, this is 'one-tile-turn'
2721 dirlogf("CheckCorridorMove: one-tile-turn, don't change dir\n");
2722 /* 'g'o bug:
2724 * ####.######
2725 * ####*......
2726 * ..@..######
2727 * ######
2729 * 'g'o right: should stop at '*', but it just goes right
2731 if (markAsTurn) *markAsTurn = IsInCorridor(ps1+game::GetMoveVector(newDir), newDir);
2733 newDir = moveDir;
2735 return newDir;
2738 dirlogf("CheckCorridorMove: can do one or two steps; move forward\n");
2739 // can do one or two steps; just move forward
2740 return moveDir;
2742 dirlogf("CheckCorridorMove: dead end\n");
2743 // can't go, assume invalid direction
2744 return -1;
2748 void character::GoOn (go *Go, truth FirstStep) {
2749 dirlogf("=== character::GoOn; dir=%d; pos=(%d,%d) ===\n", Go->GetDirection(), GetPos().X, GetPos().Y);
2750 if (FirstStep) {
2751 dirlogf("FirstStep\n");
2752 mPrevMoveDir = Go->GetDirection();
2753 Go->SetIsWalkingInOpen(!IsInCorridor(Go->GetDirection()));
2756 v2 MoveVector = ApplyStateModification(game::GetMoveVector(Go->GetDirection()));
2757 lsquare *MoveToSquare[MAX_SQUARES_UNDER];
2758 int Squares = CalculateNewSquaresUnder(MoveToSquare, GetPos()+MoveVector);
2759 int moveDir = game::MoveVectorToDirection(MoveVector);
2761 if (!Squares || !CanMoveOn(MoveToSquare[0])) {
2762 dirlogf("just can't move\n");
2763 Go->Terminate(false);
2764 return;
2767 if (!FirstStep) {
2768 if (!Go->GetPrevWasTurn() && Go->IsWalkingInOpen() != !IsInCorridor(GetPos(), moveDir)) {
2769 dirlogf("moved to/from open place\n");
2770 Go->Terminate(false);
2771 return;
2774 uInt OldRoomIndex = GetLSquareUnder()->GetRoomIndex();
2775 uInt CurrentRoomIndex = MoveToSquare[0]->GetRoomIndex();
2777 if (OldRoomIndex && (CurrentRoomIndex != OldRoomIndex)) {
2778 // room about to be changed, stop here
2779 dirlogf("room about to be changed\n");
2780 Go->Terminate(false);
2781 return;
2784 // stop near the dangerous square
2785 for (int c = 0; c < Squares; ++c) {
2786 if ((MoveToSquare[c]->GetCharacter() && GetTeam() != MoveToSquare[c]->GetCharacter()->GetTeam()) ||
2787 MoveToSquare[c]->IsDangerous(this)) {
2788 dirlogf("sense the danger\n");
2789 Go->Terminate(false);
2790 return;
2795 if (moveDir != Go->GetDirection()) {
2796 // state modified the direction, move and stop
2797 dirlogf("move affected by state\n");
2798 if (TryMove(MoveVector, true, game::PlayerIsRunning())) {
2799 game::DrawEverything();
2800 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
2802 Go->Terminate(false);
2803 return;
2806 truth doStop = false, markAsTurn = false;
2808 if (!FirstStep) {
2809 // continuous walking
2810 if (Go->IsWalkingInOpen() || !orthoDir[moveDir]) {
2811 // walking in open space or diagonal walking
2812 v2 newPos(GetPos()+MoveVector);
2813 int ood, ond, nod, nnd;
2815 * open: stop if # of possible dirs in next step != # of possible dirs in current step
2816 * (or next step is in corridor)
2818 dirlogf("open walking\n");
2819 if (IsInCorridor(newPos, moveDir)) {
2820 // trying to enter the corridor, stop right here
2821 dirlogf("entering the corridor\n");
2822 Go->Terminate(false);
2823 return;
2825 CountPossibleMoveDirs(GetPos(), &ood, &ond);
2826 CountPossibleMoveDirs(newPos, &nod, &nnd);
2827 if (ood != nod || ond != nnd) {
2828 // # of directions to walk to changed, stop right here
2829 dirlogf("# of directions changed from (%d:%d) to (%d:%d)\n", ood, ond, nod, nnd);
2830 //Go->Terminate(false);
2831 //return;
2832 doStop = true;
2834 // ok, we can do this move
2835 } else {
2836 // ortho-walking thru the corridor
2837 int newDir = CheckCorridorMove(MoveVector, GetPos(), moveDir, &markAsTurn);
2839 if (newDir < 0) {
2840 // ah, something weird; stop right here
2841 Go->Terminate(false);
2842 return;
2844 Go->SetDirection(newDir); // perform possible turn
2846 } else {
2847 // first step, just do it
2849 // now try to perform the move
2851 dirlogf("trying to make the move\n");
2852 // stop near the dangerous square (fuckin' copypasta)
2853 Squares = CalculateNewSquaresUnder(MoveToSquare, GetPos()+MoveVector);
2855 for (int c = 0; c < Squares; ++c) {
2856 if ((MoveToSquare[c]->GetCharacter() && GetTeam() != MoveToSquare[c]->GetCharacter()->GetTeam()) ||
2857 MoveToSquare[c]->IsDangerous(this)) {
2858 dirlogf(" danger!\n");
2859 Go->Terminate(false);
2860 return;
2865 square *BeginSquare = GetSquareUnder();
2866 uInt OldRoomIndex = GetLSquareUnder()->GetRoomIndex();
2867 uInt CurrentRoomIndex = MoveToSquare[0]->GetRoomIndex();
2869 // stop on the square with something interesting
2870 if (!doStop) {
2871 // idiotic code!
2872 area *ca = GetSquareUnder()->GetArea();
2873 v2 npos = GetPos()+MoveVector;
2875 for (int f = 0; f < MDIR_STAND; ++f) {
2876 v2 np = npos+game::GetMoveVector(f);
2878 if (np.X >= 0 && np.Y >= 0 && np.X < ca->GetXSize() && np.Y < ca->GetYSize()) {
2879 lsquare *sq = static_cast<lsquare *>(ca->GetSquare(np.X, np.Y));
2880 olterrain *terra = sq->GetOLTerrain();
2882 if (terra) {
2883 dirlogf("** OK terra at %d; door: %s; seen: %s\n", f, (terra->IsDoor() ? "yes" : "no"), (sq->IsGoSeen() ? "yes" : "no"));
2884 if (terra->IsDoor()) {
2885 if (ivanconfig::GetStopOnSeenDoors() || !sq->IsGoSeen()) {
2886 dirlogf(" *** stop near the door\n");
2887 doStop = true;
2888 break;
2895 if (!doStop) {
2896 for (int c = 0; c < Squares; ++c) {
2897 if (MoveToSquare[c]->GetStack()->HasSomethingFunny(this, ivanconfig::GetStopOnCorpses(), ivanconfig::GetStopOnSeenItems())) {
2898 dirlogf(" stepped near something interesting\n");
2899 doStop = true;
2900 break;
2906 Go->SetPrevWasTurn(markAsTurn && MoveVector.X && MoveVector.Y); // diagonal move?
2908 truth moveOk = TryMove(MoveVector, true, game::PlayerIsRunning());
2910 if (!moveOk || BeginSquare == GetSquareUnder() || (CurrentRoomIndex && (OldRoomIndex != CurrentRoomIndex))) {
2911 dirlogf(" stopped\n");
2912 if (moveOk) {
2913 game::DrawEverything();
2914 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
2916 Go->Terminate(false);
2917 return;
2920 if (FirstStep) {
2921 mPrevMoveDir = Go->GetDirection();
2922 Go->SetIsWalkingInOpen(!IsInCorridor(moveDir));
2925 game::DrawEverything();
2926 if (ivanconfig::GetGoingDelay()) DELAY(ivanconfig::GetGoingDelay());
2927 if (doStop) Go->Terminate(false);
2931 void character::SetTeam (team *What) {
2932 /*k8 if(Team) int esko = esko = 2; */
2933 Team = What;
2934 SetTeamIterator(What->Add(this));
2938 void character::ChangeTeam (team *What) {
2939 if (Team) Team->Remove(GetTeamIterator());
2940 Team = What;
2941 SendNewDrawRequest();
2942 if (Team) SetTeamIterator(Team->Add(this));
2946 truth character::ChangeRandomAttribute (int HowMuch) {
2947 for (int c = 0; c < 50; ++c) {
2948 int AttribID = RAND()%ATTRIBUTES;
2949 if (EditAttribute(AttribID, HowMuch)) return true;
2951 return false;
2955 int character::RandomizeReply (sLong &Said, int Replies) {
2956 truth NotSaid = false;
2957 for (int c = 0; c < Replies; ++c) {
2958 if (!(Said & (1 << c))) {
2959 NotSaid = true;
2960 break;
2963 if (!NotSaid) Said = 0;
2964 sLong ToSay;
2965 while (Said & 1 << (ToSay = RAND() % Replies));
2966 Said |= 1 << ToSay;
2967 return ToSay;
2971 void character::DisplayInfo (festring &Msg) {
2972 if (IsPlayer()) {
2973 Msg << " You are " << GetStandVerb() << " here.";
2974 } else {
2975 Msg << ' ' << GetName(INDEFINITE).CapitalizeCopy() << " is " << GetStandVerb() << " here. " << GetPersonalPronoun().CapitalizeCopy();
2976 cchar *Separator1 = GetAction() ? "," : " and";
2977 cchar *Separator2 = " and";
2978 if (GetTeam() == PLAYER->GetTeam()) {
2979 Msg << " is tame";
2980 } else {
2981 int Relation = GetRelation(PLAYER);
2982 if (Relation == HOSTILE) Msg << " is hostile";
2983 else if (Relation == UNCARING) {
2984 Msg << " does not care about you";
2985 Separator1 = Separator2 = " and is";
2986 } else {
2987 Msg << " is friendly";
2990 if (StateIsActivated(PANIC)) {
2991 Msg << Separator1 << " panicked";
2992 Separator2 = " and";
2994 if (GetAction()) Msg << Separator2 << ' ' << GetAction()->GetDescription();
2995 Msg << '.';
3000 void character::TestWalkability () {
3001 if (!IsEnabled()) return;
3002 square *SquareUnder = !game::IsInWilderness() ? GetSquareUnder() : PLAYER->GetSquareUnder();
3003 if (SquareUnder->IsFatalToStay() && !CanMoveOn(SquareUnder)) {
3004 truth Alive = false;
3005 if (!game::IsInWilderness() || IsPlayer()) {
3006 for (int d = 0; d < GetNeighbourSquares(); ++d) {
3007 square *Square = GetNeighbourSquare(d);
3008 if (Square && CanMoveOn(Square) && IsFreeForMe(Square)) {
3009 if (IsPlayer()) ADD_MESSAGE("%s.", SquareUnder->SurviveMessage(this));
3010 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE), SquareUnder->MonsterSurviveMessage(this));
3011 Move(Square->GetPos(), true); // actually, this shouldn't be a teleport move
3012 SquareUnder->SurviveEffect(this);
3013 Alive = true;
3014 break;
3018 if (!Alive) {
3019 if (IsPlayer()) {
3020 Remove();
3021 SendToHell();
3022 festring DeathMsg = festring(SquareUnder->DeathMessage(this));
3023 game::AskForEscPress(DeathMsg+".");
3024 festring Msg = SquareUnder->ScoreEntry(this);
3025 PLAYER->AddScoreEntry(Msg);
3026 game::End(Msg);
3027 } else {
3028 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE), SquareUnder->MonsterDeathVerb(this));
3029 Die(0, SquareUnder->ScoreEntry(this), DISALLOW_MSG);
3036 int character::GetSize () const {
3037 return GetTorso()->GetSize();
3041 void character::SetMainMaterial (material *NewMaterial, int SpecialFlags) {
3042 NewMaterial->SetVolume(GetBodyPart(0)->GetMainMaterial()->GetVolume());
3043 GetBodyPart(0)->SetMainMaterial(NewMaterial, SpecialFlags);
3044 for (int c = 1; c < BodyParts; ++c) {
3045 NewMaterial = NewMaterial->SpawnMore(GetBodyPart(c)->GetMainMaterial()->GetVolume());
3046 GetBodyPart(c)->SetMainMaterial(NewMaterial, SpecialFlags);
3051 void character::ChangeMainMaterial (material *NewMaterial, int SpecialFlags) {
3052 NewMaterial->SetVolume(GetBodyPart(0)->GetMainMaterial()->GetVolume());
3053 GetBodyPart(0)->ChangeMainMaterial(NewMaterial, SpecialFlags);
3054 for (int c = 1; c < BodyParts; ++c) {
3055 NewMaterial = NewMaterial->SpawnMore(GetBodyPart(c)->GetMainMaterial()->GetVolume());
3056 GetBodyPart(c)->ChangeMainMaterial(NewMaterial, SpecialFlags);
3061 void character::SetSecondaryMaterial (material *, int) {
3062 ABORT("Illegal character::SetSecondaryMaterial call!");
3066 void character::ChangeSecondaryMaterial (material *, int) {
3067 ABORT("Illegal character::ChangeSecondaryMaterial call!");
3071 void character::TeleportRandomly (truth Intentional) {
3072 v2 TelePos = ERROR_V2;
3073 if (StateIsActivated(TELEPORT_CONTROL)) {
3074 if (IsPlayer()) {
3075 v2 Input = game::PositionQuestion(CONST_S("Where do you wish to teleport? [direction keys move cursor, space accepts]"), GetPos(), &game::TeleportHandler, 0, false);
3076 if (Input == ERROR_V2) Input = GetPos(); // esc pressed
3077 lsquare *Square = GetNearLSquare(Input);
3078 if (CanMoveOn(Square) || game::GoThroughWallsCheatIsActive()) {
3079 if (Square->GetPos() == GetPos()) {
3080 ADD_MESSAGE("You disappear and reappear.");
3081 return;
3083 if (IsFreeForMe(Square)) {
3084 if ((Input-GetPos()).GetLengthSquare() <= GetTeleportRangeSquare()) {
3085 EditExperience(INTELLIGENCE, 100, 1 << 10);
3086 TelePos = Input;
3087 } else {
3088 ADD_MESSAGE("You cannot concentrate yourself enough to control a teleport that far.");
3090 } else {
3091 character *C = Square->GetCharacter();
3092 if (C) ADD_MESSAGE("For a moment you feel very much like %s.", C->CHAR_NAME(INDEFINITE));
3093 else ADD_MESSAGE("You feel that something weird has happened, but can't really tell what exactly.");
3095 } else {
3096 ADD_MESSAGE("You feel like having been hit by something really hard from the inside.");
3098 } else if (!Intentional) {
3099 if (IsGoingSomeWhere() && GetLevel()->IsValidPos(GoingTo)) {
3100 v2 Where = GetLevel()->GetNearestFreeSquare(this, GoingTo);
3101 if (Where != ERROR_V2 && (Where-GetPos()).GetLengthSquare() <= GetTeleportRangeSquare()) {
3102 EditExperience(INTELLIGENCE, 100, 1 << 10);
3103 Where = TelePos;
3109 if (IsPlayer()) {
3110 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.");
3113 //if (TelePos != ERROR_V2) Move(TelePos, true);
3114 //else Move(GetLevel()->GetRandomSquare(this), true);
3115 //if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE));
3116 //if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
3118 if (TelePos == ERROR_V2) TelePos = GetLevel()->GetRandomSquare(this);
3120 room *PossibleRoom = game::GetCurrentLevel()->GetLSquare(TelePos)->GetRoom();
3122 if (!PossibleRoom) {
3123 //if it's outside of a room
3124 if (TelePos != ERROR_V2) Move(TelePos, true);
3125 else Move(GetLevel()->GetRandomSquare(this), true);
3126 if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE));
3127 if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
3128 } else if (PossibleRoom && PossibleRoom->IsOKToTeleportInto()) {
3129 // If it's inside of a room, check whether a ward is active that might impede the player
3130 if (TelePos != ERROR_V2) Move(TelePos, true);
3131 else Move(GetLevel()->GetRandomSquare(this), true);
3132 if (!IsPlayer() && CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", CHAR_NAME(INDEFINITE));
3133 if (GetAction() && GetAction()->IsVoluntary()) GetAction()->Terminate(false);
3134 } else {
3135 if (IsPlayer()){
3136 ADD_MESSAGE("A mighty force blasts you back to where you were standing. A ward prevents you from teleporting.");
3138 game::GetCurrentLevel()->Explosion(this, CONST_S("killed by an explosion triggered when attempting to teleport into room protected by a ward"), PLAYER->GetPos(), 300 >> 3, false);
3140 beamdata Beam
3142 this,
3143 CONST_S("killed by an explosion triggered when attempting to teleport into room protected by a ward"),
3144 YOURSELF,
3145 3 // or 0 ?
3147 lsquare* Square = GetNearLSquare(GetPos());
3148 Square->DrawParticles(RED);
3149 Square->FireBall(Beam);*/
3154 void character::DoDetecting () {
3155 material *TempMaterial;
3157 for (;;) {
3158 festring Temp = game::DefaultQuestion(CONST_S("What material do you want to detect?"), game::GetDefaultDetectMaterial());
3159 TempMaterial = protosystem::CreateMaterial(Temp);
3160 if (TempMaterial) break;
3161 game::DrawEverythingNoBlit();
3164 level *Level = GetLevel();
3165 int Squares = Level->DetectMaterial(TempMaterial);
3167 if (Squares > GetAttribute(INTELLIGENCE) * (25+RAND()%51)) {
3168 ADD_MESSAGE("An enormous burst of geographical information overwhelms your consciousness. Your mind cannot cope with it and your memories blur.");
3169 Level->BlurMemory();
3170 BeginTemporaryState(CONFUSED, 1000 + RAND() % 1000);
3171 EditExperience(INTELLIGENCE, -100, 1 << 12);
3172 } else if (!Squares) {
3173 ADD_MESSAGE("You feel a sudden urge to imagine the dark void of a starless night sky.");
3174 EditExperience(INTELLIGENCE, 200, 1 << 12);
3175 } else {
3176 ADD_MESSAGE("You feel attracted to all things made of %s.", TempMaterial->GetName(false, false).CStr());
3177 game::PositionQuestion(CONST_S("Detecting material [direction keys move cursor, space exits]"), GetPos(), 0, 0, false);
3178 EditExperience(INTELLIGENCE, 300, 1 << 12);
3181 delete TempMaterial;
3182 Level->CalculateLuminances();
3183 game::SendLOSUpdateRequest();
3187 void character::RestoreHP () {
3188 doforbodyparts()(this, &bodypart::FastRestoreHP);
3189 HP = MaxHP;
3193 void character::RestoreLivingHP () {
3194 HP = 0;
3195 for (int c = 0; c < BodyParts; ++c) {
3196 bodypart *BodyPart = GetBodyPart(c);
3197 if (BodyPart && BodyPart->CanRegenerate()) {
3198 BodyPart->FastRestoreHP();
3199 HP += BodyPart->GetHP();
3205 truth character::AllowDamageTypeBloodSpill (int Type) {
3206 switch (Type&0xFFF) {
3207 case PHYSICAL_DAMAGE:
3208 case SOUND:
3209 case ENERGY:
3210 return true;
3211 case ACID:
3212 case FIRE:
3213 case DRAIN:
3214 case POISON:
3215 case ELECTRICITY:
3216 case MUSTARD_GAS_DAMAGE:
3217 case PSI:
3218 return false;
3220 ABORT("Unknown blood effect destroyed the dungeon!");
3221 return false;
3225 /* Returns truly done damage */
3226 int character::ReceiveBodyPartDamage (character *Damager, int Damage, int Type, int BodyPartIndex,
3227 int Direction, truth PenetrateResistance, truth Critical, truth ShowNoDamageMsg, truth CaptureBodyPart)
3229 bodypart *BodyPart = GetBodyPart(BodyPartIndex);
3230 if (!Damager || Damager->AttackMayDamageArmor()) BodyPart->DamageArmor(Damager, Damage, Type);
3231 if (!PenetrateResistance) {
3232 Damage -= (BodyPart->GetTotalResistance(Type)>>1)+RAND()%((BodyPart->GetTotalResistance(Type)>>1)+1);
3234 if (int(Damage) < 1) {
3235 if (Critical) {
3236 Damage = 1;
3237 } else {
3238 if (ShowNoDamageMsg) {
3239 if (IsPlayer()) ADD_MESSAGE("You are not hurt.");
3240 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s is not hurt.", GetPersonalPronoun().CStr());
3242 return 0;
3246 if (Critical && AllowDamageTypeBloodSpill(Type) && !game::IsInWilderness()) {
3247 BodyPart->SpillBlood(2+(RAND()&1));
3248 for (int d = 0; d < GetNeighbourSquares(); ++d) {
3249 lsquare *Square = GetNeighbourLSquare(d);
3250 if (Square && Square->IsFlyable()) BodyPart->SpillBlood(1, Square->GetPos());
3254 if (BodyPart->ReceiveDamage(Damager, Damage, Type, Direction) && BodyPartCanBeSevered(BodyPartIndex)) {
3255 if (DamageTypeDestroysBodyPart(Type)) {
3256 if (IsPlayer()) ADD_MESSAGE("Your %s is destroyed!", BodyPart->GetBodyPartName().CStr());
3257 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s is destroyed!", GetPossessivePronoun().CStr(), BodyPart->GetBodyPartName().CStr());
3258 GetBodyPart(BodyPartIndex)->DropEquipment();
3259 item *Severed = SevereBodyPart(BodyPartIndex);
3260 if (Severed) Severed->DestroyBodyPart(!game::IsInWilderness() ? GetStackUnder() : GetStack());
3261 SendNewDrawRequest();
3262 if (IsPlayer()) game::AskForEscPress(CONST_S("Bodypart destroyed!"));
3263 } else {
3264 if (IsPlayer()) ADD_MESSAGE("Your %s is severed off!", BodyPart->GetBodyPartName().CStr());
3265 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s is severed off!", GetPossessivePronoun().CStr(), BodyPart->GetBodyPartName().CStr());
3266 item *Severed = SevereBodyPart(BodyPartIndex);
3267 SendNewDrawRequest();
3268 if (Severed) {
3269 if (CaptureBodyPart) {
3270 Damager->GetLSquareUnder()->AddItem(Severed);
3271 } else if (!game::IsInWilderness()) {
3272 /** No multi-tile humanoid support! */
3273 GetStackUnder()->AddItem(Severed);
3274 if (Direction != YOURSELF) Severed->Fly(0, Direction, Damage);
3275 } else {
3276 GetStack()->AddItem(Severed);
3278 Severed->DropEquipment();
3279 } else if (IsPlayer() || CanBeSeenByPlayer()) {
3280 ADD_MESSAGE("It vanishes.");
3282 if (IsPlayer()) game::AskForEscPress(CONST_S("Bodypart severed!"));
3284 if (CanPanicFromSeveredBodyPart() && RAND()%100 < GetPanicLevel() && !StateIsActivated(PANIC) && !IsDead()) {
3285 BeginTemporaryState(PANIC, 1000+RAND()%1001);
3287 SpecialBodyPartSeverReaction();
3290 if (!IsDead()) CheckPanic(500);
3292 return Damage;
3296 /* Returns 0 if bodypart disappears */
3297 item *character::SevereBodyPart (int BodyPartIndex, truth ForceDisappearance, stack *EquipmentDropStack) {
3298 bodypart *BodyPart = GetBodyPart(BodyPartIndex);
3299 if (StateIsActivated(LEPROSY)) BodyPart->GetMainMaterial()->SetIsInfectedByLeprosy(true);
3300 if (ForceDisappearance || BodyPartsDisappearWhenSevered() || StateIsActivated(POLYMORPHED) || game::AllBodyPartsVanish()) {
3301 BodyPart->DropEquipment(EquipmentDropStack);
3302 BodyPart->RemoveFromSlot();
3303 CalculateAttributeBonuses();
3304 CalculateBattleInfo();
3305 BodyPart->SendToHell();
3306 SignalPossibleTransparencyChange();
3307 RemoveTraps(BodyPartIndex);
3308 return 0;
3310 BodyPart->SetOwnerDescription("of " + GetName(INDEFINITE));
3311 BodyPart->SetIsUnique(LeftOversAreUnique());
3312 UpdateBodyPartPicture(BodyPartIndex, true);
3313 BodyPart->RemoveFromSlot();
3314 BodyPart->RandomizePosition();
3315 CalculateAttributeBonuses();
3316 CalculateBattleInfo();
3317 BodyPart->Enable();
3318 SignalPossibleTransparencyChange();
3319 RemoveTraps(BodyPartIndex);
3320 return BodyPart;
3324 /* The second int is actually TargetFlags, which is not used here, but seems to be used in humanoid::ReceiveDamage.
3325 * Returns true if the character really receives damage */
3326 truth character::ReceiveDamage (character *Damager, int Damage, int Type, int, int Direction,
3327 truth, truth PenetrateArmor, truth Critical, truth ShowMsg)
3329 truth Affected = ReceiveBodyPartDamage(Damager, Damage, Type, 0, Direction, PenetrateArmor, Critical, ShowMsg);
3330 if (DamageTypeAffectsInventory(Type)) {
3331 for (int c = 0; c < GetEquipments(); ++c) {
3332 item *Equipment = GetEquipment(c);
3333 if (Equipment) Equipment->ReceiveDamage(Damager, Damage, Type);
3335 GetStack()->ReceiveDamage(Damager, Damage, Type);
3337 return Affected;
3341 festring character::GetDescription (int Case) const {
3342 if (IsPlayer()) return CONST_S("you");
3343 if (CanBeSeenByPlayer()) return GetName(Case);
3344 return CONST_S("something");
3348 festring character::GetPersonalPronoun (truth PlayersView) const {
3349 if (IsPlayer() && PlayersView) return CONST_S("you");
3350 if (GetSex() == UNDEFINED || (PlayersView && !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("it");
3351 if (GetSex() == MALE) return CONST_S("he");
3352 return CONST_S("she");
3356 festring character::GetPossessivePronoun (truth PlayersView) const {
3357 if (IsPlayer() && PlayersView) return CONST_S("your");
3358 if (GetSex() == UNDEFINED || (PlayersView && !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("its");
3359 if (GetSex() == MALE) return CONST_S("his");
3360 return CONST_S("her");
3364 festring character::GetObjectPronoun (truth PlayersView) const {
3365 if (IsPlayer() && PlayersView) return CONST_S("you");
3366 if (GetSex() == UNDEFINED || (PlayersView && !CanBeSeenByPlayer() && !game::GetSeeWholeMapCheatMode())) return CONST_S("it");
3367 if (GetSex() == MALE) return CONST_S("him");
3368 return CONST_S("her");
3372 void character::AddName (festring &String, int Case) const {
3373 if (AssignedName.IsEmpty()) {
3374 id::AddName(String, Case);
3375 } else if (!(Case & PLURAL)) {
3376 if (!ShowClassDescription()) {
3377 String << AssignedName;
3378 } else {
3379 String << AssignedName << ' ';
3380 id::AddName(String, (Case|ARTICLE_BIT)&~INDEFINE_BIT);
3382 } else {
3383 id::AddName(String, Case);
3384 String << " named " << AssignedName;
3389 int character::GetHungerState () const {
3390 if (!UsesNutrition()) return NOT_HUNGRY;
3391 if (GetNP() > OVER_FED_LEVEL) return OVER_FED;
3392 if (GetNP() > BLOATED_LEVEL) return BLOATED;
3393 if (GetNP() > SATIATED_LEVEL) return SATIATED;
3394 if (GetNP() > NOT_HUNGER_LEVEL) return NOT_HUNGRY;
3395 if (GetNP() > HUNGER_LEVEL) return HUNGRY;
3396 if (GetNP() > VERY_HUNGER_LEVEL) return VERY_HUNGRY;
3397 return STARVING;
3401 truth character::CanConsume (material *Material) const {
3402 return GetConsumeFlags() & Material->GetConsumeType();
3406 void character::SetTemporaryStateCounter (sLong State, int What) {
3407 for (int c = 0; c < STATES; ++c) {
3408 if ((1 << c) & State) TemporaryStateCounter[c] = What;
3413 void character::EditTemporaryStateCounter (sLong State, int What) {
3414 for (int c = 0; c < STATES; ++c) {
3415 if ((1 << c) & State) TemporaryStateCounter[c] += What;
3420 int character::GetTemporaryStateCounter (sLong State) const {
3421 for (int c = 0; c < STATES; ++c) {
3422 if ((1 << c) & State) return TemporaryStateCounter[c];
3424 ABORT("Illegal GetTemporaryStateCounter request!");
3425 return 0;
3429 truth character::CheckKick () const {
3430 if (!CanKick()) {
3431 if (IsPlayer()) ADD_MESSAGE("This race can't kick.");
3432 return false;
3434 return true;
3438 int character::GetResistance (int Type) const {
3439 switch (Type&0xFFF) {
3440 case PHYSICAL_DAMAGE:
3441 case SOUND:
3442 case DRAIN:
3443 case MUSTARD_GAS_DAMAGE:
3444 case PSI:
3445 return 0;
3446 case ENERGY: return GetEnergyResistance();
3447 case FIRE: return GetFireResistance();
3448 case POISON: return GetPoisonResistance();
3449 case ELECTRICITY: return GetElectricityResistance();
3450 case ACID: return GetAcidResistance();
3452 ABORT("Resistance lack detected!");
3453 return 0;
3457 void character::Regenerate () {
3458 if (HP == MaxHP) return;
3459 sLong RegenerationBonus = 0;
3460 truth NoHealableBodyParts = true;
3461 for (int c = 0; c < BodyParts; ++c) {
3462 bodypart *BodyPart = GetBodyPart(c);
3463 if (BodyPart && BodyPart->CanRegenerate()) {
3464 RegenerationBonus += BodyPart->GetMaxHP();
3465 if (NoHealableBodyParts && BodyPart->GetHP() < BodyPart->GetMaxHP()) NoHealableBodyParts = false;
3468 if (!RegenerationBonus || NoHealableBodyParts) return;
3469 RegenerationBonus *= (50+GetAttribute(ENDURANCE));
3471 if (Action && Action->IsRest()) {
3472 if (SquaresUnder == 1) RegenerationBonus *= GetSquareUnder()->GetRestModifier() << 1;
3473 else {
3474 int Lowest = GetSquareUnder(0)->GetRestModifier();
3475 for (int c = 1; c < GetSquaresUnder(); ++c) {
3476 int Mod = GetSquareUnder(c)->GetRestModifier();
3477 if (Mod < Lowest) Lowest = Mod;
3479 RegenerationBonus *= Lowest << 1;
3483 RegenerationCounter += RegenerationBonus;
3485 while (RegenerationCounter > 1250000) {
3486 bodypart *BodyPart = HealHitPoint();
3487 if (!BodyPart) break;
3488 EditNP(-Max(7500/MaxHP, 1));
3489 RegenerationCounter -= 1250000;
3490 int HP = BodyPart->GetHP();
3491 EditExperience(ENDURANCE, Min(1000*BodyPart->GetMaxHP()/(HP*HP), 300), 1000);
3496 void character::PrintInfo () const {
3497 felist Info(CONST_S("Information about ")+GetName(DEFINITE));
3498 for (int c = 0; c < GetEquipments(); ++c) {
3499 item *Equipment = GetEquipment(c);
3500 if ((EquipmentEasilyRecognized(c) || game::WizardModeIsActive()) && Equipment) {
3501 int ImageKey = game::AddToItemDrawVector(itemvector(1, Equipment));
3502 Info.AddEntry(festring(GetEquipmentName(c))+": "+Equipment->GetName(INDEFINITE), LIGHT_GRAY, 0, ImageKey, true);
3505 if (Info.IsEmpty()) {
3506 ADD_MESSAGE("There's nothing special to tell about %s.", CHAR_NAME(DEFINITE));
3507 } else {
3508 game::SetStandardListAttributes(Info);
3509 Info.SetEntryDrawer(game::ItemEntryDrawer);
3510 Info.Draw();
3512 game::ClearItemDrawVector();
3516 truth character::TryToRiseFromTheDead () {
3517 for (int c = 0; c < BodyParts; ++c) {
3518 bodypart *BodyPart = GetBodyPart(c);
3519 if (BodyPart) {
3520 BodyPart->ResetSpoiling();
3521 if (BodyPart->CanRegenerate() || BodyPart->GetHP() < 1) BodyPart->SetHP(1);
3524 ResetStates();
3525 return true;
3529 truth character::RaiseTheDead (character *) {
3530 truth Useful = false;
3531 for (int c = 0; c < BodyParts; ++c) {
3532 bodypart *BodyPart = GetBodyPart(c);
3533 if (!BodyPart && CanCreateBodyPart(c)) {
3534 CreateBodyPart(c)->SetHP(1);
3535 if (IsPlayer()) ADD_MESSAGE("Suddenly you grow a new %s.", GetBodyPartName(c).CStr());
3536 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE), GetBodyPartName(c).CStr());
3537 Useful = true;
3538 } else if (BodyPart && BodyPart->CanRegenerate() && BodyPart->GetHP() < 1) {
3539 BodyPart->SetHP(1);
3542 if (!Useful) {
3543 if (IsPlayer()) ADD_MESSAGE("You shudder.");
3544 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s shudders.", CHAR_NAME(DEFINITE));
3546 return Useful;
3550 void character::SetSize (int Size) {
3551 for (int c = 0; c < BodyParts; ++c) {
3552 bodypart *BodyPart = GetBodyPart(c);
3553 if (BodyPart) BodyPart->SetSize(GetBodyPartSize(c, Size));
3558 sLong character::GetBodyPartSize (int I, int TotalSize) const {
3559 if (I == TORSO_INDEX) return TotalSize;
3560 ABORT("Weird bodypart size request for a character!");
3561 return 0;
3565 sLong character::GetBodyPartVolume (int I) const {
3566 if (I == TORSO_INDEX) return GetTotalVolume();
3567 ABORT("Weird bodypart volume request for a character!");
3568 return 0;
3572 void character::CreateBodyParts (int SpecialFlags) {
3573 for (int c = 0; c < BodyParts; ++c) if (CanCreateBodyPart(c)) CreateBodyPart(c, SpecialFlags);
3577 void character::RestoreBodyParts () {
3578 for (int c = 0; c < BodyParts; ++c) if (!GetBodyPart(c) && CanCreateBodyPart(c)) CreateBodyPart(c);
3582 void character::UpdatePictures () {
3583 if (!PictureUpdatesAreForbidden()) for (int c = 0; c < BodyParts; ++c) UpdateBodyPartPicture(c, false);
3587 bodypart *character::MakeBodyPart (int I) const {
3588 if (I == TORSO_INDEX) return normaltorso::Spawn(0, NO_MATERIALS);
3589 ABORT("Weird bodypart to make for a character!");
3590 return 0;
3594 bodypart *character::CreateBodyPart (int I, int SpecialFlags) {
3595 bodypart *BodyPart = MakeBodyPart(I);
3596 material *Material = CreateBodyPartMaterial(I, GetBodyPartVolume(I));
3597 BodyPart->InitMaterials(Material, false);
3598 BodyPart->SetSize(GetBodyPartSize(I, GetTotalSize()));
3599 BodyPart->SetBloodMaterial(GetBloodMaterial());
3600 BodyPart->SetNormalMaterial(Material->GetConfig());
3601 BodyPart->SetHP(1);
3602 SetBodyPart(I, BodyPart);
3603 BodyPart->InitSpecialAttributes();
3604 if (!(SpecialFlags & NO_PIC_UPDATE)) UpdateBodyPartPicture(I, false);
3605 if (!IsInitializing()) {
3606 CalculateBattleInfo();
3607 SendNewDrawRequest();
3608 SignalPossibleTransparencyChange();
3610 return BodyPart;
3614 v2 character::GetBodyPartBitmapPos (int I, truth) const {
3615 if (I == TORSO_INDEX) return GetTorsoBitmapPos();
3616 ABORT("Weird bodypart BitmapPos request for a character!");
3617 return v2();
3621 void character::UpdateBodyPartPicture (int I, truth Severed) {
3622 bodypart *BP = GetBodyPart(I);
3623 if (BP) {
3624 BP->SetBitmapPos(GetBodyPartBitmapPos(I, Severed));
3625 BP->GetMainMaterial()->SetSkinColor(GetBodyPartColorA(I, Severed));
3626 BP->GetMainMaterial()->SetSkinColorIsSparkling(GetBodyPartSparkleFlags(I) & SPARKLING_A);
3627 BP->SetMaterialColorB(GetBodyPartColorB(I, Severed));
3628 BP->SetMaterialColorC(GetBodyPartColorC(I, Severed));
3629 BP->SetMaterialColorD(GetBodyPartColorD(I, Severed));
3630 BP->SetSparkleFlags(GetBodyPartSparkleFlags(I));
3631 BP->SetSpecialFlags(GetSpecialBodyPartFlags(I));
3632 BP->SetWobbleData(GetBodyPartWobbleData(I));
3633 BP->UpdatePictures();
3638 void character::LoadDataBaseStats () {
3639 for (int c = 0; c < BASE_ATTRIBUTES; ++c) {
3640 BaseExperience[c] = DataBase->NaturalExperience[c];
3641 if (BaseExperience[c]) LimitRef(BaseExperience[c], MIN_EXP, MAX_EXP);
3643 SetMoney(GetDefaultMoney());
3644 SetInitialSweatMaterial(GetSweatMaterial());
3645 const fearray<sLong> &Skills = GetKnownCWeaponSkills();
3646 if (Skills.Size) {
3647 const fearray<sLong> &Hits = GetCWeaponSkillHits();
3648 if (Hits.Size == 1) {
3649 for (uInt c = 0; c < Skills.Size; ++c) {
3650 if (Skills[c] < AllowedWeaponSkillCategories) CWeaponSkill[Skills[c]].AddHit(Hits[0]*100);
3652 } else if (Hits.Size == Skills.Size) {
3653 for (uInt c = 0; c < Skills.Size; ++c) {
3654 if (Skills[c] < AllowedWeaponSkillCategories) CWeaponSkill[Skills[c]].AddHit(Hits[c]*100);
3656 } else {
3657 ABORT("Illegal weapon skill hit array size detected!");
3663 character *characterprototype::SpawnAndLoad (inputfile &SaveFile) const {
3664 character *Char = Spawner(0, LOAD);
3665 Char->Load(SaveFile);
3666 Char->CalculateAll();
3667 return Char;
3671 void character::Initialize (int NewConfig, int SpecialFlags) {
3672 Flags |= C_INITIALIZING|C_IN_NO_MSG_MODE;
3673 CalculateBodyParts();
3674 CalculateAllowedWeaponSkillCategories();
3675 CalculateSquaresUnder();
3676 BodyPartSlot = new bodypartslot[BodyParts];
3677 OriginalBodyPartID = new std::list<feuLong>[BodyParts];
3678 CWeaponSkill = new cweaponskill[AllowedWeaponSkillCategories];
3679 SquareUnder = new square*[SquaresUnder];
3681 if (SquaresUnder == 1) *SquareUnder = 0; else memset(SquareUnder, 0, SquaresUnder*sizeof(square *));
3683 for (int c = 0; c < BodyParts; ++c) BodyPartSlot[c].SetMaster(this);
3685 if (!(SpecialFlags & LOAD)) {
3686 ID = game::CreateNewCharacterID(this);
3687 databasecreator<character>::InstallDataBase(this, NewConfig);
3688 LoadDataBaseStats();
3689 TemporaryState |= GetClassStates();
3690 if (TemporaryState) {
3691 for (int c = 0; c < STATES; ++c) if (TemporaryState & (1 << c)) TemporaryStateCounter[c] = PERMANENT;
3694 CreateBodyParts(SpecialFlags | NO_PIC_UPDATE);
3695 InitSpecialAttributes();
3696 CommandFlags = GetDefaultCommandFlags();
3698 if (GetAttribute(INTELLIGENCE, false) < 8) CommandFlags &= ~DONT_CONSUME_ANYTHING_VALUABLE; // gum
3699 if (!GetDefaultName().IsEmpty()) SetAssignedName(GetDefaultName());
3702 if (!(SpecialFlags & LOAD)) PostConstruct();
3704 if (!(SpecialFlags & LOAD)) {
3705 if (!(SpecialFlags & NO_EQUIPMENT)) CreateInitialEquipment((SpecialFlags & NO_EQUIPMENT_PIC_UPDATE) >> 1);
3706 if (!(SpecialFlags & NO_PIC_UPDATE)) UpdatePictures();
3707 CalculateAll();
3708 RestoreHP();
3709 RestoreStamina();
3712 Flags &= ~(C_INITIALIZING|C_IN_NO_MSG_MODE);
3716 truth character::TeleportNear (character *Caller) {
3717 v2 Where = GetLevel()->GetNearestFreeSquare(this, Caller->GetPos());
3718 if (Where == ERROR_V2) return false;
3719 Move(Where, true);
3720 return true;
3724 void character::ReceiveHeal (sLong Amount) {
3725 int c;
3726 for (c = 0; c < Amount / 10; ++c) if (!HealHitPoint()) break;
3727 Amount -= c*10;
3728 if (RAND()%10 < Amount) HealHitPoint();
3729 if (Amount >= 250 || RAND()%250 < Amount) {
3730 bodypart *NewBodyPart = GenerateRandomBodyPart();
3731 if (!NewBodyPart) return;
3732 NewBodyPart->SetHP(1);
3733 if (IsPlayer()) ADD_MESSAGE("You grow a new %s.", NewBodyPart->GetBodyPartName().CStr());
3734 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s grows a new %s.", CHAR_NAME(DEFINITE), NewBodyPart->GetBodyPartName().CStr());
3739 void character::AddHealingLiquidConsumeEndMessage () const {
3740 if (IsPlayer()) ADD_MESSAGE("You feel better.");
3741 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks healthier.", CHAR_NAME(DEFINITE));
3745 void character::ReceiveSchoolFood (sLong SizeOfEffect) {
3746 SizeOfEffect += RAND()%SizeOfEffect;
3747 if (SizeOfEffect >= 250) VomitAtRandomDirection(SizeOfEffect);
3748 if (!(RAND() % 3) && SizeOfEffect >= 500 && EditAttribute(ENDURANCE, SizeOfEffect/500)) {
3749 if (IsPlayer()) ADD_MESSAGE("You gain a little bit of toughness for surviving this stuff.");
3750 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks tougher.", CHAR_NAME(DEFINITE));
3752 BeginTemporaryState(POISONED, (SizeOfEffect>>1));
3756 void character::AddSchoolFoodConsumeEndMessage () const {
3757 if (IsPlayer()) ADD_MESSAGE("Yuck! This stuff tasted like vomit and old mousepads.");
3761 void character::AddSchoolFoodHitMessage () const {
3762 if (IsPlayer()) ADD_MESSAGE("Yuck! This stuff feels like vomit and old mousepads.");
3766 void character::ReceiveNutrition (sLong SizeOfEffect) {
3767 EditNP(SizeOfEffect);
3771 void character::ReceiveOmmelUrine (sLong Amount) {
3772 EditExperience(ARM_STRENGTH, 500, Amount<<4);
3773 EditExperience(LEG_STRENGTH, 500, Amount<<4);
3774 if (IsPlayer()) game::DoEvilDeed(Amount/25);
3778 void character::ReceiveOmmelCerumen (sLong Amount) {
3779 EditExperience(INTELLIGENCE, 500, Amount << 5);
3780 EditExperience(WISDOM, 500, Amount << 5);
3781 if (IsPlayer()) game::DoEvilDeed(Amount / 25);
3785 void character::ReceiveOmmelSweat (sLong Amount) {
3786 EditExperience(AGILITY, 500, Amount << 4);
3787 EditExperience(DEXTERITY, 500, Amount << 4);
3788 RestoreStamina();
3789 if (IsPlayer()) game::DoEvilDeed(Amount / 25);
3793 void character::ReceiveOmmelTears (sLong Amount) {
3794 EditExperience(PERCEPTION, 500, Amount << 4);
3795 EditExperience(CHARISMA, 500, Amount << 4);
3796 if (IsPlayer()) game::DoEvilDeed(Amount / 25);
3800 void character::ReceiveOmmelSnot (sLong Amount) {
3801 EditExperience(ENDURANCE, 500, Amount << 5);
3802 RestoreLivingHP();
3803 if (IsPlayer()) game::DoEvilDeed(Amount / 25);
3807 void character::ReceiveOmmelBone (sLong Amount) {
3808 EditExperience(ARM_STRENGTH, 500, Amount << 6);
3809 EditExperience(LEG_STRENGTH, 500, Amount << 6);
3810 EditExperience(DEXTERITY, 500, Amount << 6);
3811 EditExperience(AGILITY, 500, Amount << 6);
3812 EditExperience(ENDURANCE, 500, Amount << 6);
3813 EditExperience(PERCEPTION, 500, Amount << 6);
3814 EditExperience(INTELLIGENCE, 500, Amount << 6);
3815 EditExperience(WISDOM, 500, Amount << 6);
3816 EditExperience(CHARISMA, 500, Amount << 6);
3817 RestoreLivingHP();
3818 RestoreStamina();
3819 if (IsPlayer()) game::DoEvilDeed(Amount / 25);
3823 void character::AddOmmelConsumeEndMessage () const {
3824 if (IsPlayer()) ADD_MESSAGE("You feel a primitive force coursing through your veins.");
3825 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks more powerful.", CHAR_NAME(DEFINITE));
3829 void character::ReceivePepsi (sLong Amount) {
3830 ReceiveDamage(0, Amount / 100, POISON, TORSO);
3831 EditExperience(PERCEPTION, Amount, 1 << 14);
3832 if (CheckDeath(CONST_S("was poisoned by pepsi"), 0)) return;
3833 if (IsPlayer()) game::DoEvilDeed(Amount / 10);
3837 void character::AddPepsiConsumeEndMessage () const {
3838 if (IsPlayer()) ADD_MESSAGE("Urgh. You feel your guruism fading away.");
3839 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks very lame.", CHAR_NAME(DEFINITE));
3843 void character::ReceiveDarkness (sLong Amount) {
3844 EditExperience(INTELLIGENCE, -Amount / 5, 1 << 13);
3845 EditExperience(WISDOM, -Amount / 5, 1 << 13);
3846 EditExperience(CHARISMA, -Amount / 5, 1 << 13);
3847 if (IsPlayer()) game::DoEvilDeed(int(Amount / 50));
3851 void character::AddFrogFleshConsumeEndMessage () const {
3852 if (IsPlayer()) ADD_MESSAGE("Arg. You feel the fate of a navastater placed upon you...");
3853 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s looks like a navastater.", CHAR_NAME(DEFINITE));
3857 void character::ReceiveKoboldFlesh (sLong) {
3858 /* As it is commonly known, the possibility of fainting per 500 cubic
3859 centimeters of kobold flesh is exactly 5%. */
3860 if (!(RAND() % 20)) {
3861 if (IsPlayer()) ADD_MESSAGE("You lose control of your legs and fall down.");
3862 LoseConsciousness(250 + RAND_N(250));
3867 void character::AddKoboldFleshConsumeEndMessage () const {
3868 if (IsPlayer()) ADD_MESSAGE("This stuff tasted really funny.");
3872 void character::AddKoboldFleshHitMessage () const {
3873 if (IsPlayer()) ADD_MESSAGE("You feel very funny.");
3877 void character::AddBoneConsumeEndMessage () const {
3878 if (IsPlayer()) ADD_MESSAGE("You feel like a hippie.");
3879 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s barks happily.", CHAR_NAME(DEFINITE)); // this suspects that nobody except dogs can eat bones
3882 truth character::RawEditAttribute (double &Experience, int Amount) const {
3883 /* Check if the attribute is disabled for creature */
3884 if (!Experience) return false;
3885 if ((Amount < 0 && Experience < 2 * EXP_MULTIPLIER) || (Amount > 0 && Experience > 999 * EXP_MULTIPLIER)) return false;
3886 Experience += Amount * EXP_MULTIPLIER;
3887 LimitRef<double>(Experience, MIN_EXP, MAX_EXP);
3888 return true;
3892 void character::DrawPanel (truth AnimationDraw) const {
3893 if (AnimationDraw) { DrawStats(true); return; }
3894 igraph::BlitBackGround(v2(19 + (game::GetScreenXSize() << 4), 0), v2(RES.X - 19 - (game::GetScreenXSize() << 4), RES.Y));
3895 igraph::BlitBackGround(v2(16, 45 + (game::GetScreenYSize() << 4)), v2(game::GetScreenXSize() << 4, 9));
3896 FONT->Printf(DOUBLE_BUFFER, v2(16, 45 + (game::GetScreenYSize() << 4)), WHITE, "%s", GetPanelName().CStr());
3897 game::UpdateAttributeMemory();
3898 int PanelPosX = RES.X - 96;
3899 int PanelPosY = DrawStats(false);
3900 PrintAttribute("End", ENDURANCE, PanelPosX, PanelPosY++);
3901 PrintAttribute("Per", PERCEPTION, PanelPosX, PanelPosY++);
3902 PrintAttribute("Int", INTELLIGENCE, PanelPosX, PanelPosY++);
3903 PrintAttribute("Wis", WISDOM, PanelPosX, PanelPosY++);
3904 PrintAttribute("Wil", WILL_POWER, PanelPosX, PanelPosY++);
3905 PrintAttribute("Cha", CHARISMA, PanelPosX, PanelPosY++);
3906 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Siz %d", GetSize());
3907 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), IsInBadCondition() ? RED : WHITE, "HP %d/%d", GetHP(), GetMaxHP());
3908 ++PanelPosY;
3909 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Gold: %d", GetMoney());
3910 ++PanelPosY;
3912 if (game::IsInWilderness())
3913 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Worldmap");
3914 else
3915 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "%s", game::GetCurrentDungeon()->GetShortLevelDescription(game::GetCurrentLevelIndex()).CapitalizeCopy().CStr());
3917 ivantime Time;
3918 game::GetTime(Time);
3919 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Day %d", Time.Day);
3920 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Time %d:%s%d", Time.Hour, Time.Min < 10 ? "0" : "", Time.Min);
3921 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Turn %d", game::GetTurn());
3923 ++PanelPosY;
3925 if (GetAction())
3926 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "%s", festring(GetAction()->GetDescription()).CapitalizeCopy().CStr());
3928 for (int c = 0; c < STATES; ++c)
3929 if (!(StateData[c].Flags & SECRET) && StateIsActivated(1 << c) && (1 << c != HASTE || !StateIsActivated(SLOW)) && (1 << c != SLOW || !StateIsActivated(HASTE)))
3930 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), (1 << c) & EquipmentState || TemporaryStateCounter[c] == PERMANENT ? BLUE : WHITE, "%s", StateData[c].Description);
3932 /* Make this more elegant!!! */
3933 if (GetHungerState() == STARVING)
3934 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), RED, "Starving");
3935 else if (GetHungerState() == VERY_HUNGRY)
3936 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), BLUE, "Very hungry");
3937 else if (GetHungerState() == HUNGRY)
3938 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), BLUE, "Hungry");
3939 else if (GetHungerState() == SATIATED)
3940 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Satiated");
3941 else if (GetHungerState() == BLOATED)
3942 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Bloated");
3943 else if (GetHungerState() == OVER_FED)
3944 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Overfed!");
3946 switch (GetBurdenState()) {
3947 case OVER_LOADED:
3948 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), RED, "Overload!");
3949 break;
3950 case STRESSED:
3951 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), BLUE, "Stressed");
3952 break;
3953 case BURDENED:
3954 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), BLUE, "Burdened");
3955 break;
3958 switch (GetTirednessState()) {
3959 case FAINTING:
3960 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), RED, "Fainting");
3961 break;
3962 case EXHAUSTED:
3963 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "Exhausted");
3964 break;
3967 if (game::PlayerIsRunning()) {
3968 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "%s", GetRunDescriptionLine(0));
3969 cchar *SecondLine = GetRunDescriptionLine(1);
3970 if (strlen(SecondLine)) FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY++ * 10), WHITE, "%s", SecondLine);
3975 void character::CalculateDodgeValue () {
3976 DodgeValue = 0.05 * GetMoveEase() * GetAttribute(AGILITY) / sqrt(GetSize());
3977 if (IsFlying()) DodgeValue *= 2;
3978 if (DodgeValue < 1) DodgeValue = 1;
3982 truth character::DamageTypeAffectsInventory (int Type) {
3983 switch (Type&0xFFF) {
3984 case SOUND:
3985 case ENERGY:
3986 case ACID:
3987 case FIRE:
3988 case ELECTRICITY:
3989 return true;
3990 case PHYSICAL_DAMAGE:
3991 case POISON:
3992 case DRAIN:
3993 case MUSTARD_GAS_DAMAGE:
3994 case PSI:
3995 return false;
3997 ABORT("Unknown reaping effect destroyed dungeon!");
3998 return false;
4002 int character::CheckForBlockWithArm (character *Enemy, item *Weapon, arm *Arm,
4003 double WeaponToHitValue, int Damage, int Success, int Type)
4005 int BlockStrength = Arm->GetBlockCapability();
4006 double BlockValue = Arm->GetBlockValue();
4007 if (BlockStrength && BlockValue) {
4008 item *Blocker = Arm->GetWielded();
4009 if (RAND() % int(100+WeaponToHitValue/BlockValue/(1<<BlocksSinceLastTurn)*(100+Success)) < 100) {
4010 int NewDamage = BlockStrength < Damage ? Damage-BlockStrength : 0;
4011 switch (Type) {
4012 case UNARMED_ATTACK: AddBlockMessage(Enemy, Blocker, Enemy->UnarmedHitNoun(), NewDamage); break;
4013 case WEAPON_ATTACK: AddBlockMessage(Enemy, Blocker, "attack", NewDamage); break;
4014 case KICK_ATTACK: AddBlockMessage(Enemy, Blocker, Enemy->KickNoun(), NewDamage); break;
4015 case BITE_ATTACK: AddBlockMessage(Enemy, Blocker, Enemy->BiteNoun(), NewDamage); break;
4017 sLong Weight = Blocker->GetWeight();
4018 sLong StrExp = Limit(15 * Weight / 200, 75, 300);
4019 sLong DexExp = Weight ? Limit(75000 / Weight, 75, 300) : 300;
4020 Arm->EditExperience(ARM_STRENGTH, StrExp, 1 << 8);
4021 Arm->EditExperience(DEXTERITY, DexExp, 1 << 8);
4022 EditStamina(-10000 / GetAttribute(ARM_STRENGTH), false);
4023 if (Arm->TwoHandWieldIsActive()) {
4024 arm *PairArm = Arm->GetPairArm();
4025 PairArm->EditExperience(ARM_STRENGTH, StrExp, 1 << 8);
4026 PairArm->EditExperience(DEXTERITY, DexExp, 1 << 8);
4028 Blocker->WeaponSkillHit(Enemy->CalculateWeaponSkillHits(this));
4029 Blocker->ReceiveDamage(this, Damage, PHYSICAL_DAMAGE);
4030 Blocker->BlockEffect(this, Enemy, Weapon, Type);
4031 if (Weapon) Weapon->ReceiveDamage(Enemy, Damage - NewDamage, PHYSICAL_DAMAGE);
4032 if (BlocksSinceLastTurn < 16) ++BlocksSinceLastTurn;
4033 return NewDamage;
4036 return Damage;
4040 sLong character::GetStateAPGain (sLong BaseAPGain) const {
4041 if (!StateIsActivated(HASTE) == !StateIsActivated(SLOW)) return BaseAPGain;
4042 if (StateIsActivated(HASTE)) return (BaseAPGain * 5) >> 2;
4043 return (BaseAPGain << 2) / 5;
4047 void character::SignalEquipmentAdd (int EquipmentIndex) {
4048 item *Equipment = GetEquipment(EquipmentIndex);
4049 if (Equipment->IsInCorrectSlot(EquipmentIndex)) {
4050 sLong AddedStates = Equipment->GetGearStates();
4051 if (AddedStates) {
4052 for (int c = 0; c < STATES; ++c) {
4053 if (AddedStates & (1 << c)) {
4054 if (!StateIsActivated(1 << c)) {
4055 if (!IsInNoMsgMode()) (this->*StateData[c].PrintBeginMessage)();
4056 EquipmentState |= 1 << c;
4057 if (StateData[c].BeginHandler) (this->*StateData[c].BeginHandler)();
4058 } else {
4059 EquipmentState |= 1 << c;
4065 if (!IsInitializing() && Equipment->IsInCorrectSlot(EquipmentIndex)) ApplyEquipmentAttributeBonuses(Equipment);
4069 void character::SignalEquipmentRemoval (int, citem *Item) {
4070 CalculateEquipmentState();
4071 if (CalculateAttributeBonuses()) CheckDeath(festring("lost ")+GetPossessivePronoun(false)+" vital "+Item->GetName(INDEFINITE));
4075 void character::CalculateEquipmentState () {
4076 sLong Back = EquipmentState;
4077 EquipmentState = 0;
4078 for (int c = 0; c < GetEquipments(); ++c) {
4079 item *Equipment = GetEquipment(c);
4080 if (Equipment && Equipment->IsInCorrectSlot(c)) EquipmentState |= Equipment->GetGearStates();
4082 for (int c = 0; c < STATES; ++c) {
4083 if (Back & (1 << c) && !StateIsActivated(1 << c)) {
4084 if (StateData[c].EndHandler) {
4085 (this->*StateData[c].EndHandler)();
4086 if (!IsEnabled()) return;
4088 if (!IsInNoMsgMode()) (this->*StateData[c].PrintEndMessage)();
4094 /* Counter = duration in ticks */
4095 void character::BeginTemporaryState (sLong State, int Counter) {
4096 if (!Counter) return;
4097 int Index;
4098 if (State == POLYMORPHED) ABORT("No Polymorphing with BeginTemporaryState!");
4099 for (Index = 0; Index < STATES; ++Index) if (1 << Index == State) break;
4100 if (Index == STATES) ABORT("BeginTemporaryState works only when State == 2^n!");
4101 if (TemporaryStateIsActivated(State)) {
4102 int OldCounter = GetTemporaryStateCounter(State);
4103 if (OldCounter != PERMANENT) EditTemporaryStateCounter(State, Max(Counter, 50-OldCounter));
4104 } else if (StateData[Index].IsAllowed == 0 || (this->*StateData[Index].IsAllowed)()) {
4105 SetTemporaryStateCounter(State, Max(Counter, 50));
4106 if (!EquipmentStateIsActivated(State)) {
4107 if (!IsInNoMsgMode()) (this->*StateData[Index].PrintBeginMessage)();
4108 ActivateTemporaryState(State);
4109 if (StateData[Index].BeginHandler) (this->*StateData[Index].BeginHandler)();
4110 } else {
4111 ActivateTemporaryState(State);
4117 void character::HandleStates () {
4118 if (!TemporaryState && !EquipmentState) return;
4119 for (int c = 0; c < STATES; ++c) {
4120 if (TemporaryState & (1 << c) && TemporaryStateCounter[c] != PERMANENT) {
4121 if (!--TemporaryStateCounter[c]) {
4122 TemporaryState &= ~(1 << c);
4123 if (!(EquipmentState & (1 << c))) {
4124 if (StateData[c].EndHandler) {
4125 (this->*StateData[c].EndHandler)();
4126 if (!IsEnabled()) return;
4128 if (!TemporaryStateCounter[c]) (this->*StateData[c].PrintEndMessage)();
4132 if (StateIsActivated(1 << c)) {
4133 if (StateData[c].Handler) (this->*StateData[c].Handler)();
4135 if (!IsEnabled()) return;
4140 void character::PrintBeginPolymorphControlMessage () const {
4141 if (IsPlayer()) ADD_MESSAGE("You feel your mind has total control over your body.");
4145 void character::PrintEndPolymorphControlMessage () const {
4146 if (IsPlayer()) ADD_MESSAGE("You are somehow uncertain of your willpower.");
4150 void character::PrintBeginLifeSaveMessage () const {
4151 if (IsPlayer()) ADD_MESSAGE("You hear Hell's gates being locked just now.");
4155 void character::PrintEndLifeSaveMessage () const {
4156 if (IsPlayer()) ADD_MESSAGE("You feel the Afterlife is welcoming you once again.");
4160 void character::PrintBeginLycanthropyMessage () const {
4161 if (IsPlayer()) ADD_MESSAGE("You suddenly notice you've always loved full moons.");
4165 void character::PrintEndLycanthropyMessage () const {
4166 if (IsPlayer()) ADD_MESSAGE("You feel the wolf inside you has had enough of your bad habits.");
4170 void character::PrintBeginVampirismMessage () const {
4171 if (IsPlayer()) ADD_MESSAGE("You suddenly decide you have always hated garlic.");
4175 void character::PrintEndVampirismMessage () const {
4176 if (IsPlayer()) ADD_MESSAGE("You recall your delight of the morning sunshine back in New Attnam. You are a vampire no longer.");
4180 void character::PrintBeginInvisibilityMessage () const {
4181 if ((PLAYER->StateIsActivated(INFRA_VISION) && IsWarm()) || (PLAYER->StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5)) {
4182 if (IsPlayer()) ADD_MESSAGE("You seem somehow transparent.");
4183 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s seems somehow transparent.", CHAR_NAME(DEFINITE));
4184 } else {
4185 if (IsPlayer()) ADD_MESSAGE("You fade away.");
4186 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s disappears!", CHAR_NAME(DEFINITE));
4191 void character::PrintEndInvisibilityMessage () const {
4192 if ((PLAYER->StateIsActivated(INFRA_VISION) && IsWarm()) || (PLAYER->StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5)) {
4193 if (IsPlayer()) ADD_MESSAGE("Your notice your transparency has ended.");
4194 else if (CanBeSeenByPlayer()) ADD_MESSAGE("The appearance of %s seems far more solid now.", CHAR_NAME(INDEFINITE));
4195 } else {
4196 if (IsPlayer()) ADD_MESSAGE("You reappear.");
4197 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Suddenly %s appears from nowhere!", CHAR_NAME(INDEFINITE));
4202 void character::PrintBeginInfraVisionMessage () const {
4203 if (IsPlayer()) {
4204 if (StateIsActivated(INVISIBLE) && IsWarm() && !(StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5))
4205 ADD_MESSAGE("You reappear.");
4206 else
4207 ADD_MESSAGE("You feel your perception being magically altered.");
4212 void character::PrintEndInfraVisionMessage () const {
4213 if (IsPlayer()) {
4214 if (StateIsActivated(INVISIBLE) && IsWarm() && !(StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5))
4215 ADD_MESSAGE("You disappear.");
4216 else
4217 ADD_MESSAGE("You feel your perception returning to normal.");
4222 void character::PrintBeginESPMessage () const {
4223 if (IsPlayer()) ADD_MESSAGE("You suddenly feel like being only a tiny part of a great network of intelligent minds.");
4227 void character::PrintEndESPMessage () const {
4228 if (IsPlayer()) ADD_MESSAGE("You are filled with desire to be just yourself from now on.");
4232 void character::PrintBeginHasteMessage () const {
4233 if (IsPlayer()) ADD_MESSAGE("Time slows down to a crawl.");
4234 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks faster!", CHAR_NAME(DEFINITE));
4238 void character::PrintEndHasteMessage () const {
4239 if (IsPlayer()) ADD_MESSAGE("Everything seems to move much faster now.");
4240 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks slower!", CHAR_NAME(DEFINITE));
4244 void character::PrintBeginSlowMessage () const {
4245 if (IsPlayer()) ADD_MESSAGE("Everything seems to move much faster now.");
4246 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks slower!", CHAR_NAME(DEFINITE));
4250 void character::PrintEndSlowMessage () const {
4251 if (IsPlayer()) ADD_MESSAGE("Time slows down to a crawl.");
4252 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks faster!", CHAR_NAME(DEFINITE));
4256 void character::EndPolymorph () {
4257 ForceEndPolymorph();
4261 character *character::ForceEndPolymorph () {
4262 if (IsPlayer()) {
4263 ADD_MESSAGE("You return to your true form.");
4264 } else if (game::IsInWilderness()) {
4265 ActivateTemporaryState(POLYMORPHED);
4266 SetTemporaryStateCounter(POLYMORPHED, 10);
4267 return this; // fast gum solution, state ends when the player enters a dungeon
4269 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s returns to %s true form.", CHAR_NAME(DEFINITE), GetPossessivePronoun().CStr());
4270 RemoveTraps();
4271 if (GetAction()) GetAction()->Terminate(false);
4272 v2 Pos = GetPos();
4273 SendToHell();
4274 Remove();
4275 character *Char = GetPolymorphBackup();
4276 Flags |= C_IN_NO_MSG_MODE;
4277 Char->Flags |= C_IN_NO_MSG_MODE;
4278 Char->PutToOrNear(Pos);
4279 Char->ChangeTeam(GetTeam());
4280 if (GetTeam()->GetLeader() == this) GetTeam()->SetLeader(Char);
4281 SetPolymorphBackup(0);
4282 Char->Enable();
4283 Char->Flags &= ~C_POLYMORPHED;
4284 GetStack()->MoveItemsTo(Char->GetStack());
4285 DonateEquipmentTo(Char);
4286 Char->SetMoney(GetMoney());
4287 Flags &= ~C_IN_NO_MSG_MODE;
4288 Char->Flags &= ~C_IN_NO_MSG_MODE;
4289 Char->CalculateAll();
4290 Char->SetAssignedName(GetAssignedName());
4291 if (IsPlayer()) {
4292 Flags &= ~C_PLAYER;
4293 game::SetPlayer(Char);
4294 game::SendLOSUpdateRequest();
4295 UpdateESPLOS();
4297 Char->TestWalkability();
4298 return Char;
4302 void character::LycanthropyHandler () {
4303 if (!(RAND() % 2000)) {
4304 if (StateIsActivated(POLYMORPH_CONTROL) && !game::TruthQuestion(CONST_S("Do you wish to change into a werewolf? [y/N]"))) return;
4305 Polymorph(werewolfwolf::Spawn(), 1000 + RAND() % 2000);
4310 void character::SaveLife () {
4311 if (TemporaryStateIsActivated(LIFE_SAVED)) {
4312 if (IsPlayer())
4313 ADD_MESSAGE("But wait! You glow briefly red and seem to be in a better shape!");
4314 else if (CanBeSeenByPlayer())
4315 ADD_MESSAGE("But wait, suddenly %s glows briefly red and seems to be in a better shape!", GetPersonalPronoun().CStr());
4316 DeActivateTemporaryState(LIFE_SAVED);
4317 } else {
4318 item *LifeSaver = 0;
4319 for (int c = 0; c < GetEquipments(); ++c) {
4320 item *Equipment = GetEquipment(c);
4321 if (Equipment && Equipment->IsInCorrectSlot(c) && Equipment->GetGearStates() & LIFE_SAVED) LifeSaver = Equipment;
4323 if (!LifeSaver) ABORT("The Universe can only kill you once!");
4324 if (IsPlayer())
4325 ADD_MESSAGE("But wait! Your %s glows briefly red and disappears and you seem to be in a better shape!", LifeSaver->CHAR_NAME(UNARTICLED));
4326 else if (CanBeSeenByPlayer())
4327 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());
4328 LifeSaver->RemoveFromSlot();
4329 LifeSaver->SendToHell();
4332 if (IsPlayer()) game::AskForEscPress(CONST_S("Life saved!"));
4334 RestoreBodyParts();
4335 ResetSpoiling();
4336 RestoreHP();
4337 RestoreStamina();
4338 ResetStates();
4340 if (GetNP() < SATIATED_LEVEL) SetNP(SATIATED_LEVEL);
4342 SendNewDrawRequest();
4344 if (GetAction()) GetAction()->Terminate(false);
4348 character *character::PolymorphRandomly (int MinDanger, int MaxDanger, int Time) {
4349 character *NewForm = 0;
4350 if (StateIsActivated(POLYMORPH_CONTROL)) {
4351 if (IsPlayer()) {
4352 if (!GetNewFormForPolymorphWithControl(NewForm)) return NewForm;
4353 } else {
4354 NewForm = protosystem::CreateMonster(MinDanger*10, MaxDanger*10, NO_EQUIPMENT);
4356 } else {
4357 NewForm = protosystem::CreateMonster(MinDanger, MaxDanger, NO_EQUIPMENT);
4359 Polymorph(NewForm, Time);
4360 return NewForm;
4364 /* In reality, the reading takes Time / (Intelligence * 10) turns */
4365 void character::StartReading (item *Item, sLong Time) {
4366 study *Read = study::Spawn(this);
4367 Read->SetLiteratureID(Item->GetID());
4368 if (game::WizardModeIsActive()) Time = 1;
4369 Read->SetCounter(Time);
4370 SetAction(Read);
4371 if (IsPlayer()) ADD_MESSAGE("You start reading %s.", Item->CHAR_NAME(DEFINITE));
4372 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s starts reading %s.", CHAR_NAME(DEFINITE), Item->CHAR_NAME(DEFINITE));
4376 /* Call when one makes something with his/her/its hands.
4377 * Difficulty of 5 takes about one turn, so it's the most common to use. */
4378 void character::DexterityAction (int Difficulty) {
4379 EditAP(-20000 * Difficulty / APBonus(GetAttribute(DEXTERITY)));
4380 EditExperience(DEXTERITY, Difficulty * 15, 1 << 7);
4384 /* If Theoretically != false, range is not a factor. */
4385 truth character::CanBeSeenByPlayer (truth Theoretically, truth IgnoreESP) const {
4386 if (IsEnabled() && !game::IsGenerating() && (Theoretically || GetSquareUnder())) {
4387 truth MayBeESPSeen = PLAYER->IsEnabled() && !IgnoreESP && PLAYER->StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5;
4388 truth MayBeInfraSeen = PLAYER->IsEnabled() && PLAYER->StateIsActivated(INFRA_VISION) && IsWarm();
4389 truth Visible = !StateIsActivated(INVISIBLE) || MayBeESPSeen || MayBeInfraSeen;
4390 if (game::IsInWilderness()) return Visible;
4391 if (MayBeESPSeen && (Theoretically || GetDistanceSquareFrom(PLAYER) <= PLAYER->GetESPRangeSquare())) return true;
4392 if (!Visible) return false;
4393 return Theoretically || SquareUnderCanBeSeenByPlayer(MayBeInfraSeen);
4395 return false;
4399 truth character::CanBeSeenBy (ccharacter *Who, truth Theoretically, truth IgnoreESP) const {
4400 if (Who->IsPlayer()) return CanBeSeenByPlayer(Theoretically, IgnoreESP);
4401 if (IsEnabled() && !game::IsGenerating() && (Theoretically || GetSquareUnder())) {
4402 truth MayBeESPSeen = Who->IsEnabled() && !IgnoreESP && Who->StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5;
4403 truth MayBeInfraSeen = Who->IsEnabled() && Who->StateIsActivated(INFRA_VISION) && IsWarm();
4404 truth Visible = !StateIsActivated(INVISIBLE) || MayBeESPSeen || MayBeInfraSeen;
4405 if (game::IsInWilderness()) return Visible;
4406 if (MayBeESPSeen && (Theoretically || GetDistanceSquareFrom(Who) <= Who->GetESPRangeSquare())) return true;
4407 if (!Visible) return false;
4408 return Theoretically || SquareUnderCanBeSeenBy(Who, MayBeInfraSeen);
4410 return false;
4414 truth character::SquareUnderCanBeSeenByPlayer (truth IgnoreDarkness) const {
4415 if (!GetSquareUnder()) return false;
4416 int S1 = SquaresUnder, S2 = PLAYER->SquaresUnder;
4417 if (S1 == 1 && S2 == 1) {
4418 if (GetSquareUnder()->CanBeSeenByPlayer(IgnoreDarkness)) return true;
4419 if (IgnoreDarkness) {
4420 int LOSRangeSquare = PLAYER->GetLOSRangeSquare();
4421 if ((GetPos() - PLAYER->GetPos()).GetLengthSquare() <= LOSRangeSquare) {
4422 eyecontroller::Map = GetLevel()->GetMap();
4423 return mapmath<eyecontroller>::DoLine(PLAYER->GetPos().X, PLAYER->GetPos().Y, GetPos().X, GetPos().Y, SKIP_FIRST);
4426 return false;
4427 } else {
4428 for (int c1 = 0; c1 < S1; ++c1) {
4429 lsquare *Square = GetLSquareUnder(c1);
4430 if (Square->CanBeSeenByPlayer(IgnoreDarkness)) return true;
4431 else if (IgnoreDarkness) {
4432 v2 Pos = Square->GetPos();
4433 int LOSRangeSquare = PLAYER->GetLOSRangeSquare();
4434 for (int c2 = 0; c2 < S2; ++c2) {
4435 v2 PlayerPos = PLAYER->GetPos(c2);
4436 if ((Pos-PlayerPos).GetLengthSquare() <= LOSRangeSquare) {
4437 eyecontroller::Map = GetLevel()->GetMap();
4438 if (mapmath<eyecontroller>::DoLine(PlayerPos.X, PlayerPos.Y, Pos.X, Pos.Y, SKIP_FIRST)) return true;
4443 return false;
4448 truth character::SquareUnderCanBeSeenBy (ccharacter *Who, truth IgnoreDarkness) const {
4449 int S1 = SquaresUnder, S2 = Who->SquaresUnder;
4450 int LOSRangeSquare = Who->GetLOSRangeSquare();
4451 if (S1 == 1 && S2 == 1) return GetSquareUnder()->CanBeSeenFrom(Who->GetPos(), LOSRangeSquare, IgnoreDarkness);
4452 for (int c1 = 0; c1 < S1; ++c1) {
4453 lsquare *Square = GetLSquareUnder(c1);
4454 for (int c2 = 0; c2 < S2; ++c2) if (Square->CanBeSeenFrom(Who->GetPos(c2), LOSRangeSquare, IgnoreDarkness)) return true;
4456 return false;
4460 int character::GetDistanceSquareFrom (ccharacter *Who) const {
4461 int S1 = SquaresUnder, S2 = Who->SquaresUnder;
4462 if (S1 == 1 && S2 == 1) return (GetPos() - Who->GetPos()).GetLengthSquare();
4463 v2 MinDist(0x7FFF, 0x7FFF);
4464 int MinLength = 0xFFFF;
4465 for (int c1 = 0; c1 < S1; ++c1) {
4466 for (int c2 = 0; c2 < S2; ++c2) {
4467 v2 Dist = GetPos(c1)-Who->GetPos(c2);
4468 if (Dist.X < 0) Dist.X = -Dist.X;
4469 if (Dist.Y < 0) Dist.Y = -Dist.Y;
4470 if (Dist.X <= MinDist.X && Dist.Y <= MinDist.Y) {
4471 MinDist = Dist;
4472 MinLength = Dist.GetLengthSquare();
4473 } else if (Dist.X < MinDist.X || Dist.Y < MinDist.Y) {
4474 int Length = Dist.GetLengthSquare();
4475 if (Length < MinLength) {
4476 MinDist = Dist;
4477 MinLength = Length;
4482 return MinLength;
4486 void character::AttachBodyPart (bodypart *BodyPart) {
4487 SetBodyPart(BodyPart->GetBodyPartIndex(), BodyPart);
4488 if (!AllowSpoil()) BodyPart->ResetSpoiling();
4489 BodyPart->ResetPosition();
4490 BodyPart->UpdatePictures();
4491 CalculateAttributeBonuses();
4492 CalculateBattleInfo();
4493 SendNewDrawRequest();
4494 SignalPossibleTransparencyChange();
4498 /* Returns true if the character has all bodyparts, false if not. */
4499 truth character::HasAllBodyParts () const {
4500 for (int c = 0; c < BodyParts; ++c) if (!GetBodyPart(c) && CanCreateBodyPart(c)) return false;
4501 return true;
4505 bodypart *character::GenerateRandomBodyPart () {
4506 int NeededBodyPart[MAX_BODYPARTS];
4507 int Index = 0;
4508 for (int c = 0; c < BodyParts; ++c) if (!GetBodyPart(c) && CanCreateBodyPart(c)) NeededBodyPart[Index++] = c;
4509 return Index ? CreateBodyPart(NeededBodyPart[RAND() % Index]) : 0;
4513 /* Searches the character's Stack and if it find some bodyparts there that are the character's
4514 * old bodyparts returns a stackiterator to one of them (choosen in random).
4515 * If no fitting bodyparts are found the function returns 0 */
4516 bodypart *character::FindRandomOwnBodyPart (truth AllowNonLiving) const {
4517 itemvector LostAndFound;
4518 for (int c = 0; c < BodyParts; ++c) {
4519 if (!GetBodyPart(c)) {
4520 for (std::list<feuLong>::iterator i = OriginalBodyPartID[c].begin(); i != OriginalBodyPartID[c].end(); ++i) {
4521 bodypart *Found = static_cast<bodypart *>(SearchForItem(*i));
4522 if (Found && (AllowNonLiving || Found->CanRegenerate())) LostAndFound.push_back(Found);
4526 if (LostAndFound.empty()) return 0;
4527 return static_cast<bodypart *>(LostAndFound[RAND() % LostAndFound.size()]);
4531 void character::PrintBeginPoisonedMessage () const {
4532 if (IsPlayer()) ADD_MESSAGE("You seem to be very ill.");
4533 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks very ill.", CHAR_NAME(DEFINITE));
4537 void character::PrintEndPoisonedMessage () const {
4538 if (IsPlayer()) ADD_MESSAGE("You feel better again.");
4539 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks better.", CHAR_NAME(DEFINITE));
4543 void character::PoisonedHandler () {
4544 if (!(RAND() % 100)) VomitAtRandomDirection(500 + RAND_N(250));
4545 int Damage = 0;
4546 for (int Used = 0; Used < GetTemporaryStateCounter(POISONED); Used += 100) if (!(RAND() % 100)) ++Damage;
4547 if (Damage) {
4548 ReceiveDamage(0, Damage, POISON, ALL, 8, false, false, false, false);
4549 CheckDeath(CONST_S("died of acute poisoning"), 0);
4554 truth character::IsWarm () const {
4555 return combinebodypartpredicates()(this, &bodypart::IsWarm, 1);
4559 void character::BeginInvisibility () {
4560 UpdatePictures();
4561 SendNewDrawRequest();
4562 SignalPossibleTransparencyChange();
4566 void character::BeginInfraVision () {
4567 if (IsPlayer()) GetArea()->SendNewDrawRequest();
4571 void character::BeginESP () {
4572 if (IsPlayer()) GetArea()->SendNewDrawRequest();
4576 void character::EndInvisibility () {
4577 UpdatePictures();
4578 SendNewDrawRequest();
4579 SignalPossibleTransparencyChange();
4583 void character::EndInfraVision () {
4584 if (IsPlayer() && IsEnabled()) GetArea()->SendNewDrawRequest();
4588 void character::EndESP () {
4589 if (IsPlayer() && IsEnabled()) GetArea()->SendNewDrawRequest();
4593 void character::Draw (blitdata &BlitData) const {
4594 col24 L = BlitData.Luminance;
4595 if (PLAYER->IsEnabled() &&
4596 ((PLAYER->StateIsActivated(ESP) && GetAttribute(INTELLIGENCE) >= 5 &&
4597 (PLAYER->GetPos() - GetPos()).GetLengthSquare() <= PLAYER->GetESPRangeSquare()) ||
4598 (PLAYER->StateIsActivated(INFRA_VISION) && IsWarm())))
4599 BlitData.Luminance = ivanconfig::GetContrastLuminance();
4601 DrawBodyParts(BlitData);
4602 BlitData.Luminance = ivanconfig::GetContrastLuminance();
4603 BlitData.Src.Y = 16;
4604 cint SquareIndex = BlitData.CustomData & SQUARE_INDEX_MASK;
4606 if (GetTeam() == PLAYER->GetTeam() && !IsPlayer() && SquareIndex == GetTameSymbolSquareIndex()) {
4607 BlitData.Src.X = 32;
4608 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData);
4611 if (IsFlying() && SquareIndex == GetFlySymbolSquareIndex()) {
4612 BlitData.Src.X = 128;
4613 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData);
4616 if (IsSwimming() && SquareIndex == GetSwimmingSymbolSquareIndex()) {
4617 BlitData.Src.X = 240;
4618 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData);
4621 if (GetAction() && GetAction()->IsUnconsciousness() && SquareIndex == GetUnconsciousSymbolSquareIndex()) {
4622 BlitData.Src.X = 224;
4623 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData);
4626 BlitData.Src.X = BlitData.Src.Y = 0;
4627 BlitData.Luminance = L;
4631 void character::DrawBodyParts (blitdata &BlitData) const {
4632 GetTorso()->Draw(BlitData);
4636 void character::PrintBeginTeleportMessage () const {
4637 if (IsPlayer()) ADD_MESSAGE("You feel jumpy.");
4641 void character::PrintEndTeleportMessage () const {
4642 if (IsPlayer()) ADD_MESSAGE("You suddenly realize you've always preferred walking to jumping.");
4646 void character::PrintBeginDetectMessage () const {
4647 if (IsPlayer()) ADD_MESSAGE("You feel curious about your surroundings.");
4651 void character::PrintEndDetectMessage () const {
4652 if (IsPlayer()) ADD_MESSAGE("You decide to rely on your intuition from now on.");
4656 void character::TeleportHandler () {
4657 if (!(RAND() % 1500) && !game::IsInWilderness()) {
4658 if (IsPlayer()) ADD_MESSAGE("You feel an urgent spatial relocation is now appropriate.");
4659 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s disappears.", CHAR_NAME(DEFINITE));
4660 TeleportRandomly();
4665 void character::DetectHandler () {
4666 if (IsPlayer()) {
4667 //the AI can't be asked position questions! So only the player can hav this state really :/ a bit daft of me
4668 if (!(RAND()%3000) && !game::IsInWilderness()) {
4669 ADD_MESSAGE("Your mind wanders in search of something.");
4670 DoDetecting(); //in fact, who knows what would happen if a dark frog had the detecting state?
4676 void character::PrintBeginPolymorphMessage () const {
4677 if (IsPlayer()) ADD_MESSAGE("An unconfortable uncertainty of who you really are overwhelms you.");
4681 void character::PrintEndPolymorphMessage () const {
4682 if (IsPlayer()) ADD_MESSAGE("You feel you are you and no one else.");
4686 void character::PolymorphHandler () {
4687 if (!(RAND() % 1500)) PolymorphRandomly(1, 999999, 200 + RAND() % 800);
4690 void character::PrintBeginTeleportControlMessage () const {
4691 if (IsPlayer()) ADD_MESSAGE("You feel very controlled.");
4695 void character::PrintEndTeleportControlMessage () const {
4696 if (IsPlayer()) ADD_MESSAGE("You feel your control slipping.");
4700 void character::DisplayStethoscopeInfo (character *) const {
4701 felist Info(CONST_S("Information about ") + GetDescription(DEFINITE));
4702 AddSpecialStethoscopeInfo(Info);
4703 Info.AddEntry(CONST_S("Endurance: ") + GetAttribute(ENDURANCE), LIGHT_GRAY);
4704 Info.AddEntry(CONST_S("Perception: ") + GetAttribute(PERCEPTION), LIGHT_GRAY);
4705 Info.AddEntry(CONST_S("Intelligence: ") + GetAttribute(INTELLIGENCE), LIGHT_GRAY);
4706 Info.AddEntry(CONST_S("Wisdom: ") + GetAttribute(WISDOM), LIGHT_GRAY);
4707 //Info.AddEntry(CONST_S("Willpower: ") + GetAttribute(WILL_POWER), LIGHT_GRAY);
4708 Info.AddEntry(CONST_S("Charisma: ") + GetAttribute(CHARISMA), LIGHT_GRAY);
4709 Info.AddEntry(CONST_S("HP: ") + GetHP() + "/" + GetMaxHP(), IsInBadCondition() ? RED : LIGHT_GRAY);
4710 if (GetAction()) Info.AddEntry(festring(GetAction()->GetDescription()).CapitalizeCopy(), LIGHT_GRAY);
4711 for (int c = 0; c < STATES; ++c) {
4712 if (StateIsActivated(1 << c) && (1 << c != HASTE || !StateIsActivated(SLOW)) && (1 << c != SLOW || !StateIsActivated(HASTE)))
4713 Info.AddEntry(StateData[c].Description, LIGHT_GRAY);
4715 switch (GetTirednessState()) {
4716 case FAINTING: Info.AddEntry("Fainting", RED); break;
4717 case EXHAUSTED: Info.AddEntry("Exhausted", LIGHT_GRAY); break;
4719 game::SetStandardListAttributes(Info);
4720 Info.Draw();
4724 truth character::CanUseStethoscope (truth PrintReason) const {
4725 if (PrintReason) ADD_MESSAGE("This type of monster can't use a stethoscope.");
4726 return false;
4730 /* Effect used by at least Sophos.
4731 * NOTICE: Doesn't check for death! */
4732 void character::TeleportSomePartsAway (int NumberToTeleport) {
4733 for (int c = 0; c < NumberToTeleport; ++c) {
4734 int RandomBodyPart = GetRandomNonVitalBodyPart();
4735 if (RandomBodyPart == NONE_INDEX) {
4736 for (; c < NumberToTeleport; ++c) {
4737 GetTorso()->SetHP((GetTorso()->GetHP() << 2) / 5);
4738 sLong TorsosVolume = GetTorso()->GetMainMaterial()->GetVolume() / 10;
4739 if (!TorsosVolume) break;
4740 sLong Amount = (RAND() % TorsosVolume)+1;
4741 item *Lump = GetTorso()->GetMainMaterial()->CreateNaturalForm(Amount);
4742 GetTorso()->GetMainMaterial()->EditVolume(-Amount);
4743 Lump->MoveTo(GetNearLSquare(GetLevel()->GetRandomSquare())->GetStack());
4744 if (IsPlayer()) ADD_MESSAGE("Parts of you teleport away.");
4745 else if (CanBeSeenByPlayer()) ADD_MESSAGE("Parts of %s teleport away.", CHAR_NAME(DEFINITE));
4747 } else {
4748 item *SeveredBodyPart = SevereBodyPart(RandomBodyPart);
4749 if (SeveredBodyPart) {
4750 GetNearLSquare(GetLevel()->GetRandomSquare())->AddItem(SeveredBodyPart);
4751 SeveredBodyPart->DropEquipment();
4752 if (IsPlayer()) ADD_MESSAGE("Your %s teleports away.", GetBodyPartName(RandomBodyPart).CStr());
4753 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s teleports away.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart).CStr());
4754 } else {
4755 if (IsPlayer()) ADD_MESSAGE("Your %s disappears.", GetBodyPartName(RandomBodyPart).CStr());
4756 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s %s disappears.", GetPossessivePronoun().CStr(), GetBodyPartName(RandomBodyPart).CStr());
4763 /* Returns an index of a random bodypart that is not vital. If no non-vital bodypart is found returns NONE_INDEX */
4764 int character::GetRandomNonVitalBodyPart () const {
4765 int OKBodyPart[MAX_BODYPARTS];
4766 int OKBodyParts = 0;
4767 for (int c = 0; c < BodyParts; ++c) if (GetBodyPart(c) && !BodyPartIsVital(c)) OKBodyPart[OKBodyParts++] = c;
4768 return OKBodyParts ? OKBodyPart[RAND() % OKBodyParts] : NONE_INDEX;
4772 void character::CalculateVolumeAndWeight () {
4773 Volume = Stack->GetVolume();
4774 Weight = Stack->GetWeight();
4775 BodyVolume = 0;
4776 CarriedWeight = Weight;
4777 for (int c = 0; c < BodyParts; ++c) {
4778 bodypart *BodyPart = GetBodyPart(c);
4779 if (BodyPart) {
4780 BodyVolume += BodyPart->GetBodyPartVolume();
4781 Volume += BodyPart->GetVolume();
4782 CarriedWeight += BodyPart->GetCarriedWeight();
4783 Weight += BodyPart->GetWeight();
4789 void character::SignalVolumeAndWeightChange () {
4790 if (!IsInitializing()) {
4791 CalculateVolumeAndWeight();
4792 if (IsEnabled()) CalculateBurdenState();
4793 if (MotherEntity) MotherEntity->SignalVolumeAndWeightChange();
4798 void character::SignalEmitationIncrease (col24 EmitationUpdate) {
4799 if (game::CompareLights(EmitationUpdate, Emitation) > 0) {
4800 game::CombineLights(Emitation, EmitationUpdate);
4801 if (MotherEntity) MotherEntity->SignalEmitationIncrease(EmitationUpdate);
4802 else if (SquareUnder[0] && !game::IsInWilderness()) {
4803 for(int c = 0; c < GetSquaresUnder(); ++c) GetLSquareUnder()->SignalEmitationIncrease(EmitationUpdate);
4809 void character::SignalEmitationDecrease (col24 EmitationUpdate) {
4810 if (game::CompareLights(EmitationUpdate, Emitation) >= 0 && Emitation) {
4811 col24 Backup = Emitation;
4812 CalculateEmitation();
4813 if (Backup != Emitation) {
4814 if (MotherEntity) MotherEntity->SignalEmitationDecrease(EmitationUpdate);
4815 else if (SquareUnder[0] && !game::IsInWilderness()) {
4816 for (int c = 0; c < GetSquaresUnder(); ++c) GetLSquareUnder(c)->SignalEmitationDecrease(EmitationUpdate);
4823 void character::CalculateEmitation () {
4824 Emitation = GetBaseEmitation();
4825 for (int c = 0; c < BodyParts; ++c) {
4826 bodypart *BodyPart = GetBodyPart(c);
4827 if (BodyPart) game::CombineLights(Emitation, BodyPart->GetEmitation());
4829 game::CombineLights(Emitation, Stack->GetEmitation());
4833 void character::CalculateAll () {
4834 Flags |= C_INITIALIZING;
4835 CalculateAttributeBonuses();
4836 CalculateVolumeAndWeight();
4837 CalculateEmitation();
4838 CalculateBodyPartMaxHPs(0);
4839 CalculateMaxStamina();
4840 CalculateBurdenState();
4841 CalculateBattleInfo();
4842 Flags &= ~C_INITIALIZING;
4846 void character::CalculateHP () {
4847 HP = sumbodypartproperties()(this, &bodypart::GetHP);
4851 void character::CalculateMaxHP () {
4852 MaxHP = sumbodypartproperties()(this, &bodypart::GetMaxHP);
4856 void character::CalculateBodyPartMaxHPs (feuLong Flags) {
4857 doforbodypartswithparam<feuLong>()(this, &bodypart::CalculateMaxHP, Flags);
4858 CalculateMaxHP();
4859 CalculateHP();
4863 truth character::EditAttribute (int Identifier, int Value) {
4864 if (Identifier == ENDURANCE && UseMaterialAttributes()) return false;
4865 if (RawEditAttribute(BaseExperience[Identifier], Value)) {
4866 if (!IsInitializing()) {
4867 if (Identifier == LEG_STRENGTH) CalculateBurdenState();
4868 else if (Identifier == ENDURANCE) CalculateBodyPartMaxHPs();
4869 else if (IsPlayer() && Identifier == PERCEPTION) game::SendLOSUpdateRequest();
4870 else if (IsPlayerKind() && (Identifier == INTELLIGENCE || Identifier == WISDOM || Identifier == CHARISMA)) UpdatePictures();
4871 CalculateBattleInfo();
4873 return true;
4875 return false;
4879 truth character::ActivateRandomState (int Flags, int Time, sLong Seed) {
4880 femath::SaveSeed();
4881 if (Seed) femath::SetSeed(Seed);
4882 sLong ToBeActivated = GetRandomState(Flags|DUR_TEMPORARY);
4883 femath::LoadSeed();
4884 if (!ToBeActivated) return false;
4885 BeginTemporaryState(ToBeActivated, Time);
4886 return true;
4890 truth character::GainRandomIntrinsic (int Flags) {
4891 sLong ToBeActivated = GetRandomState(Flags|DUR_PERMANENT);
4892 if (!ToBeActivated) return false;
4893 GainIntrinsic(ToBeActivated);
4894 return true;
4898 /* Returns 0 if state not found */
4899 sLong character::GetRandomState (int Flags) const {
4900 sLong OKStates[STATES];
4901 int NumberOfOKStates = 0;
4902 for (int c = 0; c < STATES; ++c) {
4903 if (StateData[c].Flags & Flags & DUR_FLAGS && StateData[c].Flags & Flags & SRC_FLAGS) OKStates[NumberOfOKStates++] = 1 << c;
4905 return NumberOfOKStates ? OKStates[RAND() % NumberOfOKStates] : 0;
4909 int characterprototype::CreateSpecialConfigurations (characterdatabase **TempConfig, int Configs, int Level) {
4910 if (Level == 0 && TempConfig[0]->CreateDivineConfigurations) {
4911 Configs = databasecreator<character>::CreateDivineConfigurations(this, TempConfig, Configs);
4913 if (Level == 1 && TempConfig[0]->CreateUndeadConfigurations) {
4914 for (int c = 1; c < protocontainer<character>::GetSize(); ++c) {
4915 const character::prototype *Proto = protocontainer<character>::GetProto(c);
4916 const character::database *const *CharacterConfigData = Proto->GetConfigData();
4917 if (!CharacterConfigData) ABORT("No database entry for character <%s>!", Proto->GetClassID());
4918 const character::database*const* End = CharacterConfigData + Proto->GetConfigSize();
4919 for (++CharacterConfigData; CharacterConfigData != End; ++CharacterConfigData) {
4920 const character::database *CharacterDataBase = *CharacterConfigData;
4921 if (CharacterDataBase->UndeadVersions) {
4922 character::database* ConfigDataBase = new character::database(**TempConfig);
4923 ConfigDataBase->InitDefaults(this, (c << 8) | CharacterDataBase->Config);
4924 ConfigDataBase->PostFix << "of ";
4925 if (CharacterDataBase->Adjective.GetSize()) {
4926 if (CharacterDataBase->UsesLongAdjectiveArticle) ConfigDataBase->PostFix << "an ";
4927 else ConfigDataBase->PostFix << "a ";
4928 ConfigDataBase->PostFix << CharacterDataBase->Adjective << ' ';
4929 } else {
4930 if (CharacterDataBase->UsesLongArticle) ConfigDataBase->PostFix << "an ";
4931 else ConfigDataBase->PostFix << "a ";
4933 ConfigDataBase->PostFix << CharacterDataBase->NameSingular;
4934 if (CharacterDataBase->PostFix.GetSize()) ConfigDataBase->PostFix << ' ' << CharacterDataBase->PostFix;
4935 int P1 = TempConfig[0]->UndeadAttributeModifier;
4936 int P2 = TempConfig[0]->UndeadVolumeModifier;
4937 int c2;
4938 for (c2 = 0; c2 < ATTRIBUTES; ++c2) ConfigDataBase->*ExpPtr[c2] = CharacterDataBase->*ExpPtr[c2] * P1 / 100;
4939 for (c2 = 0; c2 < EQUIPMENT_DATAS; ++c2) ConfigDataBase->*EquipmentDataPtr[c2] = contentscript<item>();
4940 ConfigDataBase->DefaultIntelligence = 5;
4941 ConfigDataBase->DefaultWisdom = 5;
4942 ConfigDataBase->DefaultCharisma = 5;
4943 ConfigDataBase->TotalSize = CharacterDataBase->TotalSize;
4944 ConfigDataBase->Sex = CharacterDataBase->Sex;
4945 ConfigDataBase->AttributeBonus = CharacterDataBase->AttributeBonus;
4946 ConfigDataBase->TotalVolume = CharacterDataBase->TotalVolume * P2 / 100;
4947 if (TempConfig[0]->UndeadCopyMaterials) {
4948 ConfigDataBase->HeadBitmapPos = CharacterDataBase->HeadBitmapPos;
4949 ConfigDataBase->HairColor = CharacterDataBase->HairColor;
4950 ConfigDataBase->EyeColor = CharacterDataBase->EyeColor;
4951 ConfigDataBase->CapColor = CharacterDataBase->CapColor;
4952 ConfigDataBase->FleshMaterial = CharacterDataBase->FleshMaterial;
4953 ConfigDataBase->BloodMaterial = CharacterDataBase->BloodMaterial;
4954 ConfigDataBase->VomitMaterial = CharacterDataBase->VomitMaterial;
4955 ConfigDataBase->SweatMaterial = CharacterDataBase->SweatMaterial;
4957 ConfigDataBase->KnownCWeaponSkills = CharacterDataBase->KnownCWeaponSkills;
4958 ConfigDataBase->CWeaponSkillHits = CharacterDataBase->CWeaponSkillHits;
4959 ConfigDataBase->PostProcess();
4960 TempConfig[Configs++] = ConfigDataBase;
4965 if (Level == 0 && TempConfig[0]->CreateGolemMaterialConfigurations) {
4966 for (int c = 1; c < protocontainer<material>::GetSize(); ++c) {
4967 const material::prototype* Proto = protocontainer<material>::GetProto(c);
4968 const material::database*const* MaterialConfigData = Proto->GetConfigData();
4969 const material::database*const* End = MaterialConfigData + Proto->GetConfigSize();
4970 for (++MaterialConfigData; MaterialConfigData != End; ++MaterialConfigData) {
4971 const material::database* MaterialDataBase = *MaterialConfigData;
4972 if (MaterialDataBase->CategoryFlags & IS_GOLEM_MATERIAL) {
4973 character::database* ConfigDataBase = new character::database(**TempConfig);
4974 ConfigDataBase->InitDefaults(this, MaterialDataBase->Config);
4975 ConfigDataBase->Adjective = MaterialDataBase->NameStem;
4976 ConfigDataBase->UsesLongAdjectiveArticle = MaterialDataBase->NameFlags & USE_AN;
4977 ConfigDataBase->AttachedGod = MaterialDataBase->AttachedGod;
4978 TempConfig[Configs++] = ConfigDataBase;
4983 return Configs;
4987 double character::GetTimeToDie (ccharacter *Enemy, int Damage, double ToHitValue, truth AttackIsBlockable, truth UseMaxHP) const {
4988 double DodgeValue = GetDodgeValue();
4989 if (!Enemy->CanBeSeenBy(this, true)) ToHitValue *= 2;
4990 if (!CanBeSeenBy(Enemy, true)) DodgeValue *= 2;
4991 double MinHits = 1000;
4992 truth First = true;
4993 for (int c = 0; c < BodyParts; ++c) {
4994 if (BodyPartIsVital(c) && GetBodyPart(c)) {
4995 double Hits = GetBodyPart(c)->GetTimeToDie(Damage, ToHitValue, DodgeValue, AttackIsBlockable, UseMaxHP);
4996 if (First) { MinHits = Hits; First = false; } else MinHits = 1/(1/MinHits+1/Hits);
4999 return MinHits;
5003 double character::GetRelativeDanger (ccharacter *Enemy, truth UseMaxHP) const {
5004 double Danger = Enemy->GetTimeToKill(this, UseMaxHP)/GetTimeToKill(Enemy, UseMaxHP);
5005 int EnemyAP = Enemy->GetMoveAPRequirement(1);
5006 int ThisAP = GetMoveAPRequirement(1);
5007 if (EnemyAP > ThisAP) Danger *= 1.25;
5008 else if (ThisAP > EnemyAP) Danger *= 0.80;
5009 if (!Enemy->CanBeSeenBy(this, true)) Danger *= Enemy->IsPlayer() ? 0.2 : 0.5;
5010 if (!CanBeSeenBy(Enemy, true)) Danger *= IsPlayer() ? 5.0 : 2.0;
5011 if (GetAttribute(INTELLIGENCE) < 10 && !IsPlayer()) Danger *= 0.80;
5012 if (Enemy->GetAttribute(INTELLIGENCE) < 10 && !Enemy->IsPlayer()) Danger *= 1.25;
5013 return Limit(Danger, 0.001, 1000.0);
5017 festring character::GetBodyPartName (int I, truth Articled) const {
5018 if (I == TORSO_INDEX) return Articled ? CONST_S("a torso") : CONST_S("torso");
5019 ABORT("Illegal character bodypart name request!");
5020 return "";
5024 item *character::SearchForItem(feuLong ID) const {
5025 item *Equipment = findequipment<feuLong>()(this, &item::HasID, ID);
5026 if (Equipment) return Equipment;
5027 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) if (i->GetID() == ID) return *i;
5028 return 0;
5032 truth character::ContentsCanBeSeenBy (ccharacter *Viewer) const {
5033 return Viewer == this;
5037 truth character::HitEffect (character *Enemy, item* Weapon, v2 HitPos, int Type, int BodyPartIndex,
5038 int Direction, truth BlockedByArmour)
5040 if (Weapon) return Weapon->HitEffect(this, Enemy, HitPos, BodyPartIndex, Direction, BlockedByArmour);
5041 switch (Type) {
5042 case UNARMED_ATTACK: return Enemy->SpecialUnarmedEffect(this, HitPos, BodyPartIndex, Direction, BlockedByArmour);
5043 case KICK_ATTACK: return Enemy->SpecialKickEffect(this, HitPos, BodyPartIndex, Direction, BlockedByArmour);
5044 case BITE_ATTACK: return Enemy->SpecialBiteEffect(this, HitPos, BodyPartIndex, Direction, BlockedByArmour);
5046 return false;
5050 void character::WeaponSkillHit (item *Weapon, int Type, int Hits) {
5051 int Category;
5052 switch (Type) {
5053 case UNARMED_ATTACK: Category = UNARMED; break;
5054 case WEAPON_ATTACK: Weapon->WeaponSkillHit(Hits); return;
5055 case KICK_ATTACK: Category = KICK; break;
5056 case BITE_ATTACK: Category = BITE; break;
5057 case THROW_ATTACK:
5058 if (!IsHumanoid()) return;
5059 Category = Weapon->GetWeaponCategory();
5060 break;
5061 default:
5062 ABORT("Illegal Type %d passed to character::WeaponSkillHit()!", Type);
5063 return;
5065 if (GetCWeaponSkill(Category)->AddHit(Hits)) {
5066 CalculateBattleInfo();
5067 if (IsPlayer()) GetCWeaponSkill(Category)->AddLevelUpMessage(Category);
5072 /* Returns 0 if character cannot be duplicated */
5073 character *character::Duplicate (feuLong Flags) {
5074 if (!(Flags & IGNORE_PROHIBITIONS) && !CanBeCloned()) return 0;
5075 character *Char = GetProtoType()->Clone(this);
5076 if (Flags & MIRROR_IMAGE) {
5077 DuplicateEquipment(Char, Flags & ~IGNORE_PROHIBITIONS);
5078 Char->SetLifeExpectancy(Flags >> LE_BASE_SHIFT & LE_BASE_RANGE, Flags >> LE_RAND_SHIFT & LE_RAND_RANGE);
5080 Char->CalculateAll();
5081 Char->CalculateEmitation();
5082 Char->UpdatePictures();
5083 Char->Flags &= ~(C_INITIALIZING|C_IN_NO_MSG_MODE);
5084 return Char;
5088 truth character::TryToEquip (item *Item) {
5089 if (!Item->AllowEquip() || !CanUseEquipment() || GetAttribute(WISDOM) >= Item->GetWearWisdomLimit() || Item->GetSquaresUnder() != 1)
5090 return false;
5091 for (int e = 0; e < GetEquipments(); ++e) {
5092 if (GetBodyPartOfEquipment(e) && EquipmentIsAllowed(e)) {
5093 sorter Sorter = EquipmentSorter(e);
5094 if ((Sorter == 0 || (Item->*Sorter)(this)) &&
5095 ((e != RIGHT_WIELDED_INDEX && e != LEFT_WIELDED_INDEX) ||
5096 Item->IsWeapon(this) || Item->IsShield(this)) && AllowEquipment(Item, e)) {
5097 item *OldEquipment = GetEquipment(e);
5098 if (BoundToUse(OldEquipment, e)) continue;
5099 lsquare *LSquareUnder = GetLSquareUnder();
5100 stack *StackUnder = LSquareUnder->GetStack();
5101 msgsystem::DisableMessages();
5102 Flags |= C_PICTURE_UPDATES_FORBIDDEN;
5103 LSquareUnder->Freeze();
5104 StackUnder->Freeze();
5105 double Danger = GetRelativeDanger(PLAYER);
5106 if (OldEquipment) OldEquipment->RemoveFromSlot();
5107 Item->RemoveFromSlot();
5108 SetEquipment(e, Item);
5109 double NewDanger = GetRelativeDanger(PLAYER);
5110 Item->RemoveFromSlot();
5111 StackUnder->AddItem(Item);
5112 if (OldEquipment) SetEquipment(e, OldEquipment);
5113 msgsystem::EnableMessages();
5114 Flags &= ~C_PICTURE_UPDATES_FORBIDDEN;
5115 LSquareUnder->UnFreeze();
5116 StackUnder->UnFreeze();
5117 if (OldEquipment) {
5118 if (NewDanger > Danger || BoundToUse(Item, e)) {
5119 room *Room = GetRoom();
5120 if (!Room || Room->PickupItem(this, Item, 1)) {
5121 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));
5122 if (Room) Room->DropItem(this, OldEquipment, 1);
5123 OldEquipment->MoveTo(StackUnder);
5124 Item->RemoveFromSlot();
5125 SetEquipment(e, Item);
5126 DexterityAction(5);
5127 return true;
5130 } else {
5131 if (NewDanger > Danger || (NewDanger == Danger && e != RIGHT_WIELDED_INDEX && e != LEFT_WIELDED_INDEX) || BoundToUse(Item, e)) {
5132 room *Room = GetRoom();
5133 if (!Room || Room->PickupItem(this, Item, 1)) {
5134 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s picks up and equips %s.", CHAR_NAME(DEFINITE), Item->CHAR_NAME(INDEFINITE));
5135 Item->RemoveFromSlot();
5136 SetEquipment(e, Item);
5137 DexterityAction(5);
5138 return true;
5145 return false;
5149 truth character::TryToConsume (item *Item) {
5150 return Item->CanBeEatenByAI(this) && ConsumeItem(Item, Item->GetConsumeMaterial(this)->GetConsumeVerb());
5154 void character::UpdateESPLOS () const {
5155 if (StateIsActivated(ESP) && !game::IsInWilderness()) {
5156 for (int c = 0; c < game::GetTeams(); ++c) {
5157 for (std::list<character *>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i) {
5158 const character *ch = *i;
5159 if (ch->IsEnabled()) ch->SendNewDrawRequest();
5166 int character::GetCWeaponSkillLevel (citem *Item) const {
5167 if (Item->GetWeaponCategory() < GetAllowedWeaponSkillCategories()) return GetCWeaponSkill(Item->GetWeaponCategory())->GetLevel();
5168 return 0;
5172 void character::PrintBeginPanicMessage () const {
5173 if (IsPlayer()) ADD_MESSAGE("You panic!");
5174 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s panics.", CHAR_NAME(DEFINITE));
5178 void character::PrintEndPanicMessage () const {
5179 if (IsPlayer()) ADD_MESSAGE("You finally calm down.");
5180 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s calms down.", CHAR_NAME(DEFINITE));
5184 void character::CheckPanic (int Ticks) {
5185 if (GetPanicLevel() > 1 && !StateIsActivated(PANIC) && GetHP() * 100 < RAND() % (GetPanicLevel() * GetMaxHP() << 1))
5186 BeginTemporaryState(PANIC, ((Ticks * 3) >> 2) + RAND() % ((Ticks >> 1) + 1)); // 25% randomness to ticks...
5190 /* returns 0 if fails else the newly created character */
5191 character *character::DuplicateToNearestSquare (character *Cloner, feuLong Flags) {
5192 character *NewlyCreated = Duplicate(Flags);
5193 if (!NewlyCreated) return 0;
5194 if (Flags & CHANGE_TEAM && Cloner) NewlyCreated->ChangeTeam(Cloner->GetTeam());
5195 NewlyCreated->PutNear(GetPos());
5196 return NewlyCreated;
5200 void character::SignalSpoil (material *m) {
5201 if (GetMotherEntity()) GetMotherEntity()->SignalSpoil(m);
5202 else Disappear(0, "spoil", &item::IsVeryCloseToSpoiling);
5206 truth character::CanHeal () const {
5207 for (int c = 0; c < BodyParts; ++c) {
5208 bodypart *BodyPart = GetBodyPart(c);
5209 if (BodyPart && BodyPart->CanRegenerate() && BodyPart->GetHP() < BodyPart->GetMaxHP()) return true;
5211 return false;
5215 int character::GetRelation (ccharacter *Who) const {
5216 return GetTeam()->GetRelation(Who->GetTeam());
5220 truth (item::*AffectTest[BASE_ATTRIBUTES])() const = {
5221 &item::AffectsEndurance,
5222 &item::AffectsPerception,
5223 &item::AffectsIntelligence,
5224 &item::AffectsWisdom,
5225 &item::AffectsWillPower,
5226 &item::AffectsCharisma,
5227 &item::AffectsMana
5231 /* Returns nonzero if endurance has decreased and death may occur */
5232 truth character::CalculateAttributeBonuses () {
5233 doforbodyparts()(this, &bodypart::CalculateAttributeBonuses);
5234 int BackupBonus[BASE_ATTRIBUTES];
5235 int BackupCarryingBonus = CarryingBonus;
5236 CarryingBonus = 0;
5237 int c1;
5238 for (c1 = 0; c1 < BASE_ATTRIBUTES; ++c1) {
5239 BackupBonus[c1] = AttributeBonus[c1];
5240 AttributeBonus[c1] = 0;
5242 for (c1 = 0; c1 < GetEquipments(); ++c1) {
5243 item *Equipment = GetEquipment(c1);
5244 if (!Equipment || !Equipment->IsInCorrectSlot(c1)) continue;
5245 for (int c2 = 0; c2 < BASE_ATTRIBUTES; ++c2) {
5246 if ((Equipment->*AffectTest[c2])()) AttributeBonus[c2] += Equipment->GetEnchantment();
5248 if (Equipment->AffectsCarryingCapacity()) CarryingBonus += Equipment->GetCarryingBonus();
5251 ApplySpecialAttributeBonuses();
5253 if (IsPlayer() && !IsInitializing() && AttributeBonus[PERCEPTION] != BackupBonus[PERCEPTION]) game::SendLOSUpdateRequest();
5254 if (IsPlayer() && !IsInitializing() && AttributeBonus[INTELLIGENCE] != BackupBonus[INTELLIGENCE]) UpdateESPLOS();
5256 if (!IsInitializing() && CarryingBonus != BackupCarryingBonus) CalculateBurdenState();
5258 if (!IsInitializing() && AttributeBonus[ENDURANCE] != BackupBonus[ENDURANCE]) {
5259 CalculateBodyPartMaxHPs();
5260 CalculateMaxStamina();
5261 return AttributeBonus[ENDURANCE] < BackupBonus[ENDURANCE];
5264 return false;
5268 void character::ApplyEquipmentAttributeBonuses (item *Equipment) {
5269 if (Equipment->AffectsEndurance()) {
5270 AttributeBonus[ENDURANCE] += Equipment->GetEnchantment();
5271 CalculateBodyPartMaxHPs();
5272 CalculateMaxStamina();
5274 if (Equipment->AffectsPerception()) {
5275 AttributeBonus[PERCEPTION] += Equipment->GetEnchantment();
5276 if (IsPlayer()) game::SendLOSUpdateRequest();
5278 if (Equipment->AffectsIntelligence()) {
5279 AttributeBonus[INTELLIGENCE] += Equipment->GetEnchantment();
5280 if (IsPlayer()) UpdateESPLOS();
5282 if (Equipment->AffectsWisdom()) AttributeBonus[WISDOM] += Equipment->GetEnchantment();
5283 if (Equipment->AffectsWillPower()) AttributeBonus[WILL_POWER] += Equipment->GetEnchantment();
5284 if (Equipment->AffectsCharisma()) AttributeBonus[CHARISMA] += Equipment->GetEnchantment();
5285 if (Equipment->AffectsMana()) AttributeBonus[MANA] += Equipment->GetEnchantment();
5286 if (Equipment->AffectsCarryingCapacity()) {
5287 CarryingBonus += Equipment->GetCarryingBonus();
5288 CalculateBurdenState();
5293 void character::ReceiveAntidote (sLong Amount) {
5294 if (StateIsActivated(POISONED)) {
5295 if (GetTemporaryStateCounter(POISONED) > Amount) {
5296 EditTemporaryStateCounter(POISONED, -Amount);
5297 Amount = 0;
5298 } else {
5299 if (IsPlayer()) ADD_MESSAGE("Aaaah... You feel much better.");
5300 Amount -= GetTemporaryStateCounter(POISONED);
5301 DeActivateTemporaryState(POISONED);
5304 if ((Amount >= 100 || RAND_N(100) < Amount) && StateIsActivated(PARASITIZED)) {
5305 if (IsPlayer()) ADD_MESSAGE("Something in your belly didn't seem to like this stuff.");
5306 DeActivateTemporaryState(PARASITIZED);
5307 Amount -= Min(100, Amount);
5309 if ((Amount >= 100 || RAND_N(100) < Amount) && StateIsActivated(LEPROSY)) {
5310 if (IsPlayer()) ADD_MESSAGE("You are not falling to pieces anymore.");
5311 DeActivateTemporaryState(LEPROSY);
5312 Amount -= Min(100, Amount);
5317 void character::AddAntidoteConsumeEndMessage () const {
5318 if (StateIsActivated(POISONED)) {
5319 // true only if the antidote didn't cure the poison completely
5320 if (IsPlayer()) ADD_MESSAGE("Your body processes the poison in your veins with rapid speed.");
5325 truth character::IsDead () const {
5326 for (int c = 0; c < BodyParts; ++c) {
5327 bodypart *BodyPart = GetBodyPart(c);
5328 if (BodyPartIsVital(c) && (!BodyPart || BodyPart->GetHP() < 1)) return true;
5330 return false;
5334 void character::SignalSpoilLevelChange (material *m) {
5335 if (GetMotherEntity()) GetMotherEntity()->SignalSpoilLevelChange(m); else UpdatePictures();
5339 void character::AddOriginalBodyPartID (int I, feuLong What) {
5340 if (std::find(OriginalBodyPartID[I].begin(), OriginalBodyPartID[I].end(), What) == OriginalBodyPartID[I].end()) {
5341 OriginalBodyPartID[I].push_back(What);
5342 if (OriginalBodyPartID[I].size() > 100) OriginalBodyPartID[I].erase(OriginalBodyPartID[I].begin());
5347 void character::AddToInventory (const fearray<contentscript<item> > &ItemArray, int SpecialFlags) {
5348 for (uInt c1 = 0; c1 < ItemArray.Size; ++c1) {
5349 if (ItemArray[c1].IsValid()) {
5350 const interval *TimesPtr = ItemArray[c1].GetTimes();
5351 int Times = TimesPtr ? TimesPtr->Randomize() : 1;
5352 for (int c2 = 0; c2 < Times; ++c2) {
5353 item *Item = ItemArray[c1].Instantiate(SpecialFlags);
5354 if (Item) {
5355 Stack->AddItem(Item);
5356 Item->SpecialGenerationHandler();
5364 truth character::HasHadBodyPart (citem *Item) const {
5365 for (int c = 0; c < BodyParts; ++c)
5366 if (std::find(OriginalBodyPartID[c].begin(), OriginalBodyPartID[c].end(), Item->GetID()) != OriginalBodyPartID[c].end())
5367 return true;
5368 return GetPolymorphBackup() && GetPolymorphBackup()->HasHadBodyPart(Item);
5372 festring &character::ProcessMessage (festring &Msg) const {
5373 SEARCH_N_REPLACE(Msg, "@nu", GetName(UNARTICLED));
5374 SEARCH_N_REPLACE(Msg, "@ni", GetName(INDEFINITE));
5375 SEARCH_N_REPLACE(Msg, "@nd", GetName(DEFINITE));
5376 SEARCH_N_REPLACE(Msg, "@du", GetDescription(UNARTICLED));
5377 SEARCH_N_REPLACE(Msg, "@di", GetDescription(INDEFINITE));
5378 SEARCH_N_REPLACE(Msg, "@dd", GetDescription(DEFINITE));
5379 SEARCH_N_REPLACE(Msg, "@pp", GetPersonalPronoun());
5380 SEARCH_N_REPLACE(Msg, "@sp", GetPossessivePronoun());
5381 SEARCH_N_REPLACE(Msg, "@op", GetObjectPronoun());
5382 SEARCH_N_REPLACE(Msg, "@Nu", GetName(UNARTICLED).CapitalizeCopy());
5383 SEARCH_N_REPLACE(Msg, "@Ni", GetName(INDEFINITE).CapitalizeCopy());
5384 SEARCH_N_REPLACE(Msg, "@Nd", GetName(DEFINITE).CapitalizeCopy());
5385 SEARCH_N_REPLACE(Msg, "@Du", GetDescription(UNARTICLED).CapitalizeCopy());
5386 SEARCH_N_REPLACE(Msg, "@Di", GetDescription(INDEFINITE).CapitalizeCopy());
5387 SEARCH_N_REPLACE(Msg, "@Dd", GetDescription(DEFINITE).CapitalizeCopy());
5388 SEARCH_N_REPLACE(Msg, "@Pp", GetPersonalPronoun().CapitalizeCopy());
5389 SEARCH_N_REPLACE(Msg, "@Sp", GetPossessivePronoun().CapitalizeCopy());
5390 SEARCH_N_REPLACE(Msg, "@Op", GetObjectPronoun().CapitalizeCopy());
5391 SEARCH_N_REPLACE(Msg, "@Gd", GetMasterGod()->GetName());
5392 return Msg;
5396 void character::ProcessAndAddMessage (festring Msg) const {
5397 ADD_MESSAGE("%s", ProcessMessage(Msg).CStr());
5401 void character::BeTalkedTo () {
5402 static sLong Said;
5403 if (GetRelation(PLAYER) == HOSTILE)
5404 ProcessAndAddMessage(GetHostileReplies()[RandomizeReply(Said, GetHostileReplies().Size)]);
5405 else
5406 ProcessAndAddMessage(GetFriendlyReplies()[RandomizeReply(Said, GetFriendlyReplies().Size)]);
5410 truth character::CheckZap () {
5411 if (!CanZap()) {
5412 ADD_MESSAGE("This monster type can't zap.");
5413 return false;
5415 return true;
5419 void character::DamageAllItems (character *Damager, int Damage, int Type) {
5420 GetStack()->ReceiveDamage(Damager, Damage, Type);
5421 for (int c = 0; c < GetEquipments(); ++c) {
5422 item *Equipment = GetEquipment(c);
5423 if (Equipment) Equipment->ReceiveDamage(Damager, Damage, Type);
5428 truth character::Equips (citem *Item) const {
5429 return combineequipmentpredicateswithparam<feuLong>()(this, &item::HasID, Item->GetID(), 1);
5433 void character::PrintBeginConfuseMessage () const {
5434 if (IsPlayer()) ADD_MESSAGE("You feel quite happy.");
5438 void character::PrintEndConfuseMessage () const {
5439 if (IsPlayer()) ADD_MESSAGE("The world is boring again.");
5443 v2 character::ApplyStateModification (v2 TryDirection) const {
5444 if (!StateIsActivated(CONFUSED) || RAND() & 15 || game::IsInWilderness()) return TryDirection;
5445 v2 To = GetLevel()->GetFreeAdjacentSquare(this, GetPos(), true);
5446 if (To == ERROR_V2) return TryDirection;
5447 To -= GetPos();
5448 if (To != TryDirection && IsPlayer()) ADD_MESSAGE("Whoa! You somehow don't manage to walk straight.");
5449 return To;
5453 void character::AddConfuseHitMessage () const {
5454 if (IsPlayer()) ADD_MESSAGE("This stuff is confusing.");
5458 item *character::SelectFromPossessions (cfestring &Topic, sorter Sorter) {
5459 itemvector ReturnVector;
5460 SelectFromPossessions(ReturnVector, Topic, NO_MULTI_SELECT, Sorter);
5461 return !ReturnVector.empty() ? ReturnVector[0] : 0;
5465 truth character::SelectFromPossessions (itemvector &ReturnVector, cfestring &Topic, int Flags, sorter Sorter) {
5466 felist List(Topic);
5467 truth InventoryPossible = GetStack()->SortedItems(this, Sorter);
5468 if (InventoryPossible) List.AddEntry(CONST_S("choose from inventory"), LIGHT_GRAY, 20, game::AddToItemDrawVector(itemvector()));
5469 truth Any = false;
5470 itemvector Item;
5471 festring Entry;
5472 int c;
5473 for (c = 0; c < BodyParts; ++c) {
5474 bodypart *BodyPart = GetBodyPart(c);
5475 if (BodyPart && (Sorter == 0 || (BodyPart->*Sorter)(this))) {
5476 Item.push_back(BodyPart);
5477 Entry.Empty();
5478 BodyPart->AddName(Entry, UNARTICLED);
5479 int ImageKey = game::AddToItemDrawVector(itemvector(1, BodyPart));
5480 List.AddEntry(Entry, LIGHT_GRAY, 20, ImageKey, true);
5481 Any = true;
5484 for (c = 0; c < GetEquipments(); ++c) {
5485 item *Equipment = GetEquipment(c);
5486 if (Equipment && (Sorter == 0 || (Equipment->*Sorter)(this))) {
5487 Item.push_back(Equipment);
5488 Entry = GetEquipmentName(c);
5489 Entry << ':';
5490 Entry.Resize(20);
5491 Equipment->AddInventoryEntry(this, Entry, 1, true);
5492 AddSpecialEquipmentInfo(Entry, c);
5493 int ImageKey = game::AddToItemDrawVector(itemvector(1, Equipment));
5494 List.AddEntry(Entry, LIGHT_GRAY, 20, ImageKey, true);
5495 Any = true;
5498 if (Any) {
5499 game::SetStandardListAttributes(List);
5500 List.SetFlags(SELECTABLE|DRAW_BACKGROUND_AFTERWARDS);
5501 List.SetEntryDrawer(game::ItemEntryDrawer);
5502 game::DrawEverythingNoBlit();
5503 int Chosen = List.Draw();
5504 game::ClearItemDrawVector();
5505 if (Chosen != ESCAPED) {
5506 if ((InventoryPossible && !Chosen) || Chosen & FELIST_ERROR_BIT) {
5507 GetStack()->DrawContents(ReturnVector, this, Topic, Flags, Sorter);
5508 } else {
5509 ReturnVector.push_back(Item[InventoryPossible ? Chosen - 1 : Chosen]);
5510 if (Flags & SELECT_PAIR && ReturnVector[0]->HandleInPairs()) {
5511 item *PairEquipment = GetPairEquipment(ReturnVector[0]->GetEquipmentIndex());
5512 if (PairEquipment && PairEquipment->CanBePiledWith(ReturnVector[0], this)) ReturnVector.push_back(PairEquipment);
5516 } else {
5517 if (!GetStack()->SortedItems(this, Sorter)) return false;
5518 game::ClearItemDrawVector();
5519 GetStack()->DrawContents(ReturnVector, this, Topic, Flags, Sorter);
5521 return true;
5525 truth character::EquipsSomething (sorter Sorter) {
5526 for (int c = 0; c < GetEquipments(); ++c) {
5527 item *Equipment = GetEquipment(c);
5528 if (Equipment && (Sorter == 0 || (Equipment->*Sorter)(this))) return true;
5530 return false;
5534 material *character::CreateBodyPartMaterial (int, sLong Volume) const {
5535 return MAKE_MATERIAL(GetFleshMaterial(), Volume);
5539 truth character::CheckTalk () {
5540 if (!CanTalk()) {
5541 ADD_MESSAGE("This monster does not know the art of talking.");
5542 return false;
5544 return true;
5548 truth character::MoveTowardsHomePos () {
5549 if (HomeDataIsValid() && IsEnabled()) {
5550 SetGoingTo(HomeData->Pos);
5551 return MoveTowardsTarget(false) || (!GetPos().IsAdjacent(HomeData->Pos) && MoveRandomly());
5553 return false;
5557 truth character::TryToChangeEquipment (stack *MainStack, stack *SecStack, int Chosen) {
5558 if (!GetBodyPartOfEquipment(Chosen)) {
5559 ADD_MESSAGE("Bodypart missing!");
5560 return false;
5562 item *OldEquipment = GetEquipment(Chosen);
5563 if (!IsPlayer() && BoundToUse(OldEquipment, Chosen)) {
5564 ADD_MESSAGE("%s refuses to unequip %s.", CHAR_DESCRIPTION(DEFINITE), OldEquipment->CHAR_NAME(DEFINITE));
5565 return false;
5567 if (OldEquipment) OldEquipment->MoveTo(MainStack);
5568 sorter Sorter = EquipmentSorter(Chosen);
5569 if (!MainStack->SortedItems(this, Sorter) && (!SecStack || !SecStack->SortedItems(this, Sorter))) {
5570 ADD_MESSAGE("You haven't got any item that could be used for this purpose.");
5571 return false;
5573 game::DrawEverythingNoBlit();
5574 itemvector ItemVector;
5575 int Return = MainStack->DrawContents(ItemVector, SecStack, this,
5576 CONST_S("Choose ") + GetEquipmentName(Chosen) + ':',
5577 SecStack ? CONST_S("Items in your inventory") : CONST_S(""),
5578 SecStack ? festring(CONST_S("Items in ") + GetPossessivePronoun() + " inventory") : CONST_S(""),
5579 SecStack ? festring(GetDescription(DEFINITE) + " is " + GetVerbalBurdenState()) : CONST_S(""),
5580 GetVerbalBurdenStateColor(),
5581 NONE_AS_CHOICE|NO_MULTI_SELECT,
5582 Sorter);
5583 if (Return == ESCAPED) {
5584 if (OldEquipment) {
5585 OldEquipment->RemoveFromSlot();
5586 SetEquipment(Chosen, OldEquipment);
5588 return false;
5590 item *Item = ItemVector.empty() ? 0 : ItemVector[0];
5591 if (Item) {
5592 if (!IsPlayer() && !AllowEquipment(Item, Chosen)) {
5593 ADD_MESSAGE("%s refuses to equip %s.", CHAR_DESCRIPTION(DEFINITE), Item->CHAR_NAME(DEFINITE));
5594 return false;
5596 Item->RemoveFromSlot();
5597 SetEquipment(Chosen, Item);
5598 if (CheckIfEquipmentIsNotUsable(Chosen)) Item->MoveTo(MainStack); // small bug?
5600 return Item != OldEquipment;
5604 void character::PrintBeginParasitizedMessage () const {
5605 if (IsPlayer()) ADD_MESSAGE("You feel you are no longer alone.");
5609 void character::PrintEndParasitizedMessage () const {
5610 if (IsPlayer()) ADD_MESSAGE("A feeling of sLong welcome emptiness overwhelms you.");
5614 void character::ParasitizedHandler () {
5615 EditNP(-5);
5616 if (!(RAND() % 250)) {
5617 if (IsPlayer()) ADD_MESSAGE("Ugh. You feel something violently carving its way through your intestines.");
5618 ReceiveDamage(0, 1, POISON, TORSO, 8, false, false, false, false);
5619 CheckDeath(CONST_S("killed by a vile parasite"), 0);
5624 truth character::CanFollow () const {
5625 return CanMove() && !StateIsActivated(PANIC) && !IsStuck();
5629 festring character::GetKillName () const {
5630 if (!GetPolymorphBackup()) return GetName(INDEFINITE);
5631 festring KillName;
5632 GetPolymorphBackup()->AddName(KillName, INDEFINITE);
5633 KillName << " polymorphed into ";
5634 id::AddName(KillName, INDEFINITE);
5635 return KillName;
5639 festring character::GetPanelName () const {
5640 festring Name;
5641 Name << AssignedName << " the " << game::GetVerbalPlayerAlignment() << ' ';
5642 id::AddName(Name, UNARTICLED);
5643 return Name;
5647 sLong character::GetMoveAPRequirement (int Difficulty) const {
5648 return (!StateIsActivated(PANIC) ? 10000000 : 8000000) * Difficulty / (APBonus(GetAttribute(AGILITY)) * GetMoveEase());
5652 bodypart *character::HealHitPoint() {
5653 int NeedHeal = 0, NeedHealIndex[MAX_BODYPARTS];
5654 for (int c = 0; c < BodyParts; ++c) {
5655 bodypart *BodyPart = GetBodyPart(c);
5656 if (BodyPart && BodyPart->CanRegenerate() && BodyPart->GetHP() < BodyPart->GetMaxHP()) NeedHealIndex[NeedHeal++] = c;
5658 if (NeedHeal) {
5659 bodypart *BodyPart = GetBodyPart(NeedHealIndex[RAND() % NeedHeal]);
5660 BodyPart->IncreaseHP();
5661 ++HP;
5662 return BodyPart;
5664 return 0;
5668 void character::CreateHomeData () {
5669 HomeData = new homedata;
5670 lsquare *Square = GetLSquareUnder();
5671 HomeData->Pos = Square->GetPos();
5672 HomeData->Dungeon = Square->GetDungeonIndex();
5673 HomeData->Level = Square->GetLevelIndex();
5674 HomeData->Room = Square->GetRoomIndex();
5678 room *character::GetHomeRoom() const {
5679 if (HomeDataIsValid() && HomeData->Room) return GetLevel()->GetRoom(HomeData->Room);
5680 return 0;
5684 void character::RemoveHomeData () {
5685 delete HomeData;
5686 HomeData = 0;
5690 void character::AddESPConsumeMessage () const {
5691 if (IsPlayer()) ADD_MESSAGE("You feel a strange mental activity.");
5695 void character::SetBodyPart (int I, bodypart *What) {
5696 BodyPartSlot[I].PutInItem(What);
5697 if (What) {
5698 What->SignalPossibleUsabilityChange();
5699 What->Disable();
5700 AddOriginalBodyPartID(I, What->GetID());
5701 if (What->GetMainMaterial()->IsInfectedByLeprosy()) GainIntrinsic(LEPROSY);
5702 else if (StateIsActivated(LEPROSY)) What->GetMainMaterial()->SetIsInfectedByLeprosy(true);
5707 truth character::ConsumeItem (item *Item, cfestring &ConsumeVerb) {
5708 if (IsPlayer() && HasHadBodyPart(Item) && !game::TruthQuestion(CONST_S("Are you sure? You may be able to put it back... [y/N]")))
5709 return false;
5710 if (Item->IsOnGround() && GetRoom() && !GetRoom()->ConsumeItem(this, Item, 1))
5711 return false;
5712 if (IsPlayer()) ADD_MESSAGE("You begin %s %s.", ConsumeVerb.CStr(), Item->CHAR_NAME(DEFINITE));
5713 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s begins %s %s.", CHAR_NAME(DEFINITE), ConsumeVerb.CStr(), Item->CHAR_NAME(DEFINITE));
5714 consume *Consume = consume::Spawn(this);
5715 Consume->SetDescription(ConsumeVerb);
5716 Consume->SetConsumingID(Item->GetID());
5717 SetAction(Consume);
5718 DexterityAction(5);
5719 return true;
5723 truth character::CheckThrow () const {
5724 if (!CanThrow()) {
5725 ADD_MESSAGE("This monster type cannot throw.");
5726 return false;
5728 return true;
5732 void character::GetHitByExplosion (const explosion *Explosion, int Damage) {
5733 int DamageDirection = GetPos() == Explosion->Pos ? RANDOM_DIR : game::CalculateRoughDirection(GetPos() - Explosion->Pos);
5734 if (!IsPet() && Explosion->Terrorist && Explosion->Terrorist->IsPet()) Explosion->Terrorist->Hostility(this);
5735 GetTorso()->SpillBlood((8 - Explosion->Size + RAND() % (8 - Explosion->Size)) >> 1);
5736 v2 SpillPos = GetPos() + game::GetMoveVector(DamageDirection);
5737 if (SquareUnder[0] && GetArea()->IsValidPos(SpillPos)) GetTorso()->SpillBlood((8-Explosion->Size+RAND()%(8-Explosion->Size))>>1, SpillPos);
5738 if (IsPlayer()) ADD_MESSAGE("You are hit by the explosion!");
5739 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s is hit by the explosion.", CHAR_NAME(DEFINITE));
5740 truth WasUnconscious = GetAction() && GetAction()->IsUnconsciousness();
5741 ReceiveDamage(Explosion->Terrorist, Damage >> 1, FIRE, ALL, DamageDirection, true, false, false, false);
5742 if (IsEnabled()) {
5743 ReceiveDamage(Explosion->Terrorist, Damage >> 1, PHYSICAL_DAMAGE, ALL, DamageDirection, true, false, false, false);
5744 CheckDeath(Explosion->DeathMsg, Explosion->Terrorist, !WasUnconscious ? IGNORE_UNCONSCIOUSNESS : 0);
5749 void character::SortAllItems (const sortdata &SortData) {
5750 GetStack()->SortAllItems(SortData);
5751 doforequipmentswithparam<const sortdata&>()(this, &item::SortAllItems, SortData);
5755 void character::PrintBeginSearchingMessage () const {
5756 if (IsPlayer()) ADD_MESSAGE("You feel you can now notice even the very smallest details around you.");
5760 void character::PrintEndSearchingMessage () const {
5761 if (IsPlayer()) ADD_MESSAGE("You feel less perceptive.");
5765 void character::SearchingHandler () {
5766 if (!game::IsInWilderness()) Search(15);
5770 void character::Search (int Perception) {
5771 for (int d = 0; d < GetExtendedNeighbourSquares(); ++d) {
5772 lsquare *LSquare = GetNeighbourLSquare(d);
5773 if (LSquare) LSquare->GetStack()->Search(this, Min(Perception, 200));
5778 // surprisingly returns 0 if fails
5779 character *character::GetRandomNeighbour (int RelationFlags) const {
5780 character *Chars[MAX_NEIGHBOUR_SQUARES];
5781 int Index = 0;
5782 for (int d = 0; d < GetNeighbourSquares(); ++d) {
5783 lsquare *LSquare = GetNeighbourLSquare(d);
5784 if (LSquare) {
5785 character *Char = LSquare->GetCharacter();
5786 if (Char && (GetRelation(Char) & RelationFlags)) Chars[Index++] = Char;
5789 return Index ? Chars[RAND() % Index] : 0;
5793 void character::ResetStates () {
5794 for (int c = 0; c < STATES; ++c) {
5795 if (1 << c != POLYMORPHED && TemporaryStateIsActivated(1 << c) && TemporaryStateCounter[c] != PERMANENT) {
5796 TemporaryState &= ~(1 << c);
5797 if (StateData[c].EndHandler) {
5798 (this->*StateData[c].EndHandler)();
5799 if (!IsEnabled())return;
5806 void characterdatabase::InitDefaults (const characterprototype *NewProtoType, int NewConfig) {
5807 IsAbstract = false;
5808 ProtoType = NewProtoType;
5809 Config = NewConfig;
5810 Alias.Clear();
5814 void character::PrintBeginGasImmunityMessage () const {
5815 if (IsPlayer()) ADD_MESSAGE("All smells fade away.");
5819 void character::PrintEndGasImmunityMessage () const {
5820 if (IsPlayer()) ADD_MESSAGE("Yuck! The world smells bad again.");
5824 void character::ShowAdventureInfo () const {
5825 static const char *lists[4][4] = {
5826 { "Show massacre history",
5827 "Show inventory",
5828 "Show message history",
5829 NULL },
5830 { "Show inventory",
5831 "Show message history",
5832 NULL,
5833 NULL },
5834 { "Show message history",
5835 NULL,
5836 NULL,
5837 NULL },
5838 { "Show massacre history",
5839 "Show message history",
5840 NULL,
5841 NULL }
5843 // massacre, inventory, messages
5844 static const int nums[4][3] = {
5845 { 0, 1, 2},
5846 {-1, 0, 1},
5847 {-1,-1, 0},
5848 { 0,-1, 0}
5850 int idx = 0;
5851 if (GetStack()->GetItems()) {
5852 idx = game::MassacreListsEmpty() ? 1 : 0;
5853 } else {
5854 idx = game::MassacreListsEmpty() ? 2 : 3;
5856 int sel = -1;
5857 for (;;) {
5858 sel = game::ListSelectorArray(sel, CONST_S("Do you want to see some funny history?"), lists[idx]);
5859 if (sel < 0) break;
5860 if (sel == nums[idx][0] && !game::MassacreListsEmpty()) {
5861 game::DisplayMassacreLists();
5863 if (sel == nums[idx][1] && GetStack()->GetItems()) {
5864 GetStack()->DrawContents(this, CONST_S("Your inventory"), NO_SELECT);
5865 for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) i->DrawContents(this);
5866 doforequipmentswithparam<ccharacter *>()(this, &item::DrawContents, this);
5868 if (sel == nums[idx][2]) {
5869 msgsystem::DrawMessageHistory();
5875 truth character::EditAllAttributes (int Amount) {
5876 if (!Amount) return true;
5877 int c;
5878 truth MayEditMore = false;
5879 for (c = 0; c < BodyParts; ++c) {
5880 bodypart *BodyPart = GetBodyPart(c);
5881 if (BodyPart && BodyPart->EditAllAttributes(Amount)) MayEditMore = true;
5883 for (c = 0; c < BASE_ATTRIBUTES; ++c) {
5884 if (BaseExperience[c]) {
5885 BaseExperience[c] += Amount * EXP_MULTIPLIER;
5886 LimitRef(BaseExperience[c], MIN_EXP, MAX_EXP);
5887 if ((Amount < 0 && BaseExperience[c] != MIN_EXP) || (Amount > 0 && BaseExperience[c] != MAX_EXP)) MayEditMore = true;
5890 CalculateAll();
5891 RestoreHP();
5892 RestoreStamina();
5893 if (IsPlayer()) {
5894 game::SendLOSUpdateRequest();
5895 UpdateESPLOS();
5897 if (IsPlayerKind()) UpdatePictures();
5898 return MayEditMore;
5902 #ifdef WIZARD
5903 void character::AddAttributeInfo (festring &Entry) const {
5904 Entry.Resize(57);
5905 Entry << GetAttribute(ENDURANCE);
5906 Entry.Resize(60);
5907 Entry << GetAttribute(PERCEPTION);
5908 Entry.Resize(63);
5909 Entry << GetAttribute(INTELLIGENCE);
5910 Entry.Resize(66);
5911 Entry << GetAttribute(WISDOM);
5912 Entry.Resize(69);
5913 Entry << GetAttribute(CHARISMA);
5914 Entry.Resize(72);
5915 Entry << GetAttribute(MANA);
5919 void character::AddDefenceInfo (felist &List) const {
5920 festring Entry;
5921 for (int c = 0; c < BodyParts; ++c) {
5922 bodypart *BodyPart = GetBodyPart(c);
5923 if (BodyPart) {
5924 Entry = CONST_S(" ");
5925 BodyPart->AddName(Entry, UNARTICLED);
5926 Entry.Resize(60);
5927 Entry << BodyPart->GetMaxHP();
5928 Entry.Resize(70);
5929 Entry << BodyPart->GetTotalResistance(PHYSICAL_DAMAGE);
5930 List.AddEntry(Entry, LIGHT_GRAY);
5936 void character::DetachBodyPart () {
5937 ADD_MESSAGE("You haven't got any extra bodyparts.");
5939 #endif
5942 void character::ReceiveHolyBanana (sLong Amount) {
5943 Amount <<= 1;
5944 EditExperience(ARM_STRENGTH, Amount, 1 << 13);
5945 EditExperience(LEG_STRENGTH, Amount, 1 << 13);
5946 EditExperience(DEXTERITY, Amount, 1 << 13);
5947 EditExperience(AGILITY, Amount, 1 << 13);
5948 EditExperience(ENDURANCE, Amount, 1 << 13);
5949 EditExperience(PERCEPTION, Amount, 1 << 13);
5950 EditExperience(INTELLIGENCE, Amount, 1 << 13);
5951 EditExperience(WISDOM, Amount, 1 << 13);
5952 EditExperience(CHARISMA, Amount, 1 << 13);
5953 RestoreLivingHP();
5957 void character::AddHolyBananaConsumeEndMessage () const {
5958 if (IsPlayer()) ADD_MESSAGE("You feel a mysterious strengthening fire coursing through your body.");
5959 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s is surrounded by a swirling fire aura.", CHAR_NAME(DEFINITE));
5963 void character::ReceiveHolyMango (sLong Amount) {
5964 Amount <<= 1;
5965 EditExperience(ARM_STRENGTH, Amount, 1 << 13);
5966 EditExperience(LEG_STRENGTH, Amount, 1 << 13);
5967 EditExperience(DEXTERITY, Amount, 1 << 13);
5968 EditExperience(AGILITY, Amount, 1 << 13);
5969 EditExperience(ENDURANCE, Amount, 1 << 13);
5970 EditExperience(PERCEPTION, Amount, 1 << 13);
5971 EditExperience(INTELLIGENCE, Amount, 1 << 13);
5972 EditExperience(WISDOM, Amount, 1 << 13);
5973 EditExperience(CHARISMA, Amount, 1 << 13);
5974 RestoreLivingHP();
5978 void character::AddHolyMangoConsumeEndMessage () const {
5979 if (IsPlayer()) ADD_MESSAGE("You feel a mysterious strengthening fire coursing through your body.");
5980 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s is surrounded by a swirling fire aura.", CHAR_NAME(DEFINITE));
5984 truth character::PreProcessForBone () {
5985 if (IsPet() && IsEnabled()) {
5986 Die(0, CONST_S(""), FORBID_REINCARNATION);
5987 return true;
5989 if (GetAction()) GetAction()->Terminate(false);
5990 if (TemporaryStateIsActivated(POLYMORPHED)) {
5991 character *PolymorphBackup = GetPolymorphBackup();
5992 EndPolymorph();
5993 PolymorphBackup->PreProcessForBone();
5994 return true;
5996 if (MustBeRemovedFromBone()) return false;
5997 if (IsUnique() && !CanBeGenerated()) game::SignalQuestMonsterFound();
5998 RestoreLivingHP();
5999 ResetStates();
6000 RemoveTraps();
6001 GetStack()->PreProcessForBone();
6002 doforequipments()(this, &item::PreProcessForBone);
6003 doforbodyparts()(this, &bodypart::PreProcessForBone);
6004 game::RemoveCharacterID(ID);
6005 ID = -ID;
6006 game::AddCharacterID(this, ID);
6007 return true;
6011 truth character::PostProcessForBone (double &DangerSum, int& Enemies) {
6012 if (PostProcessForBone()) {
6013 if (GetRelation(PLAYER) == HOSTILE) {
6014 double Danger = GetRelativeDanger(PLAYER, true);
6015 if (Danger > 99.0) game::SetTooGreatDangerFound(true);
6016 else if (!IsUnique() && !IgnoreDanger()) {
6017 DangerSum += Danger;
6018 ++Enemies;
6021 return true;
6023 return false;
6027 truth character::PostProcessForBone () {
6028 feuLong NewID = game::CreateNewCharacterID(this);
6029 game::GetBoneCharacterIDMap().insert(std::make_pair(-ID, NewID));
6030 game::RemoveCharacterID(ID);
6031 ID = NewID;
6032 if (IsUnique() && CanBeGenerated()) {
6033 if (DataBase->Flags & HAS_BEEN_GENERATED) return false;
6034 SignalGeneration();
6036 GetStack()->PostProcessForBone();
6037 doforequipments()(this, &item::PostProcessForBone);
6038 doforbodyparts()(this, &bodypart::PostProcessForBone);
6039 return true;
6043 void character::FinalProcessForBone () {
6044 Flags &= ~C_PLAYER;
6045 GetStack()->FinalProcessForBone();
6046 doforequipments()(this, &item::FinalProcessForBone);
6047 int c;
6048 for (c = 0; c < BodyParts; ++c) {
6049 for (std::list<feuLong>::iterator i = OriginalBodyPartID[c].begin(); i != OriginalBodyPartID[c].end();) {
6050 boneidmap::iterator BI = game::GetBoneItemIDMap().find(*i);
6051 if (BI == game::GetBoneItemIDMap().end()) {
6052 std::list<feuLong>::iterator Dirt = i++;
6053 OriginalBodyPartID[c].erase(Dirt);
6054 } else {
6055 *i = BI->second;
6056 ++i;
6063 void character::SetSoulID (feuLong What) {
6064 if (GetPolymorphBackup()) GetPolymorphBackup()->SetSoulID(What);
6068 truth character::SearchForItem (citem *Item) const {
6069 if (combineequipmentpredicateswithparam<feuLong>()(this, &item::HasID, Item->GetID(), 1)) return true;
6070 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) if (*i == Item) return true;
6071 return false;
6075 item *character::SearchForItem (const sweaponskill *SWeaponSkill) const {
6076 for (int c = 0; c < GetEquipments(); ++c) {
6077 item *Equipment = GetEquipment(c);
6078 if (Equipment && SWeaponSkill->IsSkillOf(Equipment)) return Equipment;
6080 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) if (SWeaponSkill->IsSkillOf(*i)) return *i;
6081 return 0;
6085 void character::PutNear (v2 Pos) {
6086 v2 NewPos = game::GetCurrentLevel()->GetNearestFreeSquare(this, Pos, false);
6087 if (NewPos == ERROR_V2) {
6088 do { NewPos = game::GetCurrentLevel()->GetRandomSquare(this); } while(NewPos == Pos);
6090 PutTo(NewPos);
6094 void character::PutToOrNear (v2 Pos) {
6095 if (game::IsInWilderness() || (CanMoveOn(game::GetCurrentLevel()->GetLSquare(Pos)) && IsFreeForMe(game::GetCurrentLevel()->GetLSquare(Pos))))
6096 PutTo(Pos);
6097 else
6098 PutNear(Pos);
6102 void character::PutTo (v2 Pos) {
6103 SquareUnder[0] = game::GetCurrentArea()->GetSquare(Pos);
6104 SquareUnder[0]->AddCharacter(this);
6108 void character::Remove () {
6109 SquareUnder[0]->RemoveCharacter();
6110 SquareUnder[0] = 0;
6114 void character::SendNewDrawRequest () const {
6115 for (int c = 0; c < SquaresUnder; ++c) {
6116 square *Square = GetSquareUnder(c);
6117 if (Square) Square->SendNewDrawRequest();
6122 truth character::IsOver (v2 Pos) const {
6123 for (int c = 0; c < SquaresUnder; ++c) {
6124 square *Square = GetSquareUnder(c);
6125 if (Square && Square->GetPos() == Pos) return true;
6127 return false;
6131 truth character::CanTheoreticallyMoveOn (const lsquare *LSquare) const { return GetMoveType() & LSquare->GetTheoreticalWalkability(); }
6132 truth character::CanMoveOn (const lsquare *LSquare) const { return GetMoveType() & LSquare->GetWalkability(); }
6133 truth character::CanMoveOn (const square *Square) const { return GetMoveType() & Square->GetSquareWalkability(); }
6134 truth character::CanMoveOn (const olterrain *OLTerrain) const { return GetMoveType() & OLTerrain->GetWalkability(); }
6135 truth character::CanMoveOn (const oterrain *OTerrain) const { return GetMoveType() & OTerrain->GetWalkability(); }
6136 truth character::IsFreeForMe(square *Square) const { return !Square->GetCharacter() || Square->GetCharacter() == this; }
6137 void character::LoadSquaresUnder () { SquareUnder[0] = game::GetSquareInLoad(); }
6139 truth character::AttackAdjacentEnemyAI () {
6140 if (!IsEnabled()) return false;
6141 character *Char[MAX_NEIGHBOUR_SQUARES];
6142 v2 Pos[MAX_NEIGHBOUR_SQUARES];
6143 int Dir[MAX_NEIGHBOUR_SQUARES];
6144 int Index = 0;
6145 for (int d = 0; d < GetNeighbourSquares(); ++d) {
6146 square *Square = GetNeighbourSquare(d);
6147 if (Square) {
6148 character *Enemy = Square->GetCharacter();
6149 if (Enemy && (GetRelation(Enemy) == HOSTILE || StateIsActivated(CONFUSED))) {
6150 Dir[Index] = d;
6151 Pos[Index] = Square->GetPos();
6152 Char[Index++] = Enemy;
6156 if (Index) {
6157 int ChosenIndex = RAND() % Index;
6158 Hit(Char[ChosenIndex], Pos[ChosenIndex], Dir[ChosenIndex]);
6159 return true;
6161 return false;
6165 void character::SignalStepFrom (lsquare **OldSquareUnder) {
6166 int c;
6167 lsquare *NewSquareUnder[MAX_SQUARES_UNDER];
6168 for (c = 0; c < GetSquaresUnder(); ++c) NewSquareUnder[c] = GetLSquareUnder(c);
6169 for (c = 0; c < GetSquaresUnder(); ++c) {
6170 if (IsEnabled() && GetLSquareUnder(c) == NewSquareUnder[c]) NewSquareUnder[c]->StepOn(this, OldSquareUnder);
6175 int character::GetSumOfAttributes () const {
6176 return GetAttribute(ENDURANCE) + GetAttribute(PERCEPTION) + GetAttribute(INTELLIGENCE) + GetAttribute(WISDOM) + GetAttribute(CHARISMA) + GetAttribute(ARM_STRENGTH) + GetAttribute(AGILITY);
6180 void character::IntelligenceAction (int Difficulty) {
6181 EditAP(-20000 * Difficulty / APBonus(GetAttribute(INTELLIGENCE)));
6182 EditExperience(INTELLIGENCE, Difficulty * 15, 1 << 7);
6186 struct walkabilitycontroller {
6187 static truth Handler (int x, int y) {
6188 return x >= 0 && y >= 0 && x < LevelXSize && y < LevelYSize && Map[x][y]->GetTheoreticalWalkability() & MoveType;
6190 static lsquare ***Map;
6191 static int LevelXSize, LevelYSize;
6192 static int MoveType;
6196 lsquare ***walkabilitycontroller::Map;
6197 int walkabilitycontroller::LevelXSize, walkabilitycontroller::LevelYSize;
6198 int walkabilitycontroller::MoveType;
6201 truth character::CreateRoute () {
6202 Route.clear();
6203 if (GetAttribute(INTELLIGENCE) >= 10 && !StateIsActivated(CONFUSED)) {
6204 v2 Pos = GetPos();
6205 walkabilitycontroller::Map = GetLevel()->GetMap();
6206 walkabilitycontroller::LevelXSize = GetLevel()->GetXSize();
6207 walkabilitycontroller::LevelYSize = GetLevel()->GetYSize();
6208 walkabilitycontroller::MoveType = GetMoveType();
6209 node *Node;
6210 for (int c = 0; c < game::GetTeams(); ++c)
6211 for (std::list<character *>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i) {
6212 character *Char = *i;
6213 if (Char->IsEnabled() && !Char->Route.empty() && (Char->GetMoveType()&GetMoveType()) == Char->GetMoveType()) {
6214 v2 CharGoingTo = Char->Route[0];
6215 v2 iPos = Char->Route.back();
6216 if ((GoingTo-CharGoingTo).GetLengthSquare() <= 100 && (Pos - iPos).GetLengthSquare() <= 100 &&
6217 mapmath<walkabilitycontroller>::DoLine(CharGoingTo.X, CharGoingTo.Y, GoingTo.X, GoingTo.Y, SKIP_FIRST) &&
6218 mapmath<walkabilitycontroller>::DoLine(Pos.X, Pos.Y, iPos.X, iPos.Y, SKIP_FIRST)) {
6219 if (!Illegal.empty() && Illegal.find(Char->Route.back()) != Illegal.end()) continue;
6220 Node = GetLevel()->FindRoute(CharGoingTo, GoingTo, Illegal, GetMoveType());
6221 if (Node) { while(Node->Last) { Route.push_back(Node->Pos); Node = Node->Last; } }
6222 else { Route.clear(); continue; }
6223 Route.insert(Route.end(), Char->Route.begin(), Char->Route.end());
6224 Node = GetLevel()->FindRoute(Pos, iPos, Illegal, GetMoveType());
6225 if (Node) { while (Node->Last) { Route.push_back(Node->Pos); Node = Node->Last; } }
6226 else { Route.clear(); continue; }
6227 IntelligenceAction(1);
6228 return true;
6232 Node = GetLevel()->FindRoute(Pos, GoingTo, Illegal, GetMoveType());
6233 if (Node) { while(Node->Last) { Route.push_back(Node->Pos); Node = Node->Last; } }
6234 else TerminateGoingTo();
6235 IntelligenceAction(5);
6236 return true;
6238 return false;
6242 void character::SetGoingTo (v2 What) {
6243 if (GoingTo != What) {
6244 GoingTo = What;
6245 Route.clear();
6246 Illegal.clear();
6251 void character::TerminateGoingTo () {
6252 GoingTo = ERROR_V2;
6253 Route.clear();
6254 Illegal.clear();
6258 truth character::CheckForFood (int Radius) {
6259 if (StateIsActivated(PANIC) || !UsesNutrition() || !IsEnabled()) return false;
6260 v2 Pos = GetPos();
6261 int x, y;
6262 for (int r = 1; r <= Radius; ++r) {
6263 x = Pos.X-r;
6264 if (x >= 0) {
6265 for (y = Pos.Y-r; y <= Pos.Y+r; ++y) if (CheckForFoodInSquare(v2(x, y))) return true;
6267 x = Pos.X+r;
6268 if (x < GetLevel()->GetXSize()) {
6269 for (y = Pos.Y-r; y <= Pos.Y+r; ++y) if (CheckForFoodInSquare(v2(x, y))) return true;
6271 y = Pos.Y-r;
6272 if (y >= 0) {
6273 for (x = Pos.X-r; x <= Pos.X+r; ++x) if (CheckForFoodInSquare(v2(x, y))) return true;
6275 y = Pos.Y+r;
6276 if (y < GetLevel()->GetYSize()) {
6277 for (x = Pos.X-r; x <= Pos.X+r; ++x) if (CheckForFoodInSquare(v2(x, y))) return true;
6280 return false;
6284 truth character::CheckForFoodInSquare (v2 Pos) {
6285 level *Level = GetLevel();
6286 if (Level->IsValidPos(Pos)) {
6287 lsquare *Square = Level->GetLSquare(Pos);
6288 stack *Stack = Square->GetStack();
6289 if (Stack->GetItems()) {
6290 for (stackiterator i = Stack->GetBottom(); i.HasItem(); ++i) {
6291 if (i->IsPickable(this) && i->CanBeSeenBy(this) && i->CanBeEatenByAI(this) && (!Square->GetRoomIndex() || Square->GetRoom()->AllowFoodSearch())) {
6292 SetGoingTo(Pos);
6293 return MoveTowardsTarget(false);
6298 return false;
6302 void character::SetConfig (int NewConfig, int SpecialFlags) {
6303 databasecreator<character>::InstallDataBase(this, NewConfig);
6304 CalculateAll();
6305 CheckIfSeen();
6306 if (!(SpecialFlags & NO_PIC_UPDATE)) UpdatePictures();
6310 truth character::IsOver (citem *Item) const {
6311 for (int c1 = 0; c1 < Item->GetSquaresUnder(); ++c1)
6312 for (int c2 = 0; c2 < SquaresUnder; ++c2)
6313 if (Item->GetPos(c1) == GetPos(c2)) return true;
6314 return false;
6318 truth character::CheckConsume (cfestring &Verb) const {
6319 if (!UsesNutrition()) {
6320 if (IsPlayer()) ADD_MESSAGE("In this form you can't and don't need to %s.", Verb.CStr());
6321 return false;
6323 return true;
6327 void character::PutTo (lsquare *To) {
6328 PutTo(To->GetPos());
6332 double character::RandomizeBabyExperience (double SumE) {
6333 if (!SumE) return 0;
6334 double E = (SumE / 4) - (SumE / 32) + (double(RAND()) / MAX_RAND) * (SumE / 16 + 1);
6335 return Limit(E, MIN_EXP, MAX_EXP);
6339 liquid *character::CreateBlood (sLong Volume) const {
6340 return liquid::Spawn(GetBloodMaterial(), Volume);
6344 void character::SpillFluid (character *Spiller, liquid *Liquid, int SquareIndex) {
6345 sLong ReserveVolume = Liquid->GetVolume() >> 1;
6346 Liquid->EditVolume(-ReserveVolume);
6347 GetStack()->SpillFluid(Spiller, Liquid, sLong(Liquid->GetVolume() * sqrt(double(GetStack()->GetVolume()) / GetVolume())));
6348 Liquid->EditVolume(ReserveVolume);
6349 int c;
6350 sLong Modifier[MAX_BODYPARTS], ModifierSum = 0;
6351 for (c = 0; c < BodyParts; ++c) {
6352 if (GetBodyPart(c)) {
6353 Modifier[c] = sLong(sqrt(GetBodyPart(c)->GetVolume()));
6354 if (Modifier[c]) Modifier[c] *= 1 + (RAND() & 3);
6355 ModifierSum += Modifier[c];
6356 } else {
6357 Modifier[c] = 0;
6360 for (c = 1; c < GetBodyParts(); ++c) {
6361 if (GetBodyPart(c) && IsEnabled())
6362 GetBodyPart(c)->SpillFluid(Spiller, Liquid->SpawnMoreLiquid(Liquid->GetVolume() * Modifier[c] / ModifierSum), SquareIndex);
6364 if (IsEnabled()) {
6365 Liquid->SetVolume(Liquid->GetVolume() * Modifier[TORSO_INDEX] / ModifierSum);
6366 GetTorso()->SpillFluid(Spiller, Liquid, SquareIndex);
6371 void character::StayOn (liquid *Liquid) {
6372 Liquid->TouchEffect(this, TORSO_INDEX);
6376 truth character::IsAlly (ccharacter *Char) const {
6377 return Char->GetTeam()->GetID() == GetTeam()->GetID();
6381 void character::ResetSpoiling () {
6382 doforbodyparts()(this, &bodypart::ResetSpoiling);
6386 item *character::SearchForItem (ccharacter *Char, sorter Sorter) const {
6387 item *Equipment = findequipment<ccharacter *>()(this, Sorter, Char);
6388 if (Equipment) return Equipment;
6389 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) if (((*i)->*Sorter)(Char)) return *i;
6390 return 0;
6394 truth character::DetectMaterial (cmaterial *Material) const {
6395 return GetStack()->DetectMaterial(Material) ||
6396 combinebodypartpredicateswithparam<cmaterial*>()(this, &bodypart::DetectMaterial, Material, 1) ||
6397 combineequipmentpredicateswithparam<cmaterial*>()(this, &item::DetectMaterial, Material, 1);
6401 truth character::DamageTypeDestroysBodyPart (int Type) {
6402 return (Type&0xFFF) != PHYSICAL_DAMAGE;
6406 truth character::CheckIfTooScaredToHit (ccharacter *Enemy) const {
6407 if (IsPlayer() && StateIsActivated(PANIC)) {
6408 for (int d = 0; d < GetNeighbourSquares(); ++d) {
6409 square *Square = GetNeighbourSquare(d);
6410 if (Square) {
6411 if(CanMoveOn(Square) && (!Square->GetCharacter() || Square->GetCharacter()->IsPet())) {
6412 ADD_MESSAGE("You are too scared to attack %s.", Enemy->CHAR_DESCRIPTION(DEFINITE));
6413 return true;
6418 return false;
6422 void character::PrintBeginLevitationMessage () const {
6423 if (!IsFlying()) {
6424 if (IsPlayer()) ADD_MESSAGE("You rise into the air like a small hot-air balloon.");
6425 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s begins to float.", CHAR_NAME(DEFINITE));
6430 void character::PrintEndLevitationMessage () const {
6431 if (!IsFlying()) {
6432 if (IsPlayer()) ADD_MESSAGE("You descend gently onto the ground.");
6433 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s drops onto the ground.", CHAR_NAME(DEFINITE));
6438 truth character::IsLimbIndex (int I) {
6439 switch (I) {
6440 case RIGHT_ARM_INDEX:
6441 case LEFT_ARM_INDEX:
6442 case RIGHT_LEG_INDEX:
6443 case LEFT_LEG_INDEX:
6444 return true;
6446 return false;
6450 void character::EditExperience (int Identifier, double Value, double Speed) {
6451 if (!AllowExperience() || (Identifier == ENDURANCE && UseMaterialAttributes())) return;
6452 int Change = RawEditExperience(BaseExperience[Identifier], GetNaturalExperience(Identifier), Value, Speed);
6453 if (!Change) return;
6454 cchar *PlayerMsg = 0, *NPCMsg = 0;
6455 switch (Identifier) {
6456 case ENDURANCE:
6457 if (Change > 0) {
6458 PlayerMsg = "You feel tougher than anything!";
6459 if (IsPet()) NPCMsg = "Suddenly %s looks tougher.";
6460 } else {
6461 PlayerMsg = "You feel less healthy.";
6462 if (IsPet()) NPCMsg = "Suddenly %s looks less healthy.";
6464 CalculateBodyPartMaxHPs();
6465 CalculateMaxStamina();
6466 break;
6467 case PERCEPTION:
6468 if (IsPlayer()) {
6469 if (Change > 0) {
6470 PlayerMsg = "You now see the world in much better detail than before.";
6471 } else {
6472 PlayerMsg = "You feel very guru.";
6473 game::GetGod(VALPURUS)->AdjustRelation(100);
6475 game::SendLOSUpdateRequest();
6477 break;
6478 case INTELLIGENCE:
6479 if (IsPlayer()) {
6480 if (Change > 0) PlayerMsg = "Suddenly the inner structure of the Multiverse around you looks quite simple.";
6481 else PlayerMsg = "It surely is hard to think today.";
6482 UpdateESPLOS();
6484 if (IsPlayerKind()) UpdatePictures();
6485 break;
6486 case WISDOM:
6487 if (IsPlayer()) {
6488 if (Change > 0) PlayerMsg = "You feel your life experience increasing all the time.";
6489 else PlayerMsg = "You feel like having done something unwise.";
6491 if (IsPlayerKind()) UpdatePictures();
6492 break;
6493 case CHARISMA:
6494 if (Change > 0) {
6495 PlayerMsg = "You feel very confident of your social skills.";
6496 if (IsPet()) {
6497 if (GetAttribute(CHARISMA) <= 15) NPCMsg = "%s looks less ugly.";
6498 else NPCMsg = "%s looks more attractive.";
6500 } else {
6501 PlayerMsg = "You feel somehow disliked.";
6502 if (IsPet()) {
6503 if (GetAttribute(CHARISMA) < 15) NPCMsg = "%s looks more ugly.";
6504 else NPCMsg = "%s looks less attractive.";
6507 if (IsPlayerKind()) UpdatePictures();
6508 break;
6509 case MANA:
6510 if (Change > 0) {
6511 PlayerMsg = "You feel magical forces coursing through your body!";
6512 NPCMsg = "You notice an odd glow around %s.";
6513 } else {
6514 PlayerMsg = "You feel your magical abilities withering slowly.";
6515 NPCMsg = "You notice strange vibrations in the air around %s. But they disappear rapidly.";
6517 break;
6520 if (IsPlayer()) ADD_MESSAGE("%s", PlayerMsg);
6521 else if (NPCMsg && CanBeSeenByPlayer()) ADD_MESSAGE(NPCMsg, CHAR_NAME(DEFINITE));
6523 CalculateBattleInfo();
6527 int character::RawEditExperience (double &Exp, double NaturalExp, double Value, double Speed) const {
6528 double OldExp = Exp;
6529 if (Speed < 0) {
6530 Speed = -Speed;
6531 Value = -Value;
6533 if(!OldExp || !Value || (Value > 0 && OldExp >= NaturalExp * (100 + Value) / 100) ||
6534 (Value < 0 && OldExp <= NaturalExp * (100 + Value) / 100)) return 0;
6535 if (!IsPlayer()) Speed *= 1.5;
6536 Exp += (NaturalExp * (100 + Value) - 100 * OldExp) * Speed * EXP_DIVISOR;
6537 LimitRef(Exp, MIN_EXP, MAX_EXP);
6538 int NewA = int(Exp * EXP_DIVISOR);
6539 int OldA = int(OldExp * EXP_DIVISOR);
6540 int Delta = NewA - OldA;
6541 if (Delta > 0) Exp = Max(Exp, (NewA + 0.05) * EXP_MULTIPLIER);
6542 else if (Delta < 0) Exp = Min(Exp, (NewA + 0.95) * EXP_MULTIPLIER);
6543 LimitRef(Exp, MIN_EXP, MAX_EXP);
6544 return Delta;
6548 int character::GetAttribute (int Identifier, truth AllowBonus) const {
6549 int A = int(BaseExperience[Identifier] * EXP_DIVISOR);
6550 if (AllowBonus && Identifier == INTELLIGENCE && BrainsHurt()) return Max((A + AttributeBonus[INTELLIGENCE]) / 3, 1);
6551 return A && AllowBonus ? Max(A + AttributeBonus[Identifier], 1) : A;
6555 void characterdatabase::PostProcess () {
6556 double AM = (100 + AttributeBonus) * EXP_MULTIPLIER / 100;
6557 for (int c = 0; c < ATTRIBUTES; ++c) NaturalExperience[c] = this->*ExpPtr[c] * AM;
6561 void character::EditDealExperience (sLong Price) {
6562 EditExperience(CHARISMA, sqrt(Price) / 5, 1 << 9);
6566 void character::PrintBeginLeprosyMessage () const {
6567 if (IsPlayer()) ADD_MESSAGE("You feel you're falling in pieces.");
6571 void character::PrintEndLeprosyMessage () const {
6572 if (IsPlayer()) ADD_MESSAGE("You feel your limbs are stuck in place tightly."); // CHANGE OR DIE
6576 void character::TryToInfectWithLeprosy (ccharacter *Infector) {
6577 if (!IsImmuneToLeprosy() &&
6578 ((GetRelation(Infector) == HOSTILE && !RAND_N(50 * GetAttribute(ENDURANCE))) ||
6579 !RAND_N(500 * GetAttribute(ENDURANCE)))) GainIntrinsic(LEPROSY);
6583 void character::SignalGeneration () {
6584 const_cast<database *>(DataBase)->Flags |= HAS_BEEN_GENERATED;
6588 void character::CheckIfSeen () {
6589 if (IsPlayer() || CanBeSeenByPlayer()) SignalSeen();
6593 void character::SignalSeen () {
6594 if (!(WarnFlags & WARNED) && GetRelation(PLAYER) == HOSTILE) {
6595 double Danger = GetRelativeDanger(PLAYER);
6596 if (Danger > 5.0) {
6597 game::SetDangerFound(Max(game::GetDangerFound(), Danger));
6598 if (Danger > 500.0 && !(WarnFlags & HAS_CAUSED_PANIC)) {
6599 WarnFlags |= HAS_CAUSED_PANIC;
6600 game::SetCausePanicFlag(true);
6602 WarnFlags |= WARNED;
6605 const_cast<database *>(DataBase)->Flags |= HAS_BEEN_SEEN;
6609 int character::GetPolymorphIntelligenceRequirement () const {
6610 if (DataBase->PolymorphIntelligenceRequirement == DEPENDS_ON_ATTRIBUTES) return Max(GetAttributeAverage() - 5, 0);
6611 return DataBase->PolymorphIntelligenceRequirement;
6615 void character::RemoveAllItems () {
6616 GetStack()->Clean();
6617 for (int c = 0; c < GetEquipments(); ++c) {
6618 item *Equipment = GetEquipment(c);
6619 if (Equipment) {
6620 Equipment->RemoveFromSlot();
6621 Equipment->SendToHell();
6627 int character::CalculateWeaponSkillHits (ccharacter *Enemy) const {
6628 if (Enemy->IsPlayer()) {
6629 configid ConfigID(GetType(), GetConfig());
6630 const dangerid& DangerID = game::GetDangerMap().find(ConfigID)->second;
6631 return Min(int(DangerID.EquippedDanger * 2000), 1000);
6633 return Min(int(GetRelativeDanger(Enemy, true) * 2000), 1000);
6637 truth character::CanUseEquipment (int I) const {
6638 return CanUseEquipment() && I < GetEquipments() && GetBodyPartOfEquipment(I) && EquipmentIsAllowed(I);
6642 /* Target mustn't have any equipment */
6643 void character::DonateEquipmentTo (character *Character) {
6644 if (IsPlayer()) {
6645 feuLong *EquipmentMemory = game::GetEquipmentMemory();
6646 for (int c = 0; c < MAX_EQUIPMENT_SLOTS; ++c) {
6647 item *Item = GetEquipment(c);
6648 if (Item) {
6649 if (Character->CanUseEquipment(c)) {
6650 Item->RemoveFromSlot();
6651 Character->SetEquipment(c, Item);
6652 } else {
6653 EquipmentMemory[c] = Item->GetID();
6654 Item->MoveTo(Character->GetStack());
6656 } else if (CanUseEquipment(c)) {
6657 EquipmentMemory[c] = 0;
6658 } else if (EquipmentMemory[c] && Character->CanUseEquipment(c)) {
6659 for (stackiterator i = Character->GetStack()->GetBottom(); i.HasItem(); ++i) {
6660 if (i->GetID() == EquipmentMemory[c]) {
6661 item *Item = *i;
6662 Item->RemoveFromSlot();
6663 Character->SetEquipment(c, Item);
6664 break;
6667 EquipmentMemory[c] = 0;
6670 } else {
6671 for (int c = 0; c < GetEquipments(); ++c) {
6672 item *Item = GetEquipment(c);
6673 if (Item) {
6674 if (Character->CanUseEquipment(c)) {
6675 Item->RemoveFromSlot();
6676 Character->SetEquipment(c, Item);
6677 } else {
6678 Item->MoveTo(Character->GetStackUnder());
6686 void character::ReceivePeaSoup (sLong) {
6687 if (!game::IsInWilderness()) {
6688 lsquare *Square = GetLSquareUnder();
6689 if (Square->IsFlyable()) Square->AddSmoke(gas::Spawn(FART, 250));
6694 void character::AddPeaSoupConsumeEndMessage () const {
6695 if (IsPlayer()) {
6696 if (CanHear()) ADD_MESSAGE("Mmmh! The soup is very tasty. You hear a small puff.");
6697 else ADD_MESSAGE("Mmmh! The soup is very tasty.");
6698 } else if (CanBeSeenByPlayer() && PLAYER->CanHear()) {
6699 // change someday
6700 ADD_MESSAGE("You hear a small puff.");
6705 void character::CalculateMaxStamina () {
6706 MaxStamina = TorsoIsAlive() ? GetAttribute(ENDURANCE) * 10000 : 0;
6710 void character::EditStamina (int Amount, truth CanCauseUnconsciousness) {
6711 if (!TorsoIsAlive()) return;
6712 int UnconsciousnessStamina = MaxStamina >> 3;
6713 if (!CanCauseUnconsciousness && Amount < 0) {
6714 if (Stamina > UnconsciousnessStamina) {
6715 Stamina += Amount;
6716 if (Stamina < UnconsciousnessStamina) Stamina = UnconsciousnessStamina;
6718 return;
6720 int OldStamina = Stamina;
6721 Stamina += Amount;
6722 if (Stamina > MaxStamina) {
6723 Stamina = MaxStamina;
6724 } else if (Stamina < 0) {
6725 Stamina = 0;
6726 LoseConsciousness(250 + RAND_N(250));
6727 } else if (IsPlayer()) {
6728 if (OldStamina >= MaxStamina >> 2 && Stamina < MaxStamina >> 2) {
6729 ADD_MESSAGE("You are getting a little tired.");
6730 } else if(OldStamina >= UnconsciousnessStamina && Stamina < UnconsciousnessStamina) {
6731 ADD_MESSAGE("You are seriously out of breath!");
6732 game::SetPlayerIsRunning(false);
6735 if (IsPlayer() && StateIsActivated(PANIC) && GetTirednessState() != FAINTING) game::SetPlayerIsRunning(true);
6739 void character::RegenerateStamina () {
6740 if (GetTirednessState() != UNTIRED) {
6741 EditExperience(ENDURANCE, 50, 1);
6742 if (Sweats() && TorsoIsAlive() && !RAND_N(30) && !game::IsInWilderness()) {
6743 // Sweat amount proportional to endurance also
6744 //sLong Volume = sLong(0.05 * sqrt(GetBodyVolume()));
6745 sLong Volume = long(0.05*sqrt(GetBodyVolume()*GetAttribute(ENDURANCE)/10));
6746 if (GetTirednessState() == FAINTING) Volume <<= 1;
6747 for (int c = 0; c < SquaresUnder; ++c) GetLSquareUnder(c)->SpillFluid(0, CreateSweat(Volume), false, false);
6750 int Bonus = 1;
6751 if (Action) {
6752 if (Action->IsRest()) {
6753 if (SquaresUnder == 1) {
6754 Bonus = GetSquareUnder()->GetRestModifier() << 1;
6755 } else {
6756 int Lowest = GetSquareUnder(0)->GetRestModifier();
6757 for (int c = 1; c < GetSquaresUnder(); ++c) {
6758 int Mod = GetSquareUnder(c)->GetRestModifier();
6759 if (Mod < Lowest) Lowest = Mod;
6761 Bonus = Lowest << 1;
6763 } else if (Action->IsUnconsciousness()) Bonus = 2;
6765 int Plus1 = 100;
6766 switch (GetBurdenState()) {
6767 case OVER_LOADED: Plus1 = 25; break;
6768 case STRESSED: Plus1 = 50; break;
6769 case BURDENED: Plus1 = 75; break;
6771 int Plus2 = 100;
6772 if (IsPlayer()) {
6773 switch (GetHungerState()) {
6774 case STARVING: Plus2 = 25; break;
6775 case VERY_HUNGRY: Plus2 = 50; break;
6776 case HUNGRY: Plus2 = 75; break;
6779 Stamina += Plus1 * Plus2 * Bonus / 1000;
6780 if (Stamina > MaxStamina) Stamina = MaxStamina;
6781 if (IsPlayer() && StateIsActivated(PANIC) && GetTirednessState() != FAINTING) game::SetPlayerIsRunning(true);
6785 void character::BeginPanic () {
6786 if (IsPlayer() && GetTirednessState() != FAINTING) game::SetPlayerIsRunning(true);
6787 DeActivateVoluntaryAction();
6791 void character::EndPanic () {
6792 if (IsPlayer()) game::SetPlayerIsRunning(false);
6796 int character::GetTirednessState () const {
6797 if (Stamina >= MaxStamina >> 2) return UNTIRED;
6798 if (Stamina >= MaxStamina >> 3) return EXHAUSTED;
6799 return FAINTING;
6803 void character::ReceiveBlackUnicorn (sLong Amount) {
6804 if (!(RAND() % 160)) game::DoEvilDeed(Amount / 50);
6805 BeginTemporaryState(TELEPORT, Amount / 100);
6806 for (int c = 0; c < STATES; ++c) {
6807 if (StateData[c].Flags & DUR_TEMPORARY) {
6808 BeginTemporaryState(1 << c, Amount / 100);
6809 if (!IsEnabled()) return;
6810 } else if (StateData[c].Flags & DUR_PERMANENT) {
6811 GainIntrinsic(1 << c);
6812 if (!IsEnabled()) return;
6818 void character::ReceiveGrayUnicorn (sLong Amount) {
6819 if (!(RAND() % 80)) game::DoEvilDeed(Amount / 50);
6820 BeginTemporaryState(TELEPORT, Amount / 100);
6821 for (int c = 0; c < STATES; ++c) {
6822 if (1 << c != TELEPORT) {
6823 DecreaseStateCounter(1 << c, -Amount / 100);
6824 if (!IsEnabled()) return;
6830 void character::ReceiveWhiteUnicorn (sLong Amount) {
6831 if (!(RAND() % 40)) game::DoEvilDeed(Amount / 50);
6832 BeginTemporaryState(TELEPORT, Amount / 100);
6833 DecreaseStateCounter(LYCANTHROPY, -Amount / 100);
6834 DecreaseStateCounter(POISONED, -Amount / 100);
6835 DecreaseStateCounter(PARASITIZED, -Amount / 100);
6836 DecreaseStateCounter(LEPROSY, -Amount / 100);
6837 DecreaseStateCounter(VAMPIRISM, -Amount / 100);
6841 /* Counter should be negative. Removes intrinsics. */
6842 void character::DecreaseStateCounter (sLong State, int Counter) {
6843 int Index;
6844 for (Index = 0; Index < STATES; ++Index) if (1 << Index == State) break;
6845 if (Index == STATES) ABORT("DecreaseTemporaryStateCounter works only when State == 2^n!");
6846 if (TemporaryState & State) {
6847 if (TemporaryStateCounter[Index] == PERMANENT || (TemporaryStateCounter[Index] += Counter) <= 0) {
6848 TemporaryState &= ~State;
6849 if (!(EquipmentState & State)) {
6850 if (StateData[Index].EndHandler) {
6851 (this->*StateData[Index].EndHandler)();
6852 if (!IsEnabled()) return;
6854 (this->*StateData[Index].PrintEndMessage)();
6861 truth character::IsImmuneToLeprosy () const {
6862 return DataBase->IsImmuneToLeprosy || UseMaterialAttributes();
6866 void character::LeprosyHandler () {
6867 EditExperience(ARM_STRENGTH, -25, 1 << 1);
6868 EditExperience(LEG_STRENGTH, -25, 1 << 1);
6869 EditExperience(DEXTERITY, -25, 1 << 1);
6870 EditExperience(AGILITY, -25, 1 << 1);
6871 EditExperience(ENDURANCE, -25, 1 << 1);
6872 EditExperience(CHARISMA, -25, 1 << 1);
6873 CheckDeath(CONST_S("killed by leprosy"));
6877 bodypart *character::SearchForOriginalBodyPart (int I) const {
6878 for (stackiterator i1 = GetStackUnder()->GetBottom(); i1.HasItem(); ++i1) {
6879 for (std::list<feuLong>::iterator i2 = OriginalBodyPartID[I].begin(); i2 != OriginalBodyPartID[I].end(); ++i2)
6880 if (i1->GetID() == *i2) return static_cast<bodypart*>(*i1);
6882 return 0;
6886 void character::SetLifeExpectancy (int Base, int RandPlus) {
6887 int c;
6888 for (c = 0; c < BodyParts; ++c) {
6889 bodypart *BodyPart = GetBodyPart(c);
6890 if (BodyPart) BodyPart->SetLifeExpectancy(Base, RandPlus);
6892 for (c = 0; c < GetEquipments(); ++c) {
6893 item *Equipment = GetEquipment(c);
6894 if (Equipment) Equipment->SetLifeExpectancy(Base, RandPlus);
6899 /* Receiver should be a fresh duplicate of this */
6900 void character::DuplicateEquipment (character *Receiver, feuLong Flags) {
6901 for (int c = 0; c < GetEquipments(); ++c) {
6902 item *Equipment = GetEquipment(c);
6903 if (Equipment) {
6904 item *Duplicate = Equipment->Duplicate(Flags);
6905 Receiver->SetEquipment(c, Duplicate);
6911 void character::Disappear (corpse *Corpse, cchar *Verb, truth (item::*ClosePredicate)() const) {
6912 truth TorsoDisappeared = false;
6913 truth CanBeSeen = Corpse ? Corpse->CanBeSeenByPlayer() : IsPlayer() || CanBeSeenByPlayer();
6914 int c;
6915 if ((GetTorso()->*ClosePredicate)()) {
6916 if (CanBeSeen) {
6917 if (Corpse) ADD_MESSAGE("%s %ss.", Corpse->CHAR_NAME(DEFINITE), Verb);
6918 else if (IsPlayer()) ADD_MESSAGE("You %s.", Verb);
6919 else ADD_MESSAGE("%s %ss.", CHAR_NAME(DEFINITE), Verb);
6921 TorsoDisappeared = true;
6922 for (c = 0; c < GetEquipments(); ++c) {
6923 item *Equipment = GetEquipment(c);
6924 if (Equipment && (Equipment->*ClosePredicate)()) {
6925 Equipment->RemoveFromSlot();
6926 Equipment->SendToHell();
6929 itemvector ItemVector;
6930 GetStack()->FillItemVector(ItemVector);
6931 for (uInt c = 0; c < ItemVector.size(); ++c) {
6932 if (ItemVector[c] && (ItemVector[c]->*ClosePredicate)()) {
6933 ItemVector[c]->RemoveFromSlot();
6934 ItemVector[c]->SendToHell();
6938 for (c = 1; c < GetBodyParts(); ++c) {
6939 bodypart *BodyPart = GetBodyPart(c);
6940 if (BodyPart) {
6941 if ((BodyPart->*ClosePredicate)()) {
6942 if (!TorsoDisappeared && CanBeSeen) {
6943 if(IsPlayer()) ADD_MESSAGE("Your %s %ss.", GetBodyPartName(c).CStr(), Verb);
6944 else ADD_MESSAGE("The %s of %s %ss.", GetBodyPartName(c).CStr(), CHAR_NAME(DEFINITE), Verb);
6946 BodyPart->DropEquipment();
6947 item *BodyPart = SevereBodyPart(c);
6948 if (BodyPart) BodyPart->SendToHell();
6949 } else if (TorsoDisappeared) {
6950 BodyPart->DropEquipment();
6951 item *BodyPart = SevereBodyPart(c);
6952 if (BodyPart) {
6953 if (Corpse) Corpse->GetSlot()->AddFriendItem(BodyPart);
6954 else if (!game::IsInWilderness()) GetStackUnder()->AddItem(BodyPart);
6955 else BodyPart->SendToHell();
6960 if (TorsoDisappeared) {
6961 if (Corpse) {
6962 Corpse->RemoveFromSlot();
6963 Corpse->SendToHell();
6964 } else {
6965 CheckDeath(festring(Verb) + "ed", 0, FORCE_DEATH|DISALLOW_CORPSE|DISALLOW_MSG);
6967 } else {
6968 CheckDeath(festring(Verb) + "ed", 0, DISALLOW_MSG);
6973 void character::SignalDisappearance () {
6974 if (GetMotherEntity()) GetMotherEntity()->SignalDisappearance();
6975 else Disappear(0, "disappear", &item::IsVeryCloseToDisappearance);
6979 truth character::HornOfFearWorks () const {
6980 return CanHear() && GetPanicLevel() > RAND() % 33;
6984 void character::BeginLeprosy () {
6985 doforbodypartswithparam<truth>()(this, &bodypart::SetIsInfectedByLeprosy, true);
6989 void character::EndLeprosy () {
6990 doforbodypartswithparam<truth>()(this, &bodypart::SetIsInfectedByLeprosy, false);
6994 truth character::IsSameAs (ccharacter *What) const {
6995 return What->GetType() == GetType() && What->GetConfig() == GetConfig();
6999 feuLong character::GetCommandFlags () const {
7000 return !StateIsActivated(PANIC) ? CommandFlags : CommandFlags|FLEE_FROM_ENEMIES;
7004 feuLong character::GetConstantCommandFlags () const {
7005 return !StateIsActivated(PANIC) ? DataBase->ConstantCommandFlags : DataBase->ConstantCommandFlags|FLEE_FROM_ENEMIES;
7009 feuLong character::GetPossibleCommandFlags () const {
7010 int Int = GetAttribute(INTELLIGENCE);
7011 feuLong Flags = ALL_COMMAND_FLAGS;
7012 if (!CanMove() || Int < 4) Flags &= ~FOLLOW_LEADER;
7013 if (!CanMove() || Int < 6) Flags &= ~FLEE_FROM_ENEMIES;
7014 if (!CanUseEquipment() || Int < 8) Flags &= ~DONT_CHANGE_EQUIPMENT;
7015 if (!UsesNutrition() || Int < 8) Flags &= ~DONT_CONSUME_ANYTHING_VALUABLE;
7016 return Flags;
7020 truth character::IsRetreating () const {
7021 return StateIsActivated(PANIC) || (CommandFlags & FLEE_FROM_ENEMIES && IsPet());
7025 truth character::ChatMenu () {
7026 if (GetAction() && !GetAction()->CanBeTalkedTo()) {
7027 ADD_MESSAGE("%s is silent.", CHAR_DESCRIPTION(DEFINITE));
7028 PLAYER->EditAP(-200);
7029 return true;
7031 feuLong ManagementFlags = GetManagementFlags();
7032 if (ManagementFlags == CHAT_IDLY || !IsPet()) return ChatIdly();
7033 static cchar *const ChatMenuEntry[CHAT_MENU_ENTRIES] = {
7034 "Change equipment",
7035 "Take items",
7036 "Give items",
7037 "Issue commands",
7038 "Chat idly",
7040 static const petmanagementfunction PMF[CHAT_MENU_ENTRIES] = {
7041 &character::ChangePetEquipment,
7042 &character::TakePetItems,
7043 &character::GivePetItems,
7044 &character::IssuePetCommands,
7045 &character::ChatIdly
7047 felist List(CONST_S("Choose action:"));
7048 game::SetStandardListAttributes(List);
7049 List.AddFlags(SELECTABLE);
7050 int c, i;
7051 for (c = 0; c < CHAT_MENU_ENTRIES; ++c) if (1 << c & ManagementFlags) List.AddEntry(ChatMenuEntry[c], LIGHT_GRAY);
7052 int Chosen = List.Draw();
7053 if (Chosen & FELIST_ERROR_BIT) return false;
7054 for (c = 0, i = 0; c < CHAT_MENU_ENTRIES; ++c) {
7055 if (1 << c & ManagementFlags && i++ == Chosen) return (this->*PMF[c])();
7057 return false; // dummy
7061 truth character::ChangePetEquipment () {
7062 if (EquipmentScreen(PLAYER->GetStack(), GetStack())) {
7063 DexterityAction(3);
7064 return true;
7066 return false;
7070 truth character::TakePetItems () {
7071 truth Success = false;
7072 stack::SetSelected(0);
7073 for (;;) {
7074 itemvector ToTake;
7075 game::DrawEverythingNoBlit();
7076 GetStack()->DrawContents(
7077 ToTake,
7079 PLAYER,
7080 CONST_S("What do you want to take from ") + CHAR_DESCRIPTION(DEFINITE) + '?',
7081 CONST_S(""),
7082 CONST_S(""),
7083 GetDescription(DEFINITE) + " is " + GetVerbalBurdenState(),
7084 GetVerbalBurdenStateColor(),
7085 REMEMBER_SELECTED);
7086 if (ToTake.empty()) break;
7087 for (uInt c = 0; c < ToTake.size(); ++c) ToTake[c]->MoveTo(PLAYER->GetStack());
7088 ADD_MESSAGE("You take %s.", ToTake[0]->GetName(DEFINITE, ToTake.size()).CStr());
7089 Success = true;
7091 if (Success) {
7092 DexterityAction(2);
7093 PLAYER->DexterityAction(2);
7095 return Success;
7099 truth character::GivePetItems () {
7100 truth Success = false;
7101 stack::SetSelected(0);
7102 for (;;) {
7103 itemvector ToGive;
7104 game::DrawEverythingNoBlit();
7105 PLAYER->GetStack()->DrawContents(
7106 ToGive,
7108 this,
7109 CONST_S("What do you want to give to ") + CHAR_DESCRIPTION(DEFINITE) + '?',
7110 CONST_S(""),
7111 CONST_S(""),
7112 GetDescription(DEFINITE) + " is " + GetVerbalBurdenState(),
7113 GetVerbalBurdenStateColor(),
7114 REMEMBER_SELECTED);
7115 if (ToGive.empty()) break;
7116 for (uInt c = 0; c < ToGive.size(); ++c) ToGive[c]->MoveTo(GetStack());
7117 ADD_MESSAGE("You give %s to %s.", ToGive[0]->GetName(DEFINITE, ToGive.size()).CStr(), CHAR_DESCRIPTION(DEFINITE));
7118 Success = true;
7120 if (Success) {
7121 DexterityAction(2);
7122 PLAYER->DexterityAction(2);
7124 return Success;
7128 truth character::IssuePetCommands () {
7129 if (!IsConscious()) {
7130 ADD_MESSAGE("%s is unconscious.", CHAR_DESCRIPTION(DEFINITE));
7131 return false;
7133 feuLong PossibleC = GetPossibleCommandFlags();
7134 if (!PossibleC) {
7135 ADD_MESSAGE("%s cannot be commanded.", CHAR_DESCRIPTION(DEFINITE));
7136 return false;
7138 feuLong OldC = GetCommandFlags();
7139 feuLong NewC = OldC, VaryFlags = 0;
7140 game::CommandScreen(CONST_S("Issue commands to ")+GetDescription(DEFINITE), PossibleC, GetConstantCommandFlags(), VaryFlags, NewC);
7141 if (NewC == OldC) return false;
7142 SetCommandFlags(NewC);
7143 PLAYER->EditAP(-500);
7144 PLAYER->EditExperience(CHARISMA, 25, 1 << 7);
7145 return true;
7149 truth character::ChatIdly () {
7150 if (!TryToTalkAboutScience()) {
7151 BeTalkedTo();
7152 PLAYER->EditExperience(CHARISMA, 75, 1 << 7);
7154 PLAYER->EditAP(-1000);
7155 return true;
7159 truth character::EquipmentScreen (stack *MainStack, stack *SecStack) {
7160 if (!CanUseEquipment()) {
7161 ADD_MESSAGE("%s cannot use equipment.", CHAR_DESCRIPTION(DEFINITE));
7162 return false;
7164 int Chosen = 0;
7165 truth EquipmentChanged = false;
7166 felist List(CONST_S("Equipment menu [ESC exits]"));
7167 festring Entry;
7168 for (;;) {
7169 List.Empty();
7170 List.EmptyDescription();
7171 if (!IsPlayer()) {
7172 List.AddDescription(CONST_S(""));
7173 List.AddDescription(festring(GetDescription(DEFINITE) + " is " + GetVerbalBurdenState()).CapitalizeCopy(), GetVerbalBurdenStateColor());
7175 for (int c = 0; c < GetEquipments(); ++c) {
7176 Entry = GetEquipmentName(c);
7177 Entry << ':';
7178 Entry.Resize(20);
7179 item *Equipment = GetEquipment(c);
7180 if (Equipment) {
7181 Equipment->AddInventoryEntry(this, Entry, 1, true);
7182 AddSpecialEquipmentInfo(Entry, c);
7183 int ImageKey = game::AddToItemDrawVector(itemvector(1, Equipment));
7184 List.AddEntry(Entry, LIGHT_GRAY, 20, ImageKey, true);
7185 } else {
7186 Entry << (GetBodyPartOfEquipment(c) ? "-" : "can't use");
7187 List.AddEntry(Entry, LIGHT_GRAY, 20, game::AddToItemDrawVector(itemvector()));
7190 game::DrawEverythingNoBlit();
7191 game::SetStandardListAttributes(List);
7192 List.SetFlags(SELECTABLE|DRAW_BACKGROUND_AFTERWARDS);
7193 List.SetEntryDrawer(game::ItemEntryDrawer);
7194 Chosen = List.Draw();
7195 game::ClearItemDrawVector();
7196 if (Chosen >= GetEquipments()) break;
7197 EquipmentChanged = TryToChangeEquipment(MainStack, SecStack, Chosen);
7199 if (EquipmentChanged) DexterityAction(5);
7200 return EquipmentChanged;
7204 feuLong character::GetManagementFlags () const {
7205 feuLong Flags = ALL_MANAGEMENT_FLAGS;
7206 if (!CanUseEquipment() || !AllowPlayerToChangeEquipment()) Flags &= ~CHANGE_EQUIPMENT;
7207 if (!GetStack()->GetItems()) Flags &= ~TAKE_ITEMS;
7208 if (!WillCarryItems()) Flags &= ~GIVE_ITEMS;
7209 if (!GetPossibleCommandFlags()) Flags &= ~ISSUE_COMMANDS;
7210 return Flags;
7214 cchar *VerbalBurdenState[] = { "overloaded", "stressed", "burdened", "unburdened" };
7215 col16 VerbalBurdenStateColor[] = { RED, BLUE, BLUE, WHITE };
7217 cchar *character::GetVerbalBurdenState () const { return VerbalBurdenState[BurdenState]; }
7218 col16 character::GetVerbalBurdenStateColor () const { return VerbalBurdenStateColor[BurdenState]; }
7219 int character::GetAttributeAverage () const { return GetSumOfAttributes()/7; }
7221 cfestring &character::GetStandVerb() const {
7222 if (ForceCustomStandVerb()) return DataBase->StandVerb;
7223 static festring Hovering = "hovering";
7224 static festring Swimming = "swimming";
7225 if (StateIsActivated(LEVITATION)) return Hovering;
7226 if (IsSwimming()) return Swimming;
7227 return DataBase->StandVerb;
7231 truth character::CheckApply () const {
7232 if (!CanApply()) {
7233 ADD_MESSAGE("This monster type cannot apply.");
7234 return false;
7236 return true;
7240 void character::EndLevitation () {
7241 if (!IsFlying() && GetSquareUnder()) {
7242 if (!game::IsInWilderness()) SignalStepFrom(0);
7243 if (game::IsInWilderness() || !GetLSquareUnder()->IsFreezed()) TestWalkability();
7248 truth character::CanMove () const {
7249 return !IsRooted() || StateIsActivated(LEVITATION);
7253 void character::CalculateEnchantments () {
7254 doforequipments()(this, &item::CalculateEnchantment);
7255 GetStack()->CalculateEnchantments();
7259 truth character::GetNewFormForPolymorphWithControl (character *&NewForm) {
7260 festring Topic, Temp;
7261 NewForm = 0;
7262 while (!NewForm) {
7263 festring Temp = game::DefaultQuestion(CONST_S("What do you want to become? [press '?' for a list]"), game::GetDefaultPolymorphTo(), &game::PolymorphControlKeyHandler);
7264 NewForm = protosystem::CreateMonster(Temp);
7265 if (NewForm) {
7266 if (NewForm->IsSameAs(this)) {
7267 delete NewForm;
7268 ADD_MESSAGE("You choose not to polymorph.");
7269 NewForm = this;
7270 return false;
7272 if (PolymorphBackup && NewForm->IsSameAs(PolymorphBackup)) {
7273 delete NewForm;
7274 NewForm = ForceEndPolymorph();
7275 return false;
7277 if (NewForm->GetPolymorphIntelligenceRequirement() > GetAttribute(INTELLIGENCE) && !game::WizardModeIsActive()) {
7278 ADD_MESSAGE("You feel your mind isn't yet powerful enough to call forth the form of %s.", NewForm->CHAR_NAME(INDEFINITE));
7279 delete NewForm;
7280 NewForm = 0;
7281 } else {
7282 NewForm->RemoveAllItems();
7286 return true;
7290 liquid *character::CreateSweat(sLong Volume) const {
7291 //return liquid::Spawn(GetSweatMaterial(), Volume);
7292 return liquid::Spawn(GetCurrentSweatMaterial(), Volume);
7296 truth character::TeleportRandomItem (truth TryToHinderVisibility) {
7297 if (IsImmuneToItemTeleport()) return false;
7298 itemvector ItemVector;
7299 std::vector<sLong> PossibilityVector;
7300 int TotalPossibility = 0;
7301 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) {
7302 ItemVector.push_back(*i);
7303 int Possibility = i->GetTeleportPriority();
7304 if (TryToHinderVisibility) Possibility += i->GetHinderVisibilityBonus(this);
7305 PossibilityVector.push_back(Possibility);
7306 TotalPossibility += Possibility;
7308 for (int c = 0; c < GetEquipments(); ++c) {
7309 item *Equipment = GetEquipment(c);
7310 if (Equipment) {
7311 ItemVector.push_back(Equipment);
7312 int Possibility = Equipment->GetTeleportPriority();
7313 if (TryToHinderVisibility) Possibility += Equipment->GetHinderVisibilityBonus(this);
7314 PossibilityVector.push_back(Possibility <<= 1);
7315 TotalPossibility += Possibility;
7318 if (!TotalPossibility) return false;
7319 int Chosen = femath::WeightedRand(PossibilityVector, TotalPossibility);
7320 item *Item = ItemVector[Chosen];
7321 truth Equipped = PLAYER->Equips(Item);
7322 truth Seen = Item->CanBeSeenByPlayer();
7323 Item->RemoveFromSlot();
7324 if (Seen) ADD_MESSAGE("%s disappears.", Item->CHAR_NAME(DEFINITE));
7325 if (Equipped) game::AskForEscPress(CONST_S("Equipment lost!"));
7326 v2 Pos = GetPos();
7327 int Range = Item->GetEmitation() && TryToHinderVisibility ? 25 : 5;
7328 rect Border(Pos + v2(-Range, -Range), Pos + v2(Range, Range));
7329 Pos = GetLevel()->GetRandomSquare(this, 0, &Border);
7330 if (Pos == ERROR_V2) Pos = GetLevel()->GetRandomSquare();
7331 GetNearLSquare(Pos)->GetStack()->AddItem(Item);
7332 if (Item->CanBeSeenByPlayer()) ADD_MESSAGE("%s appears.", Item->CHAR_NAME(INDEFINITE));
7333 return true;
7337 truth character::HasClearRouteTo (v2 Pos) const {
7338 pathcontroller::Map = GetLevel()->GetMap();
7339 pathcontroller::Character = this;
7340 v2 ThisPos = GetPos();
7341 return mapmath<pathcontroller>::DoLine(ThisPos.X, ThisPos.Y, Pos.X, Pos.Y, SKIP_FIRST);
7345 truth character::IsTransparent () const {
7346 return !IsEnormous() || GetTorso()->GetMainMaterial()->IsTransparent() || StateIsActivated(INVISIBLE);
7350 void character::SignalPossibleTransparencyChange () {
7351 if (!game::IsInWilderness()) {
7352 for (int c = 0; c < SquaresUnder; ++c) {
7353 lsquare *Square = GetLSquareUnder(c);
7354 if (Square) Square->SignalPossibleTransparencyChange();
7360 int character::GetCursorData () const {
7361 int Bad = 0;
7362 int Color = game::PlayerIsRunning() ? BLUE_CURSOR : DARK_CURSOR;
7363 for (int c = 0; c < BodyParts; ++c) {
7364 bodypart *BodyPart = GetBodyPart(c);
7365 if (BodyPart && BodyPart->IsUsable()) {
7366 int ConditionColorIndex = BodyPart->GetConditionColorIndex();
7367 if ((BodyPartIsVital(c) && !ConditionColorIndex) || (ConditionColorIndex <= 1 && ++Bad == 2)) return Color|CURSOR_FLASH;
7368 } else if (++Bad == 2) return Color|CURSOR_FLASH;
7370 Color = game::PlayerIsRunning() ? YELLOW_CURSOR : RED_CURSOR;
7371 return Bad ? Color|CURSOR_FLASH : Color;
7375 void character::TryToName () {
7376 if (!IsPet()) ADD_MESSAGE("%s refuses to let YOU decide what %s's called.", CHAR_NAME(DEFINITE), CHAR_PERSONAL_PRONOUN);
7377 else if (IsPlayer()) ADD_MESSAGE("You can't rename yourself.");
7378 else if (!IsNameable()) ADD_MESSAGE("%s refuses to be called anything else but %s.", CHAR_NAME(DEFINITE), CHAR_NAME(DEFINITE));
7379 else {
7380 festring Topic = CONST_S("What name will you give to ")+GetName(DEFINITE)+'?';
7381 festring Name = game::StringQuestion(Topic, WHITE, 0, 80, true);
7382 if (Name.GetSize()) SetAssignedName(Name);
7387 double character::GetSituationDanger (ccharacter *Enemy, v2 ThisPos, v2 EnemyPos, truth SeesEnemy) const {
7388 double Danger;
7389 if (IgnoreDanger() && !IsPlayer()) {
7390 if (Enemy->IgnoreDanger() && !Enemy->IsPlayer()) {
7391 Danger = double(GetHP())*GetHPRequirementForGeneration()/(Enemy->GetHP()*Enemy->GetHPRequirementForGeneration());
7393 else {
7394 Danger = 0.25*GetHPRequirementForGeneration()/Enemy->GetHP();
7396 } else if (Enemy->IgnoreDanger() && !Enemy->IsPlayer()) {
7397 Danger = 4.0*GetHP()/Enemy->GetHPRequirementForGeneration();
7398 } else {
7399 Danger = GetRelativeDanger(Enemy);
7401 Danger *= 3.0/((EnemyPos-ThisPos).GetManhattanLength()+2);
7402 if (!SeesEnemy) Danger *= 0.2;
7403 if (StateIsActivated(PANIC)) Danger *= 0.2;
7404 Danger *= double(GetHP())*Enemy->GetMaxHP()/(Enemy->GetHP()*GetMaxHP());
7405 return Danger;
7409 void character::ModifySituationDanger (double &Danger) const {
7410 switch (GetTirednessState()) {
7411 case FAINTING: Danger *= 1.5;
7412 case EXHAUSTED: Danger *= 1.25;
7414 for (int c = 0; c < STATES; ++c) {
7415 if (StateIsActivated(1 << c) && StateData[c].SituationDangerModifier != 0) (this->*StateData[c].SituationDangerModifier)(Danger);
7420 void character::LycanthropySituationDangerModifier (double &Danger) const {
7421 character *Wolf = werewolfwolf::Spawn();
7422 double DangerToWolf = GetRelativeDanger(Wolf);
7423 Danger *= pow(DangerToWolf, 0.1);
7424 delete Wolf;
7428 void character::PoisonedSituationDangerModifier (double &Danger) const {
7429 int C = GetTemporaryStateCounter(POISONED);
7430 Danger *= (1+(C*C)/(GetHP()*10000.0*(GetGlobalResistance(POISON)+1)));
7434 void character::PolymorphingSituationDangerModifier (double &Danger) const {
7435 if (!StateIsActivated(POLYMORPH_CONTROL)) Danger *= 1.5;
7439 void character::PanicSituationDangerModifier (double &Danger) const {
7440 Danger *= 1.5;
7444 void character::ConfusedSituationDangerModifier (double &Danger) const {
7445 Danger *= 1.5;
7449 void character::ParasitizedSituationDangerModifier (double &Danger) const {
7450 Danger *= 1.25;
7454 void character::LeprosySituationDangerModifier (double &Danger) const {
7455 Danger *= 1.5;
7459 void character::AddRandomScienceName (festring &String) const {
7460 festring Science = GetScienceTalkName().GetRandomElement().CStr();
7461 if (Science[0] == '!') {
7462 String << Science.CStr()+1;
7463 return;
7465 festring Attribute = GetScienceTalkAdjectiveAttribute().GetRandomElement();
7466 festring Prefix;
7467 truth NoAttrib = Attribute.IsEmpty(), NoSecondAdjective = false;
7468 if (!Attribute.IsEmpty() && Attribute[0] == '!') {
7469 NoSecondAdjective = true;
7470 Attribute.Erase(0, 1);
7472 if (!Science.Find("the ")) {
7473 Science.Erase(0, 4);
7474 if (!Attribute.Find("the ", 0, 4)) Attribute << " the"; else Attribute.Insert(0, "the ", 4);
7476 if (islower(Science[0]) && Science.Find(' ') == festring::NPos && Science.Find('-') == festring::NPos &&
7477 Science.Find("phobia") == festring::NPos) {
7478 Prefix = GetScienceTalkPrefix().GetRandomElement();
7479 if (!Prefix.IsEmpty() && Science.Find(Prefix) != festring::NPos) Prefix.Empty();
7481 int L = Prefix.GetSize();
7482 if (L && Prefix[L-1] == Science[0]) Science.Erase(0, 1);
7483 if (!NoAttrib && !NoSecondAdjective == !RAND_GOOD(3)) {
7484 int S1 = NoSecondAdjective ? 0 : GetScienceTalkAdjectiveAttribute().Size;
7485 int S2 = GetScienceTalkSubstantiveAttribute().Size;
7486 festring OtherAttribute;
7487 int Chosen = RAND_GOOD(S1+S2);
7488 if (Chosen < S1) OtherAttribute = GetScienceTalkAdjectiveAttribute()[Chosen];
7489 else OtherAttribute = GetScienceTalkSubstantiveAttribute()[Chosen - S1];
7490 if (!OtherAttribute.IsEmpty() && OtherAttribute.Find("the ", 0, 4) && Attribute.Find(OtherAttribute) == festring::NPos) {
7491 String << Attribute << ' ' << OtherAttribute << ' ' << Prefix << Science;
7492 return;
7495 String << Attribute;
7496 if (!NoAttrib) String << ' ';
7497 String << Prefix << Science;
7501 truth character::TryToTalkAboutScience () {
7502 if (GetRelation(PLAYER) == HOSTILE ||
7503 GetScienceTalkPossibility() <= RAND_GOOD(100) ||
7504 PLAYER->GetAttribute(INTELLIGENCE) < GetScienceTalkIntelligenceRequirement() ||
7505 PLAYER->GetAttribute(WISDOM) < GetScienceTalkWisdomRequirement() ||
7506 PLAYER->GetAttribute(CHARISMA) < GetScienceTalkCharismaRequirement())
7507 return false;
7508 festring Science;
7509 if (RAND_GOOD(3)) {
7510 AddRandomScienceName(Science);
7511 } else {
7512 festring S1, S2;
7513 AddRandomScienceName(S1);
7514 AddRandomScienceName(S2);
7515 if (S1.Find(S2) == festring::NPos && S2.Find(S1) == festring::NPos) {
7516 switch (RAND_GOOD(3)) {
7517 case 0: Science = "the relation of "; break;
7518 case 1: Science = "the differences of "; break;
7519 case 2: Science = "the similarities of "; break;
7521 Science << S1 << " and " << S2;
7523 else {
7524 AddRandomScienceName(Science);
7527 switch ((RAND() + GET_TICK()) % 10) {
7528 case 0:
7529 ADD_MESSAGE("You have a rather pleasant chat about %s with %s.", Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
7530 break;
7531 case 1:
7532 ADD_MESSAGE("%s explains a few of %s opinions regarding %s to you.", CHAR_DESCRIPTION(DEFINITE), CHAR_POSSESSIVE_PRONOUN, Science.CStr());
7533 break;
7534 case 2:
7535 ADD_MESSAGE("%s reveals a number of %s insightful views of %s to you.", CHAR_DESCRIPTION(DEFINITE), CHAR_POSSESSIVE_PRONOUN, Science.CStr());
7536 break;
7537 case 3:
7538 ADD_MESSAGE("You exhange some information pertaining to %s with %s.", Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
7539 break;
7540 case 4:
7541 ADD_MESSAGE("You engage in a pretty intriguing conversation about %s with %s.", Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
7542 break;
7543 case 5:
7544 ADD_MESSAGE("You discuss at length about %s with %s.", Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
7545 break;
7546 case 6:
7547 ADD_MESSAGE("You have a somewhat boring talk concerning %s with %s.", Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
7548 break;
7549 case 7:
7550 ADD_MESSAGE("You are drawn into a heated argument regarding %s with %s.", Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
7551 break;
7552 case 8:
7553 ADD_MESSAGE("%s delivers a sLong monologue concerning eg. %s.", CHAR_DESCRIPTION(DEFINITE), Science.CStr());
7554 break;
7555 case 9:
7556 ADD_MESSAGE("You dive into a brief but thought-provoking debate over %s with %s", Science.CStr(), CHAR_DESCRIPTION(DEFINITE));
7557 break;
7559 PLAYER->EditExperience(INTELLIGENCE, 1000, 50. * GetScienceTalkIntelligenceModifier() / ++ScienceTalks);
7560 PLAYER->EditExperience(WISDOM, 1000, 50. * GetScienceTalkWisdomModifier() / ++ScienceTalks);
7561 PLAYER->EditExperience(CHARISMA, 1000, 50. * GetScienceTalkCharismaModifier() / ++ScienceTalks);
7562 return true;
7566 truth character::IsUsingWeaponOfCategory (int Category) const {
7567 return
7568 ((GetMainWielded() && GetMainWielded()->GetWeaponCategory() == Category) ||
7569 (GetSecondaryWielded() && GetSecondaryWielded()->GetWeaponCategory() == Category));
7573 truth character::TryToUnStickTraps (v2 Dir) {
7574 if (!TrapData) return true;
7575 std::vector<trapdata> TrapVector;
7576 for (const trapdata *T = TrapData; T; T = T->Next) TrapVector.push_back(*TrapData);
7577 for (uInt c = 0; c < TrapVector.size(); ++c) {
7578 if (IsEnabled()) {
7579 entity *Trap = game::SearchTrap(TrapVector[c].TrapID);
7580 /*k8:??? if(!Trap->Exists()) int esko = esko = 2; */
7581 if (!Trap->Exists()) continue; /*k8: ??? added by me; what this means? */
7582 if (Trap->GetVictimID() == GetID() && Trap->TryToUnStick(this, Dir)) break;
7585 return !TrapData && IsEnabled();
7589 struct trapidcomparer {
7590 trapidcomparer (feuLong ID) : ID(ID) {}
7591 truth operator () (const trapdata *T) const { return T->TrapID == ID; }
7592 feuLong ID;
7596 void character::RemoveTrap (feuLong ID) {
7597 trapdata *&T = ListFind(TrapData, trapidcomparer(ID));
7598 T = T->Next;
7599 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange);
7603 void character::AddTrap (feuLong ID, feuLong BodyParts) {
7604 trapdata *&T = ListFind(TrapData, trapidcomparer(ID));
7605 if (T) T->BodyParts |= BodyParts;
7606 else T = new trapdata(ID, GetID(), BodyParts);
7607 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange);
7611 truth character::IsStuckToTrap (feuLong ID) const {
7612 for (const trapdata *T = TrapData; T; T = T->Next) if (T->TrapID == ID) return true;
7613 return false;
7617 void character::RemoveTraps () {
7618 for (trapdata *T = TrapData; T;) {
7619 entity *Trap = game::SearchTrap(T->TrapID);
7620 if (Trap) Trap->UnStick();
7621 trapdata *ToDel = T;
7622 T = T->Next;
7623 delete ToDel;
7625 TrapData = 0;
7626 doforbodyparts()(this, &bodypart::SignalPossibleUsabilityChange);
7630 void character::RemoveTraps (int BodyPartIndex) {
7631 feuLong Flag = 1 << BodyPartIndex;
7632 for (trapdata **T = &TrapData; *T;) {
7633 if ((*T)->BodyParts & Flag) {
7634 entity *Trap = game::SearchTrap((*T)->TrapID);
7635 if (!((*T)->BodyParts &= ~Flag)) {
7636 if (Trap) Trap->UnStick();
7637 trapdata *ToDel = *T;
7638 *T = (*T)->Next;
7639 delete ToDel;
7640 } else {
7641 if (Trap) Trap->UnStick(BodyPartIndex);
7642 T = &(*T)->Next;
7645 else {
7646 T = &(*T)->Next;
7649 if (GetBodyPart(BodyPartIndex)) GetBodyPart(BodyPartIndex)->SignalPossibleUsabilityChange();
7653 festring character::GetTrapDescription () const {
7654 festring Desc;
7655 std::pair<entity *, int> TrapStack[3];
7656 int Index = 0;
7657 for (const trapdata *T = TrapData; T; T = T->Next) {
7658 if (Index < 3) {
7659 entity *Trap = game::SearchTrap(T->TrapID);
7660 if (Trap) {
7661 int c;
7662 for (c = 0; c < Index; ++c) if (TrapStack[c].first->GetTrapType() == Trap->GetTrapType()) ++TrapStack[c].second;
7663 if (c == Index) TrapStack[Index++] = std::make_pair(Trap, 1);
7665 } else {
7666 ++Index;
7667 break;
7670 if (Index <= 3) {
7671 TrapStack[0].first->AddTrapName(Desc, TrapStack[0].second);
7672 if (Index == 2) {
7673 Desc << " and ";
7674 TrapStack[1].first->AddTrapName(Desc, TrapStack[1].second);
7675 } else if (Index == 3) {
7676 Desc << ", ";
7677 TrapStack[1].first->AddTrapName(Desc, TrapStack[1].second);
7678 Desc << " and ";
7679 TrapStack[2].first->AddTrapName(Desc, TrapStack[2].second);
7681 } else {
7682 Desc << "lots of traps";
7684 return Desc;
7688 int character::RandomizeHurtBodyPart (feuLong BodyParts) const {
7689 int BodyPartIndex[MAX_BODYPARTS];
7690 int Index = 0;
7691 for (int c = 0; c < GetBodyParts(); ++c) {
7692 if (1 << c & BodyParts) {
7693 /*k8: ??? if(!GetBodyPart(c)) int esko = esko = 2; */
7694 if (!GetBodyPart(c)) continue;
7695 BodyPartIndex[Index++] = c;
7697 /*k8: ??? if(!Index) int esko = esko = 2;*/
7699 if (!Index) {
7700 fprintf(stderr, "FATAL: RandomizeHurtBodyPart -- Index==0\n");
7701 abort();
7703 return BodyPartIndex[RAND_N(Index)];
7707 truth character::BodyPartIsStuck (int I) const {
7708 for (const trapdata *T = TrapData; T; T = T->Next) if (1 << I & T->BodyParts) return true;
7709 return false;
7713 void character::PrintAttribute (cchar *Desc, int I, int PanelPosX, int PanelPosY) const {
7714 int Attribute = GetAttribute(I);
7715 int NoBonusAttribute = GetAttribute(I, false);
7716 col16 C = game::GetAttributeColor(I);
7717 festring String = Desc;
7718 String.Resize(5);
7719 String << Attribute;
7720 String.Resize(8);
7721 FONT->Printf(DOUBLE_BUFFER, v2(PanelPosX, PanelPosY * 10), C, "%s", String.CStr());
7722 if (Attribute != NoBonusAttribute) {
7723 int Where = PanelPosX + ((String.GetSize() + 1) << 3);
7724 FONT->Printf(DOUBLE_BUFFER, v2(Where, PanelPosY * 10), LIGHT_GRAY, "%d", NoBonusAttribute);
7729 truth character::AllowUnconsciousness () const {
7730 return DataBase->AllowUnconsciousness && TorsoIsAlive();
7734 truth character::CanPanic () const {
7735 return !Action || !Action->IsUnconsciousness();
7739 int character::GetRandomBodyPart (feuLong Possible) const {
7740 int OKBodyPart[MAX_BODYPARTS];
7741 int OKBodyParts = 0;
7742 for (int c = 0; c < BodyParts; ++c) if (1 << c & Possible && GetBodyPart(c)) OKBodyPart[OKBodyParts++] = c;
7743 return OKBodyParts ? OKBodyPart[RAND_N(OKBodyParts)] : NONE_INDEX;
7747 void character::EditNP (sLong What) {
7748 int OldState = GetHungerState();
7749 NP += What;
7750 int NewState = GetHungerState();
7751 if (OldState > VERY_HUNGRY && NewState == VERY_HUNGRY) DeActivateVoluntaryAction(CONST_S("You are getting really hungry."));
7752 if (OldState > STARVING && NewState == STARVING) DeActivateVoluntaryAction(CONST_S("You are getting extremely hungry."));
7756 truth character::IsSwimming () const {
7757 return !IsFlying() && GetSquareUnder() && GetSquareUnder()->GetSquareWalkability() & SWIM;
7761 void character::AddBlackUnicornConsumeEndMessage () const {
7762 if (IsPlayer()) ADD_MESSAGE("You feel dirty and loathsome.");
7766 void character::AddGrayUnicornConsumeEndMessage () const {
7767 if (IsPlayer()) ADD_MESSAGE("You feel neutralized.");
7771 void character::AddWhiteUnicornConsumeEndMessage () const {
7772 if (IsPlayer()) ADD_MESSAGE("You feel purified.");
7776 void character::AddOmmelBoneConsumeEndMessage () const {
7777 if (IsPlayer()) ADD_MESSAGE("You feel the power of all your canine ancestors combining in your body.");
7778 else if (CanBeSeenByPlayer()) ADD_MESSAGE("For a moment %s looks extremely ferocious. You shudder.", CHAR_NAME(DEFINITE));
7782 void character::AddLiquidHorrorConsumeEndMessage () const {
7783 if (IsPlayer()) ADD_MESSAGE("Untold horrors flash before your eyes. The melancholy of the world is on your shoulders!");
7784 else if (CanBeSeenByPlayer()) ADD_MESSAGE("%s looks as if the melancholy of the world is on %s shoulders!.", CHAR_NAME(DEFINITE), GetPossessivePronoun().CStr());
7788 int character::GetBodyPartSparkleFlags (int) const {
7789 return
7790 ((GetNaturalSparkleFlags() & SKIN_COLOR ? SPARKLING_A : 0) |
7791 (GetNaturalSparkleFlags() & TORSO_MAIN_COLOR ? SPARKLING_B : 0) |
7792 (GetNaturalSparkleFlags() & TORSO_SPECIAL_COLOR ? SPARKLING_D : 0));
7796 truth character::IsAnimated () const {
7797 return combinebodypartpredicates()(this, &bodypart::IsAnimated, 1);
7801 double character::GetNaturalExperience (int Identifier) const {
7802 return DataBase->NaturalExperience[Identifier];
7806 truth character::HasBodyPart (sorter Sorter) const {
7807 if (Sorter == 0) return true;
7808 return combinebodypartpredicateswithparam<ccharacter*>()(this, Sorter, this, 1);
7812 truth character::PossessesItem (sorter Sorter) const {
7813 if (Sorter == 0) return true;
7814 return
7815 (GetStack()->SortedItems(this, Sorter) ||
7816 combinebodypartpredicateswithparam<ccharacter*>()(this, Sorter, this, 1) ||
7817 combineequipmentpredicateswithparam<ccharacter*>()(this, Sorter, this, 1));
7821 truth character::MoreThanOnePossessesItem (sorter Sorter) const {
7822 if (Sorter) {
7823 int count = 0;
7825 for (int c = 0; c < BodyParts; ++c) {
7826 bodypart *BodyPart = GetBodyPart(c);
7828 if (BodyPart && (Sorter == 0 || (BodyPart->*Sorter)(this))) {
7829 if (++count > 1) return true;
7832 for (int c = 0; c < GetEquipments(); ++c) {
7833 item *Equipment = GetEquipment(c);
7835 if (Equipment && (Sorter == 0 || (Equipment->*Sorter)(this))) {
7836 if (++count > 1) return true;
7839 for (int c = 0; c < GetStack()->GetItems(); ++c) {
7840 item *Stk = GetStack()->GetItem(c);
7842 if (Stk && (Sorter == 0 || (Stk->*Sorter)(this))) {
7843 if (++count > 1) return true;
7846 return false;
7848 return false;
7852 item *character::FirstPossessesItem (sorter Sorter) const {
7853 if (Sorter) {
7854 for (int c = 0; c < BodyParts; ++c) {
7855 bodypart *BodyPart = GetBodyPart(c);
7857 if (BodyPart && (Sorter == 0 || (BodyPart->*Sorter)(this))) return BodyPart;
7859 for (int c = 0; c < GetEquipments(); ++c) {
7860 item *Equipment = GetEquipment(c);
7862 if (Equipment && (Sorter == 0 || (Equipment->*Sorter)(this))) return Equipment;
7864 for (int c = 0; c < GetStack()->GetItems(); ++c) {
7865 item *Stk = GetStack()->GetItem(c);
7867 if (Stk && (Sorter == 0 || (Stk->*Sorter)(this))) return Stk;
7870 return 0;
7874 /* 0 <= I <= 1 */
7875 cchar *character::GetRunDescriptionLine (int I) const {
7876 if (!GetRunDescriptionLineOne().IsEmpty()) return !I ? GetRunDescriptionLineOne().CStr() : GetRunDescriptionLineTwo().CStr();
7877 if (IsFlying()) return !I ? "Flying" : "very fast";
7878 if (IsSwimming()) return !I ? "Swimming" : "very fast";
7879 return !I ? "Running" : "";
7883 void character::VomitAtRandomDirection (int Amount) {
7884 if (game::IsInWilderness()) return;
7885 /* Lacks support of multitile monsters */
7886 v2 Possible[9];
7887 int Index = 0;
7888 for (int d = 0; d < 9; ++d) {
7889 lsquare *Square = GetLSquareUnder()->GetNeighbourLSquare(d);
7890 if (Square && !Square->VomitingIsDangerous(this)) Possible[Index++] = Square->GetPos();
7892 if (Index) Vomit(Possible[RAND_N(Index)], Amount);
7893 else Vomit(GetPos(), Amount);
7897 void character::RemoveLifeSavers () {
7898 for (int c = 0; c < GetEquipments(); ++c) {
7899 item *Equipment = GetEquipment(c);
7900 if (Equipment && Equipment->IsInCorrectSlot(c) && Equipment->GetGearStates() & LIFE_SAVED) {
7901 Equipment->SendToHell();
7902 Equipment->RemoveFromSlot();
7908 ccharacter *character::FindCarrier () const {
7909 return this; //check
7913 void character::PrintBeginHiccupsMessage () const {
7914 if (IsPlayer()) ADD_MESSAGE("Your diaphragm is spasming vehemently.");
7918 void character::PrintEndHiccupsMessage () const {
7919 if (IsPlayer()) ADD_MESSAGE("You feel your annoying hiccoughs have finally subsided.");
7923 void character::HiccupsHandler () {
7925 if (!(RAND() % 2000)) {
7926 if (IsPlayer()) ADD_MESSAGE("");
7927 else if (CanBeSeenByPlayer()) ADD_MESSAGE("");
7928 else if ((PLAYER->GetPos()-GetPos()).GetLengthSquare() <= 400) ADD_MESSAGE("");
7929 game::CallForAttention(GetPos(), 400);
7935 void character::VampirismHandler () {
7936 //EditExperience(ARM_STRENGTH, -25, 1 << 1);
7937 //EditExperience(LEG_STRENGTH, -25, 1 << 1);
7938 //EditExperience(DEXTERITY, -25, 1 << 1);
7939 //EditExperience(AGILITY, -25, 1 << 1);
7940 //EditExperience(ENDURANCE, -25, 1 << 1);
7941 EditExperience(CHARISMA, -25, 1 << 1);
7942 EditExperience(WISDOM, -25, 1 << 1);
7943 EditExperience(INTELLIGENCE, -25, 1 << 1);
7944 CheckDeath(CONST_S("killed by vampirism"));
7948 void character::HiccupsSituationDangerModifier (double &Danger) const {
7949 Danger *= 1.25;
7953 void character::VampirismSituationDangerModifier (double &Danger) const {
7954 character *Vampire = vampire::Spawn();
7955 double DangerToVampire = GetRelativeDanger(Vampire);
7956 Danger *= pow(DangerToVampire, 0.1);
7957 delete Vampire;
7961 bool character::IsConscious () const {
7962 return !Action || !Action->IsUnconsciousness();
7966 wsquare *character::GetNearWSquare (v2 Pos) const {
7967 return static_cast<wsquare *>(GetSquareUnder()->GetArea()->GetSquare(Pos));
7971 wsquare *character::GetNearWSquare (int x, int y) const {
7972 return static_cast<wsquare *>(GetSquareUnder()->GetArea()->GetSquare(x, y));
7976 void character::ForcePutNear (v2 Pos) {
7977 /* GUM SOLUTION!!! */
7978 v2 NewPos = game::GetCurrentLevel()->GetNearestFreeSquare(PLAYER, Pos, false);
7979 if (NewPos == ERROR_V2) do { NewPos = game::GetCurrentLevel()->GetRandomSquare(this); } while(NewPos == Pos);
7980 PutTo(NewPos);
7984 void character::ReceiveMustardGas (int BodyPart, sLong Volume) {
7985 if (Volume) GetBodyPart(BodyPart)->AddFluid(liquid::Spawn(MUSTARD_GAS_LIQUID, Volume), CONST_S("skin"), 0, true);
7989 void character::ReceiveMustardGasLiquid (int BodyPartIndex, sLong Modifier) {
7990 bodypart *BodyPart = GetBodyPart(BodyPartIndex);
7991 if (BodyPart->GetMainMaterial()->GetInteractionFlags() & IS_AFFECTED_BY_MUSTARD_GAS) {
7992 sLong Tries = Modifier;
7993 Modifier -= Tries; //opt%?
7994 int Damage = 0;
7995 for (sLong c = 0; c < Tries; ++c) if (!(RAND() % 100)) ++Damage;
7996 if (Modifier && !(RAND() % 1000 / Modifier)) ++Damage;
7997 if (Damage) {
7998 feuLong Minute = game::GetTotalMinutes();
7999 if (GetLastAcidMsgMin() != Minute && (CanBeSeenByPlayer() || IsPlayer())) {
8000 SetLastAcidMsgMin(Minute);
8001 if (IsPlayer()) ADD_MESSAGE("Mustard gas dissolves the skin of your %s.", BodyPart->GetBodyPartName().CStr());
8002 else ADD_MESSAGE("Mustard gas dissolves %s.", CHAR_NAME(DEFINITE));
8004 ReceiveBodyPartDamage(0, Damage, MUSTARD_GAS_DAMAGE, BodyPartIndex, YOURSELF, false, false, false);
8005 CheckDeath(CONST_S("killed by a fatal exposure to mustard gas"));
8011 truth character::IsBadPath (v2 Pos) const {
8012 if (!IsGoingSomeWhere()) return false;
8013 v2 TPos = !Route.empty() ? Route.back() : GoingTo;
8014 return ((TPos - Pos).GetManhattanLength() > (TPos - GetPos()).GetManhattanLength());
8018 double &character::GetExpModifierRef (expid E) {
8019 return ExpModifierMap.insert(std::make_pair(E, 1.)).first->second;
8023 /* Should probably do more. Now only makes Player forget gods */
8024 truth character::ForgetRandomThing () {
8025 if (IsPlayer()) {
8026 /* hopefully this code isn't some where else */
8027 std::vector<god *> Known;
8028 for (int c = 1; c <= GODS; ++c) if (game::GetGod(c)->IsKnown()) Known.push_back(game::GetGod(c));
8029 if (Known.empty()) return false;
8030 int RandomGod = RAND_N(Known.size());
8031 Known.at(RAND_N(Known.size()))->SetIsKnown(false);
8032 ADD_MESSAGE("You forget how to pray to %s.", Known.at(RandomGod)->GetName());
8033 return true;
8035 return false;
8039 int character::CheckForBlock (character *Enemy, item *Weapon, double ToHitValue, int Damage, int Success, int Type) {
8040 return Damage;
8044 void character::ApplyAllGodsKnownBonus () {
8045 stack *AddPlace = GetStackUnder();
8046 if (game::IsInWilderness()) AddPlace = GetStack(); else AddPlace = GetStackUnder();
8047 pantheonbook *NewBook = pantheonbook::Spawn();
8048 AddPlace->AddItem(NewBook);
8049 ADD_MESSAGE("\"MORTAL! BEHOLD THE HOLY SAGA\"");
8050 ADD_MESSAGE("%s materializes near your feet.", NewBook->CHAR_NAME(INDEFINITE));
8054 void character::ReceiveSirenSong (character *Siren) {
8055 if (Siren->GetTeam() == GetTeam()) return;
8056 if (!RAND_N(4)) {
8057 if (IsPlayer()) ADD_MESSAGE("The beautiful melody of %s makes you feel sleepy.", Siren->CHAR_NAME(DEFINITE));
8058 else if (CanBeSeenByPlayer()) ADD_MESSAGE("The beautiful melody of %s makes %s look sleepy.", Siren->CHAR_NAME(DEFINITE), CHAR_NAME(DEFINITE)); /*k8*/
8059 Stamina -= (1 + RAND_N(4)) * 10000;
8060 return;
8062 if (!IsPlayer() && IsCharmable() && !RAND_N(5)) {
8063 ChangeTeam(Siren->GetTeam());
8064 ADD_MESSAGE("%s seems to be totally brainwashed by %s melodies.", CHAR_NAME(DEFINITE), Siren->CHAR_NAME(DEFINITE));
8065 return;
8067 if (!RAND_N(4)) {
8068 item *What = GiveMostExpensiveItem(Siren);
8069 if (What) {
8070 if (IsPlayer()) {
8071 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);
8072 } else {
8073 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);
8075 } else {
8076 if (IsPlayer()) ADD_MESSAGE("You would like to give something to %s.", Siren->CHAR_NAME(DEFINITE));
8078 return;
8083 // return 0, if no item found
8084 item *character::FindMostExpensiveItem () const {
8085 int MaxPrice = -1;
8086 item *MostExpensive = 0;
8087 for (stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i) {
8088 if ((*i)->GetPrice() > MaxPrice) {
8089 MaxPrice = (*i)->GetPrice();
8090 MostExpensive = (*i);
8093 for (int c = 0; c < GetEquipments(); ++c) {
8094 item *Equipment = GetEquipment(c);
8095 if (Equipment && Equipment->GetPrice() > MaxPrice) {
8096 MaxPrice = Equipment->GetPrice();
8097 MostExpensive = Equipment;
8100 return MostExpensive;
8104 // returns 0 if no items available
8105 item *character::GiveMostExpensiveItem(character *ToWhom) {
8106 item *ToGive = FindMostExpensiveItem();
8107 if (!ToGive) return 0;
8108 truth Equipped = PLAYER->Equips(ToGive);
8109 ToGive->RemoveFromSlot();
8110 if (Equipped) game::AskForEscPress(CONST_S("Equipment lost!"));
8111 ToWhom->ReceiveItemAsPresent(ToGive);
8112 EditAP(-1000);
8113 return ToGive;
8117 void character::ReceiveItemAsPresent (item *Present) {
8118 if (TestForPickup(Present)) GetStack()->AddItem(Present); else GetStackUnder()->AddItem(Present);
8122 /* returns 0 if no enemies in sight */
8123 character *character::GetNearestEnemy () const {
8124 character *NearestEnemy = 0;
8125 sLong NearestEnemyDistance = 0x7FFFFFFF;
8126 v2 Pos = GetPos();
8127 for (int c = 0; c < game::GetTeams(); ++c) {
8128 if (GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE) {
8129 for (std::list<character*>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i) {
8130 if ((*i)->IsEnabled()) {
8131 sLong ThisDistance = Max<sLong>(abs((*i)->GetPos().X - Pos.X), abs((*i)->GetPos().Y - Pos.Y));
8132 if ((ThisDistance < NearestEnemyDistance || (ThisDistance == NearestEnemyDistance && !(RAND() % 3))) && (*i)->CanBeSeenBy(this)) {
8133 NearestEnemy = *i;
8134 NearestEnemyDistance = ThisDistance;
8140 return NearestEnemy;
8144 truth character::MindWormCanPenetrateSkull (mindworm *) const {
8145 return false;
8149 truth character::CanTameWithDulcis (const character *Tamer) const {
8150 int TamingDifficulty = GetTamingDifficulty();
8151 if (TamingDifficulty == NO_TAMING) return false;
8152 if (GetAttachedGod() == DULCIS) return true;
8153 int Modifier = Tamer->GetAttribute(WISDOM) + Tamer->GetAttribute(CHARISMA);
8154 if (Tamer->IsPlayer()) Modifier += game::GetGod(DULCIS)->GetRelation() / 20;
8155 else if (Tamer->GetAttachedGod() == DULCIS) Modifier += 50;
8156 if (TamingDifficulty == 0) {
8157 if (!IgnoreDanger()) TamingDifficulty = int(10 * GetRelativeDanger(Tamer));
8158 else TamingDifficulty = 10 * GetHPRequirementForGeneration()/Max(Tamer->GetHP(), 1);
8160 return Modifier >= TamingDifficulty * 3;
8164 truth character::CanTameWithLyre (const character *Tamer) const {
8165 int TamingDifficulty = GetTamingDifficulty();
8166 if (TamingDifficulty == NO_TAMING) return false;
8167 if (TamingDifficulty == 0) {
8168 if (!IgnoreDanger()) TamingDifficulty = int(10 * GetRelativeDanger(Tamer));
8169 else TamingDifficulty = 10*GetHPRequirementForGeneration()/Max(Tamer->GetHP(), 1);
8171 return Tamer->GetAttribute(CHARISMA) >= TamingDifficulty;
8175 truth character::CanTameWithScroll (const character *Tamer) const {
8176 int TamingDifficulty = GetTamingDifficulty();
8177 return
8178 (TamingDifficulty != NO_TAMING &&
8179 (TamingDifficulty == 0 ||
8180 Tamer->GetAttribute(INTELLIGENCE) * 4 + Tamer->GetAttribute(CHARISMA) >= TamingDifficulty * 5));
8184 truth character::CheckSadism () {
8185 if (!IsSadist() || !HasSadistAttackMode() || !IsSmall()) return false; // gum
8186 if (!RAND_N(10)) {
8187 for (int d = 0; d < 8; ++d) {
8188 square *Square = GetNeighbourSquare(d);
8189 if (Square) {
8190 character *Char = Square->GetCharacter();
8191 if (Char && Char->IsMasochist() && GetRelation(Char) == FRIEND &&
8192 Char->GetHP() * 3 >= Char->GetMaxHP() * 2 && Hit(Char, Square->GetPos(), d, SADIST_HIT)) {
8193 TerminateGoingTo();
8194 return true;
8199 return false;
8203 truth character::CheckForBeverage () {
8204 if (StateIsActivated(PANIC) || !IsEnabled() || !UsesNutrition() || CheckIfSatiated()) return false;
8205 itemvector ItemVector;
8206 GetStack()->FillItemVector(ItemVector);
8207 for (uInt c = 0; c < ItemVector.size(); ++c) if (ItemVector[c]->IsBeverage(this) && TryToConsume(ItemVector[c])) return true;
8208 return false;
8212 void character::Haste () {
8213 doforbodyparts()(this, &bodypart::Haste);
8214 doforequipments()(this, &item::Haste);
8215 BeginTemporaryState(HASTE, 500 + RAND() % 1000);
8219 void character::Slow () {
8220 doforbodyparts()(this, &bodypart::Slow);
8221 doforequipments()(this, &item::Slow);
8222 //BeginTemporaryState(HASTE, 500 + RAND() % 1000); // this seems to be a bug
8223 BeginTemporaryState(SLOW, 500 + RAND() % 1000);
8227 void character::SurgicallyDetachBodyPart () {
8228 ADD_MESSAGE("You haven't got any extra bodyparts.");
8232 truth character::CanHear() const
8234 return DataBase->CanHear && HasHead();
8238 truth character::IsAllowedInDungeon (int dunIndex) {
8239 const fearray<int> &dlist = GetAllowedDungeons();
8241 for (uInt f = 0; f < dlist.Size; ++f) {
8242 if (dlist[f] == ALL_DUNGEONS || dlist[f] == dunIndex) {
8243 fprintf(stderr, "OK!\n");
8244 return true;
8247 fprintf(stderr, "NO!\n");
8248 return false;
8252 truth character::IsESPBlockedByEquipment () const {
8253 for (int c = 0; c < GetEquipments(); ++c) {
8254 item *Item = GetEquipment(c);
8256 if (Item && Item->IsHelmet(this) &&
8257 ((Item->GetMainMaterial() && Item->GetMainMaterial()->BlockESP()) ||
8258 (Item->GetSecondaryMaterial() && Item->GetSecondaryMaterial()->BlockESP()))) return true;
8260 return false;
8264 truth character::TemporaryStateIsActivated (sLong What) const {
8265 if ((What&ESP) && (TemporaryState&ESP) && IsESPBlockedByEquipment()) {
8266 return ((TemporaryState&What)&(~ESP));
8268 return (TemporaryState & What);
8272 truth character::StateIsActivated (sLong What) const {
8273 if ((What&ESP) && ((TemporaryState|EquipmentState)&ESP) && IsESPBlockedByEquipment()) {
8274 return ((TemporaryState&What)&(~ESP)) || ((EquipmentState&What)&(~ESP));
8276 return (TemporaryState & What) || (EquipmentState & What);