1 /*
2  *
3  *  Iter Vehemens ad Necem (IVAN)
4  *  Copyright (C) Timo Kiviluoto
5  *  Released under the GNU General
6  *  Public License
7  *
8  *  See LICENSING which should be included
9  *  along with this file for more details
10  *
11  */
12 
13 /* Compiled through charsset.cpp */
14 
GetUnarmedMinDamage() const15 int nonhumanoid::GetUnarmedMinDamage() const { return int(UnarmedDamage * 0.75); }
GetUnarmedMaxDamage() const16 int nonhumanoid::GetUnarmedMaxDamage() const { return int(UnarmedDamage * 1.25 + 1); }
GetKickMinDamage() const17 int nonhumanoid::GetKickMinDamage() const { return int(KickDamage * 0.75); }
GetKickMaxDamage() const18 int nonhumanoid::GetKickMaxDamage() const { return int(KickDamage * 1.25 + 1); }
GetBiteMinDamage() const19 int nonhumanoid::GetBiteMinDamage() const { return int(BiteDamage * 0.75); }
GetBiteMaxDamage() const20 int nonhumanoid::GetBiteMaxDamage() const { return int(BiteDamage * 1.25 + 1); }
GetCarryingStrength() const21 int nonhumanoid::GetCarryingStrength() const { return (Max(GetAttribute(LEG_STRENGTH), 1) << 1) + CarryingBonus; }
UseMaterialAttributes() const22 truth nonhumanoid::UseMaterialAttributes() const { return GetTorso()->UseMaterialAttributes(); }
23 
SpecialEnemySightedReaction(character *)24 truth elpuri::SpecialEnemySightedReaction(character*) { return !(Active = true); }
25 
FirstPersonBiteVerb() const26 cchar* billswill::FirstPersonBiteVerb() const { return "emit psi waves at"; }
FirstPersonCriticalBiteVerb() const27 cchar* billswill::FirstPersonCriticalBiteVerb() const { return "emit powerful psi waves at"; }
ThirdPersonBiteVerb() const28 cchar* billswill::ThirdPersonBiteVerb() const { return "emits psi waves at"; }
ThirdPersonCriticalBiteVerb() const29 cchar* billswill::ThirdPersonCriticalBiteVerb() const { return "emits powerful psi waves at"; }
GetBodyPartWobbleData(int) const30 int billswill::GetBodyPartWobbleData(int) const { return WOBBLE_HORIZONTALLY|(2 << WOBBLE_FREQ_SHIFT); }
31 
GetBodyPartWobbleData(int) const32 int mommo::GetBodyPartWobbleData(int) const
33 { return (GetConfig() % 2 != 0 ? WOBBLE_HORIZONTALLY : WOBBLE_VERTICALLY)|(2 << WOBBLE_FREQ_SHIFT); }
34 
MakeBodyPart(int) const35 bodypart* dog::MakeBodyPart(int) const { return dogtorso::Spawn(0, NO_MATERIALS); }
36 
MakeBodyPart(int) const37 bodypart* spider::MakeBodyPart(int) const { return spidertorso::Spawn(0, NO_MATERIALS); }
38 
MakeBodyPart(int) const39 bodypart* snake::MakeBodyPart(int) const { return snaketorso::Spawn(0, NO_MATERIALS); }
40 
MakeBodyPart(int) const41 bodypart* magpie::MakeBodyPart(int) const { return magpietorso::Spawn(0, NO_MATERIALS); }
42 
GetSpecialBodyPartFlags(int) const43 int dolphin::GetSpecialBodyPartFlags(int) const { return RAND() & (MIRROR|ROTATE); }
44 
MakeBodyPart(int) const45 bodypart* bat::MakeBodyPart(int) const { return battorso::Spawn(0, NO_MATERIALS); }
46 
GetSkinColor() const47 col16 chameleon::GetSkinColor() const { return MakeRGB16(60 + RAND() % 190, 60 + RAND() % 190, 60 + RAND() % 190); }
48 
SetWayPoints(const fearray<packv2> & What)49 void floatingeye::SetWayPoints(const fearray<packv2>& What) { ArrayToVector(What, WayPoints); }
50 
MakeBodyPart(int) const51 bodypart* eddy::MakeBodyPart(int) const { return eddytorso::Spawn(0, NO_MATERIALS); }
GetBodyPartWobbleData(int) const52 int eddy::GetBodyPartWobbleData(int) const { return WOBBLE_VERTICALLY|(2 << WOBBLE_FREQ_SHIFT); }
53 
MakeBodyPart(int) const54 bodypart* magicmushroom::MakeBodyPart(int) const { return magicmushroomtorso::Spawn(0, NO_MATERIALS); }
55 
MakeBodyPart(int) const56 bodypart* fusanga::MakeBodyPart(int) const { return fusangatorso::Spawn(0, NO_MATERIALS); }
57 
FirstPersonBiteVerb() const58 cchar* magpie::FirstPersonBiteVerb() const { return "peck"; }
FirstPersonCriticalBiteVerb() const59 cchar* magpie::FirstPersonCriticalBiteVerb() const { return "critically peck"; }
ThirdPersonBiteVerb() const60 cchar* magpie::ThirdPersonBiteVerb() const { return "pecks"; }
ThirdPersonCriticalBiteVerb() const61 cchar* magpie::ThirdPersonCriticalBiteVerb() const { return "critically pecks"; }
62 
MakeBodyPart(int) const63 bodypart* largecreature::MakeBodyPart(int) const { return largetorso::Spawn(0, NO_MATERIALS); }
GetNeighbourLSquare(int I) const64 lsquare* largecreature::GetNeighbourLSquare(int I) const { return static_cast<lsquare*>(GetNeighbourSquare(I)); }
GetNeighbourWSquare(int I) const65 wsquare* largecreature::GetNeighbourWSquare(int I) const { return static_cast<wsquare*>(GetNeighbourSquare(I)); }
66 
GetSpecialBodyPartFlags(int) const67 int hattifattener::GetSpecialBodyPartFlags(int) const { return ST_LIGHTNING; }
GetBodyPartWobbleData(int) const68 int hattifattener::GetBodyPartWobbleData(int) const
69 { return WOBBLE_HORIZONTALLY|(1 << WOBBLE_SPEED_SHIFT)|(1 << WOBBLE_FREQ_SHIFT); }
70 
GetSkinColor() const71 col16 vladimir::GetSkinColor() const { return MakeRGB16(60 + RAND() % 190, 60 + RAND() % 190, 60 + RAND() % 190); }
72 
GetSkinColor() const73 col16 fusanga::GetSkinColor() const { return MakeRGB16(60 + RAND() % 190, 60 + RAND() % 190, 60 + RAND() % 190); }
74 
MakeBodyPart(int) const75 bodypart* blinkdog::MakeBodyPart(int) const { return blinkdogtorso::Spawn(0, NO_MATERIALS); }
76 
GetBodyPartWobbleData(int) const77 int mysticfrog::GetBodyPartWobbleData(int) const
78 { return WOBBLE_HORIZONTALLY|(1 << WOBBLE_SPEED_SHIFT)|(3 << WOBBLE_FREQ_SHIFT); }
MakeBodyPart(int) const79 bodypart* mysticfrog::MakeBodyPart(int) const { return mysticfrogtorso::Spawn(0, NO_MATERIALS); }
80 
MakeBodyPart(int) const81 bodypart* lobhse::MakeBodyPart(int) const { return lobhsetorso::Spawn(0, NO_MATERIALS); }
82 
Hit(character * Enemy,v2,int,int Flags)83 truth elpuri::Hit(character* Enemy, v2, int, int Flags)
84 {
85   if(CheckIfTooScaredToHit(Enemy))
86     return false;
87 
88   character* EnemyHit[MAX_NEIGHBOUR_SQUARES];
89   int EnemiesHit = 0;
90 
91   for(int d = 0; d < GetExtendedNeighbourSquares(); ++d)
92     if(IsEnabled())
93     {
94       lsquare* Square = GetNeighbourLSquare(d);
95 
96       if(Square)
97       {
98         character* ByStander = Square->GetCharacter();
99 
100         if(ByStander && (ByStander == Enemy || GetRelation(ByStander) == HOSTILE))
101         {
102           truth Abort = false;
103 
104           for(int c = 0; c < EnemiesHit; ++c)
105             if(EnemyHit[c] == ByStander)
106               Abort = true;
107 
108           if(!Abort)
109           {
110             nonhumanoid::Hit(ByStander, Square->GetPos(), YOURSELF, Flags);
111             ByStander->DamageAllItems(this, RAND() % 36 + RAND() % 36, PHYSICAL_DAMAGE);
112             EnemyHit[EnemiesHit++] = ByStander;
113           }
114         }
115 
116         Square->GetStack()->ReceiveDamage(this, RAND() % 36 + RAND() % 36, PHYSICAL_DAMAGE,
117                                           game::GetLargeMoveDirection(d));
118       }
119     }
120 
121   EditAP(-500);
122   return true;
123 }
124 
Catches(item * Thingy)125 truth canine::Catches(item* Thingy)
126 {
127   if(Thingy->DogWillCatchAndConsume(this))
128   {
129     if(ConsumeItem(Thingy, CONST_S("eating")))
130     {
131       if(IsPlayer())
132         ADD_MESSAGE("You catch %s in mid-air and consume it.", Thingy->CHAR_NAME(DEFINITE));
133       else
134       {
135         if(CanBeSeenByPlayer())
136           ADD_MESSAGE("%s catches %s and eats it.", CHAR_NAME(DEFINITE), Thingy->CHAR_NAME(DEFINITE));
137 
138         if(PLAYER->GetRelativeDanger(this, true) > 0.1)
139           ChangeTeam(PLAYER->GetTeam());
140           ADD_MESSAGE("%s seems to be much more friendly towards you.", CHAR_NAME(DEFINITE));
141       }
142     }
143     else if(IsPlayer())
144       ADD_MESSAGE("You catch %s in mid-air.", Thingy->CHAR_NAME(DEFINITE));
145     else if(CanBeSeenByPlayer())
146       ADD_MESSAGE("%s catches %s.", CHAR_NAME(DEFINITE), Thingy->CHAR_NAME(DEFINITE));
147 
148     return true;
149   }
150   else
151     return false;
152 }
153 
Catches(item * Thingy)154 truth feline::Catches(item* Thingy)
155 {
156   if(Thingy->CatWillCatchAndConsume(this))
157   {
158     if(ConsumeItem(Thingy, CONST_S("eating")))
159     {
160       if(IsPlayer())
161         ADD_MESSAGE("You catch %s in mid-air and consume it.", Thingy->CHAR_NAME(DEFINITE));
162       else
163       {
164         if(CanBeSeenByPlayer())
165           ADD_MESSAGE("%s catches %s and eats it.", CHAR_NAME(DEFINITE), Thingy->CHAR_NAME(DEFINITE));
166 
167         if(PLAYER->GetRelativeDanger(this, true) > 0.1)
168           ChangeTeam(PLAYER->GetTeam());
169           ADD_MESSAGE("%s seems to be much more friendly towards you.", CHAR_NAME(DEFINITE));
170       }
171     }
172     else if(IsPlayer())
173       ADD_MESSAGE("You catch %s in mid-air.", Thingy->CHAR_NAME(DEFINITE));
174     else if(CanBeSeenByPlayer())
175       ADD_MESSAGE("%s catches %s.", CHAR_NAME(DEFINITE), Thingy->CHAR_NAME(DEFINITE));
176 
177     return true;
178   }
179   else
180     return false;
181 }
182 
SpecialEnemySightedReaction(character *)183 truth unicorn::SpecialEnemySightedReaction(character*)
184 {
185   if(!(RAND() & 15))
186   {
187     MonsterTeleport(" happily");
188     return true;
189   }
190 
191   if(StateIsActivated(PANIC) || (RAND() & 1 && IsInBadCondition()))
192   {
193     MonsterTeleport("");
194     return true;
195   }
196 
197   if(!(RAND() % 3) && MoveRandomly())
198     return true;
199 
200   return false;
201 }
202 
Save(outputfile & SaveFile) const203 void nonhumanoid::Save(outputfile& SaveFile) const
204 {
205   character::Save(SaveFile);
206   SaveFile << StrengthExperience << AgilityExperience;
207 }
208 
Load(inputfile & SaveFile)209 void nonhumanoid::Load(inputfile& SaveFile)
210 {
211   character::Load(SaveFile);
212   SaveFile >> StrengthExperience >> AgilityExperience;
213 }
214 
CalculateUnarmedDamage()215 void nonhumanoid::CalculateUnarmedDamage()
216 {
217   UnarmedDamage = sqrt(5e-12 * GetAttribute(ARM_STRENGTH))
218                   * GetBaseUnarmedStrength() * GetCWeaponSkill(UNARMED)->GetBonus();
219 }
220 
CalculateUnarmedToHitValue()221 void nonhumanoid::CalculateUnarmedToHitValue()
222 {
223   UnarmedToHitValue = GetAttribute(DEXTERITY) * sqrt(2.5 * GetAttribute(PERCEPTION))
224                       * GetCWeaponSkill(UNARMED)->GetBonus() * GetMoveEase() / 500000;
225 }
226 
CalculateUnarmedAPCost()227 void nonhumanoid::CalculateUnarmedAPCost()
228 {
229   UnarmedAPCost = Max(long(10000000000. / (APBonus(GetAttribute(DEXTERITY)) * GetMoveEase()
230                                            * GetCWeaponSkill(UNARMED)->GetBonus())), 100L);
231 }
232 
CalculateKickDamage()233 void nonhumanoid::CalculateKickDamage()
234 {
235   KickDamage = sqrt(5e-12 * GetAttribute(LEG_STRENGTH))
236                * GetBaseKickStrength() * GetCWeaponSkill(KICK)->GetBonus();
237 }
238 
CalculateKickToHitValue()239 void nonhumanoid::CalculateKickToHitValue()
240 {
241   KickToHitValue = GetAttribute(AGILITY) * sqrt(2.5 * GetAttribute(PERCEPTION))
242                    * GetCWeaponSkill(KICK)->GetBonus() * GetMoveEase() / 1000000;
243 }
244 
CalculateKickAPCost()245 void nonhumanoid::CalculateKickAPCost()
246 {
247   KickAPCost = Max(long(20000000000. / (APBonus(GetAttribute(AGILITY))
248                                         * GetMoveEase() * GetCWeaponSkill(KICK)->GetBonus())), 1000L);
249 }
250 
CalculateBiteDamage()251 void nonhumanoid::CalculateBiteDamage()
252 {
253   BiteDamage = sqrt(5e-12 * GetAttribute(ARM_STRENGTH))
254                * GetBaseBiteStrength() * GetCWeaponSkill(BITE)->GetBonus();
255 }
256 
CalculateBiteToHitValue()257 void nonhumanoid::CalculateBiteToHitValue()
258 {
259   BiteToHitValue = GetAttribute(AGILITY) * sqrt(2.5 * GetAttribute(PERCEPTION))
260                    * GetCWeaponSkill(BITE)->GetBonus() * GetMoveEase() / 1000000;
261 }
262 
CalculateBiteAPCost()263 void nonhumanoid::CalculateBiteAPCost()
264 {
265   BiteAPCost = Max(long(10000000000. / (APBonus(GetAttribute(DEXTERITY))
266                                         * GetMoveEase() * GetCWeaponSkill(BITE)->GetBonus())), 100L);
267 }
268 
InitSpecialAttributes()269 void nonhumanoid::InitSpecialAttributes()
270 {
271   StrengthExperience = GetNaturalExperience(ARM_STRENGTH);
272   AgilityExperience = GetNaturalExperience(AGILITY);
273   LimitRef(StrengthExperience, MIN_EXP, MAX_EXP);
274   LimitRef(AgilityExperience, MIN_EXP, MAX_EXP);
275 }
276 
Bite(character * Enemy,v2 HitPos,int Direction,truth ForceHit)277 void nonhumanoid::Bite(character* Enemy, v2 HitPos, int Direction, truth ForceHit)
278 {
279   EditNP(-50);
280   EditAP(-GetBiteAPCost());
281   EditExperience(ARM_STRENGTH, 75, 1 << 8);
282   EditExperience(AGILITY, 150, 1 << 8);
283   EditStamina(GetAdjustedStaminaCost(-1000, GetAttribute(AGILITY)), false);
284   Enemy->TakeHit(this, 0, GetTorso(), HitPos, GetBiteDamage(), GetBiteToHitValue(), RAND() % 26 - RAND() % 26,
285                  BITE_ATTACK, Direction, !(RAND() % GetCriticalModifier()), ForceHit);
286 }
287 
Kick(lsquare * Square,int Direction,truth ForceHit)288 void nonhumanoid::Kick(lsquare* Square, int Direction, truth ForceHit)
289 {
290   EditNP(-50);
291   EditAP(-GetKickAPCost());
292   EditStamina(GetAdjustedStaminaCost(-1000, GetAttribute(ARM_STRENGTH)), false);
293 
294   if(Square->BeKicked(this, 0, GetTorso(), GetKickDamage(), GetKickToHitValue(), RAND() % 26 - RAND() % 26,
295                       Direction, !(RAND() % GetCriticalModifier()), ForceHit))
296   {
297     EditExperience(LEG_STRENGTH, 150, 1 << 8);
298     EditExperience(AGILITY, 75, 1 << 8);
299   }
300 }
301 
Hit(character * Enemy,v2 HitPos,int Direction,int Flags)302 truth nonhumanoid::Hit(character* Enemy, v2 HitPos, int Direction, int Flags)
303 {
304   if(CheckIfTooScaredToHit(Enemy))
305     return false;
306 
307   if(IsPlayer())
308   {
309     if(!(Enemy->IsMasochist() && GetRelation(Enemy) == FRIEND) && GetRelation(Enemy) != HOSTILE
310        && !game::TruthQuestion(CONST_S("This might cause a hostile reaction. Are you sure? [y/N]")))
311       return false;
312   }
313   else if(GetAttribute(WISDOM) >= Enemy->GetAttackWisdomLimit())
314     return false;
315 
316   if(GetBurdenState() == OVER_LOADED)
317   {
318     if(IsPlayer())
319       ADD_MESSAGE("You cannot fight while carrying so much.");
320 
321     return false;
322   }
323 
324   /* Behold this Terrible Father of Gum Solutions! */
325 
326   int AttackStyle = GetAttackStyle();
327 
328   if(AttackStyle & USE_LEGS)
329   {
330     room* Room = GetNearLSquare(HitPos)->GetRoom();
331 
332     if(Room && !Room->AllowKick(this, GetNearLSquare(HitPos)))
333       AttackStyle &= ~USE_LEGS;
334   }
335 
336   int c, AttackStyles;
337 
338   for(c = 0, AttackStyles = 0; c < 8; ++c)
339     if(AttackStyle & (1 << c))
340       ++AttackStyles;
341 
342   int Chosen = RAND() % AttackStyles;
343 
344   for(c = 0, AttackStyles = 0; c < 8; ++c)
345     if(AttackStyle & (1 << c) && AttackStyles++ == Chosen)
346     {
347       Chosen = 1 << c;
348       break;
349     }
350 
351   switch(Chosen)
352   {
353    case USE_ARMS:
354     msgsystem::EnterBigMessageMode();
355     Hostility(Enemy);
356     UnarmedHit(Enemy, HitPos, Direction, Flags & SADIST_HIT);
357     msgsystem::LeaveBigMessageMode();
358     return true;
359    case USE_LEGS:
360     msgsystem::EnterBigMessageMode();
361     Hostility(Enemy);
362     Kick(GetNearLSquare(HitPos), Direction, Flags & SADIST_HIT);
363     msgsystem::LeaveBigMessageMode();
364     return true;
365    case USE_HEAD:
366     msgsystem::EnterBigMessageMode();
367     Hostility(Enemy);
368     Bite(Enemy, HitPos, Direction, Flags & SADIST_HIT);
369     msgsystem::LeaveBigMessageMode();
370     return true;
371    default:
372     ABORT("Strange alien attack style requested!");
373     return false;
374   }
375 }
376 
UnarmedHit(character * Enemy,v2 HitPos,int Direction,truth ForceHit)377 void nonhumanoid::UnarmedHit(character* Enemy, v2 HitPos, int Direction, truth ForceHit)
378 {
379   EditNP(-50);
380   EditAP(-GetUnarmedAPCost());
381   EditStamina(GetAdjustedStaminaCost(-1000, GetAttribute(ARM_STRENGTH)), false);
382 
383   switch(Enemy->TakeHit(this, 0, GetTorso(), HitPos, GetUnarmedDamage(), GetUnarmedToHitValue(),
384                         RAND() % 26 - RAND() % 26, UNARMED_ATTACK, Direction,
385                         !(RAND() % GetCriticalModifier()), ForceHit))
386   {
387    case HAS_HIT:
388    case HAS_BLOCKED:
389    case HAS_DIED:
390    case DID_NO_DAMAGE:
391     EditExperience(ARM_STRENGTH, 150, 1 << 8);
392    case HAS_DODGED:
393     EditExperience(DEXTERITY, 75, 1 << 8);
394   }
395 }
396 
397 /* Returns the average number of APs required to kill Enemy */
398 
GetTimeToKill(ccharacter * Enemy,truth UseMaxHP) const399 double nonhumanoid::GetTimeToKill(ccharacter* Enemy, truth UseMaxHP) const
400 {
401   double Effectivity = 0;
402   int AttackStyles = 0;
403 
404   if(IsUsingArms())
405   {
406     Effectivity += 1 / (Enemy->GetTimeToDie(this, int(GetUnarmedDamage()) + 1, GetUnarmedToHitValue(),
407                                             AttackIsBlockable(UNARMED_ATTACK), UseMaxHP) * GetUnarmedAPCost());
408     ++AttackStyles;
409   }
410 
411   if(IsUsingLegs())
412   {
413     Effectivity += 1 / (Enemy->GetTimeToDie(this, int(GetKickDamage()) + 1, GetKickToHitValue(),
414                                             AttackIsBlockable(KICK_ATTACK), UseMaxHP) * GetKickAPCost());
415     ++AttackStyles;
416   }
417 
418   if(IsUsingHead())
419   {
420     Effectivity += 1 / (Enemy->GetTimeToDie(this, int(GetBiteDamage()) + 1, GetBiteToHitValue(),
421                                             AttackIsBlockable(BITE_ATTACK), UseMaxHP) * GetBiteAPCost());
422     ++AttackStyles;
423   }
424 
425   if(StateIsActivated(HASTE))
426     Effectivity *= 2;
427 
428   if(StateIsActivated(SLOW))
429     Effectivity /= 2;
430 
431   return AttackStyles / Effectivity;
432 }
433 
GetAttribute(int Identifier,truth AllowBonus) const434 int nonhumanoid::GetAttribute(int Identifier, truth AllowBonus) const
435 {
436   if(Identifier < BASE_ATTRIBUTES)
437     return character::GetAttribute(Identifier, AllowBonus);
438   else if(Identifier == ARM_STRENGTH || Identifier == LEG_STRENGTH)
439   {
440     if(!UseMaterialAttributes())
441       return int(StrengthExperience * EXP_DIVISOR);
442     else
443       return GetTorso()->GetMainMaterial()->GetStrengthValue();
444   }
445   else if(Identifier == DEXTERITY || Identifier == AGILITY)
446   {
447     if(!UseMaterialAttributes())
448       return int(AgilityExperience * EXP_DIVISOR);
449     else
450       return (GetTorso()->GetMainMaterial()->GetFlexibility() << 2);
451   }
452   else
453   {
454     ABORT("Illegal nonhumanoid attribute %d request!", Identifier);
455     return 0xABBE;
456   }
457 }
458 
EditAttribute(int Identifier,int Value)459 truth nonhumanoid::EditAttribute(int Identifier, int Value)
460 {
461   if(Identifier < BASE_ATTRIBUTES)
462     return character::EditAttribute(Identifier, Value);
463   else if(Identifier == ARM_STRENGTH || Identifier == LEG_STRENGTH)
464     return !UseMaterialAttributes() && RawEditAttribute(StrengthExperience, Value);
465   else if(Identifier == DEXTERITY || Identifier == AGILITY)
466     return !UseMaterialAttributes() && RawEditAttribute(AgilityExperience, Value);
467   else
468   {
469     ABORT("Illegal nonhumanoid attribute %d edit request!", Identifier);
470     return false;
471   }
472 }
473 
EditExperience(int Identifier,double Value,double Speed)474 void nonhumanoid::EditExperience(int Identifier, double Value, double Speed)
475 {
476   if(!AllowExperience())
477     return;
478 
479   if(Identifier < BASE_ATTRIBUTES)
480     character::EditExperience(Identifier, Value, Speed);
481   else if(Identifier == ARM_STRENGTH || Identifier == LEG_STRENGTH)
482   {
483     if(!UseMaterialAttributes())
484     {
485       int Change = RawEditExperience(StrengthExperience,
486                                      GetNaturalExperience(ARM_STRENGTH),
487                                      Value, Speed / 2);
488 
489       if(Change)
490       {
491         cchar* Adj = Change > 0 ? "stronger" : "weaker";
492 
493         if(IsPlayer())
494           ADD_MESSAGE("Your feel %s!", Adj);
495         else if(IsPet() && CanBeSeenByPlayer())
496           ADD_MESSAGE("Suddenly %s looks %s.", CHAR_NAME(DEFINITE), Adj);
497 
498         CalculateBurdenState();
499         CalculateBattleInfo();
500       }
501     }
502   }
503   else if(Identifier == DEXTERITY || Identifier == AGILITY)
504   {
505     if(!UseMaterialAttributes())
506     {
507       int Change = RawEditExperience(AgilityExperience,
508                                      GetNaturalExperience(AGILITY),
509                                      Value, Speed / 2);
510 
511       if(Change)
512       {
513         cchar* Adj = Change > 0 ? "very agile" : "sluggish";
514 
515         if(IsPlayer())
516           ADD_MESSAGE("Your feel %s!", Adj);
517         else if(IsPet() && CanBeSeenByPlayer())
518           ADD_MESSAGE("Suddenly %s looks %s.", CHAR_NAME(DEFINITE), Adj);
519 
520         CalculateBattleInfo();
521       }
522     }
523   }
524   else
525     ABORT("Illegal nonhumanoid attribute %d experience edit request!", Identifier);
526 }
527 
DrawStats(truth AnimationDraw) const528 int nonhumanoid::DrawStats(truth AnimationDraw) const
529 {
530   if(AnimationDraw)
531     return 3;
532 
533   int PanelPosX = RES.X - 96, PanelPosY = 3;
534   PrintAttribute("Str", ARM_STRENGTH, PanelPosX, PanelPosY++);
535   PrintAttribute("Agi", AGILITY, PanelPosX, PanelPosY++);
536   return PanelPosY;
537 }
538 
CalculateBattleInfo()539 void nonhumanoid::CalculateBattleInfo()
540 {
541   CalculateDodgeValue();
542   CalculateUnarmedAttackInfo();
543   CalculateKickAttackInfo();
544   CalculateBiteAttackInfo();
545 }
546 
CalculateUnarmedAttackInfo()547 void nonhumanoid::CalculateUnarmedAttackInfo()
548 {
549   CalculateUnarmedDamage();
550   CalculateUnarmedToHitValue();
551   CalculateUnarmedAPCost();
552 }
553 
CalculateKickAttackInfo()554 void nonhumanoid::CalculateKickAttackInfo()
555 {
556   CalculateKickDamage();
557   CalculateKickToHitValue();
558   CalculateKickAPCost();
559 }
560 
CalculateBiteAttackInfo()561 void nonhumanoid::CalculateBiteAttackInfo()
562 {
563   CalculateBiteDamage();
564   CalculateBiteToHitValue();
565   CalculateBiteAPCost();
566 }
567 
BeTalkedTo()568 void dog::BeTalkedTo()
569 {
570   if(StateIsActivated(CONFUSED))
571   {
572     ADD_MESSAGE("%s looks a bit confused: \"Meow.\"", CHAR_NAME(DEFINITE));
573     return;
574   }
575 
576   if(GetPos().IsAdjacent(PLAYER->GetPos()))
577   {
578     if(GetRelation(PLAYER) != HOSTILE)
579     {
580       static truth Last;
581       cchar* Reply;
582 
583       if(GetHP() << 1 > GetMaxHP())
584         Reply = Last ? "barks happily" : "wags its tail";
585       else
586         Reply = Last ? "yelps" : "howls";
587 
588       ADD_MESSAGE("%s %s.", CHAR_NAME(DEFINITE), Reply);
589       Last = !Last;
590     }
591     else if(!RAND_N(100))
592       ADD_MESSAGE("%s scoffs at you: \"Can't you understand I can't speak?\"", CHAR_NAME(DEFINITE));
593     else
594       ADD_MESSAGE("%s snarls at you.", CHAR_NAME(DEFINITE));
595     return;
596   }
597 
598   character::BeTalkedTo();
599 }
600 
CreateCorpse(lsquare * Square)601 void dog::CreateCorpse(lsquare* Square)
602 {
603   if(GetConfig() == SKELETON_DOG)
604   {
605     Square->AddItem(skull::Spawn(PUPPY_SKULL));
606 
607     int Amount = 2 + (RAND() & 3);
608 
609     for(int c = 0; c < Amount; ++c)
610       Square->AddItem(bone::Spawn());
611 
612     SendToHell();
613   }
614   else
615     nonhumanoid::CreateCorpse(Square);
616 }
617 
GetSkinColor() const618 col16 wolf::GetSkinColor() const
619 {
620   int Element = 40 + RAND() % 50;
621   return MakeRGB16(Element, Element, Element);
622 }
623 
GetAICommand()624 void genetrixvesana::GetAICommand()
625 {
626   ++TurnsExisted;
627 
628   SeekLeader(GetLeader());
629 
630   if(FollowLeader(GetLeader()))
631     return;
632 
633   if(!(RAND() % 60))
634   {
635     int NumberOfPlants = RAND() % 3 + RAND() % 3 + RAND() % 3 + RAND() % 3;
636 
637     for(int c1 = 0; c1 < 50 && NumberOfPlants; ++c1)
638     {
639       for(int c2 = 0; c2 < game::GetTeams() && NumberOfPlants; ++c2)
640         if(GetTeam()->GetRelation(game::GetTeam(c2)) == HOSTILE)
641           for(character* p : game::GetTeam(c2)->GetMember())
642           {
643             if(!NumberOfPlants)
644               break;
645 
646             if(p->IsEnabled())
647             {
648               lsquare* LSquare = p->GetNeighbourLSquare(RAND() % GetNeighbourSquares());
649 
650               if(LSquare && (LSquare->GetWalkability() & WALK) && !LSquare->GetCharacter())
651               {
652                 character* NewPlant;
653                 long RandomValue = RAND() % TurnsExisted;
654 
655                 if(RandomValue < 250)
656                   NewPlant = carnivorousplant::Spawn();
657                 else if(RandomValue < 1500)
658                   NewPlant = carnivorousplant::Spawn(GREATER);
659                 else
660                   NewPlant = carnivorousplant::Spawn(GIANT);
661 
662                 for(int c = 3; c < TurnsExisted / 500; ++c)
663                   NewPlant->EditAllAttributes(1);
664 
665                 NewPlant->SetGenerationDanger(GetGenerationDanger());
666                 NewPlant->SetTeam(GetTeam());
667                 NewPlant->PutTo(LSquare->GetPos());
668                 --NumberOfPlants;
669 
670                 if(NewPlant->CanBeSeenByPlayer())
671                 {
672                   if(p->IsPlayer())
673                     ADD_MESSAGE("%s sprouts from the ground near you.", NewPlant->CHAR_NAME(INDEFINITE));
674                   else if(p->CanBeSeenByPlayer())
675                     ADD_MESSAGE("%s sprouts from the ground near %s.", NewPlant->CHAR_NAME(INDEFINITE), p->CHAR_NAME(DEFINITE));
676                   else
677                     ADD_MESSAGE("%s sprouts from the ground.", NewPlant->CHAR_NAME(INDEFINITE));
678                 }
679               }
680             }
681           }
682     }
683 
684     EditAP(-2000);
685     return;
686   }
687 
688   if(AttackAdjacentEnemyAI())
689     return;
690 
691   if(MoveRandomly())
692     return;
693 
694   EditAP(-1000);
695 }
696 
GetTorsoSpecialColor() const697 col16 carnivorousplant::GetTorsoSpecialColor() const // the flower
698 {
699   if(!GetConfig())
700     return MakeRGB16(RAND() % 100, 125 + RAND() % 125, RAND() % 100);
701   else if(GetConfig() == GREATER)
702     return MakeRGB16(RAND() % 100, RAND() % 100, 125 + RAND() % 125);
703   else
704     return MakeRGB16(125 + RAND() % 125, 125 + RAND() % 125, RAND() % 100);
705 }
706 
GetAICommand()707 void ostrich::GetAICommand()
708 {
709   if(game::TweraifIsFree() ||
710      (GetDungeon()->GetIndex() != NEW_ATTNAM)
711    ) // Behave normally outside of New Attnam.
712   {
713     nonhumanoid::GetAICommand();
714     return;
715   }
716 
717   if(CheckForEnemies(false, false, true, true))
718     return;
719 
720   if(!IsEnabled())
721     return;
722 
723   if(GetPos() == v2(45, 45))
724     HasDroppedBananas = true;
725 
726   itemvector ItemVector;
727   GetStackUnder()->FillItemVector(ItemVector);
728   int BananasPicked = 0;
729 
730   for(uint c = 0; c < ItemVector.size(); ++c)
731     if(ItemVector[c]->IsBanana() && ItemVector[c]->CanBeSeenBy(this)
732        && ItemVector[c]->IsPickable(this)
733        && !MakesBurdened(GetCarriedWeight() + ItemVector[c]->GetWeight()))
734     {
735       ItemVector[c]->MoveTo(GetStack());
736       ++BananasPicked;
737     }
738 
739   if(BananasPicked)
740   {
741     if(CanBeSeenByPlayer())
742       ADD_MESSAGE("%s picks up %s.", CHAR_NAME(DEFINITE), BananasPicked == 1 ? "the banana" : "some bananas");
743 
744     return;
745   }
746 
747   if(!HasDroppedBananas)
748   {
749     SetGoingTo(v2(45, 45));
750 
751     if(MoveTowardsTarget(true))
752       return;
753   }
754   else if(GetPos().Y == 54)
755   {
756     if(CanBeSeenByPlayer())
757       ADD_MESSAGE("%s leaves the town.", CHAR_NAME(DEFINITE));
758 
759     itemvector ItemVector;
760     GetStack()->FillItemVector(ItemVector);
761 
762     for(uint c = 0; c < ItemVector.size(); ++c)
763     {
764       ItemVector[c]->RemoveFromSlot();
765       ItemVector[c]->SendToHell();
766     }
767 
768     v2 Where = GetLevel()->GetNearestFreeSquare(this, v2(45, 0));
769 
770     if(Where == ERROR_V2)
771       Where = GetLevel()->GetRandomSquare(this, NOT_IN_ROOM); // this is odd but at least it doesn't crash
772 
773     Move(Where, true);
774     RestoreHP();
775     RestoreStamina();
776     ResetStates();
777     TemporaryState = 0;
778     GainIntrinsic(LEVITATION);
779 
780     if(CanBeSeenByPlayer())
781       ADD_MESSAGE("%s enters the town.", CHAR_NAME(INDEFINITE));
782 
783     HasDroppedBananas = false;
784   }
785   else
786   {
787     SetGoingTo(v2(45, 54));
788 
789     if(MoveTowardsTarget(true))
790       return;
791   }
792 
793   EditAP(-1000);
794 }
795 
Save(outputfile & SaveFile) const796 void ostrich::Save(outputfile& SaveFile) const
797 {
798   nonhumanoid::Save(SaveFile);
799   SaveFile << HasDroppedBananas;
800 }
801 
Load(inputfile & SaveFile)802 void ostrich::Load(inputfile& SaveFile)
803 {
804   nonhumanoid::Load(SaveFile);
805   SaveFile >> HasDroppedBananas;
806 }
807 
HandleCharacterBlockingTheWay(character * Char,v2 Pos,int Dir)808 truth ostrich::HandleCharacterBlockingTheWay(character* Char, v2 Pos, int Dir)
809 {
810   return Char->GetPos() == v2(45, 45) && (Displace(Char, true) || Hit(Char, Pos, Dir));
811 }
812 
Save(outputfile & SaveFile) const813 void elpuri::Save(outputfile& SaveFile) const
814 {
815   largecreature::Save(SaveFile);
816   SaveFile << Active;
817 }
818 
Load(inputfile & SaveFile)819 void elpuri::Load(inputfile& SaveFile)
820 {
821   largecreature::Load(SaveFile);
822   SaveFile >> Active;
823 }
824 
GetAICommand()825 void elpuri::GetAICommand()
826 {
827   if(Active)
828     character::GetAICommand();
829   else
830   {
831     if(CheckForEnemies(false, false, false))
832       return;
833 
834     EditAP(-1000);
835   }
836 }
837 
ReceiveBodyPartDamage(character * Damager,int Damage,int Type,int BodyPartIndex,int Direction,truth PenetrateResistance,truth Critical,truth ShowNoDamageMsg,truth CaptureBodyPart)838 int elpuri::ReceiveBodyPartDamage(character* Damager, int Damage, int Type, int BodyPartIndex,
839                                   int Direction, truth PenetrateResistance, truth Critical,
840                                   truth ShowNoDamageMsg, truth CaptureBodyPart)
841 {
842   Active = true;
843   return character::ReceiveBodyPartDamage(Damager, Damage, Type, BodyPartIndex, Direction,
844                                           PenetrateResistance, Critical, ShowNoDamageMsg, CaptureBodyPart);
845 }
846 
CreateCorpse(lsquare * Square)847 void mommo::CreateCorpse(lsquare* Square)
848 {
849   for(int d = 0; d < GetExtendedNeighbourSquares(); ++d)
850   {
851     lsquare* NeighbourSquare = Square->GetNeighbourLSquare(d);
852 
853     if(NeighbourSquare)
854       NeighbourSquare->SpillFluid(0, static_cast<liquid*>(GetTorso()->GetMainMaterial()->SpawnMore(250 + RAND() % 250)));
855   }
856 
857   SendToHell();
858 }
859 
CreateCorpse(lsquare * Square)860 void carnivorousplant::CreateCorpse(lsquare* Square)
861 {
862   int Amount = !GetConfig() ? (RAND() % 7 ? 0 : 1) : GetConfig() == GREATER ?
863                (RAND() & 1 ? 0 : (RAND() % 5 ? 1 : (RAND() % 5 ? 2 : 3))) :
864                (!(RAND() % 3) ? 0 : (RAND() % 3 ? 1 : (RAND() % 3 ? 2 : 3)));
865 
866   for(int c = 0; c < Amount; ++c)
867     Square->AddItem(kiwi::Spawn());
868 
869   nonhumanoid::CreateCorpse(Square);
870 }
871 
CreateCorpse(lsquare * Square)872 void genetrixvesana::CreateCorpse(lsquare* Square)
873 {
874   for(int c = 0; c < 3; ++c)
875     Square->AddItem(pineapple::Spawn());
876 
877   largecreature::CreateCorpse(Square);
878 }
879 
AddSpecialStethoscopeInfo(felist & Info) const880 void nonhumanoid::AddSpecialStethoscopeInfo(felist& Info) const
881 {
882   Info.AddEntry(CONST_S("Strength:     ") + GetAttribute(ARM_STRENGTH), LIGHT_GRAY);
883   Info.AddEntry(CONST_S("Agility:      ") + GetAttribute(AGILITY), LIGHT_GRAY);
884 }
885 
Save(outputfile & SaveFile) const886 void floatingeye::Save(outputfile& SaveFile) const
887 {
888   nonhumanoid::Save(SaveFile);
889   SaveFile << WayPoints << NextWayPoint;
890 }
891 
Load(inputfile & SaveFile)892 void floatingeye::Load(inputfile& SaveFile)
893 {
894   nonhumanoid::Load(SaveFile);
895   SaveFile >> WayPoints >> NextWayPoint;
896 }
897 
GetAICommand()898 void floatingeye::GetAICommand()
899 {
900   if(WayPoints.size() && !IsGoingSomeWhere())
901   {
902     if(GetPos() == WayPoints[NextWayPoint])
903     {
904       if(NextWayPoint < WayPoints.size() - 1)
905         ++NextWayPoint;
906       else
907         NextWayPoint = 0;
908     }
909 
910     GoingTo = WayPoints[NextWayPoint];
911   }
912 
913   SeekLeader(GetLeader());
914 
915   if(CheckForEnemies(false, false, true))
916     return;
917 
918   if(FollowLeader(GetLeader()))
919     return;
920 
921   if(MoveRandomly())
922     return;
923 
924   EditAP(-1000);
925 }
926 
Hit(character * Enemy,v2,int,int)927 truth floatingeye::Hit(character* Enemy, v2, int, int)
928 {
929   if(IsPlayer())
930     ADD_MESSAGE("You stare at %s.", Enemy->CHAR_DESCRIPTION(DEFINITE));
931   else if(Enemy->IsPlayer() && CanBeSeenByPlayer())
932     ADD_MESSAGE("%s stares at you.", CHAR_NAME(DEFINITE));
933 
934   EditAP(-1000);
935   return true;
936 }
937 
TakeHit(character * Enemy,item * Weapon,bodypart * EnemyBodyPart,v2 HitPos,double Damage,double ToHitValue,int Success,int Type,int Direction,truth Critical,truth ForceHit)938 int floatingeye::TakeHit(character* Enemy, item* Weapon, bodypart* EnemyBodyPart,
939                          v2 HitPos, double Damage, double ToHitValue, int Success,
940                          int Type, int Direction, truth Critical, truth ForceHit)
941 {
942   if(CanBeSeenBy(Enemy) && Enemy->HasEyes() && RAND() % 3
943      && Enemy->LoseConsciousness(150 + RAND_N(150))) /* Changes for fainting 2 out of 3 */
944   {
945     if(!Enemy->IsPlayer())
946       Enemy->EditExperience(WISDOM, 75, 1 << 13);
947 
948     return HAS_FAILED;
949   }
950   else
951     return nonhumanoid::TakeHit(Enemy, Weapon, EnemyBodyPart, HitPos, Damage, ToHitValue,
952                                 Success, Type, Direction, Critical, ForceHit);
953 }
954 
CreateCorpse(lsquare * Square)955 void elpuri::CreateCorpse(lsquare* Square)
956 {
957   largecreature::CreateCorpse(Square);
958   Square->AddItem(headofelpuri::Spawn());
959 }
960 
SpecialBiteEffect(character * Char,v2,int,int,truth BlockedByArmour,truth Critical,int DoneDamage)961 truth snake::SpecialBiteEffect(character* Char, v2, int, int, truth BlockedByArmour, truth Critical, int DoneDamage)
962 {
963   if(!BlockedByArmour || Critical)
964   {
965     switch (GetConfig())
966     {
967       case RED_SNAKE: Char->BeginTemporaryState(PANIC, 400 + RAND_N(200)); break;
968       case GREEN_SNAKE: Char->BeginTemporaryState(POISONED, 400 + RAND_N(200)); break;
969       case BLUE_SNAKE: Char->BeginTemporaryState(SLOW, 400 + RAND_N(200)); break;
970     }
971     return true;
972   }
973   else
974     return false;
975 }
976 
SpecialBiteEffect(character * Victim,v2 HitPos,int BodyPartIndex,int Direction,truth BlockedByArmour,truth Critical,int DoneDamage)977 truth spider::SpecialBiteEffect(character* Victim, v2 HitPos, int BodyPartIndex, int Direction, truth BlockedByArmour, truth Critical, int DoneDamage)
978 {
979   if(!BlockedByArmour || Critical)
980   {
981     if(GetConfig() == GIANT_GOLD)
982     {
983       bodypart* BodyPart = Victim->GetBodyPart(BodyPartIndex);
984 
985       if(BodyPart && BodyPart->IsMaterialChangeable())
986       {
987         festring Desc;
988         int CurrentHP = BodyPart->GetHP();
989         BodyPart->AddName(Desc, UNARTICLED);
990 
991         // Instead of a cockatrice turning you to stone, gold spider will turn you to gold!
992         delete BodyPart->SetMainMaterial(MAKE_MATERIAL(GOLD));
993 
994         // Here changing material would revert all damage done, but we don't want that.
995         CurrentHP = Min(CurrentHP, BodyPart->GetHP());
996         BodyPart->SetHP(CurrentHP);
997 
998         if(Victim->IsPlayer())
999         {
1000           Desc << " tingles painfully";
1001           ADD_MESSAGE("Your %s.", Desc.CStr());
1002         }
1003         else if(Victim->CanBeSeenByPlayer())
1004         {
1005           Desc << " vibrates and changes into gold";
1006           ADD_MESSAGE("%s's %s.", Victim->CHAR_DESCRIPTION(DEFINITE), Desc.CStr());
1007         }
1008 
1009         return true;
1010       }
1011     }
1012     else
1013     {
1014       Victim->BeginTemporaryState(POISONED, GetConfig() == LARGE ? 80 + RAND_N(40) : 400 + RAND_N(200));
1015       return true;
1016     }
1017   }
1018 
1019   return false;
1020 }
1021 
SpecialBiteEffect(character * Victim,v2 HitPos,int BodyPartIndex,int Direction,truth BlockedByArmour,truth Critical,int DoneDamage)1022 truth vampirebat::SpecialBiteEffect(character* Victim, v2 HitPos, int BodyPartIndex, int Direction, truth BlockedByArmour, truth Critical, int DoneDamage)
1023 {
1024   if(!BlockedByArmour && Victim->IsWarmBlooded() && (!(RAND() % 3) || Critical) && !Victim->AllowSpoil())
1025   {
1026     if(IsPlayer())
1027       ADD_MESSAGE("You drain some precious lifeblood from %s!", Victim->CHAR_DESCRIPTION(DEFINITE));
1028     else if(Victim->IsPlayer() || Victim->CanBeSeenByPlayer() || CanBeSeenByPlayer())
1029       ADD_MESSAGE("%s drains some precious lifeblood from %s!", CHAR_DESCRIPTION(DEFINITE), Victim->CHAR_DESCRIPTION(DEFINITE));
1030 
1031     if(Victim->IsHumanoid() && !Victim->StateIsActivated(LYCANTHROPY) && !Victim->StateIsActivated(DISEASE_IMMUNITY))
1032       Victim->BeginTemporaryState(VAMPIRISM, 500 + RAND_N(250));
1033 
1034       // HP recieved is about half the damage done; against werewolves this is full
1035       int DrainDamage = (DoneDamage >> 1) + 1;
1036       if(Victim->StateIsActivated(LYCANTHROPY))
1037         DrainDamage = DoneDamage + 1;
1038 
1039     return Victim->ReceiveBodyPartDamage(this, DrainDamage, DRAIN, BodyPartIndex, Direction);
1040   }
1041   else
1042     return false;
1043 }
1044 
TakeHit(character * Enemy,item * Weapon,bodypart * EnemyBodyPart,v2 HitPos,double Damage,double ToHitValue,int Success,int Type,int Direction,truth Critical,truth ForceHit)1045 int nerfbat::TakeHit(character* Enemy, item* Weapon, bodypart* EnemyBodyPart, v2 HitPos, double Damage,
1046                      double ToHitValue, int Success, int Type, int Direction, truth Critical, truth ForceHit)
1047 {
1048   int Return = nonhumanoid::TakeHit(Enemy, Weapon, EnemyBodyPart, HitPos, Damage, ToHitValue,
1049                                     Success, Type, Direction, Critical, ForceHit);
1050 
1051   if(Return != HAS_DIED)
1052   {
1053     // Compare Mana against enemy Willpower to see if they resist polymorph.
1054     if(RAND_N(GetAttribute(MANA)) > RAND_N(Enemy->GetAttribute(WILL_POWER)))
1055     {
1056       if(IsPlayer())
1057         ADD_MESSAGE("You are engulfed in a malignant aura!.");
1058       else if(CanBeSeenByPlayer())
1059         ADD_MESSAGE("%s is engulfed in a malignant aura!", CHAR_DESCRIPTION(DEFINITE));
1060 
1061       if(Weapon)
1062         Weapon->Polymorph(this, Enemy);
1063       else if(EnemyBodyPart)
1064         Enemy->PolymorphRandomly(1, 999999, (int)(Damage * 300 + RAND() % 500));
1065     }
1066     else
1067     {
1068       if(IsPlayer())
1069         ADD_MESSAGE("You are engulfed in a malignant aura, but nothing seems to happen.");
1070       else if(CanBeSeenByPlayer())
1071         ADD_MESSAGE("%s is engulfed in a malignant aura, but nothing seems to happen.", CHAR_DESCRIPTION(DEFINITE));
1072 
1073       Enemy->EditExperience(WILL_POWER, 100, 1 << 12);
1074     }
1075   }
1076 
1077   return Return;
1078 }
1079 
ChameleonPolymorphRandomly(chameleon * c)1080 bool ChameleonPolymorphRandomly(chameleon* c){
1081   character* NewForm = c->PolymorphRandomly(100, 1000, 500 + RAND() % 500);
1082 
1083   if(NewForm != NULL)
1084   {
1085     NewForm->GainIntrinsic(POLYMORPH);
1086     return true;
1087   }
1088 
1089   return false;
1090 }
1091 
SpecialEnemySightedReaction(character *)1092 truth chameleon::SpecialEnemySightedReaction(character*)
1093 {
1094   if(HP != MaxHP || !(RAND() % 3))
1095     if(ChameleonPolymorphRandomly(this))
1096       return true;
1097 
1098   return false;
1099 }
1100 
TakeHit(character * Enemy,item * Weapon,bodypart * EnemyBodyPart,v2 HitPos,double Damage,double ToHitValue,int Success,int Type,int Direction,truth Critical,truth ForceHit)1101 int chameleon::TakeHit(character* Enemy, item* Weapon, bodypart* EnemyBodyPart, v2 HitPos, double Damage,
1102                        double ToHitValue, int Success, int Type, int Direction, truth Critical, truth ForceHit)
1103 {DBG1(GetNameSingular().CStr());
1104   int Return = nonhumanoid::TakeHit(Enemy, Weapon, EnemyBodyPart, HitPos, Damage, ToHitValue,
1105                                     Success, Type, Direction, Critical, ForceHit);
1106 
1107   if(Return != HAS_DIED)
1108     ChameleonPolymorphRandomly(this);
1109 
1110   return Return;
1111 }
1112 
Hit(character * Enemy,v2,int,int)1113 truth eddy::Hit(character* Enemy, v2, int, int)
1114 {
1115   if(IsPlayer())
1116   {
1117     if(!(Enemy->IsMasochist() && GetRelation(Enemy) == FRIEND) && GetRelation(Enemy) != HOSTILE
1118        && !game::TruthQuestion(CONST_S("This might cause a hostile reaction. Are you sure? [y/N]")))
1119       return false;
1120   }
1121 
1122   Hostility(Enemy);
1123 
1124   if(RAND() & 1)
1125   {
1126     if(IsPlayer())
1127       ADD_MESSAGE("You engulf %s.", Enemy->CHAR_DESCRIPTION(DEFINITE));
1128     else if(Enemy->IsPlayer() || CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer())
1129       ADD_MESSAGE("%s engulfs %s.", CHAR_DESCRIPTION(DEFINITE), Enemy->CHAR_DESCRIPTION(DEFINITE));
1130 
1131     Enemy->TeleportRandomly();
1132   }
1133   else if(IsPlayer())
1134     ADD_MESSAGE("You miss %s.", Enemy->CHAR_DESCRIPTION(DEFINITE));
1135 
1136   EditAP(-500);
1137   return true;
1138 }
1139 
Save(outputfile & SaveFile) const1140 void mushroom::Save(outputfile& SaveFile) const
1141 {
1142   nonhumanoid::Save(SaveFile);
1143   SaveFile << Species;
1144 }
1145 
Load(inputfile & SaveFile)1146 void mushroom::Load(inputfile& SaveFile)
1147 {
1148   nonhumanoid::Load(SaveFile);
1149   SaveFile >> Species;
1150 }
1151 
GetAICommand()1152 void mushroom::GetAICommand()
1153 {
1154   SeekLeader(GetLeader());
1155 
1156   if(FollowLeader(GetLeader()))
1157     return;
1158 
1159   lsquare* CradleSquare = GetNeighbourLSquare(RAND() % 8);
1160 
1161   if(CradleSquare && !CradleSquare->GetCharacter()
1162      && (CradleSquare->GetWalkability() & WALK))
1163   {
1164     int SpoiledItems = 0;
1165     int MushroomsNear = 0;
1166 
1167     for(int d = 0; d < 8; ++d)
1168     {
1169       lsquare* Square = CradleSquare->GetNeighbourLSquare(d);
1170 
1171       if(Square)
1172       {
1173         character* Char = Square->GetCharacter();
1174 
1175         if(Char && Char->IsMushroom())
1176           ++MushroomsNear;
1177 
1178         SpoiledItems += Square->GetSpoiledItems();
1179       }
1180     }
1181 
1182     if((SpoiledItems && MushroomsNear < 5 && !RAND_N(50))
1183        || (MushroomsNear < 3 && !RAND_N((1 + MushroomsNear) * 100)))
1184     {
1185       mushroom* Child = static_cast<mushroom*>(GetProtoType()->Spawn(GetConfig()));
1186       Child->SetSpecies(Species);
1187       Child->SetTeam(GetTeam());
1188       Child->SetGenerationDanger(GetGenerationDanger());
1189       Child->PutTo(CradleSquare->GetPos());
1190 
1191       for(int c = 0; c < BASE_ATTRIBUTES; ++c)
1192         Child->BaseExperience[c] = RandomizeBabyExperience(BaseExperience[c] * 4);
1193 
1194       if(Child->CanBeSeenByPlayer())
1195         ADD_MESSAGE("%s pops out from the ground.", Child->CHAR_NAME(INDEFINITE));
1196     }
1197   }
1198 
1199   if(AttackAdjacentEnemyAI())
1200     return;
1201 
1202   if(MoveRandomly())
1203     return;
1204 
1205   EditAP(-1000);
1206 }
1207 
PostConstruct()1208 void mushroom::PostConstruct()
1209 {
1210   switch(RAND() % 3)
1211   {
1212    case 0: SetSpecies(MakeRGB16(125 + RAND() % 125, RAND() % 100, RAND() % 100)); break;
1213    case 1: SetSpecies(MakeRGB16(RAND() % 100, 125 + RAND() % 125, RAND() % 100)); break;
1214    case 2: SetSpecies(MakeRGB16(RAND() % 100, RAND() % 100, 125 + RAND() % 125)); break;
1215   }
1216 }
1217 
1218 /**
1219  * This is not gameplay wise as far AI events will not happen,
1220  * but will allow the game to still be playable at least...
1221  * Use this on any NPC class that may encumber the CPU too much.
1222  */
CPUwiseAI(nonhumanoid * nh)1223 bool CPUwiseAI(nonhumanoid* nh)
1224 {
1225   if(!nh->IsRooted())return true; //only NPCs that can't move
1226   if(nh->StateIsActivated(LEVITATION))return true; //this keeps levitating ones still active what may be good TODO add user option to deny them?
1227 
1228   int iDist = ivanconfig::GetDistLimitMagicMushrooms();
1229   if(iDist==0)return true;
1230 
1231   int iSqDist = nh->GetDistanceSquareFrom(PLAYER);
1232   int iSqLim = iDist*iDist;
1233   int iMaxActiveAI = iDist*2 * iDist*2;
1234 
1235   static int iPreviousTurnActivatedAIs=0;
1236   static int iTurnChkAI = 0;
1237   if(iTurnChkAI != game::GetTurn()){ DBG4(iTurnChkAI,iPreviousTurnActivatedAIs,iSqLim,iMaxActiveAI);
1238     iPreviousTurnActivatedAIs=0;
1239     iTurnChkAI = game::GetTurn();
1240   }
1241 
1242   bool bActivated = false;
1243   if(iTurnChkAI==game::GetTurn()){
1244     if(iPreviousTurnActivatedAIs<iMaxActiveAI && iSqDist<=iSqLim){
1245       bActivated=true;
1246       iPreviousTurnActivatedAIs++;
1247     }
1248   }
1249 
1250   return bActivated;
1251 }
1252 
GetAICommand()1253 void magicmushroom::GetAICommand()
1254 {
1255   if(!CPUwiseAI(this))
1256     return;
1257 
1258   if(!(RAND() % 750))
1259   {
1260     if(CanBeSeenByPlayer())
1261       ADD_MESSAGE("%s disappears.", CHAR_NAME(DEFINITE));
1262 
1263     TeleportRandomly(true);
1264     EditAP(-1000);
1265   }
1266   else if(!(RAND() % 50))
1267   {
1268     lsquare* Square = GetNeighbourLSquare(RAND() % 8);
1269 
1270     if(Square && Square->IsFlyable())
1271     {
1272       if(CanBeSeenByPlayer())
1273         ADD_MESSAGE("%s releases odd-looking gas.", CHAR_NAME(DEFINITE));
1274 
1275       Square->AddSmoke(gas::Spawn(MAGIC_VAPOUR, 1000));
1276       EditAP(-1000);
1277     }
1278   }
1279   else
1280     mushroom::GetAICommand();
1281 }
1282 
SetSpecies(int What)1283 void mushroom::SetSpecies(int What)
1284 {
1285   Species = What;
1286   UpdatePictures();
1287 }
1288 
Hit(character * Enemy,v2 HitPos,int Direction,int Flags)1289 truth twoheadedmoose::Hit(character* Enemy, v2 HitPos, int Direction, int Flags)
1290 {
1291   if(CheckIfTooScaredToHit(Enemy))
1292     return false;
1293 
1294   if(IsPlayer())
1295   {
1296     if(!(Enemy->IsMasochist() && GetRelation(Enemy) == FRIEND) && GetRelation(Enemy) != HOSTILE
1297        && !game::TruthQuestion(CONST_S("This might cause a hostile reaction. Are you sure? [y/N]")))
1298       return false;
1299   }
1300   else if(GetAttribute(WISDOM) >= Enemy->GetAttackWisdomLimit())
1301     return false;
1302 
1303   if(GetBurdenState() == OVER_LOADED)
1304   {
1305     if(IsPlayer())
1306       ADD_MESSAGE("You cannot fight while carrying so much.");
1307 
1308     return false;
1309   }
1310 
1311   Hostility(Enemy);
1312   msgsystem::EnterBigMessageMode();
1313   Bite(Enemy, HitPos, Direction, Flags & SADIST_HIT);
1314   v2 Pos[MAX_NEIGHBOUR_SQUARES];
1315   character* Char[MAX_NEIGHBOUR_SQUARES];
1316   int Index = 0;
1317 
1318   for(int d = 0; d < GetNeighbourSquares(); ++d)
1319   {
1320     lsquare* LSquare = GetNeighbourLSquare(d);
1321 
1322     if(LSquare)
1323     {
1324       character* Enemy = LSquare->GetCharacter();
1325 
1326       if(Enemy && GetRelation(Enemy) == HOSTILE && GetAttribute(WISDOM) < Enemy->GetAttackWisdomLimit())
1327       {
1328         Pos[Index] = LSquare->GetPos();
1329         Char[Index++] = Enemy;
1330       }
1331     }
1332   }
1333 
1334   if(Index)
1335   {
1336     int ChosenIndex = RAND() % Index;
1337     Bite(Char[ChosenIndex], Pos[ChosenIndex],
1338          game::GetDirectionForVector(Pos[ChosenIndex] - GetPos()), Flags & SADIST_HIT);
1339   }
1340 
1341   msgsystem::LeaveBigMessageMode();
1342   return true;
1343 }
1344 
IsRetreating() const1345 truth magpie::IsRetreating() const
1346 {
1347   if(nonhumanoid::IsRetreating())
1348     return true;
1349 
1350   for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
1351     if((*i)->GetSparkleFlags())
1352       return true;
1353 
1354   return false;
1355 }
1356 
GetAICommand()1357 void magpie::GetAICommand()
1358 {
1359   if(!IsRetreating())
1360   {
1361     character* Char = GetRandomNeighbour();
1362 
1363     if(Char)
1364     {
1365       itemvector Sparkling;
1366 
1367       for(stackiterator i = Char->GetStack()->GetBottom(); i.HasItem(); ++i)
1368       {
1369         if((*i)->GetSparkleFlags() && !MakesBurdened((*i)->GetWeight()))
1370           Sparkling.push_back(*i);
1371       }
1372 
1373       if(!Sparkling.empty())
1374       {
1375         item* ToSteal = Sparkling[RAND() % Sparkling.size()];
1376         ToSteal->RemoveFromSlot();
1377         GetStack()->AddItem(ToSteal);
1378 
1379         if(Char->IsPlayer())
1380           ADD_MESSAGE("%s steals your %s.", CHAR_NAME(DEFINITE), ToSteal->CHAR_NAME(UNARTICLED));
1381 
1382         EditAP(-500);
1383         return;
1384       }
1385     }
1386   }
1387 
1388   nonhumanoid::GetAICommand();
1389 }
1390 
GetAICommand()1391 void eddy::GetAICommand()
1392 {
1393   if(!GetLSquareUnder()->GetOLTerrain() && !(RAND() % 500))
1394   {
1395     decoration* Couch = decoration::Spawn(RAND_N(5) ? COUCH : DOUBLE_BED);
1396 
1397     if(CanBeSeenByPlayer())
1398       ADD_MESSAGE("%s spits out %s.", CHAR_NAME(DEFINITE), Couch->CHAR_NAME(INDEFINITE));
1399 
1400     GetLSquareUnder()->ChangeOLTerrainAndUpdateLights(Couch);
1401     EditAP(-1000);
1402     return;
1403   }
1404 
1405   if(GetStackUnder()->GetItems() && !(RAND() % 10))
1406   {
1407     if(CanBeSeenByPlayer())
1408       ADD_MESSAGE("%s engulfs something under it.", CHAR_NAME(DEFINITE));
1409 
1410     GetStackUnder()->TeleportRandomly(3);
1411     EditAP(-1000);
1412     return;
1413   }
1414 
1415   if(!(RAND() % 100))
1416   {
1417     if(CanBeSeenByPlayer())
1418       ADD_MESSAGE("%s engulfs itself.", CHAR_NAME(DEFINITE));
1419 
1420     TeleportRandomly(true);
1421     EditAP(-1000);
1422     return;
1423   }
1424 
1425   nonhumanoid::GetAICommand();
1426 }
1427 
GetAICommand()1428 void skunk::GetAICommand()
1429 {
1430   if(!IsRetreating())
1431   {
1432     if(!RAND_N(4))
1433     {
1434       character* Char = GetRandomNeighbour(HOSTILE);
1435 
1436       if(Char)
1437       {
1438         int Amount = 500 / Char->GetSquaresUnder();
1439         truth Success = false;
1440 
1441         for(int c = 0; c < Char->GetSquaresUnder(); ++c)
1442           if(Char->GetLSquareUnder(c)->IsFlyable())
1443           {
1444             Success = true;
1445             Char->GetLSquareUnder(c)->AddSmoke(gas::Spawn(SKUNK_SMELL, Amount));
1446           }
1447 
1448         if(Success)
1449         {
1450           if(CanBeSeenByPlayer())
1451             ADD_MESSAGE("%s stinks.", CHAR_NAME(DEFINITE));
1452 
1453           EditAP(-1000);
1454           return;
1455         }
1456       }
1457     }
1458   }
1459   else if(RAND_N(2))
1460   {
1461     if(CanBeSeenByPlayer())
1462       ADD_MESSAGE("%s stinks.", CHAR_NAME(DEFINITE));
1463 
1464     GetLSquareUnder()->AddSmoke(gas::Spawn(SKUNK_SMELL, 500));
1465   }
1466 
1467   nonhumanoid::GetAICommand();
1468 }
1469 
TryToRiseFromTheDead()1470 truth elpuri::TryToRiseFromTheDead()
1471 {
1472   character::TryToRiseFromTheDead();
1473 
1474   for(int c = 0; c < GetSquaresUnder(); ++c)
1475     for(stackiterator i = GetLSquareUnder(c)->GetStack()->GetBottom(); i.HasItem(); ++i)
1476       if(i->IsHeadOfElpuri())
1477       {
1478         i->SendToHell();
1479         i->RemoveFromSlot();
1480         return true;
1481       }
1482 
1483   if(CanBeSeenByPlayer())
1484   {
1485     ADD_MESSAGE("The headless body of %s vibrates violently.", CHAR_NAME(DEFINITE));
1486     ADD_MESSAGE("%s dies.", CHAR_NAME(DEFINITE));
1487   }
1488 
1489   return false;
1490 }
1491 
EditAllAttributes(int Amount)1492 truth nonhumanoid::EditAllAttributes(int Amount)
1493 {
1494   if(!Amount)
1495     return true;
1496 
1497   LimitRef(StrengthExperience += Amount * EXP_MULTIPLIER, MIN_EXP, MAX_EXP);
1498   LimitRef(AgilityExperience += Amount * EXP_MULTIPLIER, MIN_EXP, MAX_EXP);
1499   return character::EditAllAttributes(Amount)
1500     || (Amount < 0
1501         && (StrengthExperience != MIN_EXP || AgilityExperience != MIN_EXP))
1502     || (Amount > 0
1503         && (StrengthExperience != MAX_EXP || AgilityExperience != MAX_EXP));
1504 }
1505 
1506 #ifdef WIZARD
1507 
AddAttributeInfo(festring & Entry) const1508 void nonhumanoid::AddAttributeInfo(festring& Entry) const
1509 {
1510   Entry.Resize(42);
1511   Entry << GetAttribute(ARM_STRENGTH);
1512   Entry.Resize(45);
1513   Entry << "-  -  " << GetAttribute(AGILITY);
1514   character::AddAttributeInfo(Entry);
1515 }
1516 
AddAttackInfo(felist & List) const1517 void nonhumanoid::AddAttackInfo(felist& List) const
1518 {
1519   festring Entry;
1520 
1521   if(IsUsingArms())
1522   {
1523     Entry = CONST_S("   unarmed attack");
1524     Entry.Resize(50);
1525     Entry << GetUnarmedMinDamage() << '-' << GetUnarmedMaxDamage();
1526     Entry.Resize(60);
1527     Entry << int(GetUnarmedToHitValue());
1528     Entry.Resize(70);
1529     Entry << GetUnarmedAPCost();
1530     List.AddEntry(Entry, LIGHT_GRAY);
1531   }
1532 
1533   if(IsUsingLegs())
1534   {
1535     Entry = CONST_S("   kick attack");
1536     Entry.Resize(50);
1537     Entry << GetKickMinDamage() << '-' << GetKickMaxDamage();
1538     Entry.Resize(60);
1539     Entry << int(GetKickToHitValue());
1540     Entry.Resize(70);
1541     Entry << GetKickAPCost();
1542     List.AddEntry(Entry, LIGHT_GRAY);
1543   }
1544 
1545   if(IsUsingHead())
1546   {
1547     Entry = CONST_S("   bite attack");
1548     Entry.Resize(50);
1549     Entry << GetBiteMinDamage() << '-' << GetBiteMaxDamage();
1550     Entry.Resize(60);
1551     Entry << int(GetBiteToHitValue());
1552     Entry.Resize(70);
1553     Entry << GetBiteAPCost();
1554     List.AddEntry(Entry, LIGHT_GRAY);
1555   }
1556 }
1557 
1558 #else
1559 
AddAttributeInfo(festring &) const1560 void nonhumanoid::AddAttributeInfo(festring&) const { }
AddAttackInfo(felist &) const1561 void nonhumanoid::AddAttackInfo(felist&) const { }
1562 
1563 #endif
1564 
MustBeRemovedFromBone() const1565 truth elpuri::MustBeRemovedFromBone() const
1566 {
1567   return !IsEnabled()
1568          || GetTeam()->GetID() != MONSTER_TEAM
1569          || GetDungeon()->GetIndex() != ELPURI_CAVE
1570          || GetLevel()->GetIndex() != DARK_LEVEL;
1571 }
1572 
MustBeRemovedFromBone() const1573 truth genetrixvesana::MustBeRemovedFromBone() const
1574 {
1575   return !IsEnabled()
1576          || GetTeam()->GetID() != MONSTER_TEAM
1577          || GetDungeon()->GetIndex() != UNDER_WATER_TUNNEL
1578          || GetLevel()->GetIndex() != VESANA_LEVEL;
1579 }
1580 
GetSquareIndex(v2 Pos) const1581 int largecreature::GetSquareIndex(v2 Pos) const
1582 {
1583   v2 RelativePos = Pos - GetPos();
1584   return RelativePos.X + (RelativePos.Y << 1);
1585 }
1586 
GetNeighbourSquare(int I) const1587 square* largecreature::GetNeighbourSquare(int I) const
1588 {
1589   square* SquareUnder = GetSquareUnder();
1590   area* Area = SquareUnder->GetArea();
1591   v2 Pos = SquareUnder->GetPos() + game::GetLargeMoveVector(I);
1592   return Area->IsValidPos(Pos) ? SquareUnder->GetArea()->GetSquare(Pos) : 0;
1593 }
1594 
CalculateNewSquaresUnder(lsquare ** NewSquare,v2 Pos) const1595 int largecreature::CalculateNewSquaresUnder(lsquare** NewSquare, v2 Pos) const
1596 {
1597   level* Level = GetLevel();
1598 
1599   for(int c = 0; c < 4; ++c)
1600   {
1601     v2 SquarePos = Pos + game::GetLargeMoveVector(12 + c);
1602 
1603     if(Level->IsValidPos(SquarePos))
1604       NewSquare[c] = Level->GetLSquare(SquarePos);
1605     else
1606       return 0;
1607   }
1608 
1609   return 4;
1610 }
1611 
IsFreeForMe(square * Square) const1612 truth largecreature::IsFreeForMe(square* Square) const
1613 {
1614   v2 Pos = Square->GetPos();
1615   area* Area = Square->GetArea();
1616 
1617   for(int c = 0; c < 4; ++c)
1618   {
1619     v2 SquarePos = Pos + game::GetLargeMoveVector(12 + c);
1620 
1621     if(!Area->IsValidPos(SquarePos)
1622        || (Area->GetSquare(SquarePos)->GetCharacter()
1623            && Area->GetSquare(SquarePos)->GetCharacter() != static_cast<ccharacter*>(this)))
1624       return false;
1625   }
1626 
1627   return true;
1628 }
1629 
CanTheoreticallyMoveOn(const lsquare * LSquare) const1630 truth largecreature::CanTheoreticallyMoveOn(const lsquare* LSquare) const
1631 {
1632   v2 Pos = LSquare->GetPos();
1633   level* Level = LSquare->GetLevel();
1634 
1635   for(int c = 0; c < 4; ++c)
1636   {
1637     v2 SquarePos = Pos + game::GetLargeMoveVector(12 + c);
1638 
1639     if(!Level->IsValidPos(SquarePos) || !(GetMoveType() & Level->GetLSquare(SquarePos)->GetTheoreticalWalkability()))
1640       return false;
1641   }
1642 
1643   return true;
1644 }
1645 
CanMoveOn(const lsquare * LSquare) const1646 truth largecreature::CanMoveOn(const lsquare* LSquare) const
1647 {
1648   v2 Pos = LSquare->GetPos();
1649   level* Level = LSquare->GetLevel();
1650 
1651   for(int c = 0; c < 4; ++c)
1652   {
1653     v2 SquarePos = Pos + game::GetLargeMoveVector(12 + c);
1654 
1655     if(!Level->IsValidPos(SquarePos) || !PartCanMoveOn(Level->GetLSquare(SquarePos)))
1656       return false;
1657   }
1658 
1659   return true;
1660 }
1661 
CanMoveOn(const square * Square) const1662 truth largecreature::CanMoveOn(const square* Square) const
1663 {
1664   v2 Pos = Square->GetPos();
1665   area* Area = Square->GetArea();
1666 
1667   for(int c = 0; c < 4; ++c)
1668   {
1669     v2 SquarePos = Pos + game::GetLargeMoveVector(12 + c);
1670     if(!Area->IsValidPos(SquarePos) || !(GetMoveType() & Area->GetSquare(SquarePos)->GetSquareWalkability()))
1671       return false;
1672   }
1673 
1674   return true;
1675 }
1676 
PutTo(v2 Pos)1677 void largecreature::PutTo(v2 Pos)
1678 {
1679   for(int c = 0; c < 4; ++c)
1680   {
1681     SquareUnder[c] = game::GetCurrentArea()->GetSquare(Pos + game::GetLargeMoveVector(12 + c));
1682     SquareUnder[c]->AddCharacter(this);
1683   }
1684 }
1685 
Remove()1686 void largecreature::Remove()
1687 {
1688   for(int c = 0; c < 4; ++c)
1689   {
1690     SquareUnder[c]->RemoveCharacter();
1691     SquareUnder[c] = 0;
1692   }
1693 }
1694 
CreateCorpse(lsquare * Square)1695 void largecreature::CreateCorpse(lsquare* Square)
1696 {
1697   if(!BodyPartsDisappearWhenSevered() && !game::AllBodyPartsVanish())
1698   {
1699     corpse* Corpse = largecorpse::Spawn(0, NO_MATERIALS);
1700     Corpse->SetDeceased(this);
1701     Square->AddItem(Corpse);
1702     Disable();
1703   }
1704   else
1705     SendToHell();
1706 }
1707 
LoadSquaresUnder()1708 void largecreature::LoadSquaresUnder()
1709 {
1710   for(int c = 0; c < 4; ++c)
1711     SquareUnder[c] = game::GetSquareInLoad()->GetArea()->GetSquare(game::GetSquareInLoad()->GetPos()
1712                                                                    + game::GetLargeMoveVector(12 + c));
1713 }
1714 
MustBeRemovedFromBone() const1715 truth vladimir::MustBeRemovedFromBone() const
1716 {
1717   return !IsEnabled()
1718          || GetTeam()->GetID() != IVAN_TEAM
1719          || GetDungeon()->GetIndex() != ELPURI_CAVE
1720          || GetLevel()->GetIndex() != IVAN_LEVEL;
1721 }
1722 
GetAICommand()1723 void hattifattener::GetAICommand()
1724 {
1725   if(!(RAND() % 7))
1726   {
1727     if(CanBeSeenByPlayer())
1728       ADD_MESSAGE("%s emits a lightning bolt!", CHAR_DESCRIPTION(DEFINITE));
1729 
1730     beamdata Beam
1731       (
1732         this,
1733         "killed by a hattifattener's lightning",
1734         GetPos(),
1735         WHITE,
1736         BEAM_LIGHTNING,
1737         RAND() & 7,
1738         1 + (RAND() & 7),
1739         0,
1740         NULL
1741       );
1742 
1743     GetLevel()->LightningBeam(Beam);
1744     EditAP(-1000);
1745     return;
1746   }
1747 
1748   SeekLeader(GetLeader());
1749 
1750   if(FollowLeader(GetLeader()))
1751     return;
1752 
1753   if(MoveRandomly())
1754     return;
1755 
1756   EditAP(-1000);
1757 }
1758 
CreateCorpse(lsquare * Square)1759 void hattifattener::CreateCorpse(lsquare* Square)
1760 {
1761   level* Level = Square->GetLevel();
1762   ulong StackSize = Level->AddRadiusToSquareStack(Square->GetPos(), 9);
1763   lsquare** SquareStack = Level->GetSquareStack();
1764   ulong c;
1765 
1766   for(c = 0; c < StackSize; ++c)
1767     SquareStack[c]->RemoveFlags(IN_SQUARE_STACK);
1768 
1769   fearray<lsquare*> Stack(SquareStack, StackSize);
1770   Level->LightningVisualizer(Stack, WHITE);
1771 
1772   for(c = 0; c < Stack.Size; ++c)
1773   {
1774     beamdata Beam
1775       (
1776         this,
1777         CONST_S("killed by electricity released by a dying hattifattener"),
1778         YOURSELF,
1779         0
1780       );
1781 
1782     Stack[c]->Lightning(Beam);
1783   }
1784 
1785   SendToHell();
1786 }
1787 
SpecialBodyDefenceEffect(character * Enemy,bodypart * BodyPart,int Type)1788 void hedgehog::SpecialBodyDefenceEffect(character* Enemy, bodypart* BodyPart, int Type)
1789 {
1790   if(Type != WEAPON_ATTACK && RAND() & 1)
1791   {
1792     if(Enemy->IsPlayer())
1793       ADD_MESSAGE("%s spines jab your %s!", CHAR_POSSESSIVE_PRONOUN, BodyPart->GetBodyPartName().CStr());
1794     else if(CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer())
1795       ADD_MESSAGE("%s spines jab %s!", CHAR_POSSESSIVE_PRONOUN, Enemy->CHAR_NAME(DEFINITE));
1796 
1797     Enemy->ReceiveBodyPartDamage(this, 1 + (RAND() & 1), PHYSICAL_DAMAGE, BodyPart->GetBodyPartIndex(),
1798                                  YOURSELF, false, false, true, false);
1799     Enemy->CheckDeath(CONST_S("killed by the pointy spines of ") + GetName(INDEFINITE), this);
1800   }
1801 }
1802 
Save(outputfile & SaveFile) const1803 void genetrixvesana::Save(outputfile& SaveFile) const
1804 {
1805   nonhumanoid::Save(SaveFile);
1806   SaveFile << TurnsExisted;
1807 }
1808 
Load(inputfile & SaveFile)1809 void genetrixvesana::Load(inputfile& SaveFile)
1810 {
1811   nonhumanoid::Load(SaveFile);
1812   SaveFile >> TurnsExisted;
1813 }
1814 
CreateRoute()1815 truth largecreature::CreateRoute()
1816 {
1817   Route.clear();
1818 
1819   if(GetAttribute(INTELLIGENCE) >= 10 && !StateIsActivated(CONFUSED))
1820   {
1821     node* Node = GetLevel()->FindRoute(GetPos(), GoingTo, Illegal, 0, this);
1822 
1823     if(Node)
1824       while(Node->Last)
1825       {
1826         Route.push_back(Node->Pos);
1827         Node = Node->Last;
1828       }
1829     else
1830       TerminateGoingTo();
1831 
1832     IntelligenceAction(5);
1833     return true;
1834   }
1835   else
1836     return false;
1837 }
1838 
GetAICommand()1839 void bunny::GetAICommand()
1840 {
1841   if(GetConfig() < 4 && GetNP() > (SATIATED_LEVEL + BLOATED_LEVEL) >> 1)
1842   {
1843     if(CanBeSeenByPlayer())
1844       ADD_MESSAGE("%s looks more mature.", CHAR_NAME(DEFINITE));
1845 
1846     GetTorso()->SetSize(GetTorso()->GetSize() << 1);
1847     LimitRef(StrengthExperience *= 2, MIN_EXP, MAX_EXP);
1848     LimitRef(AgilityExperience *= 2, MIN_EXP, MAX_EXP);
1849 
1850     for(int c = 0; c < BASE_ATTRIBUTES; ++c)
1851       BaseExperience[c] = Limit(BaseExperience[c] * 2, MIN_EXP, MAX_EXP);
1852 
1853     GetTorso()->GetMainMaterial()->SetVolume(GetTorso()->GetMainMaterial()->GetVolume() << 1);
1854     SetConfig(GetConfig() + 2);
1855     RestoreHP();
1856     RestoreStamina();
1857   }
1858 
1859   SeekLeader(GetLeader());
1860 
1861   if(FollowLeader(GetLeader()))
1862     return;
1863 
1864   if(CheckForEnemies(true, true, true))
1865     return;
1866 
1867   if(CheckForUsefulItemsOnGround())
1868     return;
1869 
1870   if(CheckForDoors())
1871     return;
1872 
1873   if(CheckForFood(5))
1874     return;
1875 
1876   if(CheckForMatePartner())
1877     return;
1878 
1879   if(MoveRandomly())
1880     return;
1881 
1882   EditAP(-1000);
1883 }
1884 
SignalNaturalGeneration()1885 void bunny::SignalNaturalGeneration()
1886 {
1887   character* Partner = bunny::Spawn(GetConfig()^1);
1888   Partner->SetTeam(GetTeam());
1889   Partner->SetGenerationDanger(GetGenerationDanger());
1890   Partner->PutNear(GetPos());
1891 }
1892 
CheckForMatePartner()1893 truth bunny::CheckForMatePartner()
1894 {
1895   if(GetConfig() == ADULT_MALE)
1896   {
1897     character* BestPartner = 0;
1898     double BestPartnerDanger = 0;
1899 
1900     for(int c = 0; c < game::GetTeams(); ++c)
1901       if(GetTeam()->GetRelation(game::GetTeam(c)) != HOSTILE)
1902         for(character* p : game::GetTeam(c)->GetMember())
1903           if(p->IsEnabled() && p->IsBunny() && p->GetConfig() == ADULT_FEMALE && p->GetNP() > SATIATED_LEVEL)
1904           {
1905             double Danger = p->GetRelativeDanger(this, true);
1906 
1907             if(Danger > BestPartnerDanger)
1908             {
1909               BestPartner = p;
1910               BestPartnerDanger = Danger;
1911             }
1912           }
1913 
1914     if(BestPartner && !GetPos().IsAdjacent(BestPartner->GetPos()))
1915     {
1916       SetGoingTo(BestPartner->GetPos());
1917       MoveTowardsTarget(true);
1918       return true;
1919     }
1920   }
1921 
1922   if(GetConfig() == ADULT_FEMALE && GetNP() > NOT_HUNGER_LEVEL + 10000)
1923   {
1924     for(int d = 0; d < GetNeighbourSquares(); ++d)
1925     {
1926       lsquare* Square = GetNeighbourLSquare(d);
1927 
1928       if(Square)
1929       {
1930         character* Father = Square->GetCharacter();
1931 
1932         if(Father && Father->IsBunny() && Father->GetConfig() == ADULT_MALE && GetRelation(Father) != HOSTILE)
1933         {
1934           if(CanBeSeenByPlayer())
1935           {
1936             if(Father->IsPlayer())
1937               ADD_MESSAGE("You have much fun with %s.", CHAR_NAME(DEFINITE));
1938             else if(Father->CanBeSeenByPlayer())
1939               ADD_MESSAGE("%s and %s seem to have much fun together.",
1940                           Father->CHAR_NAME(DEFINITE), CHAR_NAME(DEFINITE));
1941             else
1942               ADD_MESSAGE("%s seems to have much fun.", CHAR_NAME(DEFINITE));
1943           }
1944           else
1945           {
1946             if(Father->IsPlayer())
1947               ADD_MESSAGE("You have much fun with something.");
1948             else if(Father->CanBeSeenByPlayer())
1949               ADD_MESSAGE("%s seems to have much fun.", Father->CHAR_NAME(DEFINITE));
1950           }
1951 
1952           bunny* Baby = bunny::Spawn(BABY_MALE + (RAND() & 1));
1953           Baby->StrengthExperience = RandomizeBabyExperience(StrengthExperience + static_cast<bunny*>(Father)->StrengthExperience);
1954           Baby->AgilityExperience = RandomizeBabyExperience(AgilityExperience + static_cast<bunny*>(Father)->AgilityExperience);
1955 
1956           if(Baby->GetConfig() == BABY_MALE)
1957           {
1958             Baby->StrengthExperience *= 4;
1959             Baby->AgilityExperience *= 4;
1960           }
1961           else
1962           {
1963             Baby->StrengthExperience *= 2;
1964             Baby->AgilityExperience *= 6;
1965           }
1966 
1967           Baby->StrengthExperience /= 3;
1968           Baby->AgilityExperience /= 5;
1969 
1970           for(int c = 0; c < BASE_ATTRIBUTES; ++c)
1971             Baby->BaseExperience[c] = RandomizeBabyExperience(BaseExperience[c] + static_cast<bunny*>(Father)->BaseExperience[c]);
1972 
1973           Baby->CalculateAll();
1974           Baby->RestoreHP();
1975           Baby->RestoreStamina();
1976           Baby->SetTeam(GetTeam());
1977           Baby->SetGenerationDanger(GetGenerationDanger());
1978           Baby->PutNear(GetPos());
1979 
1980           if(Baby->CanBeSeenByPlayer())
1981             ADD_MESSAGE("%s is born.", Baby->CHAR_NAME(INDEFINITE));
1982 
1983           EditNP(-10000);
1984           Father->EditAP(-3000);
1985           EditAP(-5000);
1986           EditStamina(-GetMaxStamina() >> 1, true);
1987           Father->EditStamina(-(Father->GetMaxStamina() << 2) / 5, true);
1988           return true;
1989         }
1990       }
1991     }
1992   }
1993 
1994   return false;
1995 }
1996 
Catches(item * Thingy)1997 truth bunny::Catches(item* Thingy)
1998 {
1999   if(Thingy->BunnyWillCatchAndConsume(this))
2000   {
2001     if(ConsumeItem(Thingy, CONST_S("eating")))
2002     {
2003       if(IsPlayer())
2004         ADD_MESSAGE("You catch %s in mid-air and consume it.", Thingy->CHAR_NAME(DEFINITE));
2005       else
2006       {
2007         if(CanBeSeenByPlayer())
2008           ADD_MESSAGE("%s catches %s and eats it.", CHAR_NAME(DEFINITE), Thingy->CHAR_NAME(DEFINITE));
2009           ADD_MESSAGE("%s seems to be much more friendly towards you.", CHAR_NAME(DEFINITE));
2010 
2011         ChangeTeam(PLAYER->GetTeam());
2012       }
2013     }
2014     else if(IsPlayer())
2015       ADD_MESSAGE("You catch %s in mid-air.", Thingy->CHAR_NAME(DEFINITE));
2016     else if(CanBeSeenByPlayer())
2017       ADD_MESSAGE("%s catches %s.", CHAR_NAME(DEFINITE), Thingy->CHAR_NAME(DEFINITE));
2018 
2019     return true;
2020   }
2021   else
2022     return false;
2023 }
2024 
PlaceIsIllegal(v2 Pos,v2 Illegal) const2025 truth largecreature::PlaceIsIllegal(v2 Pos, v2 Illegal) const
2026 {
2027   for(int c = 0; c < 4; ++c)
2028     if(Pos + game::GetLargeMoveVector(12 + c) == Illegal)
2029       return true;
2030 
2031   return false;
2032 }
2033 
Hit(character * Enemy,v2 Pos,int,int)2034 truth mommo::Hit(character* Enemy, v2 Pos, int, int)
2035 {
2036   if(CheckIfTooScaredToHit(Enemy))
2037     return false;
2038 
2039   if(IsPlayer())
2040   {
2041     if(!(Enemy->IsMasochist() && GetRelation(Enemy) == FRIEND) && GetRelation(Enemy) != HOSTILE
2042        && !game::TruthQuestion(CONST_S("This might cause a hostile reaction. Are you sure? [y/N]")))
2043       return false;
2044   }
2045   else if(GetAttribute(WISDOM) >= Enemy->GetAttackWisdomLimit())
2046     return false;
2047 
2048   Hostility(Enemy);
2049 
2050   if(IsPlayer())
2051     ADD_MESSAGE("You spill %s at %s.", GetTorso()->GetMainMaterial()->GetName(false, false).CStr(),
2052                 Enemy->CHAR_DESCRIPTION(DEFINITE));
2053   else if(Enemy->IsPlayer() || CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer())
2054     ADD_MESSAGE("%s spills %s at %s.", CHAR_DESCRIPTION(DEFINITE),
2055                 GetTorso()->GetMainMaterial()->GetName(false, false).CStr(), Enemy->CHAR_DESCRIPTION(DEFINITE));
2056 
2057   Vomit(Pos, 250 + RAND() % 250, false);
2058   EditAP(-1000);
2059   return true;
2060 }
2061 
GetAICommand()2062 void mommo::GetAICommand()
2063 {
2064   SeekLeader(GetLeader());
2065 
2066   if(CheckForEnemies(false, false, true))
2067     return;
2068 
2069   if(!(RAND() % 10))
2070   {
2071     VomitAtRandomDirection(350 + RAND() % 350);
2072     EditAP(-1000);
2073     return;
2074   }
2075 
2076   if(FollowLeader(GetLeader()))
2077     return;
2078 
2079   if(MoveRandomly())
2080     return;
2081 
2082   EditAP(-1000);
2083 }
2084 
GetAICommand()2085 void dog::GetAICommand()
2086 {
2087   if(!game::IsInWilderness() && !(RAND() & 7))
2088     GetLSquareUnder()->SpillFluid(this, liquid::Spawn(DOG_DROOL, 25 + RAND() % 50), false, false);
2089 
2090   character::GetAICommand();
2091 }
2092 
SpecialEnemySightedReaction(character *)2093 truth blinkdog::SpecialEnemySightedReaction(character*)
2094 {
2095   if(!(RAND() & 15) && SummonFriend())
2096     return true;
2097 
2098   if(!(RAND() & 31))
2099   {
2100     MonsterTeleport("a playful bark");
2101     return true;
2102   }
2103 
2104   if((!(RAND() & 3) && StateIsActivated(PANIC))
2105      || (!(RAND() & 7) && IsInBadCondition()))
2106   {
2107     MonsterTeleport("a frightened howl");
2108     return true;
2109   }
2110 
2111   return false;
2112 }
2113 
MonsterTeleport(cchar * BarkMsg)2114 void blinkdog::MonsterTeleport(cchar* BarkMsg)
2115 {
2116   if(IsPlayer())
2117     ADD_MESSAGE("You vanish.");
2118   else if(CanBeSeenByPlayer())
2119     ADD_MESSAGE("You hear %s inside your head as %s vanishes!", BarkMsg, CHAR_NAME(DEFINITE));
2120   else
2121     ADD_MESSAGE("You hear %s inside your head.", BarkMsg);
2122 
2123   v2 Pos = GetPos();
2124   rect Border(Pos + v2(-5, -5), Pos + v2(5, 5));
2125   Pos = GetLevel()->GetRandomSquare(this, 0, &Border);
2126 
2127   if(Pos == ERROR_V2)
2128     Pos = GetLevel()->GetRandomSquare(this);
2129 
2130   Move(Pos, true);
2131 
2132   if(IsPlayer())
2133     ADD_MESSAGE("You materialize.");
2134   else if(CanBeSeenByPlayer())
2135     ADD_MESSAGE("%s materializes from nowhere!", CHAR_NAME(INDEFINITE));
2136 
2137   EditAP(-1000);
2138 }
2139 
TakeHit(character * Enemy,item * Weapon,bodypart * EnemyBodyPart,v2 HitPos,double Damage,double ToHitValue,int Success,int Type,int Direction,truth Critical,truth ForceHit)2140 int unicorn::TakeHit(character* Enemy, item* Weapon, bodypart* EnemyBodyPart, v2 HitPos, double Damage,
2141                      double ToHitValue, int Success, int Type, int Direction, truth Critical, truth ForceHit)
2142 {
2143   int Return = nonhumanoid::TakeHit(Enemy, Weapon, EnemyBodyPart, HitPos, Damage, ToHitValue,
2144                                     Success, Type, Direction, Critical, ForceHit);
2145 
2146   if(Return != HAS_DIED
2147      && (StateIsActivated(PANIC)
2148          || (RAND() & 1 && IsInBadCondition())
2149          || !(RAND() & 7)))
2150     MonsterTeleport(" in terror");
2151 
2152   return Return;
2153 }
2154 
TakeHit(character * Enemy,item * Weapon,bodypart * EnemyBodyPart,v2 HitPos,double Damage,double ToHitValue,int Success,int Type,int Direction,truth Critical,truth ForceHit)2155 int blinkdog::TakeHit(character* Enemy, item* Weapon, bodypart* EnemyBodyPart, v2 HitPos, double Damage,
2156                       double ToHitValue, int Success, int Type, int Direction, truth Critical, truth ForceHit)
2157 {
2158   int Return = nonhumanoid::TakeHit(Enemy, Weapon, EnemyBodyPart, HitPos, Damage, ToHitValue,
2159                                     Success, Type, Direction, Critical, ForceHit);
2160 
2161   if(Return != HAS_DIED)
2162   {
2163     if(!(RAND() & 15) && SummonFriend())
2164       return Return;
2165 
2166     if((RAND() & 1 && StateIsActivated(PANIC))
2167        || (!(RAND() & 3) && IsInBadCondition())
2168        || !(RAND() & 15))
2169     MonsterTeleport("a terrified yelp");
2170   }
2171 
2172   return Return;
2173 }
2174 
MonsterTeleport(cchar * NeighDescription)2175 void unicorn::MonsterTeleport(cchar* NeighDescription)
2176 {
2177   if(IsPlayer())
2178     ADD_MESSAGE("You neigh%s and disappear.", NeighDescription);
2179   else if(CanBeSeenByPlayer())
2180     ADD_MESSAGE("%s neighs%s and disappears!", CHAR_NAME(DEFINITE), NeighDescription);
2181 
2182   Move(GetLevel()->GetRandomSquare(this), true);
2183 
2184   if(IsPlayer())
2185     ADD_MESSAGE("You reappear.");
2186   else if(CanBeSeenByPlayer())
2187     ADD_MESSAGE("Suddenly %s appears from nothing!", CHAR_NAME(INDEFINITE));
2188 
2189   EditAP(-1000);
2190 }
2191 
SummonFriend()2192 truth blinkdog::SummonFriend()
2193 {
2194   if(!SummonModifier)
2195     return false;
2196 
2197   --SummonModifier;
2198   blinkdog* Buddy = blinkdog::Spawn();
2199   Buddy->SummonModifier = SummonModifier;
2200   Buddy->SetTeam(GetTeam());
2201   Buddy->SetGenerationDanger(GetGenerationDanger());
2202   Buddy->PutNear(GetPos());
2203 
2204   if(CanBeSeenByPlayer())
2205   {
2206     ADD_MESSAGE("%s wags its tail in a mysterious pattern.", CHAR_NAME(DEFINITE));
2207 
2208     if(Buddy->CanBeSeenByPlayer())
2209       ADD_MESSAGE("Another of its kin appears!");
2210   }
2211   else if(Buddy->CanBeSeenByPlayer())
2212     ADD_MESSAGE("%s appears!", Buddy->CHAR_NAME(INDEFINITE));
2213 
2214   EditAP(-1000);
2215   return true;
2216 }
2217 
blinkdog()2218 blinkdog::blinkdog()
2219 {
2220   if(!game::IsLoading())
2221     SummonModifier = RAND_2 + RAND_2 + RAND_2 + RAND_2 + RAND_2 + RAND_2 + RAND_2;
2222 }
2223 
Save(outputfile & SaveFile) const2224 void blinkdog::Save(outputfile& SaveFile) const
2225 {
2226   dog::Save(SaveFile);
2227   SaveFile << SummonModifier;
2228 }
2229 
Load(inputfile & SaveFile)2230 void blinkdog::Load(inputfile& SaveFile)
2231 {
2232   dog::Load(SaveFile);
2233   SaveFile >> SummonModifier;
2234 }
2235 
FinalProcessForBone()2236 void genetrixvesana::FinalProcessForBone()
2237 {
2238   largecreature::FinalProcessForBone();
2239   TurnsExisted = 0;
2240 }
2241 
GetAICommand()2242 void carnivorousplant::GetAICommand()
2243 {
2244   if(!CPUwiseAI(this))return;
2245 
2246   SeekLeader(GetLeader());
2247 
2248   if(FollowLeader(GetLeader()))
2249     return;
2250 
2251   if(AttackAdjacentEnemyAI())
2252     return;
2253 
2254   if(CheckForUsefulItemsOnGround())
2255     return;
2256 
2257   if(MoveRandomly())
2258     return;
2259 
2260   EditAP(-1000);
2261 }
2262 
GetAICommand()2263 void mysticfrog::GetAICommand()
2264 {
2265   SeekLeader(GetLeader());
2266 
2267   if(FollowLeader(GetLeader()))
2268     return;
2269 
2270   character* NearestEnemy = 0;
2271   long NearestEnemyDistance = 0x7FFFFFFF;
2272   character* RandomFriend = 0;
2273   charactervector Friend;
2274   v2 Pos = GetPos();
2275   truth Enemies = false;
2276 
2277   for(int c = 0; c < game::GetTeams(); ++c)
2278   {
2279     if(GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE)
2280     {
2281       for(character* p : game::GetTeam(c)->GetMember())
2282         if(p->IsEnabled())
2283         {
2284           Enemies = true;
2285           long ThisDistance = Max<long>(abs(p->GetPos().X - Pos.X), abs(p->GetPos().Y - Pos.Y));
2286 
2287           if((ThisDistance < NearestEnemyDistance || (ThisDistance == NearestEnemyDistance && !(RAND() % 3))) && p->CanBeSeenBy(this))
2288           {
2289             NearestEnemy = p;
2290             NearestEnemyDistance = ThisDistance;
2291           }
2292         }
2293     }
2294     else if(GetTeam()->GetRelation(game::GetTeam(c)) == FRIEND)
2295     {
2296       for(character* p : game::GetTeam(c)->GetMember())
2297         if(p->IsEnabled() && p->CanBeSeenBy(this))
2298           Friend.push_back(p);
2299     }
2300   }
2301 
2302   if(NearestEnemy && NearestEnemy->GetPos().IsAdjacent(Pos))
2303   {
2304     if(NearestEnemy->IsSmall()
2305        && GetAttribute(WISDOM) < NearestEnemy->GetAttackWisdomLimit()
2306        && !(RAND() % 5)
2307        && Hit(NearestEnemy, NearestEnemy->GetPos(), game::GetDirectionForVector(NearestEnemy->GetPos() - GetPos())))
2308       return;
2309     else if(!(RAND() & 3))
2310     {
2311       if(CanBeSeenByPlayer())
2312         ADD_MESSAGE("%s invokes a spell and disappears.", CHAR_NAME(DEFINITE));
2313 
2314       TeleportRandomly(true);
2315       EditAP(-GetSpellAPCost());
2316       return;
2317     }
2318   }
2319 
2320   if(NearestEnemy && (NearestEnemyDistance < 10 || StateIsActivated(PANIC)) && RAND() & 3)
2321   {
2322     SetGoingTo((Pos << 1) - NearestEnemy->GetPos());
2323 
2324     if(MoveTowardsTarget(true))
2325       return;
2326   }
2327 
2328   if(Friend.size() && !(RAND() & 3))
2329   {
2330     RandomFriend = Friend[RAND() % Friend.size()];
2331     NearestEnemy = 0;
2332   }
2333 
2334   if(GetRelation(PLAYER) == HOSTILE && PLAYER->CanBeSeenBy(this) && !RAND_4)
2335     NearestEnemy = PLAYER;
2336 
2337   beamdata Beam
2338     (
2339       this,
2340       CONST_S("killed by the spells of ") + GetName(INDEFINITE),
2341       YOURSELF,
2342       0
2343     );
2344 
2345   if(NearestEnemy)
2346   {
2347     lsquare* Square = NearestEnemy->GetLSquareUnder();
2348     EditAP(-GetSpellAPCost());
2349 
2350     if(CanBeSeenByPlayer())
2351       ADD_MESSAGE("%s invokes a spell!", CHAR_NAME(DEFINITE));
2352 
2353     switch(RAND() % 20)
2354     {
2355      case 0:
2356      case 1:
2357      case 2:
2358      case 3:
2359      case 4:
2360      case 5: Square->DrawParticles(RED); if(NearestEnemy->TeleportRandomItem(GetConfig() == DARK)) break;
2361      case 6:
2362      case 7:
2363      case 8:
2364      case 9:
2365      case 10: Square->DrawParticles(RED); Square->Teleport(Beam); break;
2366      case 11:
2367      case 12:
2368      case 13:
2369      case 14: Square->DrawParticles(RED); Square->Slow(Beam); break;
2370      case 15: Square->DrawParticles(RED); Square->LowerEnchantment(Beam); break;
2371      default: Square->DrawLightning(v2(8, 8), WHITE, YOURSELF); Square->Lightning(Beam); break;
2372     }
2373 
2374     if(CanBeSeenByPlayer())
2375       NearestEnemy->DeActivateVoluntaryAction(CONST_S("The spell of ") + GetName(DEFINITE)
2376                                               + CONST_S(" interrupts you."));
2377     else
2378       NearestEnemy->DeActivateVoluntaryAction(CONST_S("The spell interrupts you."));
2379 
2380     return;
2381   }
2382 
2383   if(RandomFriend && Enemies)
2384   {
2385     lsquare* Square = RandomFriend->GetLSquareUnder();
2386     EditAP(-GetSpellAPCost());
2387     Square->DrawParticles(RED);
2388 
2389     if(RAND() & 1)
2390       Square->Invisibility(Beam);
2391     else
2392       Square->Haste(Beam);
2393 
2394     return;
2395   }
2396 
2397   StandIdleAI();
2398 }
2399 
PartCanMoveOn(const lsquare * LSquare) const2400 truth largecreature::PartCanMoveOn(const lsquare* LSquare) const
2401 {
2402   int Walkability = LSquare->GetWalkability();
2403 
2404   if(GetMoveType() & Walkability)
2405     return true;
2406 
2407   if(DestroysWalls() && Walkability & ETHEREAL)
2408   {
2409     olterrain* Terrain = LSquare->GetOLTerrain();
2410 
2411     if(Terrain && Terrain->WillBeDestroyedBy(this))
2412     {
2413       room* Room = LSquare->GetRoom();
2414 
2415       if(!Room || Room->IsOKToDestroyWalls(this))
2416         return true;
2417     }
2418   }
2419 
2420   return false;
2421 }
2422 
GetAICommand()2423 void spider::GetAICommand()
2424 {
2425   SeekLeader(GetLeader());
2426 
2427   if(FollowLeader(GetLeader()))
2428     return;
2429 
2430   character* NearestChar = 0;
2431   long NearestDistance = 0x7FFFFFFF;
2432   v2 Pos = GetPos();
2433   int Hostiles = 0;
2434 
2435   for(int c = 0; c < game::GetTeams(); ++c)
2436     if(GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE)
2437       for(character* p : game::GetTeam(c)->GetMember())
2438         if(p->IsEnabled() && GetAttribute(WISDOM) < p->GetAttackWisdomLimit())
2439         {
2440           long ThisDistance = Max<long>(abs(p->GetPos().X - Pos.X), abs(p->GetPos().Y - Pos.Y));
2441           ++Hostiles;
2442 
2443           if((ThisDistance < NearestDistance
2444               || (ThisDistance == NearestDistance && !(RAND() % 3)))
2445              && p->CanBeSeenBy(this, false, false /*IsGoingSomeWhere()*/)
2446              && (!IsGoingSomeWhere() || HasClearRouteTo(p->GetPos())))
2447           {
2448             NearestChar = p;
2449             NearestDistance = ThisDistance;
2450           }
2451         }
2452 
2453   if(Hostiles && !RAND_N(Max(80 / Hostiles, 8)) && GetLSquareUnder()->IsFlyable())
2454   {
2455     web* Web = web::Spawn();
2456     Web->SetStrength(GetConfig() * 10);
2457 
2458     if(GetLSquareUnder()->AddTrap(Web))
2459     {
2460       if(CanBeSeenByPlayer())
2461         ADD_MESSAGE("%s spins a web.", CHAR_NAME(DEFINITE));
2462 
2463       EditAP(-1000);
2464       return;
2465     }
2466   }
2467 
2468   if(NearestChar)
2469   {
2470     if(NearestChar->IsStuck() || GetConfig() == ARANEA ||
2471        (GetConfig() == PHASE && !CanBeSeenBy(NearestChar)))
2472       SetGoingTo(NearestChar->GetPos());
2473     else
2474       SetGoingTo((Pos << 1) - NearestChar->GetPos());
2475 
2476     if(MoveTowardsTarget(true))
2477       return;
2478   }
2479 
2480   if(MoveRandomly())
2481     return;
2482 
2483   // Attack if trapped in a corner.
2484   if(AttackAdjacentEnemyAI())
2485     return;
2486 
2487   EditAP(-1000);
2488 }
2489 
Save(outputfile & SaveFile) const2490 void largecat::Save(outputfile& SaveFile) const
2491 {
2492   nonhumanoid::Save(SaveFile);
2493   SaveFile << Lives;
2494 }
2495 
Load(inputfile & SaveFile)2496 void largecat::Load(inputfile& SaveFile)
2497 {
2498   nonhumanoid::Load(SaveFile);
2499   SaveFile >> Lives;
2500 }
2501 
SpecialSaveLife()2502 truth largecat::SpecialSaveLife()
2503 {
2504   if(--Lives <= 0 || game::IsInWilderness())
2505     return false;
2506 
2507   if(IsPlayer())
2508     ADD_MESSAGE("But wait! You seem to have miraculously avoided certain death!");
2509   else if(CanBeSeenByPlayer())
2510     ADD_MESSAGE("But wait, %s seems to have miraculously avoided certain death!", GetPersonalPronoun().CStr());
2511 
2512   v2 Pos = GetPos();
2513   rect Border(Pos + v2(-20, -20), Pos + v2(20, 20));
2514   Pos = GetLevel()->GetRandomSquare(this, 0, &Border);
2515 
2516   if(Pos == ERROR_V2)
2517     Pos = GetLevel()->GetRandomSquare(this);
2518 
2519   Move(Pos, true);
2520 
2521   if(!IsPlayer() && CanBeSeenByPlayer())
2522     ADD_MESSAGE("%s appears!", CHAR_NAME(INDEFINITE));
2523 
2524   if(IsPlayer())
2525     game::AskForKeyPress(CONST_S("Life saved! [press any key to continue]"));
2526 
2527   RestoreBodyParts();
2528   ResetSpoiling();
2529   ResetBurning();
2530   RestoreHP();
2531   RestoreStamina();
2532   ResetStates();
2533 
2534   if(GetNP() < SATIATED_LEVEL)
2535     SetNP(SATIATED_LEVEL);
2536 
2537   SendNewDrawRequest();
2538 
2539   if(GetAction())
2540     GetAction()->Terminate(false);
2541 
2542   return true;
2543 }
2544 
MustBeRemovedFromBone() const2545 truth lobhse::MustBeRemovedFromBone() const
2546 {
2547   return !IsEnabled()
2548          || GetTeam()->GetID() != MONSTER_TEAM
2549          || GetDungeon()->GetIndex() != UNDER_WATER_TUNNEL
2550          || GetLevel()->GetIndex() != SPIDER_LEVEL;
2551 }
2552 
FinalProcessForBone()2553 void lobhse::FinalProcessForBone()
2554 {
2555   largecreature::FinalProcessForBone();
2556   TurnsExisted = 0;
2557 }
2558 
Bite(character * Enemy,v2 HitPos,int Direction,truth ForceHit)2559 void lobhse::Bite(character* Enemy, v2 HitPos, int Direction, truth ForceHit)
2560 {
2561   if(!RAND_N(7))
2562   {
2563     if(IsPlayer())
2564       ADD_MESSAGE("You vomit at %s.", Enemy->CHAR_DESCRIPTION(DEFINITE));
2565     else if(Enemy->IsPlayer() || CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer())
2566       ADD_MESSAGE("%s vomits at %s.", CHAR_DESCRIPTION(DEFINITE), Enemy->CHAR_DESCRIPTION(DEFINITE));
2567 
2568     Vomit(HitPos, 500 + RAND() % 500, false);
2569   }
2570   else
2571     nonhumanoid::Bite(Enemy, HitPos, Direction, ForceHit);
2572 }
2573 
SpecialBiteEffect(character * Char,v2,int,int,truth BlockedByArmour,truth Critical,int DoneDamage)2574 truth lobhse::SpecialBiteEffect(character* Char, v2, int, int, truth BlockedByArmour, truth Critical, int DoneDamage)
2575 {
2576   if(!BlockedByArmour || Critical)
2577   {
2578     int Effect = Char->StateIsActivated(DISEASE_IMMUNITY) ? 6 : RAND() % 10;
2579     switch(Effect)
2580     {
2581      case 0: Char->BeginTemporaryState(LYCANTHROPY, 6000 + RAND_N(2000)); break;
2582      case 1: Char->BeginTemporaryState(VAMPIRISM, 5000 + RAND_N(2500)); break;
2583      case 2: Char->BeginTemporaryState(PARASITE_TAPE_WORM, 6000 + RAND_N(3000)); break;
2584      case 3: Char->BeginTemporaryState(PARASITE_MIND_WORM, 400 + RAND_N(200)); break;
2585      case 4: Char->GainIntrinsic(LEPROSY); break;
2586      default: Char->BeginTemporaryState(POISONED, 80 + RAND() % 40); break;
2587     }
2588     return true;
2589   }
2590   else
2591     return false;
2592 }
2593 
Save(outputfile & SaveFile) const2594 void lobhse::Save(outputfile& SaveFile) const
2595 {
2596   nonhumanoid::Save(SaveFile);
2597   SaveFile << TurnsExisted;
2598 }
2599 
Load(inputfile & SaveFile)2600 void lobhse::Load(inputfile& SaveFile)
2601 {
2602   nonhumanoid::Load(SaveFile);
2603   SaveFile >> TurnsExisted;
2604 }
2605 
CreateCorpse(lsquare * Square)2606 void lobhse::CreateCorpse(lsquare* Square)
2607 {
2608   largecreature::CreateCorpse(Square);
2609   Square->AddItem(mangoseedling::Spawn());
2610   game::SetFreedomStoryState(2);
2611 }
2612 
GetAICommand()2613 void lobhse::GetAICommand()
2614 {
2615   ++TurnsExisted;
2616 
2617   /* Follow the leader, if any. */
2618   SeekLeader(GetLeader());
2619 
2620   if(FollowLeader(GetLeader()))
2621     return;
2622 
2623   /*
2624    Summon spiders
2625     Lobh-se will summon some spiders to harass the player, but only if she's
2626     hostile. As she can be tamed, we don't want to allow the player to amass
2627     a free spidery army. We can explain it away as her summoning being tied
2628     to SPIDER_LEVEL or something, if someone nags. ;)
2629    */
2630   if(!(RAND() % 60) && GetRelation(PLAYER) == HOSTILE && !GetPos().IsAdjacent(PLAYER->GetPos()))
2631   {
2632     int NumberOfSpiders = RAND() % 3 + RAND() % 3 + RAND() % 3 + RAND() % 3;
2633 
2634     for(int i = 0; i < NumberOfSpiders; i++)
2635     {
2636       lsquare* LSquare = PLAYER->GetNeighbourLSquare(RAND() % GetNeighbourSquares());
2637 
2638       if(LSquare && (LSquare->GetWalkability() & WALK) && !LSquare->GetCharacter())
2639       {
2640         character* NewSpider;
2641         long RandomValue = RAND() % TurnsExisted;
2642 
2643         if(RandomValue < 250)
2644           NewSpider = spider::Spawn(!RAND_N(5) ? LARGE : GIANT);
2645         else if(RandomValue < 1500)
2646           NewSpider = spider::Spawn(ARANEA);
2647         else
2648           NewSpider = spider::Spawn(PHASE);
2649 
2650         for(int c = 3; c < TurnsExisted / 500; ++c)
2651           NewSpider->EditAllAttributes(1);
2652 
2653         NewSpider->SetGenerationDanger(GetGenerationDanger());
2654         NewSpider->SetTeam(GetTeam());
2655         NewSpider->PutTo(LSquare->GetPos());
2656 
2657         if(NewSpider->CanBeSeenByPlayer())
2658           ADD_MESSAGE("%s descends from the darkness above.", NewSpider->CHAR_NAME(INDEFINITE));
2659       }
2660     }
2661 
2662     EditAP(-2000);
2663     return;
2664   }
2665 
2666   if(GetHP() > (GetMaxHP() / 2))
2667   {
2668     /* Boss fight, first phase: Spin webs and attack when player is entangled. */
2669     character* NearestChar = 0;
2670     long NearestDistance = 0x7FFFFFFF;
2671     v2 Pos = GetPos();
2672     int Hostiles = 0;
2673 
2674     for(int c = 0; c < game::GetTeams(); ++c)
2675       if(GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE)
2676         for(character* p : game::GetTeam(c)->GetMember())
2677           if(p->IsEnabled() && GetAttribute(WISDOM) < p->GetAttackWisdomLimit())
2678           {
2679             long ThisDistance = Max<long>(abs(p->GetPos().X - Pos.X), abs(p->GetPos().Y - Pos.Y));
2680             ++Hostiles;
2681 
2682             if((ThisDistance < NearestDistance
2683                 || (ThisDistance == NearestDistance && !(RAND() % 3)))
2684                && p->CanBeSeenBy(this, false, IsGoingSomeWhere())
2685                && (!IsGoingSomeWhere() || HasClearRouteTo(p->GetPos())))
2686             {
2687               NearestChar = p;
2688               NearestDistance = ThisDistance;
2689             }
2690           }
2691 
2692     if(Hostiles && !RAND_N(Max(80 / Hostiles, 8)))
2693     {
2694       web* Web = web::Spawn();
2695       Web->SetStrength(30);
2696 
2697       if(GetLSquareUnder()->AddTrap(Web))
2698       {
2699         if(CanBeSeenByPlayer())
2700           ADD_MESSAGE("%s spins a web.", CHAR_NAME(DEFINITE));
2701 
2702         EditAP(-1000);
2703         return;
2704       }
2705     }
2706 
2707     if(NearestChar)
2708     {
2709       if(NearestChar->IsStuck())
2710         SetGoingTo(NearestChar->GetPos());
2711       else
2712         SetGoingTo((Pos << 1) - NearestChar->GetPos());
2713 
2714       if(MoveTowardsTarget(true))
2715         return;
2716     }
2717 
2718     if(MoveRandomly())
2719       return;
2720 
2721     // Attack if trapped in a corner.
2722     if(AttackAdjacentEnemyAI())
2723       return;
2724 
2725     EditAP(-1000);
2726     return;
2727   }
2728   else
2729   {
2730     /* Boss fight, second phase: Attack all the time. */
2731     character::GetAICommand();
2732   }
2733 }
2734 
GetAICommand()2735 void mindworm::GetAICommand()
2736 {
2737   character* NeighbourEnemy = GetRandomNeighbour(HOSTILE);
2738   character* NearestEnemy = GetNearestEnemy();
2739 
2740   if(GetConfig() == BOIL && NeighbourEnemy)
2741   {
2742     if(NeighbourEnemy->HasHead() && !NeighbourEnemy->StateIsActivated(PARASITE_MIND_WORM))
2743     {
2744       if(TryToImplantLarvae(NeighbourEnemy))
2745         return;
2746     }
2747   }
2748   if(NearestEnemy && !NearestEnemy->IsESPBlockedByEquipment() && !StateIsActivated(CONFUSED) && !(RAND() & 2))
2749   {
2750     PsiAttack(NearestEnemy);
2751     return;
2752   }
2753   else
2754     nonhumanoid::GetAICommand();
2755 }
2756 
TryToImplantLarvae(character * Victim)2757 truth mindworm::TryToImplantLarvae(character* Victim)
2758 {
2759   if(Victim->MindWormCanPenetrateSkull(this) && Victim->CanBeParasitized())
2760   {
2761     if(Victim->IsPlayer())
2762     {
2763       ADD_MESSAGE("%s digs through your skull, lays %s eggs and jumps out.",
2764                   CHAR_NAME(DEFINITE), CHAR_POSSESSIVE_PRONOUN);
2765     }
2766     else if(Victim->CanBeSeenByPlayer())
2767     {
2768       ADD_MESSAGE("%s digs through %s's skull, lays %s eggs and jumps out.",
2769                   CHAR_NAME(DEFINITE), Victim->CHAR_NAME(DEFINITE), CHAR_POSSESSIVE_PRONOUN);
2770     }
2771 
2772     Victim->BeginTemporaryState(PARASITE_MIND_WORM, 400 + RAND_N(200));
2773 
2774     MoveRandomly();
2775     EditAP(-1000);
2776     return true;
2777   }
2778   else
2779     return false;
2780 }
2781 
PsiAttack(character * Victim)2782 void mindworm::PsiAttack(character* Victim)
2783 {
2784   if(Victim->IsPlayer())
2785   {
2786     ADD_MESSAGE("Your brain is on fire.");
2787   }
2788   else if(Victim->CanBeSeenByPlayer() && PLAYER->GetAttribute(PERCEPTION) > RAND_N(20))
2789   {
2790     ADD_MESSAGE("%s looks pained.", Victim->CHAR_NAME(DEFINITE));
2791   }
2792 
2793   Victim->ReceiveDamage(this, 1, PSI, HEAD, YOURSELF, true);
2794   Victim->CheckDeath(CONST_S("killed by ") + GetName(INDEFINITE) + "'s psi attack", this);
2795   EditAP(-2000);
2796   EditStamina(GetAdjustedStaminaCost(-1000, GetAttribute(INTELLIGENCE)), false);
2797 }
2798 
GetAICommand()2799 void bat::GetAICommand()
2800 {
2801   if(!IsRetreating() && PLAYER->WillGetTurnSoon() &&
2802      GetPos().IsAdjacent(PLAYER->GetPos()))
2803   {
2804     // Bats sometimes move away from the player.
2805     SetGoingTo((GetPos() << 1) - PLAYER->GetPos());
2806 
2807     if(MoveTowardsTarget(true))
2808       return;
2809   }
2810 
2811   nonhumanoid::GetAICommand();
2812 }
2813 
GetAICommand()2814 void invisiblestalker::GetAICommand()
2815 {
2816   if(GetPos().IsAdjacent(PLAYER->GetPos()))
2817   {
2818     if(CanBeSeenByPlayer() &&
2819        (GetHP() < (GetMaxHP() >> 1) || IsRetreating())
2820      )
2821     {
2822       ADD_MESSAGE("%s notices you looking and disappears.", CHAR_NAME(DEFINITE));
2823 
2824       TeleportRandomly(true);
2825       EditAP(-1000);
2826       return;
2827     }
2828     else if(!IsRetreating() && PLAYER->WillGetTurnSoon())
2829     {
2830       SetGoingTo((GetPos() << 1) - PLAYER->GetPos());
2831 
2832       if(MoveTowardsTarget(true))
2833         return;
2834     }
2835   }
2836 
2837   nonhumanoid::GetAICommand();
2838 }
2839 
IsRetreating() const2840 truth fruitbat::IsRetreating() const
2841 {
2842   if(nonhumanoid::IsRetreating())
2843     return true;
2844 
2845   for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
2846     if((*i)->IsFood())
2847       return true;
2848 
2849   return false;
2850 }
2851 
GetAICommand()2852 void fruitbat::GetAICommand()
2853 {
2854   if(!IsRetreating())
2855   {
2856     character* Char = GetRandomNeighbour();
2857 
2858     if(Char)
2859     {
2860       itemvector Fruits;
2861 
2862       for(stackiterator i = Char->GetStack()->GetBottom(); i.HasItem(); ++i)
2863       {
2864         if((*i)->IsFood() && !MakesBurdened((*i)->GetWeight()))
2865           Fruits.push_back(*i);
2866       }
2867 
2868       if(!Fruits.empty())
2869       {
2870         item* ToSteal = Fruits[RAND() % Fruits.size()];
2871         ToSteal->RemoveFromSlot();
2872         GetStack()->AddItem(ToSteal);
2873 
2874         if(Char->IsPlayer())
2875           ADD_MESSAGE("%s steals your %s.", CHAR_NAME(DEFINITE), ToSteal->CHAR_NAME(UNARTICLED));
2876 
2877         EditAP(-500);
2878         return;
2879       }
2880     }
2881   }
2882 
2883   bat::GetAICommand();
2884 }
2885 
GetAICommand()2886 void fusanga::GetAICommand()
2887 {
2888   if(AttackAdjacentEnemyAI())
2889     return;
2890 
2891   /* Chaos magic */
2892   lsquare* Square = GetLevel()->GetLSquare(GetLevel()->GetRandomSquare(0, HAS_NO_OTERRAIN));
2893 
2894   if(Square && !RAND_N(20))
2895   {
2896     if(CanBeSeenByPlayer())
2897       ADD_MESSAGE("%s radiates pure magic.", CHAR_NAME(DEFINITE));
2898 
2899     switch (RAND_4)
2900     {
2901       case 0: // Random spell
2902       {
2903         int BeamEffect = RAND_N(17); // Change if more beams are added.
2904         beamdata Beam
2905           (
2906             this,
2907             CONST_S("killed by the sorcery of ") + GetName(DEFINITE),
2908             GetPos(),
2909             RANDOM_COLOR,
2910             BeamEffect,
2911             YOURSELF,
2912             1,
2913             0,
2914             NULL
2915           );
2916         (Square->*lsquare::GetBeamEffect(BeamEffect))(Beam);
2917         break;
2918       }
2919       case 1: // Create gas
2920       {
2921         // Change if more gases are added.
2922         if(!RAND_2)
2923           GetLevel()->GasExplosion(gas::Spawn(GAS_ID + RAND_N(14) + 3, 100), Square, this);
2924         else
2925           Square->AddSmoke(gas::Spawn(GAS_ID + RAND_N(14) + 3, 100));
2926 
2927         ADD_MESSAGE("You hear the hiss of gas.");
2928         break;
2929       }
2930       default: // Create rain
2931       {
2932         beamdata Beam
2933           (
2934             this,
2935             CONST_S("killed by the showers of ") + GetName(DEFINITE),
2936             YOURSELF,
2937             0
2938           );
2939         Square->LiquidRain(Beam, LIQUID_ID + RAND_N(60) + 1); // Change if more liquids are added.
2940 
2941         if(Square->CanBeSeenByPlayer())
2942           ADD_MESSAGE("A drizzle comes down.");
2943         else
2944           ADD_MESSAGE("You hear the sounds of rainfall.");
2945 
2946         break;
2947       }
2948     }
2949 
2950     EditAP(-4000);
2951     return;
2952   }
2953 
2954   /* Spawn mushrooms */
2955   if(!RAND_N(40))
2956   {
2957     int NumberOfMushrooms = RAND() % 3 + RAND() % 3 + RAND() % 3 + RAND() % 3;
2958 
2959     if(CanBeSeenByPlayer())
2960       ADD_MESSAGE("%s radiates strange magic.", CHAR_NAME(DEFINITE));
2961 
2962     for(int i = 0; i < NumberOfMushrooms; i++)
2963     {
2964       character* NewShroom;
2965 
2966       switch (RAND_4)
2967       {
2968         case 2: NewShroom = magicmushroom::Spawn(); break;
2969         //case 3: NewShroom = weepmushroom::Spawn(); break;
2970         default: NewShroom = mushroom::Spawn(); break;
2971       }
2972 
2973       NewShroom->SetGenerationDanger(GetGenerationDanger());
2974       NewShroom->SetTeam(GetTeam());
2975       NewShroom->PutTo(GetLevel()->GetRandomSquare(NewShroom));
2976 
2977       if(NewShroom->CanBeSeenByPlayer())
2978         ADD_MESSAGE("%s sprouts from the ground.", NewShroom->CHAR_NAME(INDEFINITE));
2979     }
2980 
2981     EditAP(-2000);
2982     return;
2983   }
2984 
2985   /* Just chill there. */
2986   EditAP(-1000);
2987 }
2988 
CreateCorpse(lsquare * Square)2989 void fusanga::CreateCorpse(lsquare* Square)
2990 {
2991   largecreature::CreateCorpse(Square);
2992 }
2993 
MustBeRemovedFromBone() const2994 truth fusanga::MustBeRemovedFromBone() const
2995 {
2996   return !IsEnabled() || GetTeam()->GetID() != MONSTER_TEAM
2997                       || GetDungeon()->GetIndex() != FUNGAL_CAVE
2998                       || GetLevel()->GetIndex() != FUSANGA_LEVEL;
2999 }
3000