save and bone files now can be compressed with ZLib (wow!)
[k8-i-v-a-n.git] / src / game / nonhuman.cpp
blob75be0e86cb95267086b943dc830d4f347179b0ec
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
13 /* Compiled through charsset.cpp */
15 int nonhumanoid::GetUnarmedMinDamage() const { return int(UnarmedDamage * 0.75); }
16 int nonhumanoid::GetUnarmedMaxDamage() const { return int(UnarmedDamage * 1.25 + 1); }
17 int nonhumanoid::GetKickMinDamage() const { return int(KickDamage * 0.75); }
18 int nonhumanoid::GetKickMaxDamage() const { return int(KickDamage * 1.25 + 1); }
19 int nonhumanoid::GetBiteMinDamage() const { return int(BiteDamage * 0.75); }
20 int nonhumanoid::GetBiteMaxDamage() const { return int(BiteDamage * 1.25 + 1); }
21 int nonhumanoid::GetCarryingStrength() const { return (Max(GetAttribute(LEG_STRENGTH), 1) << 1) + CarryingBonus; }
22 truth nonhumanoid::UseMaterialAttributes() const { return GetTorso()->UseMaterialAttributes(); }
24 truth elpuri::SpecialEnemySightedReaction(character*) { return !(Active = true); }
26 cchar* billswill::FirstPersonBiteVerb() const { return "emit psi waves at"; }
27 cchar* billswill::FirstPersonCriticalBiteVerb() const { return "emit powerful psi waves at"; }
28 cchar* billswill::ThirdPersonBiteVerb() const { return "emits psi waves at"; }
29 cchar* billswill::ThirdPersonCriticalBiteVerb() const { return "emits powerful psi waves at"; }
30 int billswill::GetBodyPartWobbleData(int) const { return WOBBLE_HORIZONTALLY|(2 << WOBBLE_FREQ_SHIFT); }
32 int mommo::GetBodyPartWobbleData(int) const { return (GetConfig() == CONICAL ? WOBBLE_HORIZONTALLY : WOBBLE_VERTICALLY)|(2 << WOBBLE_FREQ_SHIFT); }
34 bodypart* dog::MakeBodyPart(int) const { return dogtorso::Spawn(0, NO_MATERIALS); }
36 bodypart* spider::MakeBodyPart(int) const { return spidertorso::Spawn(0, NO_MATERIALS); }
38 int dolphin::GetSpecialBodyPartFlags(int) const { return RAND() & (MIRROR|ROTATE); }
40 bodypart* bat::MakeBodyPart(int) const { return battorso::Spawn(0, NO_MATERIALS); }
42 col16 chameleon::GetSkinColor() const { return MakeRGB16(60 + RAND() % 190, 60 + RAND() % 190, 60 + RAND() % 190); }
44 void floatingeye::SetWayPoints(const fearray<packv2>& What) { ArrayToVector(What, WayPoints); }
46 bodypart* eddy::MakeBodyPart(int) const { return eddytorso::Spawn(0, NO_MATERIALS); }
47 int eddy::GetBodyPartWobbleData(int) const { return WOBBLE_VERTICALLY|(2 << WOBBLE_FREQ_SHIFT); }
49 bodypart* magicmushroom::MakeBodyPart(int) const { return magicmushroomtorso::Spawn(0, NO_MATERIALS); }
51 cchar* ghost::FirstPersonBiteVerb() const { return "touch"; }
52 cchar* ghost::FirstPersonCriticalBiteVerb() const { return "awfully touch"; }
53 cchar* ghost::ThirdPersonBiteVerb() const { return "touches"; }
54 cchar* ghost::ThirdPersonCriticalBiteVerb() const { return "awfully touches"; }
55 truth ghost::SpecialEnemySightedReaction(character*) { return !(Active = true); }
56 int ghost::GetBodyPartWobbleData(int) const { return WOBBLE_HORIZONTALLY|(2 << WOBBLE_FREQ_SHIFT); }
58 cchar* magpie::FirstPersonBiteVerb() const { return "peck"; }
59 cchar* magpie::FirstPersonCriticalBiteVerb() const { return "critically peck"; }
60 cchar* magpie::ThirdPersonBiteVerb() const { return "pecks"; }
61 cchar* magpie::ThirdPersonCriticalBiteVerb() const { return "critically pecks"; }
63 bodypart* largecreature::MakeBodyPart(int) const { return largetorso::Spawn(0, NO_MATERIALS); }
64 lsquare* largecreature::GetNeighbourLSquare(int I) const { return static_cast<lsquare*>(GetNeighbourSquare(I)); }
65 wsquare* largecreature::GetNeighbourWSquare(int I) const { return static_cast<wsquare*>(GetNeighbourSquare(I)); }
67 int hattifattener::GetSpecialBodyPartFlags(int) const { return ST_LIGHTNING; }
68 int hattifattener::GetBodyPartWobbleData(int) const { return WOBBLE_HORIZONTALLY|(1 << WOBBLE_SPEED_SHIFT)|(1 << WOBBLE_FREQ_SHIFT); }
70 col16 vladimir::GetSkinColor() const { return MakeRGB16(60 + RAND() % 190, 60 + RAND() % 190, 60 + RAND() % 190); }
72 bodypart* blinkdog::MakeBodyPart(int) const { return blinkdogtorso::Spawn(0, NO_MATERIALS); }
74 int mysticfrog::GetBodyPartWobbleData(int) const { return WOBBLE_HORIZONTALLY|(1 << WOBBLE_SPEED_SHIFT)|(3 << WOBBLE_FREQ_SHIFT); }
75 bodypart* mysticfrog::MakeBodyPart(int) const { return mysticfrogtorso::Spawn(0, NO_MATERIALS); }
77 bodypart* lobhse::MakeBodyPart(int) const { return lobhsetorso::Spawn(0, NO_MATERIALS); }
79 truth elpuri::Hit(character* Enemy, v2, int, int Flags)
81 if(CheckIfTooScaredToHit(Enemy))
82 return false;
84 character* EnemyHit[MAX_NEIGHBOUR_SQUARES];
85 int EnemiesHit = 0;
87 for(int d = 0; d < GetExtendedNeighbourSquares(); ++d)
88 if(IsEnabled())
90 lsquare* Square = GetNeighbourLSquare(d);
92 if(Square)
94 character* ByStander = Square->GetCharacter();
96 if(ByStander && (ByStander == Enemy || GetRelation(ByStander) == HOSTILE))
98 truth Abort = false;
100 for(int c = 0; c < EnemiesHit; ++c)
101 if(EnemyHit[c] == ByStander)
102 Abort = true;
104 if(!Abort)
106 nonhumanoid::Hit(ByStander, Square->GetPos(), YOURSELF, Flags);
107 ByStander->DamageAllItems(this, RAND() % 36 + RAND() % 36, PHYSICAL_DAMAGE);
108 EnemyHit[EnemiesHit++] = ByStander;
112 Square->GetStack()->ReceiveDamage(this, RAND() % 36 + RAND() % 36, PHYSICAL_DAMAGE, game::GetLargeMoveDirection(d));
116 EditAP(-500);
117 return true;
120 truth dog::Catches (item *Thingy) {
121 if (Thingy->DogWillCatchAndConsume(this)) {
122 if (ConsumeItem(Thingy, CONST_S("eating"))) {
123 if (IsPlayer()) {
124 ADD_MESSAGE("You catch %s in mid-air and consume it.", Thingy->CHAR_NAME(DEFINITE));
125 } else {
126 if (CanBeSeenByPlayer()) ADD_MESSAGE("%s catches %s and eats it.", CHAR_NAME(DEFINITE), Thingy->CHAR_NAME(DEFINITE));
127 ChangeTeam(PLAYER->GetTeam());
129 } else if (IsPlayer()) {
130 ADD_MESSAGE("You catch %s in mid-air.", Thingy->CHAR_NAME(DEFINITE));
131 } else if (CanBeSeenByPlayer()) {
132 ADD_MESSAGE("%s catches %s.", CHAR_NAME(DEFINITE), Thingy->CHAR_NAME(DEFINITE));
134 return true;
136 return false;
139 truth unicorn::SpecialEnemySightedReaction(character*)
141 if(!(RAND() & 15))
143 MonsterTeleport("neighs happily");
144 return true;
147 if(StateIsActivated(PANIC) || (RAND() & 1 && IsInBadCondition()))
149 MonsterTeleport("neighs");
150 return true;
153 if(!(RAND() % 3) && MoveRandomly())
154 return true;
156 return false;
159 void nonhumanoid::Save(outputfile& SaveFile) const
161 character::Save(SaveFile);
162 SaveFile << StrengthExperience << AgilityExperience;
165 void nonhumanoid::Load(inputfile& SaveFile)
167 character::Load(SaveFile);
168 SaveFile >> StrengthExperience >> AgilityExperience;
171 void nonhumanoid::CalculateUnarmedDamage()
173 UnarmedDamage = sqrt(5e-12 * GetAttribute(ARM_STRENGTH)) * GetBaseUnarmedStrength() * GetCWeaponSkill(UNARMED)->GetBonus();
176 void nonhumanoid::CalculateUnarmedToHitValue()
178 UnarmedToHitValue = GetAttribute(DEXTERITY) * sqrt(2.5 * GetAttribute(PERCEPTION)) * GetCWeaponSkill(UNARMED)->GetBonus() * GetMoveEase() / 500000;
181 void nonhumanoid::CalculateUnarmedAPCost()
183 UnarmedAPCost = Max(sLong(10000000000. / (APBonus(GetAttribute(DEXTERITY)) * GetMoveEase() * GetCWeaponSkill(UNARMED)->GetBonus())), 100);
186 void nonhumanoid::CalculateKickDamage()
188 KickDamage = sqrt(5e-12 * GetAttribute(LEG_STRENGTH)) * GetBaseKickStrength() * GetCWeaponSkill(KICK)->GetBonus();
191 void nonhumanoid::CalculateKickToHitValue()
193 KickToHitValue = GetAttribute(AGILITY) * sqrt(2.5 * GetAttribute(PERCEPTION)) * GetCWeaponSkill(KICK)->GetBonus() * GetMoveEase() / 1000000;
196 void nonhumanoid::CalculateKickAPCost()
198 KickAPCost = Max(sLong(20000000000. / (APBonus(GetAttribute(AGILITY)) * GetMoveEase() * GetCWeaponSkill(KICK)->GetBonus())), 1000);
201 void nonhumanoid::CalculateBiteDamage()
203 BiteDamage = sqrt(5e-12 * GetAttribute(ARM_STRENGTH)) * GetBaseBiteStrength() * GetCWeaponSkill(BITE)->GetBonus();
206 void nonhumanoid::CalculateBiteToHitValue()
208 BiteToHitValue = GetAttribute(AGILITY) * sqrt(2.5 * GetAttribute(PERCEPTION)) * GetCWeaponSkill(BITE)->GetBonus() * GetMoveEase() / 1000000;
211 void nonhumanoid::CalculateBiteAPCost()
213 BiteAPCost = Max(sLong(10000000000. / (APBonus(GetAttribute(DEXTERITY)) * GetMoveEase() * GetCWeaponSkill(BITE)->GetBonus())), 100);
216 void nonhumanoid::InitSpecialAttributes()
218 StrengthExperience = GetNaturalExperience(ARM_STRENGTH);
219 AgilityExperience = GetNaturalExperience(AGILITY);
220 LimitRef(StrengthExperience, MIN_EXP, MAX_EXP);
221 LimitRef(AgilityExperience, MIN_EXP, MAX_EXP);
224 void nonhumanoid::Bite(character* Enemy, v2 HitPos, int Direction, truth ForceHit)
226 EditNP(-50);
227 EditAP(-GetBiteAPCost());
228 EditExperience(ARM_STRENGTH, 75, 1 << 8);
229 EditExperience(AGILITY, 150, 1 << 8);
230 EditStamina(-10000 / GetAttribute(ARM_STRENGTH), false);
231 Enemy->TakeHit(this, 0, GetTorso(), HitPos, GetBiteDamage(), GetBiteToHitValue(), RAND() % 26 - RAND() % 26, BITE_ATTACK, Direction, !(RAND() % GetCriticalModifier()), ForceHit);
234 void nonhumanoid::Kick(lsquare* Square, int Direction, truth ForceHit)
236 EditNP(-50);
237 EditAP(-GetKickAPCost());
238 EditStamina(-10000 / GetAttribute(ARM_STRENGTH), false);
240 if(Square->BeKicked(this, 0, GetTorso(), GetKickDamage(), GetKickToHitValue(), RAND() % 26 - RAND() % 26, Direction, !(RAND() % GetCriticalModifier()), ForceHit))
242 EditExperience(LEG_STRENGTH, 150, 1 << 8);
243 EditExperience(AGILITY, 75, 1 << 8);
247 truth nonhumanoid::Hit(character* Enemy, v2 HitPos, int Direction, int Flags)
249 if(CheckIfTooScaredToHit(Enemy))
250 return false;
252 if(IsPlayer())
254 if(!(Enemy->IsMasochist() && GetRelation(Enemy) == FRIEND) && GetRelation(Enemy) != HOSTILE && !game::TruthQuestion(CONST_S("This might cause a hostile reaction. Are you sure? [y/N]")))
255 return false;
257 else if(GetAttribute(WISDOM) >= Enemy->GetAttackWisdomLimit())
258 return false;
260 if(GetBurdenState() == OVER_LOADED)
262 if(IsPlayer())
263 ADD_MESSAGE("You cannot fight while carrying so much.");
265 return false;
268 /* Behold this Terrible Father of Gum Solutions! */
270 int AttackStyle = GetAttackStyle();
272 if(AttackStyle & USE_LEGS)
274 room* Room = GetNearLSquare(HitPos)->GetRoom();
276 if(Room && !Room->AllowKick(this, GetNearLSquare(HitPos)))
277 AttackStyle &= ~USE_LEGS;
280 int c, AttackStyles;
282 for(c = 0, AttackStyles = 0; c < 8; ++c)
283 if(AttackStyle & (1 << c))
284 ++AttackStyles;
286 int Chosen = RAND() % AttackStyles;
288 for(c = 0, AttackStyles = 0; c < 8; ++c)
289 if(AttackStyle & (1 << c) && AttackStyles++ == Chosen)
291 Chosen = 1 << c;
292 break;
295 switch(Chosen)
297 case USE_ARMS:
298 msgsystem::EnterBigMessageMode();
299 Hostility(Enemy);
300 UnarmedHit(Enemy, HitPos, Direction, Flags & SADIST_HIT);
301 msgsystem::LeaveBigMessageMode();
302 return true;
303 case USE_LEGS:
304 msgsystem::EnterBigMessageMode();
305 Hostility(Enemy);
306 Kick(GetNearLSquare(HitPos), Direction, Flags & SADIST_HIT);
307 msgsystem::LeaveBigMessageMode();
308 return true;
309 case USE_HEAD:
310 msgsystem::EnterBigMessageMode();
311 Hostility(Enemy);
312 Bite(Enemy, HitPos, Direction, Flags & SADIST_HIT);
313 msgsystem::LeaveBigMessageMode();
314 return true;
315 default:
316 ABORT("Strange alien attack style requested!");
317 return false;
321 void nonhumanoid::UnarmedHit(character* Enemy, v2 HitPos, int Direction, truth ForceHit)
323 EditNP(-50);
324 EditAP(-GetUnarmedAPCost());
325 EditStamina(-10000 / GetAttribute(ARM_STRENGTH), false);
327 switch(Enemy->TakeHit(this, 0, GetTorso(), HitPos, GetUnarmedDamage(), GetUnarmedToHitValue(), RAND() % 26 - RAND() % 26, UNARMED_ATTACK, Direction, !(RAND() % GetCriticalModifier()), ForceHit))
329 case HAS_HIT:
330 case HAS_BLOCKED:
331 case HAS_DIED:
332 case DID_NO_DAMAGE:
333 EditExperience(ARM_STRENGTH, 150, 1 << 8);
334 case HAS_DODGED:
335 EditExperience(DEXTERITY, 75, 1 << 8);
339 /* Returns the average number of APs required to kill Enemy */
341 double nonhumanoid::GetTimeToKill(ccharacter* Enemy, truth UseMaxHP) const
343 double Effectivity = 0;
344 int AttackStyles = 0;
346 if(IsUsingArms())
348 Effectivity += 1 / (Enemy->GetTimeToDie(this, int(GetUnarmedDamage()) + 1, GetUnarmedToHitValue(), AttackIsBlockable(UNARMED_ATTACK), UseMaxHP) * GetUnarmedAPCost());
349 ++AttackStyles;
352 if(IsUsingLegs())
354 Effectivity += 1 / (Enemy->GetTimeToDie(this, int(GetKickDamage()) + 1, GetKickToHitValue(), AttackIsBlockable(KICK_ATTACK), UseMaxHP) * GetKickAPCost());
355 ++AttackStyles;
358 if(IsUsingHead())
360 Effectivity += 1 / (Enemy->GetTimeToDie(this, int(GetBiteDamage()) + 1, GetBiteToHitValue(), AttackIsBlockable(BITE_ATTACK), UseMaxHP) * GetBiteAPCost());
361 ++AttackStyles;
364 if(StateIsActivated(HASTE))
365 Effectivity *= 2;
367 if(StateIsActivated(SLOW))
368 Effectivity /= 2;
370 return AttackStyles / Effectivity;
373 int nonhumanoid::GetAttribute(int Identifier, truth AllowBonus) const
375 if(Identifier < BASE_ATTRIBUTES)
376 return character::GetAttribute(Identifier, AllowBonus);
377 else if(Identifier == ARM_STRENGTH || Identifier == LEG_STRENGTH)
379 if(!UseMaterialAttributes())
380 return int(StrengthExperience * EXP_DIVISOR);
381 else
382 return GetTorso()->GetMainMaterial()->GetStrengthValue();
384 else if(Identifier == DEXTERITY || Identifier == AGILITY)
386 if(!UseMaterialAttributes())
387 return int(AgilityExperience * EXP_DIVISOR);
388 else
389 return (GetTorso()->GetMainMaterial()->GetFlexibility() << 2);
391 else
393 ABORT("Illegal nonhumanoid attribute %d request!", Identifier);
394 return 0xABBE;
398 truth nonhumanoid::EditAttribute(int Identifier, int Value)
400 if(Identifier < BASE_ATTRIBUTES)
401 return character::EditAttribute(Identifier, Value);
402 else if(Identifier == ARM_STRENGTH || Identifier == LEG_STRENGTH)
403 return !UseMaterialAttributes() && RawEditAttribute(StrengthExperience, Value);
404 else if(Identifier == DEXTERITY || Identifier == AGILITY)
405 return !UseMaterialAttributes() && RawEditAttribute(AgilityExperience, Value);
406 else
408 ABORT("Illegal nonhumanoid attribute %d edit request!", Identifier);
409 return false;
413 void nonhumanoid::EditExperience(int Identifier, double Value, double Speed)
415 if(!AllowExperience())
416 return;
418 if(Identifier < BASE_ATTRIBUTES)
419 character::EditExperience(Identifier, Value, Speed);
420 else if(Identifier == ARM_STRENGTH || Identifier == LEG_STRENGTH)
422 if(!UseMaterialAttributes())
424 int Change = RawEditExperience(StrengthExperience,
425 GetNaturalExperience(ARM_STRENGTH),
426 Value, Speed / 2);
428 if(Change)
430 cchar* Adj = Change > 0 ? "stronger" : "weaker";
432 if(IsPlayer())
433 ADD_MESSAGE("Your feel %s!", Adj);
434 else if(IsPet() && CanBeSeenByPlayer())
435 ADD_MESSAGE("Suddenly %s looks %s.", CHAR_NAME(DEFINITE), Adj);
437 CalculateBurdenState();
438 CalculateBattleInfo();
442 else if(Identifier == DEXTERITY || Identifier == AGILITY)
444 if(!UseMaterialAttributes())
446 int Change = RawEditExperience(AgilityExperience,
447 GetNaturalExperience(AGILITY),
448 Value, Speed / 2);
450 if(Change)
452 cchar* Adj = Change > 0 ? "very agile" : "sluggish";
454 if(IsPlayer())
455 ADD_MESSAGE("Your feel %s!", Adj);
456 else if(IsPet() && CanBeSeenByPlayer())
457 ADD_MESSAGE("Suddenly %s looks %s.", CHAR_NAME(DEFINITE), Adj);
459 CalculateBattleInfo();
463 else
464 ABORT("Illegal nonhumanoid attribute %d experience edit request!", Identifier);
467 int nonhumanoid::DrawStats(truth AnimationDraw) const
469 if(AnimationDraw)
470 return 3;
472 int PanelPosX = RES.X - 96, PanelPosY = 3;
473 PrintAttribute("Str", ARM_STRENGTH, PanelPosX, PanelPosY++);
474 PrintAttribute("Agi", AGILITY, PanelPosX, PanelPosY++);
475 return PanelPosY;
478 void nonhumanoid::CalculateBattleInfo()
480 CalculateDodgeValue();
481 CalculateUnarmedAttackInfo();
482 CalculateKickAttackInfo();
483 CalculateBiteAttackInfo();
486 void nonhumanoid::CalculateUnarmedAttackInfo()
488 CalculateUnarmedDamage();
489 CalculateUnarmedToHitValue();
490 CalculateUnarmedAPCost();
493 void nonhumanoid::CalculateKickAttackInfo()
495 CalculateKickDamage();
496 CalculateKickToHitValue();
497 CalculateKickAPCost();
500 void nonhumanoid::CalculateBiteAttackInfo()
502 CalculateBiteDamage();
503 CalculateBiteToHitValue();
504 CalculateBiteAPCost();
507 void dog::BeTalkedTo () {
508 if (RAND_N(5)) {
509 if (GetRelation(PLAYER) != HOSTILE) {
510 static truth Last;
511 cchar *Reply;
512 if (GetHP()<< 1 > GetMaxHP()) Reply = Last ? "barks happily" : "wags its tail happily";
513 else Reply = Last ? "yelps" : "howls";
514 ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE), Reply);
515 Last = !Last;
516 } else {
517 character::BeTalkedTo();
519 } else if (RAND_N(5)) {
520 ADD_MESSAGE("\"Can't you understand I can't speak?\"");
521 } else {
522 ADD_MESSAGE("\"Meow.\"");
526 col16 wolf::GetSkinColor() const
528 int Element = 40 + RAND() % 50;
529 return MakeRGB16(Element, Element, Element);
532 void genetrixvesana::GetAICommand()
534 ++TurnsExisted;
536 SeekLeader(GetLeader());
538 if(FollowLeader(GetLeader()))
539 return;
541 if(!(RAND() % 60))
543 int NumberOfPlants = RAND() % 3 + RAND() % 3 + RAND() % 3 + RAND() % 3;
545 for(int c1 = 0; c1 < 50 && NumberOfPlants; ++c1)
547 for(int c2 = 0; c2 < game::GetTeams() && NumberOfPlants; ++c2)
548 if(GetTeam()->GetRelation(game::GetTeam(c2)) == HOSTILE)
549 for(std::list<character*>::const_iterator i = game::GetTeam(c2)->GetMember().begin(); i != game::GetTeam(c2)->GetMember().end() && NumberOfPlants; ++i)
550 if((*i)->IsEnabled())
552 lsquare* LSquare = (*i)->GetNeighbourLSquare(RAND() % GetNeighbourSquares());
554 if(LSquare && (LSquare->GetWalkability() & WALK) && !LSquare->GetCharacter())
556 character* NewPlant;
557 sLong RandomValue = RAND() % TurnsExisted;
559 if(RandomValue < 250)
560 NewPlant = carnivorousplant::Spawn();
561 else if(RandomValue < 1500)
562 NewPlant = carnivorousplant::Spawn(GREATER);
563 else
564 NewPlant = carnivorousplant::Spawn(GIANTIC);
566 for(int c = 3; c < TurnsExisted / 500; ++c)
567 NewPlant->EditAllAttributes(1);
569 NewPlant->SetGenerationDanger(GetGenerationDanger());
570 NewPlant->SetTeam(GetTeam());
571 NewPlant->PutTo(LSquare->GetPos());
572 --NumberOfPlants;
574 if(NewPlant->CanBeSeenByPlayer())
576 if((*i)->IsPlayer())
577 ADD_MESSAGE("%s sprouts from the ground near you.", NewPlant->CHAR_NAME(INDEFINITE));
578 else if((*i)->CanBeSeenByPlayer())
579 ADD_MESSAGE("%s sprouts from the ground near %s.", NewPlant->CHAR_NAME(INDEFINITE), (*i)->CHAR_NAME(DEFINITE));
580 else
581 ADD_MESSAGE("%s sprouts from the ground.", NewPlant->CHAR_NAME(INDEFINITE));
587 EditAP(-2000);
588 return;
591 if(AttackAdjacentEnemyAI())
592 return;
594 if(MoveRandomly())
595 return;
597 EditAP(-1000);
600 col16 carnivorousplant::GetTorsoSpecialColor() const // the flower
602 if(!GetConfig())
603 return MakeRGB16(RAND() % 100, 125 + RAND() % 125, RAND() % 100);
604 else if(GetConfig() == GREATER)
605 return MakeRGB16(RAND() % 100, RAND() % 100, 125 + RAND() % 125);
606 else
607 return MakeRGB16(125 + RAND() % 125, 125 + RAND() % 125, RAND() % 100);
610 void ostrich::GetAICommand()
612 if(game::TweraifIsFree())
614 nonhumanoid::GetAICommand();
615 return;
618 if(CheckForEnemies(false, false, true, true))
619 return;
621 if(!IsEnabled())
622 return;
624 if(GetPos() == v2(45, 45))
625 HasDroppedBananas = true;
627 itemvector ItemVector;
628 GetStackUnder()->FillItemVector(ItemVector);
629 int BananasPicked = 0;
631 for(uInt c = 0; c < ItemVector.size(); ++c)
632 if(ItemVector[c]->IsBanana() && ItemVector[c]->CanBeSeenBy(this)
633 && ItemVector[c]->IsPickable(this)
634 && !MakesBurdened(GetCarriedWeight() + ItemVector[c]->GetWeight()))
636 ItemVector[c]->MoveTo(GetStack());
637 ++BananasPicked;
640 if(BananasPicked)
642 if(CanBeSeenByPlayer())
643 ADD_MESSAGE("%s picks up %s.", CHAR_NAME(DEFINITE), BananasPicked == 1 ? "the banana" : "some bananas");
645 return;
648 if(!HasDroppedBananas)
650 SetGoingTo(v2(45, 45));
652 if(MoveTowardsTarget(true))
653 return;
655 else if(GetPos().Y == 54)
657 if(CanBeSeenByPlayer())
658 ADD_MESSAGE("%s leaves the town.", CHAR_NAME(DEFINITE));
660 itemvector ItemVector;
661 GetStack()->FillItemVector(ItemVector);
663 for(uInt c = 0; c < ItemVector.size(); ++c)
665 ItemVector[c]->RemoveFromSlot();
666 ItemVector[c]->SendToHell();
669 v2 Where = GetLevel()->GetNearestFreeSquare(this, v2(45, 0));
671 if(Where == ERROR_V2)
672 Where = GetLevel()->GetRandomSquare(this, NOT_IN_ROOM); // this is odd but at least it doesn't crash
674 Move(Where, true);
675 RestoreHP();
676 RestoreStamina();
677 ResetStates();
678 TemporaryState = 0;
679 GainIntrinsic(LEVITATION);
681 if(CanBeSeenByPlayer())
682 ADD_MESSAGE("%s enters the town.", CHAR_NAME(INDEFINITE));
684 HasDroppedBananas = false;
686 else
688 SetGoingTo(v2(45, 54));
690 if(MoveTowardsTarget(true))
691 return;
694 EditAP(-1000);
697 void ostrich::Save(outputfile& SaveFile) const
699 nonhumanoid::Save(SaveFile);
700 SaveFile << HasDroppedBananas;
703 void ostrich::Load(inputfile& SaveFile)
705 nonhumanoid::Load(SaveFile);
706 SaveFile >> HasDroppedBananas;
709 truth ostrich::HandleCharacterBlockingTheWay(character* Char, v2 Pos, int Dir)
711 return Char->GetPos() == v2(45, 45) && (Displace(Char, true) || Hit(Char, Pos, Dir));
714 void elpuri::Save(outputfile& SaveFile) const
716 largecreature::Save(SaveFile);
717 SaveFile << Active;
720 void elpuri::Load(inputfile& SaveFile)
722 largecreature::Load(SaveFile);
723 SaveFile >> Active;
726 void elpuri::GetAICommand()
728 if(Active)
729 character::GetAICommand();
730 else
732 if(CheckForEnemies(false, false, false))
733 return;
735 EditAP(-1000);
739 int elpuri::ReceiveBodyPartDamage(character* Damager, int Damage, int Type, int BodyPartIndex, int Direction, truth PenetrateResistance, truth Critical, truth ShowNoDamageMsg, truth CaptureBodyPart)
741 Active = true;
742 return character::ReceiveBodyPartDamage(Damager, Damage, Type, BodyPartIndex, Direction, PenetrateResistance, Critical, ShowNoDamageMsg, CaptureBodyPart);
745 void mommo::CreateCorpse(lsquare* Square)
747 for(int d = 0; d < GetExtendedNeighbourSquares(); ++d)
749 lsquare* NeighbourSquare = Square->GetNeighbourLSquare(d);
751 if(NeighbourSquare)
752 NeighbourSquare->SpillFluid(0, static_cast<liquid*>(GetTorso()->GetMainMaterial()->SpawnMore(250 + RAND() % 250)));
755 SendToHell();
758 void carnivorousplant::CreateCorpse(lsquare* Square)
760 int Amount = !GetConfig() ? (RAND() % 7 ? 0 : 1) : GetConfig() == GREATER ? (RAND() & 1 ? 0 : (RAND() % 5 ? 1 : (RAND() % 5 ? 2 : 3))) : (!(RAND() % 3) ? 0 : (RAND() % 3 ? 1 : (RAND() % 3 ? 2 : 3)));
762 for(int c = 0; c < Amount; ++c)
763 Square->AddItem(kiwi::Spawn());
765 nonhumanoid::CreateCorpse(Square);
768 void genetrixvesana::CreateCorpse(lsquare* Square)
770 for(int c = 0; c < 3; ++c)
771 Square->AddItem(pineapple::Spawn());
773 largecreature::CreateCorpse(Square);
776 void nonhumanoid::AddSpecialStethoscopeInfo(felist& Info) const
778 Info.AddEntry(CONST_S("Strength: ") + GetAttribute(ARM_STRENGTH), LIGHT_GRAY);
779 Info.AddEntry(CONST_S("Agility: ") + GetAttribute(AGILITY), LIGHT_GRAY);
782 void floatingeye::Save(outputfile& SaveFile) const
784 nonhumanoid::Save(SaveFile);
785 SaveFile << WayPoints << NextWayPoint;
788 void floatingeye::Load(inputfile& SaveFile)
790 nonhumanoid::Load(SaveFile);
791 SaveFile >> WayPoints >> NextWayPoint;
794 void floatingeye::GetAICommand()
796 if(WayPoints.size() && !IsGoingSomeWhere())
798 if(GetPos() == WayPoints[NextWayPoint]) {
799 if(NextWayPoint < WayPoints.size() - 1) ++NextWayPoint; else NextWayPoint = 0;
802 GoingTo = WayPoints[NextWayPoint];
805 SeekLeader(GetLeader());
807 if(CheckForEnemies(false, false, true))
808 return;
810 if(FollowLeader(GetLeader()))
811 return;
813 if(MoveRandomly())
814 return;
816 EditAP(-1000);
819 truth floatingeye::Hit(character* Enemy, v2, int, int)
821 if(IsPlayer())
822 ADD_MESSAGE("You stare at %s.", Enemy->CHAR_DESCRIPTION(DEFINITE));
823 else if(Enemy->IsPlayer() && CanBeSeenByPlayer())
824 ADD_MESSAGE("%s stares at you.", CHAR_NAME(DEFINITE));
826 EditAP(-1000);
827 return true;
830 int floatingeye::TakeHit(character* Enemy, item* Weapon, bodypart* EnemyBodyPart, v2 HitPos, double Damage, double ToHitValue, int Success, int Type, int Direction, truth Critical, truth ForceHit)
832 if(CanBeSeenBy(Enemy) && Enemy->HasEyes() && RAND() % 3 && Enemy->LoseConsciousness(150 + RAND_N(150))) /* Changes for fainting 2 out of 3 */
834 if(!Enemy->IsPlayer())
835 Enemy->EditExperience(WISDOM, 75, 1 << 13);
837 return HAS_FAILED;
839 else
840 return nonhumanoid::TakeHit(Enemy, Weapon, EnemyBodyPart, HitPos, Damage, ToHitValue, Success, Type, Direction, Critical, ForceHit);
843 void elpuri::CreateCorpse(lsquare* Square)
845 largecreature::CreateCorpse(Square);
846 Square->AddItem(headofelpuri::Spawn());
849 truth snake::SpecialBiteEffect(character* Char, v2, int, int, truth BlockedByArmour)
851 if(!BlockedByArmour)
853 Char->BeginTemporaryState(POISONED, 400 + RAND_N(200));
854 return true;
856 else
857 return false;
860 truth spider::SpecialBiteEffect(character* Char, v2, int, int, truth BlockedByArmour)
862 if(!BlockedByArmour)
864 Char->BeginTemporaryState(POISONED, GetConfig() == LARGE ? 80 + RAND_N(40) : 400 + RAND_N(200));
865 return true;
867 else
868 return false;
871 truth chameleon::SpecialEnemySightedReaction(character*)
873 if(HP != MaxHP || !(RAND() % 3))
875 character* NewForm = PolymorphRandomly(100, 1000, 500 + RAND() % 500);
876 NewForm->GainIntrinsic(POLYMORPH);
877 return true;
880 return false;
883 int chameleon::TakeHit(character* Enemy, item* Weapon, bodypart* EnemyBodyPart, v2 HitPos, double Damage, double ToHitValue, int Success, int Type, int Direction, truth Critical, truth ForceHit)
885 int Return = nonhumanoid::TakeHit(Enemy, Weapon, EnemyBodyPart, HitPos, Damage, ToHitValue, Success, Type, Direction, Critical, ForceHit);
887 if(Return != HAS_DIED)
889 character* NewForm = PolymorphRandomly(100, 1000, 500 + RAND() % 500);
890 NewForm->GainIntrinsic(POLYMORPH);
893 return Return;
896 truth eddy::Hit(character* Enemy, v2, int, int)
898 if(IsPlayer())
900 if(!(Enemy->IsMasochist() && GetRelation(Enemy) == FRIEND) && GetRelation(Enemy) != HOSTILE && !game::TruthQuestion(CONST_S("This might cause a hostile reaction. Are you sure? [y/N]")))
901 return false;
904 Hostility(Enemy);
906 if(RAND() & 1)
908 if(IsPlayer())
909 ADD_MESSAGE("You engulf %s.", Enemy->CHAR_DESCRIPTION(DEFINITE));
910 else if(Enemy->IsPlayer() || CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer())
911 ADD_MESSAGE("%s engulfs %s.", CHAR_DESCRIPTION(DEFINITE), Enemy->CHAR_DESCRIPTION(DEFINITE));
913 Enemy->TeleportRandomly();
915 else if(IsPlayer())
916 ADD_MESSAGE("You miss %s.", Enemy->CHAR_DESCRIPTION(DEFINITE));
918 EditAP(-500);
919 return true;
922 void mushroom::Save(outputfile& SaveFile) const
924 nonhumanoid::Save(SaveFile);
925 SaveFile << Species;
928 void mushroom::Load(inputfile& SaveFile)
930 nonhumanoid::Load(SaveFile);
931 SaveFile >> Species;
934 void mushroom::GetAICommand()
936 SeekLeader(GetLeader());
938 if(FollowLeader(GetLeader()))
939 return;
941 lsquare* CradleSquare = GetNeighbourLSquare(RAND() % 8);
943 if(CradleSquare && !CradleSquare->GetCharacter()
944 && (CradleSquare->GetWalkability() & WALK))
946 int SpoiledItems = 0;
947 int MushroomsNear = 0;
949 for(int d = 0; d < 8; ++d)
951 lsquare* Square = CradleSquare->GetNeighbourLSquare(d);
953 if(Square)
955 character* Char = Square->GetCharacter();
957 if(Char && Char->IsMushroom())
958 ++MushroomsNear;
960 SpoiledItems += Square->GetSpoiledItems();
964 if((SpoiledItems && MushroomsNear < 5 && !RAND_N(50)) || (MushroomsNear < 3 && !RAND_N((1 + MushroomsNear) * 100)))
966 mushroom* Child = static_cast<mushroom*>(GetProtoType()->Spawn(GetConfig()));
967 Child->SetSpecies(Species);
968 Child->SetTeam(GetTeam());
969 Child->SetGenerationDanger(GetGenerationDanger());
970 Child->PutTo(CradleSquare->GetPos());
972 for(int c = 0; c < BASE_ATTRIBUTES; ++c)
973 Child->BaseExperience[c] = RandomizeBabyExperience(BaseExperience[c] * 4);
975 if(Child->CanBeSeenByPlayer())
976 ADD_MESSAGE("%s pops out from the ground.", Child->CHAR_NAME(INDEFINITE));
980 if(AttackAdjacentEnemyAI())
981 return;
983 if(MoveRandomly())
984 return;
986 EditAP(-1000);
989 void mushroom::PostConstruct()
991 switch(RAND() % 3)
993 case 0: SetSpecies(MakeRGB16(125 + RAND() % 125, RAND() % 100, RAND() % 100)); break;
994 case 1: SetSpecies(MakeRGB16(RAND() % 100, 125 + RAND() % 125, RAND() % 100)); break;
995 case 2: SetSpecies(MakeRGB16(RAND() % 100, RAND() % 100, 125 + RAND() % 125)); break;
999 void magicmushroom::GetAICommand()
1001 if(!(RAND() % 750))
1003 if(CanBeSeenByPlayer())
1004 ADD_MESSAGE("%s disappears.", CHAR_NAME(DEFINITE));
1006 TeleportRandomly(true);
1007 EditAP(-1000);
1009 else if(!(RAND() % 50))
1011 lsquare* Square = GetNeighbourLSquare(RAND() % 8);
1013 if(Square && Square->IsFlyable())
1015 if(CanBeSeenByPlayer())
1016 ADD_MESSAGE("%s releases odd-looking gas.", CHAR_NAME(DEFINITE));
1018 Square->AddSmoke(gas::Spawn(MAGIC_VAPOUR, 1000));
1019 EditAP(-1000);
1022 else
1023 mushroom::GetAICommand();
1026 void mushroom::SetSpecies(int What)
1028 Species = What;
1029 UpdatePictures();
1032 truth twoheadedmoose::Hit(character* Enemy, v2 HitPos, int Direction, int Flags)
1034 if(CheckIfTooScaredToHit(Enemy))
1035 return false;
1037 if(IsPlayer())
1039 if(!(Enemy->IsMasochist() && GetRelation(Enemy) == FRIEND) && GetRelation(Enemy) != HOSTILE && !game::TruthQuestion(CONST_S("This might cause a hostile reaction. Are you sure? [y/N]")))
1040 return false;
1042 else if(GetAttribute(WISDOM) >= Enemy->GetAttackWisdomLimit())
1043 return false;
1045 if(GetBurdenState() == OVER_LOADED)
1047 if(IsPlayer())
1048 ADD_MESSAGE("You cannot fight while carrying so much.");
1050 return false;
1053 Hostility(Enemy);
1054 msgsystem::EnterBigMessageMode();
1055 Bite(Enemy, HitPos, Direction, Flags & SADIST_HIT);
1056 v2 Pos[MAX_NEIGHBOUR_SQUARES];
1057 character* Char[MAX_NEIGHBOUR_SQUARES];
1058 int Index = 0;
1060 for(int d = 0; d < GetNeighbourSquares(); ++d)
1062 lsquare* LSquare = GetNeighbourLSquare(d);
1064 if(LSquare)
1066 character* Enemy = LSquare->GetCharacter();
1068 if(Enemy && GetRelation(Enemy) == HOSTILE && GetAttribute(WISDOM) < Enemy->GetAttackWisdomLimit())
1070 Pos[Index] = LSquare->GetPos();
1071 Char[Index++] = Enemy;
1076 if(Index)
1078 int ChosenIndex = RAND() % Index;
1079 Bite(Char[ChosenIndex], Pos[ChosenIndex], game::GetDirectionForVector(Pos[ChosenIndex] - GetPos()), Flags & SADIST_HIT);
1082 msgsystem::LeaveBigMessageMode();
1083 return true;
1086 truth magpie::IsRetreating() const
1088 if(nonhumanoid::IsRetreating())
1089 return true;
1091 for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
1092 if((*i)->GetSparkleFlags())
1093 return true;
1095 return false;
1098 void magpie::GetAICommand()
1100 if(!IsRetreating())
1102 character* Char = GetRandomNeighbour();
1104 if(Char)
1106 itemvector Sparkling;
1108 for(stackiterator i = Char->GetStack()->GetBottom(); i.HasItem(); ++i)
1110 if((*i)->GetSparkleFlags() && !MakesBurdened((*i)->GetWeight()))
1111 Sparkling.push_back(*i);
1114 if(!Sparkling.empty())
1116 item* ToSteal = Sparkling[RAND() % Sparkling.size()];
1117 ToSteal->RemoveFromSlot();
1118 GetStack()->AddItem(ToSteal);
1120 if(Char->IsPlayer())
1121 ADD_MESSAGE("%s steals your %s.", CHAR_NAME(DEFINITE), ToSteal->CHAR_NAME(UNARTICLED));
1123 EditAP(-500);
1124 return;
1129 nonhumanoid::GetAICommand();
1132 void eddy::GetAICommand()
1134 if(!GetLSquareUnder()->GetOLTerrain() && !(RAND() % 500))
1136 decoration* Couch = decoration::Spawn(RAND_N(5) ? COUCH : DOUBLE_BED);
1138 if(CanBeSeenByPlayer())
1139 ADD_MESSAGE("%s spits out %s.", CHAR_NAME(DEFINITE), Couch->CHAR_NAME(INDEFINITE));
1141 GetLSquareUnder()->ChangeOLTerrainAndUpdateLights(Couch);
1142 EditAP(-1000);
1143 return;
1146 if(GetStackUnder()->GetItems() && !(RAND() % 10))
1148 if(CanBeSeenByPlayer())
1149 ADD_MESSAGE("%s engulfs something under it.", CHAR_NAME(DEFINITE));
1151 GetStackUnder()->TeleportRandomly(3);
1152 EditAP(-1000);
1153 return;
1156 if(!(RAND() % 100))
1158 if(CanBeSeenByPlayer())
1159 ADD_MESSAGE("%s engulfs itself.", CHAR_NAME(DEFINITE));
1161 TeleportRandomly(true);
1162 EditAP(-1000);
1163 return;
1166 nonhumanoid::GetAICommand();
1169 void skunk::GetAICommand()
1171 if(!IsRetreating())
1173 if(!RAND_N(4))
1175 character* Char = GetRandomNeighbour(HOSTILE);
1177 if(Char)
1179 int Amount = 500 / Char->GetSquaresUnder();
1180 truth Success = false;
1182 for(int c = 0; c < Char->GetSquaresUnder(); ++c)
1183 if(Char->GetLSquareUnder(c)->IsFlyable())
1185 Success = true;
1186 Char->GetLSquareUnder(c)->AddSmoke(gas::Spawn(SKUNK_SMELL, Amount));
1189 if(Success)
1191 if(CanBeSeenByPlayer())
1192 ADD_MESSAGE("%s stinks.", CHAR_NAME(DEFINITE));
1194 EditAP(-1000);
1195 return;
1200 else if(RAND_N(2))
1202 if(CanBeSeenByPlayer())
1203 ADD_MESSAGE("%s stinks.", CHAR_NAME(DEFINITE));
1205 GetLSquareUnder()->AddSmoke(gas::Spawn(SKUNK_SMELL, 500));
1208 nonhumanoid::GetAICommand();
1211 truth elpuri::TryToRiseFromTheDead()
1213 character::TryToRiseFromTheDead();
1215 for(int c = 0; c < GetSquaresUnder(); ++c)
1216 for(stackiterator i = GetLSquareUnder(c)->GetStack()->GetBottom(); i.HasItem(); ++i)
1217 if(i->IsHeadOfElpuri())
1219 i->SendToHell();
1220 i->RemoveFromSlot();
1221 return true;
1224 if(CanBeSeenByPlayer())
1226 ADD_MESSAGE("The headless body of %s vibrates violently.", CHAR_NAME(DEFINITE));
1227 ADD_MESSAGE("%s dies.", CHAR_NAME(DEFINITE));
1230 return false;
1233 truth nonhumanoid::EditAllAttributes(int Amount)
1235 if(!Amount)
1236 return true;
1238 LimitRef(StrengthExperience += Amount * EXP_MULTIPLIER, MIN_EXP, MAX_EXP);
1239 LimitRef(AgilityExperience += Amount * EXP_MULTIPLIER, MIN_EXP, MAX_EXP);
1240 return character::EditAllAttributes(Amount)
1241 || (Amount < 0
1242 && (StrengthExperience != MIN_EXP || AgilityExperience != MIN_EXP))
1243 || (Amount > 0
1244 && (StrengthExperience != MAX_EXP || AgilityExperience != MAX_EXP));
1247 #ifdef WIZARD
1249 void nonhumanoid::AddAttributeInfo(festring& Entry) const
1251 Entry.Resize(45);
1252 Entry << GetAttribute(ARM_STRENGTH);
1253 Entry.Resize(48);
1254 Entry << "- - " << GetAttribute(AGILITY);
1255 character::AddAttributeInfo(Entry);
1258 void nonhumanoid::AddAttackInfo(felist& List) const
1260 festring Entry;
1262 if(IsUsingArms())
1264 Entry = CONST_S(" unarmed attack");
1265 Entry.Resize(50);
1266 Entry << GetUnarmedMinDamage() << '-' << GetUnarmedMaxDamage();
1267 Entry.Resize(60);
1268 Entry << int(GetUnarmedToHitValue());
1269 Entry.Resize(70);
1270 Entry << GetUnarmedAPCost();
1271 List.AddEntry(Entry, LIGHT_GRAY);
1274 if(IsUsingLegs())
1276 Entry = CONST_S(" kick attack");
1277 Entry.Resize(50);
1278 Entry << GetKickMinDamage() << '-' << GetKickMaxDamage();
1279 Entry.Resize(60);
1280 Entry << int(GetKickToHitValue());
1281 Entry.Resize(70);
1282 Entry << GetKickAPCost();
1283 List.AddEntry(Entry, LIGHT_GRAY);
1286 if(IsUsingHead())
1288 Entry = CONST_S(" bite attack");
1289 Entry.Resize(50);
1290 Entry << GetBiteMinDamage() << '-' << GetBiteMaxDamage();
1291 Entry.Resize(60);
1292 Entry << int(GetBiteToHitValue());
1293 Entry.Resize(70);
1294 Entry << GetBiteAPCost();
1295 List.AddEntry(Entry, LIGHT_GRAY);
1299 #else
1301 void nonhumanoid::AddAttributeInfo(festring&) const { }
1302 void nonhumanoid::AddAttackInfo(felist&) const { }
1304 #endif
1306 truth elpuri::MustBeRemovedFromBone() const
1308 return !IsEnabled() || GetTeam()->GetID() != MONSTER_TEAM || GetDungeon()->GetIndex() != ELPURI_CAVE || GetLevel()->GetIndex() != DARK_LEVEL;
1311 truth genetrixvesana::MustBeRemovedFromBone() const
1313 return !IsEnabled() || GetTeam()->GetID() != MONSTER_TEAM || GetDungeon()->GetIndex() != UNDER_WATER_TUNNEL || GetLevel()->GetIndex() != VESANA_LEVEL;
1316 void ghost::AddName(festring& String, int Case) const
1318 if(OwnerSoul.IsEmpty() || Case & PLURAL)
1319 character::AddName(String, Case);
1320 else
1322 character::AddName(String, (Case|ARTICLE_BIT)&~INDEFINE_BIT);
1323 String << " of " << OwnerSoul;
1327 void ghost::Save(outputfile& SaveFile) const
1329 nonhumanoid::Save(SaveFile);
1330 SaveFile << OwnerSoul << Active;
1333 void ghost::Load(inputfile& SaveFile)
1335 nonhumanoid::Load(SaveFile);
1336 SaveFile >> OwnerSoul >> Active;
1339 truth ghost::RaiseTheDead(character* Summoner)
1341 itemvector ItemVector;
1342 GetStackUnder()->FillItemVector(ItemVector);
1344 for(uInt c = 0; c < ItemVector.size(); ++c)
1345 if(ItemVector[c]->SuckSoul(this, Summoner))
1346 return true;
1348 if(IsPlayer())
1349 ADD_MESSAGE("You shudder.");
1350 else if(CanBeSeenByPlayer())
1351 ADD_MESSAGE("%s shudders.", CHAR_NAME(DEFINITE));
1353 return false;
1356 int ghost::ReceiveBodyPartDamage(character* Damager, int Damage, int Type, int BodyPartIndex, int Direction, truth PenetrateResistance, truth Critical, truth ShowNoDamageMsg, truth CaptureBodyPart)
1358 if(Type != SOUND)
1360 Active = true;
1361 return character::ReceiveBodyPartDamage(Damager, Damage, Type, BodyPartIndex, Direction, PenetrateResistance, Critical, ShowNoDamageMsg, CaptureBodyPart);
1363 else
1364 return 0;
1367 void ghost::GetAICommand()
1369 if(Active)
1370 character::GetAICommand();
1371 else
1373 if(CheckForEnemies(false, false, false))
1374 return;
1376 EditAP(-1000);
1380 int largecreature::GetSquareIndex(v2 Pos) const
1382 v2 RelativePos = Pos - GetPos();
1383 return RelativePos.X + (RelativePos.Y << 1);
1386 square* largecreature::GetNeighbourSquare(int I) const
1388 square* SquareUnder = GetSquareUnder();
1389 area* Area = SquareUnder->GetArea();
1390 v2 Pos = SquareUnder->GetPos() + game::GetLargeMoveVector(I);
1391 return Area->IsValidPos(Pos) ? SquareUnder->GetArea()->GetSquare(Pos) : 0;
1394 int largecreature::CalculateNewSquaresUnder(lsquare** NewSquare, v2 Pos) const
1396 level* Level = GetLevel();
1398 for(int c = 0; c < 4; ++c)
1400 v2 SquarePos = Pos + game::GetLargeMoveVector(12 + c);
1402 if(Level->IsValidPos(SquarePos))
1403 NewSquare[c] = Level->GetLSquare(SquarePos);
1404 else
1405 return 0;
1408 return 4;
1411 truth largecreature::IsFreeForMe(square* Square) const
1413 v2 Pos = Square->GetPos();
1414 area* Area = Square->GetArea();
1416 for(int c = 0; c < 4; ++c)
1418 v2 SquarePos = Pos + game::GetLargeMoveVector(12 + c);
1420 if(!Area->IsValidPos(SquarePos) || (Area->GetSquare(SquarePos)->GetCharacter() && Area->GetSquare(SquarePos)->GetCharacter() != static_cast<ccharacter*>(this)))
1421 return false;
1424 return true;
1427 truth largecreature::CanTheoreticallyMoveOn(const lsquare* LSquare) const
1429 v2 Pos = LSquare->GetPos();
1430 level* Level = LSquare->GetLevel();
1432 for(int c = 0; c < 4; ++c)
1434 v2 SquarePos = Pos + game::GetLargeMoveVector(12 + c);
1436 if(!Level->IsValidPos(SquarePos) || !(GetMoveType() & Level->GetLSquare(SquarePos)->GetTheoreticalWalkability()))
1437 return false;
1440 return true;
1443 truth largecreature::CanMoveOn(const lsquare* LSquare) const
1445 v2 Pos = LSquare->GetPos();
1446 level* Level = LSquare->GetLevel();
1448 for(int c = 0; c < 4; ++c)
1450 v2 SquarePos = Pos + game::GetLargeMoveVector(12 + c);
1452 if(!Level->IsValidPos(SquarePos) || !PartCanMoveOn(Level->GetLSquare(SquarePos)))
1453 return false;
1456 return true;
1459 truth largecreature::CanMoveOn(const square* Square) const
1461 v2 Pos = Square->GetPos();
1462 area* Area = Square->GetArea();
1464 for(int c = 0; c < 4; ++c)
1466 v2 SquarePos = Pos + game::GetLargeMoveVector(12 + c);
1467 if(!Area->IsValidPos(SquarePos) || !(GetMoveType() & Area->GetSquare(SquarePos)->GetSquareWalkability()))
1468 return false;
1471 return true;
1474 void largecreature::PutTo(v2 Pos)
1476 for(int c = 0; c < 4; ++c)
1478 SquareUnder[c] = game::GetCurrentArea()->GetSquare(Pos + game::GetLargeMoveVector(12 + c));
1479 SquareUnder[c]->AddCharacter(this);
1483 void largecreature::Remove()
1485 for(int c = 0; c < 4; ++c)
1487 SquareUnder[c]->RemoveCharacter();
1488 SquareUnder[c] = 0;
1492 void largecreature::CreateCorpse(lsquare* Square)
1494 if(!BodyPartsDisappearWhenSevered() && !game::AllBodyPartsVanish())
1496 corpse* Corpse = largecorpse::Spawn(0, NO_MATERIALS);
1497 Corpse->SetDeceased(this);
1498 Square->AddItem(Corpse);
1499 Disable();
1501 else
1502 SendToHell();
1505 void largecreature::LoadSquaresUnder()
1507 for(int c = 0; c < 4; ++c)
1508 SquareUnder[c] = game::GetSquareInLoad()->GetArea()->GetSquare(game::GetSquareInLoad()->GetPos() + game::GetLargeMoveVector(12 + c));
1511 truth vladimir::MustBeRemovedFromBone() const
1513 return !IsEnabled() || GetTeam()->GetID() != IVAN_TEAM || GetDungeon()->GetIndex() != ELPURI_CAVE|| GetLevel()->GetIndex() != IVAN_LEVEL;
1516 void hattifattener::GetAICommand()
1518 if(!(RAND() % 7))
1520 if(CanBeSeenByPlayer())
1521 ADD_MESSAGE("%s emits a lightning bolt!", CHAR_DESCRIPTION(DEFINITE));
1523 beamdata Beam
1525 this,
1526 "killed by a hattifattener's lightning",
1527 GetPos(),
1528 WHITE,
1529 BEAM_LIGHTNING,
1530 RAND() & 7,
1531 1 + (RAND() & 7),
1535 GetLevel()->LightningBeam(Beam);
1536 EditAP(-1000);
1537 return;
1540 SeekLeader(GetLeader());
1542 if(FollowLeader(GetLeader()))
1543 return;
1545 if(MoveRandomly())
1546 return;
1548 EditAP(-1000);
1551 void hattifattener::CreateCorpse(lsquare* Square)
1553 level* Level = Square->GetLevel();
1554 feuLong StackSize = Level->AddRadiusToSquareStack(Square->GetPos(), 9);
1555 lsquare** SquareStack = Level->GetSquareStack();
1556 feuLong c;
1558 for(c = 0; c < StackSize; ++c)
1559 SquareStack[c]->RemoveFlags(IN_SQUARE_STACK);
1561 fearray<lsquare*> Stack(SquareStack, StackSize);
1562 Level->LightningVisualizer(Stack, WHITE);
1564 for(c = 0; c < Stack.Size; ++c)
1566 beamdata Beam
1568 this,
1569 CONST_S("killed by electricity released by a dying hattifattener"),
1570 YOURSELF,
1574 Stack[c]->Lightning(Beam);
1577 SendToHell();
1580 void hedgehog::SpecialBodyDefenceEffect(character* Enemy, bodypart* BodyPart, int Type)
1582 if(Type != WEAPON_ATTACK && RAND() & 1)
1584 if(Enemy->IsPlayer())
1585 ADD_MESSAGE("%s spines jab your %s!", CHAR_POSSESSIVE_PRONOUN, BodyPart->GetBodyPartName().CStr());
1586 else if(CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer())
1587 ADD_MESSAGE("%s spines jab %s!", CHAR_POSSESSIVE_PRONOUN, Enemy->CHAR_NAME(DEFINITE));
1589 Enemy->ReceiveBodyPartDamage(this, 1 + (RAND() & 1), PHYSICAL_DAMAGE, BodyPart->GetBodyPartIndex(), YOURSELF, false, false, true, false);
1590 Enemy->CheckDeath(CONST_S("killed by the pointy spines of ") + GetName(INDEFINITE), this);
1594 void genetrixvesana::Save(outputfile& SaveFile) const
1596 nonhumanoid::Save(SaveFile);
1597 SaveFile << TurnsExisted;
1600 void genetrixvesana::Load(inputfile& SaveFile)
1602 nonhumanoid::Load(SaveFile);
1603 SaveFile >> TurnsExisted;
1606 truth largecreature::CreateRoute()
1608 Route.clear();
1610 if(GetAttribute(INTELLIGENCE) >= 10 && !StateIsActivated(CONFUSED))
1612 node* Node = GetLevel()->FindRoute(GetPos(), GoingTo, Illegal, 0, this);
1614 if(Node)
1615 while(Node->Last)
1617 Route.push_back(Node->Pos);
1618 Node = Node->Last;
1620 else
1621 TerminateGoingTo();
1623 IntelligenceAction(5);
1624 return true;
1626 else
1627 return false;
1630 void bunny::GetAICommand()
1632 if(GetConfig() < 4 && GetNP() > (SATIATED_LEVEL + BLOATED_LEVEL) >> 1)
1634 if(CanBeSeenByPlayer())
1635 ADD_MESSAGE("%s looks more mature.", CHAR_NAME(DEFINITE));
1637 GetTorso()->SetSize(GetTorso()->GetSize() << 1);
1638 LimitRef(StrengthExperience *= 2, MIN_EXP, MAX_EXP);
1639 LimitRef(AgilityExperience *= 2, MIN_EXP, MAX_EXP);
1641 for(int c = 0; c < BASE_ATTRIBUTES; ++c)
1642 BaseExperience[c] = Limit(BaseExperience[c] * 2, MIN_EXP, MAX_EXP);
1644 GetTorso()->GetMainMaterial()->SetVolume(GetTorso()->GetMainMaterial()->GetVolume() << 1);
1645 SetConfig(GetConfig() + 2);
1646 RestoreHP();
1647 RestoreStamina();
1650 SeekLeader(GetLeader());
1652 if(FollowLeader(GetLeader()))
1653 return;
1655 if(CheckForEnemies(true, true, true))
1656 return;
1658 if(CheckForUsefulItemsOnGround())
1659 return;
1661 if(CheckForDoors())
1662 return;
1664 if(CheckForFood(5))
1665 return;
1667 if(CheckForMatePartner())
1668 return;
1670 if(MoveRandomly())
1671 return;
1673 EditAP(-1000);
1676 void bunny::SignalNaturalGeneration()
1678 character* Partner = bunny::Spawn(GetConfig()^1);
1679 Partner->SetTeam(GetTeam());
1680 Partner->SetGenerationDanger(GetGenerationDanger());
1681 Partner->PutNear(GetPos());
1684 truth bunny::CheckForMatePartner()
1686 if(GetConfig() == ADULT_MALE)
1688 character* BestPartner = 0;
1689 double BestPartnerDanger = 0;
1691 for(int c = 0; c < game::GetTeams(); ++c)
1692 if(GetTeam()->GetRelation(game::GetTeam(c)) != HOSTILE)
1693 for(std::list<character*>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i)
1694 if((*i)->IsEnabled() && (*i)->IsBunny() && (*i)->GetConfig() == ADULT_FEMALE && (*i)->GetNP() > SATIATED_LEVEL)
1696 double Danger = (*i)->GetRelativeDanger(this, true);
1698 if(Danger > BestPartnerDanger)
1700 BestPartner = *i;
1701 BestPartnerDanger = Danger;
1705 if(BestPartner && !GetPos().IsAdjacent(BestPartner->GetPos()))
1707 SetGoingTo(BestPartner->GetPos());
1708 MoveTowardsTarget(true);
1709 return true;
1713 if(GetConfig() == ADULT_FEMALE && GetNP() > NOT_HUNGER_LEVEL + 10000)
1715 for(int d = 0; d < GetNeighbourSquares(); ++d)
1717 lsquare* Square = GetNeighbourLSquare(d);
1719 if(Square)
1721 character* Father = Square->GetCharacter();
1723 if(Father && Father->IsBunny() && Father->GetConfig() == ADULT_MALE && GetRelation(Father) != HOSTILE)
1725 if(CanBeSeenByPlayer())
1727 if(Father->IsPlayer())
1728 ADD_MESSAGE("You have much fun with %s.", CHAR_NAME(DEFINITE));
1729 else if(Father->CanBeSeenByPlayer())
1730 ADD_MESSAGE("%s and %s seem to have much fun together.", Father->CHAR_NAME(DEFINITE), CHAR_NAME(DEFINITE));
1731 else
1732 ADD_MESSAGE("%s seems to have much fun.", CHAR_NAME(DEFINITE));
1734 else
1736 if(Father->IsPlayer())
1737 ADD_MESSAGE("You have much fun with something.");
1738 else if(Father->CanBeSeenByPlayer())
1739 ADD_MESSAGE("%s seems to have much fun.", Father->CHAR_NAME(DEFINITE));
1742 bunny* Baby = bunny::Spawn(BABY_MALE + (RAND() & 1));
1743 Baby->StrengthExperience = RandomizeBabyExperience(StrengthExperience + static_cast<bunny*>(Father)->StrengthExperience);
1744 Baby->AgilityExperience = RandomizeBabyExperience(AgilityExperience + static_cast<bunny*>(Father)->AgilityExperience);
1746 if(Baby->GetConfig() == BABY_MALE)
1748 Baby->StrengthExperience *= 4;
1749 Baby->AgilityExperience *= 4;
1751 else
1753 Baby->StrengthExperience *= 2;
1754 Baby->AgilityExperience *= 6;
1757 Baby->StrengthExperience /= 3;
1758 Baby->AgilityExperience /= 5;
1760 for(int c = 0; c < BASE_ATTRIBUTES; ++c)
1761 Baby->BaseExperience[c] = RandomizeBabyExperience(BaseExperience[c] + static_cast<bunny*>(Father)->BaseExperience[c]);
1763 Baby->CalculateAll();
1764 Baby->RestoreHP();
1765 Baby->RestoreStamina();
1766 Baby->SetTeam(GetTeam());
1767 Baby->SetGenerationDanger(GetGenerationDanger());
1768 Baby->PutNear(GetPos());
1770 if(Baby->CanBeSeenByPlayer())
1771 ADD_MESSAGE("%s is born.", Baby->CHAR_NAME(INDEFINITE));
1773 EditNP(-10000);
1774 Father->EditAP(-3000);
1775 EditAP(-5000);
1776 EditStamina(-GetMaxStamina() >> 1, true);
1777 Father->EditStamina(-(Father->GetMaxStamina() << 2) / 5, true);
1778 return true;
1784 return false;
1787 truth bunny::Catches(item* Thingy)
1789 if(Thingy->BunnyWillCatchAndConsume(this))
1791 if(ConsumeItem(Thingy, CONST_S("eating")))
1793 if(IsPlayer())
1794 ADD_MESSAGE("You catch %s in mid-air and consume it.", Thingy->CHAR_NAME(DEFINITE));
1795 else
1797 if(CanBeSeenByPlayer())
1798 ADD_MESSAGE("%s catches %s and eats it.", CHAR_NAME(DEFINITE), Thingy->CHAR_NAME(DEFINITE));
1800 ChangeTeam(PLAYER->GetTeam());
1803 else if(IsPlayer())
1804 ADD_MESSAGE("You catch %s in mid-air.", Thingy->CHAR_NAME(DEFINITE));
1805 else if(CanBeSeenByPlayer())
1806 ADD_MESSAGE("%s catches %s.", CHAR_NAME(DEFINITE), Thingy->CHAR_NAME(DEFINITE));
1808 return true;
1810 else
1811 return false;
1814 truth largecreature::PlaceIsIllegal(v2 Pos, v2 Illegal) const
1816 for(int c = 0; c < 4; ++c)
1817 if(Pos + game::GetLargeMoveVector(12 + c) == Illegal)
1818 return true;
1820 return false;
1823 truth mommo::Hit(character* Enemy, v2 Pos, int, int)
1825 if(CheckIfTooScaredToHit(Enemy))
1826 return false;
1828 if(IsPlayer())
1830 if(!(Enemy->IsMasochist() && GetRelation(Enemy) == FRIEND) && GetRelation(Enemy) != HOSTILE && !game::TruthQuestion(CONST_S("This might cause a hostile reaction. Are you sure? [y/N]")))
1831 return false;
1833 else if(GetAttribute(WISDOM) >= Enemy->GetAttackWisdomLimit())
1834 return false;
1836 Hostility(Enemy);
1838 if(IsPlayer())
1839 ADD_MESSAGE("You spill acidous slime at %s.", Enemy->CHAR_DESCRIPTION(DEFINITE));
1840 else if(Enemy->IsPlayer() || CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer())
1841 ADD_MESSAGE("%s spills acidous slime at %s.", CHAR_DESCRIPTION(DEFINITE), Enemy->CHAR_DESCRIPTION(DEFINITE));
1843 Vomit(Pos, 250 + RAND() % 250, false);
1844 EditAP(-1000);
1845 return true;
1848 void mommo::GetAICommand()
1850 SeekLeader(GetLeader());
1852 if(CheckForEnemies(false, false, true))
1853 return;
1855 if(!(RAND() % 10))
1857 VomitAtRandomDirection(350 + RAND() % 350);
1858 EditAP(-1000);
1859 return;
1862 if(FollowLeader(GetLeader()))
1863 return;
1865 if(MoveRandomly())
1866 return;
1868 EditAP(-1000);
1871 void dog::GetAICommand () {
1872 if (!game::IsInWilderness() && !(RAND()&7))
1873 GetLSquareUnder()->SpillFluid(this, liquid::Spawn(DOG_DROOL, 25+RAND()%50), false, false);
1874 character::GetAICommand();
1877 truth blinkdog::SpecialEnemySightedReaction(character*)
1879 if(!(RAND() & 15) && SummonFriend())
1880 return true;
1882 if(!(RAND() & 31))
1884 MonsterTeleport("playful bark");
1885 return true;
1888 if((!(RAND() & 3) && StateIsActivated(PANIC))
1889 || (!(RAND() & 7) && IsInBadCondition()))
1891 MonsterTeleport("frightened howl");
1892 return true;
1895 return false;
1898 void blinkdog::MonsterTeleport(cchar* BarkMsg)
1900 if(CanBeSeenByPlayer())
1901 ADD_MESSAGE("You hear a %s inside your head as %s vanishes!", BarkMsg, CHAR_NAME(DEFINITE));
1902 else
1903 ADD_MESSAGE("You hear a %s inside your head.", BarkMsg);
1905 v2 Pos = GetPos();
1906 rect Border(Pos + v2(-5, -5), Pos + v2(5, 5));
1907 Pos = GetLevel()->GetRandomSquare(this, 0, &Border);
1909 if(Pos == ERROR_V2)
1910 Pos = GetLevel()->GetRandomSquare(this);
1912 Move(Pos, true);
1914 if(CanBeSeenByPlayer())
1915 ADD_MESSAGE("%s materializes from nowhere!", CHAR_NAME(INDEFINITE));
1917 EditAP(-1000);
1920 int unicorn::TakeHit(character* Enemy, item* Weapon, bodypart* EnemyBodyPart, v2 HitPos, double Damage, double ToHitValue, int Success, int Type, int Direction, truth Critical, truth ForceHit)
1922 int Return = nonhumanoid::TakeHit(Enemy, Weapon, EnemyBodyPart, HitPos, Damage, ToHitValue, Success, Type, Direction, Critical, ForceHit);
1924 if(Return != HAS_DIED
1925 && (StateIsActivated(PANIC)
1926 || (RAND() & 1 && IsInBadCondition())
1927 || !(RAND() & 7)))
1928 MonsterTeleport("neighs in terror");
1930 return Return;
1933 int blinkdog::TakeHit(character* Enemy, item* Weapon, bodypart* EnemyBodyPart, v2 HitPos, double Damage, double ToHitValue, int Success, int Type, int Direction, truth Critical, truth ForceHit)
1935 int Return = nonhumanoid::TakeHit(Enemy, Weapon, EnemyBodyPart, HitPos, Damage, ToHitValue, Success, Type, Direction, Critical, ForceHit);
1937 if(Return != HAS_DIED)
1939 if(!(RAND() & 15) && SummonFriend())
1940 return Return;
1942 if((RAND() & 1 && StateIsActivated(PANIC))
1943 || (!(RAND() & 3) && IsInBadCondition())
1944 || !(RAND() & 15))
1945 MonsterTeleport("terrified yelp");
1948 return Return;
1951 void unicorn::MonsterTeleport(cchar* NeighMsg)
1953 if(CanBeSeenByPlayer())
1954 ADD_MESSAGE("%s %s and disappears!", CHAR_NAME(DEFINITE), NeighMsg);
1956 Move(GetLevel()->GetRandomSquare(this), true);
1958 if(CanBeSeenByPlayer())
1959 ADD_MESSAGE("Suddenly %s appears from nothing!", CHAR_NAME(INDEFINITE));
1961 EditAP(-1000);
1964 truth blinkdog::SummonFriend()
1966 if(!SummonModifier)
1967 return false;
1969 --SummonModifier;
1970 blinkdog* Buddy = blinkdog::Spawn();
1971 Buddy->SummonModifier = SummonModifier;
1972 Buddy->SetTeam(GetTeam());
1973 Buddy->SetGenerationDanger(GetGenerationDanger());
1974 Buddy->PutNear(GetPos());
1976 if(CanBeSeenByPlayer())
1978 ADD_MESSAGE("%s wags its tail in a mysterious pattern.", CHAR_NAME(DEFINITE));
1980 if(Buddy->CanBeSeenByPlayer())
1981 ADD_MESSAGE("Another of its kin appears!");
1983 else if(Buddy->CanBeSeenByPlayer())
1984 ADD_MESSAGE("%s appears!", Buddy->CHAR_NAME(INDEFINITE));
1986 EditAP(-1000);
1987 return true;
1990 blinkdog::blinkdog()
1992 if(!game::IsLoading())
1993 SummonModifier = RAND_2 + RAND_2 + RAND_2 + RAND_2 + RAND_2 + RAND_2 + RAND_2;
1996 void blinkdog::Save(outputfile& SaveFile) const
1998 dog::Save(SaveFile);
1999 SaveFile << SummonModifier;
2002 void blinkdog::Load(inputfile& SaveFile)
2004 dog::Load(SaveFile);
2005 SaveFile >> SummonModifier;
2008 void genetrixvesana::FinalProcessForBone()
2010 largecreature::FinalProcessForBone();
2011 TurnsExisted = 0;
2014 void carnivorousplant::GetAICommand()
2016 SeekLeader(GetLeader());
2018 if(FollowLeader(GetLeader()))
2019 return;
2021 if(AttackAdjacentEnemyAI())
2022 return;
2024 if(CheckForUsefulItemsOnGround())
2025 return;
2027 if(MoveRandomly())
2028 return;
2030 EditAP(-1000);
2033 void mysticfrog::GetAICommand()
2035 SeekLeader(GetLeader());
2037 if(FollowLeader(GetLeader()))
2038 return;
2040 character* NearestEnemy = 0;
2041 sLong NearestEnemyDistance = 0x7FFFFFFF;
2042 character* RandomFriend = 0;
2043 charactervector Friend;
2044 v2 Pos = GetPos();
2045 truth Enemies = false;
2047 for(int c = 0; c < game::GetTeams(); ++c)
2049 if(GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE)
2051 for(std::list<character*>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i)
2052 if((*i)->IsEnabled())
2054 Enemies = true;
2055 sLong ThisDistance = Max<sLong>(abs((*i)->GetPos().X - Pos.X), abs((*i)->GetPos().Y - Pos.Y));
2057 if((ThisDistance < NearestEnemyDistance || (ThisDistance == NearestEnemyDistance && !(RAND() % 3))) && (*i)->CanBeSeenBy(this))
2059 NearestEnemy = *i;
2060 NearestEnemyDistance = ThisDistance;
2064 else if(GetTeam()->GetRelation(game::GetTeam(c)) == FRIEND)
2066 for(std::list<character*>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i)
2067 if((*i)->IsEnabled() && (*i)->CanBeSeenBy(this))
2068 Friend.push_back(*i);
2072 if(NearestEnemy && NearestEnemy->GetPos().IsAdjacent(Pos))
2074 if(NearestEnemy->IsSmall()
2075 && GetAttribute(WISDOM) < NearestEnemy->GetAttackWisdomLimit()
2076 && !(RAND() % 5)
2077 && Hit(NearestEnemy, NearestEnemy->GetPos(), game::GetDirectionForVector(NearestEnemy->GetPos() - GetPos())))
2078 return;
2079 else if(!(RAND() & 3))
2081 if(CanBeSeenByPlayer())
2082 ADD_MESSAGE("%s invokes a spell and disappears.", CHAR_NAME(DEFINITE));
2084 TeleportRandomly(true);
2085 EditAP(-GetSpellAPCost());
2086 return;
2090 if(NearestEnemy && (NearestEnemyDistance < 10 || StateIsActivated(PANIC)) && RAND() & 3)
2092 SetGoingTo((Pos << 1) - NearestEnemy->GetPos());
2094 if(MoveTowardsTarget(true))
2095 return;
2098 if(Friend.size() && !(RAND() & 3))
2100 RandomFriend = Friend[RAND() % Friend.size()];
2101 NearestEnemy = 0;
2104 if(GetRelation(PLAYER) == HOSTILE && PLAYER->CanBeSeenBy(this) && !RAND_4)
2105 NearestEnemy = PLAYER;
2107 beamdata Beam
2109 this,
2110 CONST_S("killed by the spells of ") + GetName(INDEFINITE),
2111 YOURSELF,
2115 if(NearestEnemy)
2117 lsquare* Square = NearestEnemy->GetLSquareUnder();
2118 EditAP(-GetSpellAPCost());
2120 if(CanBeSeenByPlayer())
2121 ADD_MESSAGE("%s invokes a spell!", CHAR_NAME(DEFINITE));
2123 switch(RAND() % 20)
2125 case 0:
2126 case 1:
2127 case 2:
2128 case 3:
2129 case 4:
2130 case 5: Square->DrawParticles(RED); if(NearestEnemy->TeleportRandomItem(GetConfig() == DARK)) break;
2131 case 6:
2132 case 7:
2133 case 8:
2134 case 9:
2135 case 10: Square->DrawParticles(RED); Square->Teleport(Beam); break;
2136 case 11:
2137 case 12:
2138 case 13:
2139 case 14: Square->DrawParticles(RED); Square->Slow(Beam); break;
2140 case 15: Square->DrawParticles(RED); Square->LowerEnchantment(Beam); break;
2141 default: Square->DrawLightning(v2(8, 8), WHITE, YOURSELF); Square->Lightning(Beam); break;
2144 if(CanBeSeenByPlayer())
2145 NearestEnemy->DeActivateVoluntaryAction(CONST_S("The spell of ") + GetName(DEFINITE) + CONST_S(" interrupts you."));
2146 else
2147 NearestEnemy->DeActivateVoluntaryAction(CONST_S("The spell interrupts you."));
2149 return;
2152 if(RandomFriend && Enemies)
2154 lsquare* Square = RandomFriend->GetLSquareUnder();
2155 EditAP(-GetSpellAPCost());
2156 Square->DrawParticles(RED);
2158 if(RAND() & 1)
2159 Square->Invisibility(Beam);
2160 else
2161 Square->Haste(Beam);
2163 return;
2166 StandIdleAI();
2169 truth largecreature::PartCanMoveOn(const lsquare* LSquare) const
2171 int Walkability = LSquare->GetWalkability();
2173 if(GetMoveType() & Walkability)
2174 return true;
2176 if(DestroysWalls() && Walkability & ETHEREAL)
2178 olterrain* Terrain = LSquare->GetOLTerrain();
2180 if(Terrain && Terrain->WillBeDestroyedBy(this))
2182 room* Room = LSquare->GetRoom();
2184 if(!Room || Room->IsOKToDestroyWalls(this))
2185 return true;
2189 return false;
2192 void spider::GetAICommand()
2194 SeekLeader(GetLeader());
2196 if(FollowLeader(GetLeader()))
2197 return;
2199 character* NearestChar = 0;
2200 sLong NearestDistance = 0x7FFFFFFF;
2201 v2 Pos = GetPos();
2202 int Hostiles = 0;
2204 for(int c = 0; c < game::GetTeams(); ++c)
2205 if(GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE)
2206 for(std::list<character*>::const_iterator i = game::GetTeam(c)->GetMember().begin();
2207 i != game::GetTeam(c)->GetMember().end(); ++i)
2208 if((*i)->IsEnabled() && GetAttribute(WISDOM) < (*i)->GetAttackWisdomLimit())
2210 sLong ThisDistance = Max<sLong>(abs((*i)->GetPos().X - Pos.X), abs((*i)->GetPos().Y - Pos.Y));
2211 ++Hostiles;
2213 if((ThisDistance < NearestDistance
2214 || (ThisDistance == NearestDistance && !(RAND() % 3)))
2215 && (*i)->CanBeSeenBy(this, false, IsGoingSomeWhere())
2216 && (!IsGoingSomeWhere() || HasClearRouteTo((*i)->GetPos())))
2218 NearestChar = *i;
2219 NearestDistance = ThisDistance;
2223 if(Hostiles && !RAND_N(Max(80 / Hostiles, 8)))
2225 web* Web = web::Spawn();
2226 Web->SetStrength(GetConfig() == LARGE ? 10 : 25);
2228 if(GetLSquareUnder()->AddTrap(Web))
2230 if(CanBeSeenByPlayer())
2231 ADD_MESSAGE("%s spins a web.", CHAR_NAME(DEFINITE));
2233 EditAP(-1000);
2234 return;
2238 if(NearestChar)
2240 if(NearestChar->IsStuck())
2241 SetGoingTo(NearestChar->GetPos());
2242 else
2243 SetGoingTo((Pos << 1) - NearestChar->GetPos());
2245 if(MoveTowardsTarget(true))
2246 return;
2249 if(MoveRandomly())
2250 return;
2252 EditAP(-1000);
2255 void largecat::Save(outputfile& SaveFile) const
2257 nonhumanoid::Save(SaveFile);
2258 SaveFile << Lives;
2261 void largecat::Load(inputfile& SaveFile)
2263 nonhumanoid::Load(SaveFile);
2264 SaveFile >> Lives;
2267 truth largecat::SpecialSaveLife()
2269 if(--Lives <= 0 || game::IsInWilderness())
2270 return false;
2272 if(IsPlayer())
2273 ADD_MESSAGE("But wait! You seem to have miraculously avoided certain death!");
2274 else if(CanBeSeenByPlayer())
2275 ADD_MESSAGE("But wait, %s seems to have miraculously avoided certain death!", GetPersonalPronoun().CStr());
2277 v2 Pos = GetPos();
2278 rect Border(Pos + v2(-20, -20), Pos + v2(20, 20));
2279 Pos = GetLevel()->GetRandomSquare(this, 0, &Border);
2281 if(Pos == ERROR_V2)
2282 Pos = GetLevel()->GetRandomSquare(this);
2284 Move(Pos, true);
2286 if(!IsPlayer() && CanBeSeenByPlayer())
2287 ADD_MESSAGE("%s appears!", CHAR_NAME(INDEFINITE));
2289 if(IsPlayer())
2290 game::AskForEscPress(CONST_S("Life saved!"));
2292 RestoreBodyParts();
2293 ResetSpoiling();
2294 RestoreHP();
2295 RestoreStamina();
2296 ResetStates();
2298 if(GetNP() < SATIATED_LEVEL)
2299 SetNP(SATIATED_LEVEL);
2301 SendNewDrawRequest();
2303 if(GetAction())
2304 GetAction()->Terminate(false);
2306 return true;
2309 truth lobhse::MustBeRemovedFromBone() const
2311 return !IsEnabled() || GetTeam()->GetID() != MONSTER_TEAM || GetDungeon()->GetIndex() != UNDER_WATER_TUNNEL || GetLevel()->GetIndex() != SPIDER_LEVEL;
2314 truth lobhse::SpecialBiteEffect(character* Char, v2, int, int, truth BlockedByArmour)
2316 if(!BlockedByArmour)
2318 Char->BeginTemporaryState(POISONED, 80 + RAND() % 40);
2319 return true;
2321 else
2322 return false;
2325 void lobhse::GetAICommand()
2327 if(MoveRandomly())
2328 return;
2330 EditAP(-1000);
2333 void lobhse::CreateCorpse(lsquare* Square)
2335 largecreature::CreateCorpse(Square);
2338 void mindworm::GetAICommand()
2340 character* NeighbourEnemy = GetRandomNeighbour(HOSTILE);
2343 if(NeighbourEnemy && NeighbourEnemy->IsHumanoid() && NeighbourEnemy->HasHead()
2344 && !NeighbourEnemy->IsInfectedByMindWorm())
2346 TryToImplantLarvae(NeighbourEnemy);
2347 return;
2350 character* NearestEnemy = GetNearestEnemy();
2352 if(NearestEnemy)
2354 PsiAttack(NearestEnemy);
2355 return;
2358 if(MoveRandomly())
2359 return;
2361 EditAP(-1000);
2364 void mindworm::TryToImplantLarvae(character* Victim)
2366 if(Victim->MindWormCanPenetrateSkull(this))
2368 Victim->SetCounterToMindWormHatch(100);
2369 if(Victim->IsPlayer())
2371 ADD_MESSAGE("%s penetrates digs through your skull, lays %s eggs and jumps out.",
2372 CHAR_NAME(DEFINITE), CHAR_POSSESSIVE_PRONOUN);
2374 else if(Victim->CanBeSeenByPlayer())
2376 ADD_MESSAGE("%s penetrates digs through %s's skull, lays %s eggs and jumps out.",
2377 CHAR_NAME(DEFINITE), Victim->CHAR_NAME(DEFINITE), CHAR_POSSESSIVE_PRONOUN);
2379 MoveRandomly();
2383 void mindworm::PsiAttack(character* Victim)
2385 if(Victim->IsPlayer())
2387 ADD_MESSAGE("Your brain is on fire.");
2389 else if(Victim->CanBeSeenByPlayer() && PLAYER->GetAttribute(PERCEPTION) > RAND_N(20))
2391 ADD_MESSAGE("%s looks scared.", Victim->CHAR_NAME(DEFINITE));
2395 Victim->ReceiveDamage(this, 1 + RAND_N(5), PSI, ALL, 8, false, false, false, false);
2396 Victim->CheckDeath(CONST_S("killed by ") + GetName(INDEFINITE) + "'s psi attack", this);
2400 ////////////////////////////////////////////////////////////////////////////////
2401 bodypart *menatrixfusanga::MakeBodyPart (int) const { return menatrixtorso::Spawn(0, NO_MATERIALS); }
2402 col16 menatrixfusanga::GetSkinColor () const { return MakeRGB16(60+RAND()%190, 60+RAND()%190, 60+RAND()%190); }
2405 void menatrixfusanga::GetAICommand () {
2406 StandIdleAI();
2407 ++TurnsExisted;
2408 SeekLeader(GetLeader());
2409 if (FollowLeader(GetLeader())) return;
2410 if (!(RAND()%10)) {
2411 int NumberOfPlants = RAND()%3+RAND()%3+RAND()%3+RAND()%3;
2412 for (int c1 = 0; c1 < 50 && NumberOfPlants; ++c1) {
2413 for (int c2 = 0; c2 < game::GetTeams() && NumberOfPlants; ++c2) {
2414 if (GetTeam()->GetRelation(game::GetTeam(c2)) == HOSTILE) {
2415 for (std::list<character*>::const_iterator i = game::GetTeam(c2)->GetMember().begin(); i != game::GetTeam(c2)->GetMember().end() && NumberOfPlants; ++i) {
2416 if ((*i)->IsEnabled()) {
2417 lsquare *LSquare = (*i)->GetNeighbourLSquare(RAND()%GetNeighbourSquares());
2418 if (LSquare && (LSquare->GetWalkability() & WALK) && !LSquare->GetCharacter()) {
2419 character *NewPlant;
2420 sLong RandomValue = RAND()%TurnsExisted;
2421 if (RandomValue < 250) NewPlant = mushroom::Spawn();
2422 else if (RandomValue < 1500) NewPlant = magicmushroom::Spawn();
2423 else NewPlant = magicmushroom::Spawn();
2424 for (int c = 3; c < TurnsExisted / 500; ++c) NewPlant->EditAllAttributes(1);
2425 NewPlant->SetGenerationDanger(GetGenerationDanger());
2426 NewPlant->SetTeam(GetTeam());
2427 NewPlant->PutTo(LSquare->GetPos());
2428 --NumberOfPlants;
2429 if (NewPlant->CanBeSeenByPlayer()) {
2430 if ((*i)->IsPlayer()) ADD_MESSAGE("%s sprouts from the ground near you.", NewPlant->CHAR_NAME(INDEFINITE));
2431 else if ((*i)->CanBeSeenByPlayer()) ADD_MESSAGE("%s sprouts from the ground near %s.", NewPlant->CHAR_NAME(INDEFINITE), (*i)->CHAR_NAME(DEFINITE));
2432 else ADD_MESSAGE("%s sprouts from the ground.", NewPlant->CHAR_NAME(INDEFINITE));
2439 SeekLeader(GetLeader());
2440 if (FollowLeader(GetLeader())) return;
2441 lsquare *CradleSquare = GetNeighbourLSquare(RAND()%8);
2442 if (CradleSquare && !CradleSquare->GetCharacter() && (CradleSquare->GetWalkability() & WALK)) {
2443 int SpoiledItems = 0;
2444 int MushroomsNear = 0;
2445 for (int d = 0; d < 8; ++d) {
2446 lsquare *Square = CradleSquare->GetNeighbourLSquare(d);
2447 if (Square) {
2448 character *Char = Square->GetCharacter();
2449 if (Char && Char->IsMushroom()) ++MushroomsNear;
2450 SpoiledItems += Square->GetSpoiledItems();
2453 if ((SpoiledItems && MushroomsNear < 1 && !RAND_N(2)) || (MushroomsNear < 3 && !RAND_N((1+MushroomsNear)*2))) {
2454 magicmushroom *Child = magicmushroom::Spawn(GetConfig());
2455 switch (RAND()%3) {
2456 case 0: SetSpecies(MakeRGB16(125+RAND()%125, RAND()%100, RAND()%100)); break;
2457 case 1: SetSpecies(MakeRGB16(RAND()%100, 125+RAND()%125, RAND()%100)); break;
2458 case 2: SetSpecies(MakeRGB16(RAND()%100, RAND()%100, 125+RAND()%125)); break;
2460 Child->SetSpecies(Species);
2461 Child->SetTeam(GetTeam());
2462 Child->SetGenerationDanger(GetGenerationDanger());
2463 Child->PutTo(CradleSquare->GetPos());
2464 if (Child->CanBeSeenByPlayer()) ADD_MESSAGE("%s pops out from the ground.", Child->CHAR_NAME(INDEFINITE));
2467 if (AttackAdjacentEnemyAI()) return;
2468 if (MoveRandomly()) return;
2469 EditAP(-1000);
2471 EditAP(-2000);
2472 return;
2474 if (AttackAdjacentEnemyAI()) return;
2475 if (MoveRandomly()) return;
2476 EditAP(-1000);
2480 void menatrixfusanga::SetSpecies (int What) {
2481 Species = What;
2482 UpdatePictures();
2486 void menatrixfusanga::CreateCorpse (lsquare *Square) {
2487 for (int c = 0; c < 1; ++c) Square->AddItem(wand::Spawn(WAND_OF_CLONING));
2488 Square->AddItem(wand::Spawn(WAND_OF_MIRRORING));
2489 Square->AddItem(solstone::Spawn());
2490 largecreature::CreateCorpse(Square);
2494 void menatrixfusanga::Save (outputfile &SaveFile) const {
2495 nonhumanoid::Save(SaveFile);
2496 SaveFile << TurnsExisted;
2497 SaveFile << Species;
2501 void menatrixfusanga::Load (inputfile &SaveFile) {
2502 nonhumanoid::Load(SaveFile);
2503 SaveFile >> TurnsExisted;
2504 SaveFile >> Species;
2508 truth menatrixfusanga::MustBeRemovedFromBone () const {
2509 return !IsEnabled() || GetTeam()->GetID() != MONSTER_TEAM || GetDungeon()->GetIndex() != UNDER_WATER_TUNNEL || GetLevel()->GetIndex() != VESANA_LEVEL;
2513 void menatrixfusanga::FinalProcessForBone () {
2514 largecreature::FinalProcessForBone();
2515 TurnsExisted = 0;
2519 void solicitus::BeTalkedTo () {
2520 if (GetRelation(PLAYER) == HOSTILE) {
2521 ADD_MESSAGE("Oh no. Now is for the figthing!!!");
2522 return;
2525 if (PLAYER->StateIsActivated(PANIC) && !game::PlayerIsSolicitusChampion()) {
2526 ADD_MESSAGE("Solicitus perks up. \"Well hullo there mortal! Would you care to be my Champion? I'll give you a free copy of my celestial monograph on Atheism!\"");
2527 if (game::TruthQuestion(CONST_S("Do you choose to become the Champion of Solicitus? [y/n]"), REQUIRES_ANSWER)) {
2528 game::TextScreen(CONST_S(
2529 "Solicitus speaks:\n"
2530 "\"Becoming my champion involves my changing your sweat material into pure liquified fear.\"\n"
2531 "\"Now, hold still while I administer to your body what powers I have left!\"\n"));
2532 game::TextScreen(CONST_S("You feel Solicitus changing your sweat glands. It feels disgusting."));
2533 game::MakePlayerSolicitusChampion();
2534 PLAYER->EditCurrentSweatMaterial(LIQUID_HORROR);
2535 (celestialmonograph::Spawn())->MoveTo(PLAYER->GetStack());
2536 //pantheonbook* NewBook = pantheonbook::Spawn();
2537 //AddPlace->AddItem(NewBook);
2538 ADD_MESSAGE("\"Go forth, you are anointed! And here's your personal copy of my monograph, mortal. Enjoy!\"");
2539 GetArea()->SendNewDrawRequest();
2540 } else {
2541 ADD_MESSAGE("\"Not a problem, perhaps another time. It's not for everyone, you know.\"");
2542 return;
2544 } else if (PLAYER->StateIsActivated(PANIC) && game::PlayerIsSolicitusChampion()) {
2545 ADD_MESSAGE("\"I suppose you want to hear my life story?\"");
2546 } else {
2547 ADD_MESSAGE("\"Maybe you should empathise with my situation first. Go drink some liquified fear and then we'll talk.\"");
2552 void solicitus::GetAICommand () {
2553 if (MoveRandomly()) return;
2554 EditAP(-2000);
2555 return;
2559 void solicitus::CreateCorpse (lsquare *Square) {
2560 //for(int c = 0; c < 3; ++c) Square->AddItem(pineapple::Spawn());
2561 //largecreature::CreateCorpse(Square);
2562 ADD_MESSAGE("You hear a booming voice: \"No, mortal! This will not be done!\"");
2563 game::GetCurrentLevel()->Explosion(this, CONST_S("killed by an explosion of the toppled-god Solicitus"), PLAYER->GetPos(), 1300>>3, false);
2564 SendToHell();
2568 truth solicitus::MustBeRemovedFromBone () const {
2569 return !IsEnabled() || GetTeam()->GetID() != MONSTER_TEAM || GetDungeon()->GetIndex() != UNDER_WATER_TUNNEL || GetLevel()->GetIndex() != VESANA_LEVEL;
2573 void solicitus::Save (outputfile &SaveFile) const {
2574 nonhumanoid::Save(SaveFile);
2578 void solicitus::Load (inputfile &SaveFile) {
2579 nonhumanoid::Load(SaveFile);
2583 void solicitus::FinalProcessForBone () {
2584 largecreature::FinalProcessForBone();
2588 // the flower
2589 col16 noxiousorchid::GetTorsoSpecialColor() const {
2590 if (!GetConfig()) return MakeRGB16(125+RAND()%100, RAND()%125, RAND()%100);
2591 if (GetConfig() == GREATER) return MakeRGB16(100+RAND()%100, RAND()%100, 155+RAND()%100);
2592 // giant
2593 return MakeRGB16(200+RAND()%55, RAND()%60, 150);
2596 void noxiousorchid::PostConstruct () {
2597 //GetTorso()->GetMainMaterial()->SetSpoilCounter(200+RAND_N(100));
2600 void noxiousorchid::CreateCorpse (lsquare *Square) {
2601 //int Amount = !GetConfig() ? (RAND() % 7 ? 0 : 1) : GetConfig() == GREATER ? (RAND() & 1 ? 0 : (RAND() % 5 ? 1 : (RAND() % 5 ? 2 : 3))) : (!(RAND() % 3) ? 0 : (RAND() % 3 ? 1 : (RAND() % 3 ? 2 : 3)));
2602 //for(int c = 0; c < Amount; ++c) Square->AddItem(kiwi::Spawn());
2603 nonhumanoid::CreateCorpse(Square);
2606 truth noxiousorchid::Hit (character *Enemy, v2 HitPos, int Direction, int Flags) {
2607 if (!(RAND()&2)) {
2608 liquid *Fluid = 0; // = liquid::Spawn(WATER, 25+RAND()%25);
2610 if (IsPlayer()) {
2611 ADD_MESSAGE("You hit %s.", Enemy->CHAR_DESCRIPTION(DEFINITE));
2612 } else if (Enemy->IsPlayer() || CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer()) {
2613 ADD_MESSAGE("%s hits %s.", CHAR_DESCRIPTION(DEFINITE), Enemy->CHAR_DESCRIPTION(DEFINITE));
2616 switch (GetConfig()) {
2617 case 0:
2618 switch (RAND() % 48) {
2619 case 0: Fluid = liquid::Spawn(ANTIDOTE_LIQUID, 15+RAND()%25); break;
2620 case 1: case 2: case 3: case 4: Fluid = liquid::Spawn(POISON_LIQUID, 15+RAND()%25); break;
2621 case 5: Fluid = liquid::Spawn(LIQUID_HORROR, 15+RAND()%25); break;
2622 case 6: case 7: case 8: Fluid = liquid::Spawn(SULPHURIC_ACID, 15+RAND()%25); break;
2623 default: break;
2625 break;
2626 case GREATER:
2627 switch (RAND()%24) {
2628 case 0: Fluid = liquid::Spawn(ANTIDOTE_LIQUID, 25+RAND()%25); break;
2629 case 1: case 2: case 3: case 4: Fluid = liquid::Spawn(POISON_LIQUID, 25+RAND()%25); break;
2630 case 5: Fluid = liquid::Spawn(LIQUID_HORROR, 25+RAND()%25); break;
2631 case 6: case 7: case 8: Fluid = liquid::Spawn(SULPHURIC_ACID, 25+RAND()%25); break;
2632 default: break;
2634 break;
2635 case GIANTIC:
2636 switch (RAND()%24) {
2637 case 0: Fluid = liquid::Spawn(ANTIDOTE_LIQUID, 50+RAND()%50); break;
2638 case 1: Fluid = liquid::Spawn(YELLOW_SLIME, 50+RAND()%50); break;
2639 case 2: case 3: case 4: Fluid = liquid::Spawn(POISON_LIQUID, 50+RAND()%50); break;
2640 case 5: Fluid = liquid::Spawn(LIQUID_HORROR, 50+RAND()%50); break;
2641 case 6: case 7: case 8: Fluid = liquid::Spawn(SULPHURIC_ACID, 50+RAND()%50); break;
2642 case 9: Fluid = liquid::Spawn(MUSTARD_GAS_LIQUID, 50+RAND()%50); break;
2643 default: break;
2645 break;
2646 default: break; //Fluid = liquid::Spawn(WATER, 25+RAND()%25); break;
2649 if (Fluid) {
2650 Enemy->SpillFluid(Enemy, Fluid);
2651 if (IsPlayer()) {
2652 ADD_MESSAGE("You spill %s on %s.", Fluid->GetName(false, false).CStr(), Enemy->CHAR_DESCRIPTION(DEFINITE));
2653 } else if (Enemy->IsPlayer() || CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer()) {
2654 ADD_MESSAGE("%s spills %s on %s.", CHAR_DESCRIPTION(DEFINITE), Fluid->GetName(false, false).CStr(), Enemy->CHAR_DESCRIPTION(DEFINITE));
2657 } else if (nonhumanoid::Hit(Enemy, HitPos, Direction, Flags)) {
2658 return true;
2659 } else if (IsPlayer()) {
2660 ADD_MESSAGE("You miss %s.", Enemy->CHAR_DESCRIPTION(DEFINITE));
2662 EditAP(-1000);
2663 return true;
2666 //k8: plant? 'follow leader'? pick up items? move? wow!
2667 void noxiousorchid::GetAICommand () {
2668 SeekLeader(GetLeader());
2669 if (FollowLeader(GetLeader())) return;
2670 if (AttackAdjacentEnemyAI()) return;
2671 if (CheckForUsefulItemsOnGround()) return;
2672 if (MoveRandomly()) return;
2673 EditAP(-1000);