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