1 /* Copyright (C) 2013-2014 Michal Brzozowski (rusolis@poczta.fm)
2 
3    This file is part of KeeperRL.
4 
5    KeeperRL is free software; you can redistribute it and/or modify it under the terms of the
6    GNU General Public License as published by the Free Software Foundation; either version 2
7    of the License, or (at your option) any later version.
8 
9    KeeperRL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
10    even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11    GNU General Public License for more details.
12 
13    You should have received a copy of the GNU General Public License along with this program.
14    If not, see http://www.gnu.org/licenses/ . */
15 
16 #include "stdafx.h"
17 
18 #include "creature.h"
19 #include "creature_factory.h"
20 #include "level.h"
21 #include "ranged_weapon.h"
22 #include "statistics.h"
23 #include "options.h"
24 #include "game.h"
25 #include "effect.h"
26 #include "item_factory.h"
27 #include "controller.h"
28 #include "player_message.h"
29 #include "attack.h"
30 #include "vision.h"
31 #include "equipment.h"
32 #include "shortest_path.h"
33 #include "spell_map.h"
34 #include "minion_task_map.h"
35 #include "tribe.h"
36 #include "creature_attributes.h"
37 #include "position.h"
38 #include "view.h"
39 #include "sound.h"
40 #include "lasting_effect.h"
41 #include "attack_type.h"
42 #include "attack_level.h"
43 #include "model.h"
44 #include "view_object.h"
45 #include "spell.h"
46 #include "body.h"
47 #include "field_of_view.h"
48 #include "furniture.h"
49 #include "creature_debt.h"
50 #include "message_generator.h"
51 
52 template <class Archive>
serialize(Archive & ar,const unsigned int version)53 void Creature::serialize(Archive& ar, const unsigned int version) {
54   ar & SUBCLASS(OwnedObject<Creature>) & SUBCLASS(Renderable) & SUBCLASS(UniqueEntity);
55   ar(attributes, position, equipment, shortestPath, knownHiding, tribe, morale);
56   ar(deathTime, hidden);
57   ar(deathReason, swapPositionCooldown);
58   ar(unknownAttackers, privateEnemies, holding);
59   ar(controllerStack, kills);
60   ar(difficultyPoints, points);
61   ar(vision, lastCombatTime, debt, lastDamageType, highestAttackValueEver);
62 }
63 
64 SERIALIZABLE(Creature)
65 
SERIALIZATION_CONSTRUCTOR_IMPL(Creature)66 SERIALIZATION_CONSTRUCTOR_IMPL(Creature)
67 
68 Creature::Creature(const ViewObject& object, TribeId t, const CreatureAttributes& attr)
69     : Renderable(object), attributes(attr), tribe(t) {
70   modViewObject().setCreatureId(getUniqueId());
71 }
72 
Creature(TribeId t,const CreatureAttributes & attr)73 Creature::Creature(TribeId t, const CreatureAttributes& attr)
74     : Creature(attr.createViewObject(), t, attr) {
75 }
76 
~Creature()77 Creature::~Creature() {
78 }
79 
stack(const vector<WCreature> & creatures)80 vector<vector<WCreature>> Creature::stack(const vector<WCreature>& creatures) {
81   map<string, vector<WCreature>> stacks;
82   for (WCreature c : creatures)
83     stacks[c->getName().stack()].push_back(c);
84   return getValues(stacks);
85 }
86 
getViewObjectFor(const Tribe * observer) const87 const ViewObject& Creature::getViewObjectFor(const Tribe* observer) const {
88   if (attributes->getIllusionViewObject() && observer->isEnemy(this))
89     return *attributes->getIllusionViewObject();
90   else
91     return getViewObject();
92 }
93 
getBody() const94 const Body& Creature::getBody() const {
95   return attributes->getBody();
96 }
97 
getBody()98 Body& Creature::getBody() {
99   return attributes->getBody();
100 }
101 
getSpellDelay(Spell * spell) const102 double Creature::getSpellDelay(Spell* spell) const {
103   CHECK(!isReady(spell));
104   return attributes->getSpellMap().getReadyTime(spell) - getGlobalTime();
105 }
106 
isReady(Spell * spell) const107 bool Creature::isReady(Spell* spell) const {
108   return attributes->getSpellMap().getReadyTime(spell) < getGlobalTime();
109 }
110 
getWillpowerMult(double sorcerySkill)111 static double getWillpowerMult(double sorcerySkill) {
112   return 2 * pow(0.25, sorcerySkill);
113 }
114 
getAttributes() const115 const CreatureAttributes& Creature::getAttributes() const {
116   return *attributes;
117 }
118 
getAttributes()119 CreatureAttributes& Creature::getAttributes() {
120   return *attributes;
121 }
122 
castSpell(Spell * spell) const123 CreatureAction Creature::castSpell(Spell* spell) const {
124   if (!attributes->getSpellMap().contains(spell))
125     return CreatureAction("You don't know this spell.");
126   CHECK(!spell->isDirected());
127   if (!isReady(spell))
128     return CreatureAction("You can't cast this spell yet.");
129   return CreatureAction(this, [=] (WCreature c) {
130     c->addSound(spell->getSound());
131     spell->addMessage(c);
132     spell->getEffect().applyToCreature(c);
133     getGame()->getStatistics().add(StatId::SPELL_CAST);
134     c->attributes->getSpellMap().setReadyTime(spell, getGlobalTime() + spell->getDifficulty()
135         * getWillpowerMult(attributes->getSkills().getValue(SkillId::SORCERY)));
136     c->spendTime(1);
137   });
138 }
139 
castSpell(Spell * spell,Vec2 dir) const140 CreatureAction Creature::castSpell(Spell* spell, Vec2 dir) const {
141   CHECK(attributes->getSpellMap().contains(spell));
142   CHECK(spell->isDirected());
143   CHECK(dir.length8() == 1);
144   if (!isReady(spell))
145     return CreatureAction("You can't cast this spell yet.");
146   return CreatureAction(this, [=] (WCreature c) {
147     c->addSound(spell->getSound());
148     thirdPerson(getName().the() + " casts a spell");
149     secondPerson("You cast " + spell->getName());
150     applyDirected(c, dir, spell->getDirEffectType());
151     getGame()->getStatistics().add(StatId::SPELL_CAST);
152     c->attributes->getSpellMap().setReadyTime(spell, getGlobalTime() + spell->getDifficulty()
153         * getWillpowerMult(attributes->getSkills().getValue(SkillId::SORCERY)));
154     c->spendTime(1);
155   });
156 }
157 
pushController(PController ctrl)158 void Creature::pushController(PController ctrl) {
159   if (auto controller = getController())
160     controller->onEndedControl();
161   controllerStack.push_back(std::move(ctrl));
162   getController()->onStartedControl();
163 }
164 
setController(PController ctrl)165 void Creature::setController(PController ctrl) {
166   if (auto controller = getController())
167     controller->onEndedControl();
168   controllerStack.clear();
169   pushController(std::move(ctrl));
170   getController()->onStartedControl();
171 }
172 
popController()173 void Creature::popController() {
174   if (!controllerStack.empty()) {
175     getController()->onEndedControl();
176     controllerStack.pop_back();
177     if (auto controller = getController())
178       controller->onStartedControl();
179   }
180 }
181 
isDead() const182 bool Creature::isDead() const {
183   return !!deathTime;
184 }
185 
getDeathTime() const186 double Creature::getDeathTime() const {
187   return *deathTime;
188 }
189 
clearLastAttacker()190 void Creature::clearLastAttacker() {
191   lastAttacker = nullptr;
192 }
193 
getDeathReason() const194 optional<string> Creature::getDeathReason() const {
195   if (deathReason)
196     return deathReason;
197   if (lastAttacker)
198     return "killed by " + lastAttacker->getName().a();
199   return none;
200 }
201 
getKills() const202 const EntitySet<Creature>& Creature::getKills() const {
203   return kills;
204 }
205 
spendTime(double t)206 void Creature::spendTime(double t) {
207   if (!isDead())
208     if (WModel m = position.getModel())
209       m->increaseLocalTime(this, 100.0 * t / (double) getAttr(AttrType::SPEED));
210   hidden = false;
211 }
212 
forceMove(Vec2 dir) const213 CreatureAction Creature::forceMove(Vec2 dir) const {
214   return forceMove(getPosition().plus(dir));
215 }
216 
forceMove(Position pos) const217 CreatureAction Creature::forceMove(Position pos) const {
218   const_cast<Creature*>(this)->forceMovement = true;
219   CreatureAction action = move(pos);
220   const_cast<Creature*>(this)->forceMovement = false;
221   if (action)
222     return action.prepend([] (WCreature c) { c->forceMovement = true; })
223       .append([] (WCreature c) { c->forceMovement = false; });
224   else
225     return action;
226 }
227 
move(Vec2 dir) const228 CreatureAction Creature::move(Vec2 dir) const {
229   return move(getPosition().plus(dir));
230 }
231 
move(Position pos) const232 CreatureAction Creature::move(Position pos) const {
233   Vec2 direction = getPosition().getDir(pos);
234   if (getHoldingCreature())
235     return CreatureAction("You can't break free!");
236   if (direction.length8() != 1)
237     return CreatureAction();
238   if (!position.canMoveCreature(direction)) {
239     if (pos.getCreature()) {
240       if (!canSwapPositionInMovement(pos.getCreature()))
241         return CreatureAction(/*"You can't swap position with " + pos.getCreature()->getName().the()*/);
242     } else
243       return CreatureAction();
244   }
245   return CreatureAction(this, [=](WCreature self) {
246     INFO << getName().the() << " moving " << direction;
247     if (isAffected(LastingEffect::ENTANGLED) || isAffected(LastingEffect::TIED_UP)) {
248       secondPerson("You can't break free!");
249       thirdPerson(getName().the() + " can't break free!");
250       self->spendTime(1);
251       return;
252     }
253     if (position.canMoveCreature(direction))
254       self->position.moveCreature(direction);
255     else {
256       self->swapPosition(direction);
257       return;
258     }
259     double oldTime = getLocalTime();
260     if (isAffected(LastingEffect::COLLAPSED)) {
261       you(MsgType::CRAWL, getPosition().getName());
262       self->spendTime(3);
263     } else
264       self->spendTime(1);
265     self->addMovementInfo({direction, oldTime, getLocalTime(), MovementInfo::MOVE});
266   });
267 }
268 
displace(double time,Vec2 dir)269 void Creature::displace(double time, Vec2 dir) {
270   position.moveCreature(dir);
271   addMovementInfo({dir, time, time + 1, MovementInfo::MOVE});
272 }
273 
canTakeItems(const vector<WItem> & items) const274 bool Creature::canTakeItems(const vector<WItem>& items) const {
275   return getBody().isHumanoid() && canCarry(items);
276 }
277 
takeItems(vector<PItem> items,WCreature from)278 void Creature::takeItems(vector<PItem> items, WCreature from) {
279   vector<WItem> ref = getWeakPointers(items);
280   equipment->addItems(std::move(items));
281   getController()->onItemsGiven(ref, from);
282 }
283 
you(MsgType type,const vector<string> & param) const284 void Creature::you(MsgType type, const vector<string>& param) const {
285   getController()->getMessageGenerator().add(this, type, param);
286 }
287 
you(MsgType type,const string & param) const288 void Creature::you(MsgType type, const string& param) const {
289   getController()->getMessageGenerator().add(this, type, param);
290 }
291 
you(const string & param) const292 void Creature::you(const string& param) const {
293   getController()->getMessageGenerator().add(this, param);
294 }
295 
secondPerson(const PlayerMessage & message) const296 void Creature::secondPerson(const PlayerMessage& message) const {
297   getController()->getMessageGenerator().addSecondPerson(this, message);
298 }
299 
getController() const300 WController Creature::getController() const {
301   if (!controllerStack.empty())
302     return controllerStack.back().get();
303   else
304     return nullptr;
305 }
306 
hasCondition(CreatureCondition condition) const307 bool Creature::hasCondition(CreatureCondition condition) const {
308   for (auto effect : LastingEffects::getCausingCondition(condition))
309     if (isAffected(effect))
310       return true;
311   return false;
312 }
313 
canSwapPositionInMovement(WCreature other) const314 bool Creature::canSwapPositionInMovement(WCreature other) const {
315   return !other->hasCondition(CreatureCondition::RESTRICTED_MOVEMENT)
316       && (swapPositionCooldown == 0 || isPlayer())
317       && !other->getAttributes().isBoulder()
318       && (!other->isPlayer() || isPlayer())
319       && !other->isEnemy(this)
320       && other->getPosition().canEnterEmpty(this)
321       && getPosition().canEnterEmpty(other);
322 }
323 
swapPosition(Vec2 direction)324 void Creature::swapPosition(Vec2 direction) {
325   CHECK(direction.length8() == 1);
326   WCreature other = NOTNULL(getPosition().plus(direction).getCreature());
327   swapPositionCooldown = 4;
328   privateMessage("Excuse me!");
329   other->privateMessage("Excuse me!");
330   position.swapCreatures(other);
331   double oldTime = getLocalTime();
332   spendTime(1);
333   addMovementInfo({direction, oldTime, getLocalTime(), MovementInfo::MOVE});
334   other->addMovementInfo({-direction, oldTime, getLocalTime(), MovementInfo::MOVE});
335 }
336 
makeMove()337 void Creature::makeMove() {
338   vision->update(this);
339   CHECK(!isDead());
340   if (hasCondition(CreatureCondition::SLEEPING)) {
341     getController()->sleeping();
342     spendTime(1);
343     return;
344   }
345   updateVisibleCreatures();
346   updateViewObject();
347   if (swapPositionCooldown)
348     --swapPositionCooldown;
349   {
350     // Calls makeMove() while preventing Controller destruction by holding a shared_ptr on stack.
351     // This is needed, otherwise Controller could be destroyed during makeMove() if creature committed suicide.
352     shared_ptr<Controller> controllerTmp = controllerStack.back().giveMeSharedPointer();
353     MEASURE(controllerTmp->makeMove(), "creature move time");
354   }
355 
356   INFO << getName().bare() << " morale " << getMorale();
357   if (!hidden)
358     modViewObject().removeModifier(ViewObject::Modifier::HIDDEN);
359   unknownAttackers.clear();
360   getBody().affectPosition(position);
361   highestAttackValueEver = max(highestAttackValueEver, getBestAttack().value);
362   vision->update(this);
363 }
364 
wait() const365 CreatureAction Creature::wait() const {
366   return CreatureAction(this, [=](WCreature self) {
367     INFO << getName().the() << " waiting";
368     bool keepHiding = hidden;
369     self->spendTime(1);
370     self->hidden = keepHiding;
371   });
372 }
373 
getEquipment() const374 const Equipment& Creature::getEquipment() const {
375   return *equipment;
376 }
377 
getEquipment()378 Equipment& Creature::getEquipment() {
379   return *equipment;
380 }
381 
steal(const vector<WItem> items)382 vector<PItem> Creature::steal(const vector<WItem> items) {
383   return equipment->removeItems(items, this);
384 }
385 
getLevel() const386 WLevel Creature::getLevel() const {
387   return getPosition().getLevel();
388 }
389 
getGame() const390 WGame Creature::getGame() const {
391   return getPosition().getGame();
392 }
393 
getPosition() const394 Position Creature::getPosition() const {
395   return position;
396 }
397 
message(const PlayerMessage & msg) const398 void Creature::message(const PlayerMessage& msg) const {
399   if (isPlayer())
400     getController()->privateMessage(msg);
401   else
402     getPosition().globalMessage(msg);
403 }
404 
privateMessage(const PlayerMessage & msg) const405 void Creature::privateMessage(const PlayerMessage& msg) const {
406   getController()->privateMessage(msg);
407 }
408 
thirdPerson(const PlayerMessage & playerCanSee) const409 void Creature::thirdPerson(const PlayerMessage& playerCanSee) const {
410   getController()->getMessageGenerator().addThirdPerson(this, playerCanSee);
411 }
412 
addSkill(Skill * skill)413 void Creature::addSkill(Skill* skill) {
414   if (!attributes->getSkills().hasDiscrete(skill->getId())) {
415     attributes->getSkills().insert(skill->getId());
416     privateMessage(skill->getHelpText());
417   }
418 }
419 
getPickUpOptions() const420 vector<WItem> Creature::getPickUpOptions() const {
421   if (!getBody().isHumanoid())
422     return vector<WItem>();
423   else
424     return getPosition().getItems();
425 }
426 
getPluralTheName(WItem item,int num) const427 string Creature::getPluralTheName(WItem item, int num) const {
428   if (num == 1)
429     return item->getTheName(false, this);
430   else
431     return toString(num) + " " + item->getTheName(true, this);
432 }
433 
getPluralAName(WItem item,int num) const434 string Creature::getPluralAName(WItem item, int num) const {
435   if (num == 1)
436     return item->getAName(false, this);
437   else
438     return toString(num) + " " + item->getAName(true, this);
439 }
440 
canCarry(const vector<WItem> & items) const441 bool Creature::canCarry(const vector<WItem>& items) const {
442   if (auto& limit = getBody().getCarryLimit()) {
443     double weight = equipment->getTotalWeight();
444     for (WItem it : items)
445       weight += it->getWeight();
446     return weight <= *limit;
447   } else
448     return true;
449 }
450 
pickUp(const vector<WItem> & items) const451 CreatureAction Creature::pickUp(const vector<WItem>& items) const {
452   if (!getBody().isHumanoid())
453     return CreatureAction("You can't pick up anything!");
454   if (!canCarry(items))
455     return CreatureAction("You are carrying too much to pick this up.");
456   return CreatureAction(this, [=](WCreature self) {
457     INFO << getName().the() << " pickup ";
458     for (auto stack : stackItems(items)) {
459       thirdPerson(getName().the() + " picks up " + getPluralAName(stack[0], stack.size()));
460       secondPerson("You pick up " + getPluralTheName(stack[0], stack.size()));
461     }
462     self->equipment->addItems(self->getPosition().removeItems(items));
463     if (auto& limit = getBody().getCarryLimit())
464       if (equipment->getTotalWeight() > *limit / 2)
465         you(MsgType::ARE, "overloaded");
466     getGame()->addEvent(EventInfo::ItemsPickedUp{self, items});
467     self->spendTime(1);
468   });
469 }
470 
stackItems(vector<WItem> items) const471 vector<vector<WItem>> Creature::stackItems(vector<WItem> items) const {
472   map<string, vector<WItem> > stacks = groupBy<WItem, string>(items,
473       [this] (WItem const& item) { return item->getNameAndModifiers(false, this); });
474   return getValues(stacks);
475 }
476 
drop(const vector<WItem> & items) const477 CreatureAction Creature::drop(const vector<WItem>& items) const {
478   if (!getBody().isHumanoid())
479     return CreatureAction("You can't drop this item!");
480   return CreatureAction(this, [=](WCreature self) {
481     INFO << getName().the() << " drop";
482     for (auto stack : stackItems(items)) {
483       thirdPerson(getName().the() + " drops " + getPluralAName(stack[0], stack.size()));
484       secondPerson("You drop " + getPluralTheName(stack[0], stack.size()));
485     }
486     getGame()->addEvent(EventInfo::ItemsDropped{self, items});
487     self->getPosition().dropItems(self->equipment->removeItems(items, self));
488     self->spendTime(1);
489   });
490 }
491 
drop(vector<PItem> items)492 void Creature::drop(vector<PItem> items) {
493   getPosition().dropItems(std::move(items));
494 }
495 
canEquipIfEmptySlot(WConstItem item,string * reason) const496 bool Creature::canEquipIfEmptySlot(WConstItem item, string* reason) const {
497   if (!getBody().isHumanoid()) {
498     if (reason)
499       *reason = "Only humanoids can equip items!";
500     return false;
501   }
502   if (!attributes->canEquip()) {
503     if (reason)
504       *reason = "You can't equip items!";
505     return false;
506   }
507   if (getBody().numGood(BodyPart::ARM) == 0) {
508     if (reason)
509       *reason = "You have no healthy arms!";
510     return false;
511   }
512   if (getBody().numGood(BodyPart::ARM) == 1 && item->isWieldedTwoHanded()) {
513     if (reason)
514       *reason = "You need two hands to wield " + item->getAName() + "!";
515     return false;
516   }
517   return item->canEquip();
518 }
519 
canEquip(WConstItem item) const520 bool Creature::canEquip(WConstItem item) const {
521   return canEquipIfEmptySlot(item, nullptr) && equipment->canEquip(item);
522 }
523 
equip(WItem item) const524 CreatureAction Creature::equip(WItem item) const {
525   string reason;
526   if (!canEquipIfEmptySlot(item, &reason))
527     return CreatureAction(reason);
528   if (equipment->getSlotItems(item->getEquipmentSlot()).contains(item))
529     return CreatureAction();
530   return CreatureAction(this, [=](WCreature self) {
531     INFO << getName().the() << " equip " << item->getName();
532     EquipmentSlot slot = item->getEquipmentSlot();
533     if (self->equipment->getSlotItems(slot).size() >= self->equipment->getMaxItems(slot)) {
534       WItem previousItem = self->equipment->getSlotItems(slot)[0];
535       self->equipment->unequip(previousItem, self);
536     }
537     secondPerson("You equip " + item->getTheName(false, self));
538     thirdPerson(getName().the() + " equips " + item->getAName());
539     self->equipment->equip(item, slot, self);
540     if (WGame game = getGame())
541       game->addEvent(EventInfo::ItemsEquipped{self, {item}});
542     self->spendTime(1);
543   });
544 }
545 
unequip(WItem item) const546 CreatureAction Creature::unequip(WItem item) const {
547   if (!equipment->isEquipped(item))
548     return CreatureAction("This item is not equipped.");
549   if (!getBody().isHumanoid())
550     return CreatureAction("You can't remove this item!");
551   if (getBody().numGood(BodyPart::ARM) == 0)
552     return CreatureAction("You have no healthy arms!");
553   return CreatureAction(this, [=](WCreature self) {
554     INFO << getName().the() << " unequip";
555     CHECK(equipment->isEquipped(item)) << "Item not equipped.";
556     EquipmentSlot slot = item->getEquipmentSlot();
557     secondPerson("You " + string(slot == EquipmentSlot::WEAPON ? " sheathe " : " remove ") +
558         item->getTheName(false, this));
559     thirdPerson(getName().the() + (slot == EquipmentSlot::WEAPON ? " sheathes " : " removes ") +
560         item->getAName());
561     self->equipment->unequip(item, self);
562     self->spendTime(1);
563   });
564 }
565 
bumpInto(Vec2 direction) const566 CreatureAction Creature::bumpInto(Vec2 direction) const {
567   if (WConstCreature other = getPosition().plus(direction).getCreature())
568     return CreatureAction(this, [=](WCreature self) {
569       other->getController()->onBump(self);
570     });
571   else
572     return CreatureAction();
573 }
574 
applySquare(Position pos) const575 CreatureAction Creature::applySquare(Position pos) const {
576   CHECK(pos.dist8(getPosition()) <= 1);
577   if (auto furniture = pos.getFurniture(FurnitureLayer::MIDDLE))
578     if (furniture->canUse(this))
579       return CreatureAction(this, [=](WCreature self) {
580         INFO << getName().the() << " applying " << getPosition().getName();
581         auto originalPos = getPosition();
582         double usageTime = furniture->getUsageTime();
583         furniture->use(pos, self);
584         double oldTime = getLocalTime();
585         self->spendTime(usageTime);
586         if (pos != getPosition() && getPosition() == originalPos)
587           self->addMovementInfo({getPosition().getDir(pos), oldTime, min(oldTime + 1, getLocalTime()),
588               MovementInfo::ATTACK});
589       });
590   return CreatureAction();
591 }
592 
hide() const593 CreatureAction Creature::hide() const {
594   if (!attributes->getSkills().hasDiscrete(SkillId::AMBUSH))
595     return CreatureAction("You don't have this skill.");
596   if (auto furniture = getPosition().getFurniture(FurnitureLayer::MIDDLE))
597     if (furniture->canHide())
598       return CreatureAction(this, [=](WCreature self) {
599         secondPerson("You hide behind the " + furniture->getName());
600         thirdPerson(getName().the() + " hides behind the " + furniture->getName());
601         self->knownHiding.clear();
602         self->modViewObject().setModifier(ViewObject::Modifier::HIDDEN);
603         for (WCreature other : getLevel()->getAllCreatures())
604           if (other->canSee(this) && other->isEnemy(this)) {
605             self->knownHiding.insert(other);
606             if (!isAffected(LastingEffect::BLIND))
607               you(MsgType::CAN_SEE_HIDING, other->getName().the());
608           }
609         self->spendTime(1);
610         self->hidden = true;
611       });
612   return CreatureAction("You can't hide here.");
613 }
614 
chatTo(WCreature other) const615 CreatureAction Creature::chatTo(WCreature other) const {
616   CHECK(other);
617   if (other->getPosition().dist8(getPosition()) == 1)
618     return CreatureAction(this, [=](WCreature self) {
619         secondPerson("You chat with " + other->getName().the());
620         thirdPerson(getName().the() + " chats with " + other->getName().the());
621         other->getAttributes().chatReaction(other, self);
622         self->spendTime(1);
623     });
624   else
625     return CreatureAction("Move closer to chat to " + other->getName().the());
626 }
627 
stealFrom(Vec2 direction,const vector<WItem> & items) const628 CreatureAction Creature::stealFrom(Vec2 direction, const vector<WItem>& items) const {
629   if (getPosition().plus(direction).getCreature())
630     return CreatureAction(this, [=](WCreature self) {
631         WCreature other = NOTNULL(getPosition().plus(direction).getCreature());
632         self->equipment->addItems(other->steal(items));
633       });
634   return CreatureAction();
635 }
636 
isHidden() const637 bool Creature::isHidden() const {
638   return hidden;
639 }
640 
knowsHiding(WConstCreature c) const641 bool Creature::knowsHiding(WConstCreature c) const {
642   return knownHiding.contains(c);
643 }
644 
addEffect(LastingEffect effect,double time,bool msg)645 void Creature::addEffect(LastingEffect effect, double time, bool msg) {
646   if (LastingEffects::affects(this, effect) && !getBody().isImmuneTo(effect)) {
647     bool was = isAffected(effect);
648     attributes->addLastingEffect(effect, getGlobalTime() + time);
649     if (!was && isAffected(effect))
650       LastingEffects::onAffected(this, effect, msg);
651   }
652 }
653 
removeEffect(LastingEffect effect,bool msg)654 void Creature::removeEffect(LastingEffect effect, bool msg) {
655   bool was = isAffected(effect);
656   attributes->clearLastingEffect(effect, getGlobalTime());
657   if (was && !isAffected(effect))
658     LastingEffects::onRemoved(this, effect, msg);
659 }
660 
addPermanentEffect(LastingEffect effect,int count)661 void Creature::addPermanentEffect(LastingEffect effect, int count) {
662   bool was = isAffected(effect);
663   attributes->addPermanentEffect(effect, count);
664   if (!was && isAffected(effect))
665     LastingEffects::onAffected(this, effect, true);
666 }
667 
removePermanentEffect(LastingEffect effect,int count)668 void Creature::removePermanentEffect(LastingEffect effect, int count) {
669   bool was = isAffected(effect);
670   attributes->removePermanentEffect(effect, count);
671   if (was && !isAffected(effect))
672     LastingEffects::onRemoved(this, effect, true);
673 }
674 
isAffected(LastingEffect effect) const675 bool Creature::isAffected(LastingEffect effect) const {
676   return attributes->isAffected(effect, getGlobalTime());
677 }
678 
getLastAffected(LastingEffect effect) const679 optional<double> Creature::getLastAffected(LastingEffect effect) const {
680   return attributes->getLastAffected(effect, getGlobalTime());
681 }
682 
getTimeRemaining(LastingEffect effect) const683 optional<double> Creature::getTimeRemaining(LastingEffect effect) const {
684   double t = attributes->getTimeOut(effect);
685   double global = getGlobalTime();
686   if (t >= global)
687     return t - global;
688   else
689     return none;
690 }
691 
isDarknessSource() const692 bool Creature::isDarknessSource() const {
693   return isAffected(LastingEffect::DARKNESS_SOURCE);
694 }
695 
696 // penalty to strength and dexterity per extra attacker in a single turn
simulAttackPen(int attackers)697 int simulAttackPen(int attackers) {
698   return max(0, (attackers - 1) * 2);
699 }
700 
getAttr(AttrType type) const701 int Creature::getAttr(AttrType type) const {
702   double def = getBody().modifyAttr(type, attributes->getRawAttr(type));
703   for (WItem item : equipment->getAllEquipped())
704     def += item->getModifier(type);
705   switch (type) {
706     case AttrType::SPEED: {
707       if (auto inc = getAttributes().getMoraleSpeedIncrease())
708         def *= pow(*inc, getMorale());
709       double totWeight = equipment->getTotalWeight();
710       // penalty is 0 till limit/2, then grows linearly to 0.3 at limit, then stays constant
711       if (auto& limit = getBody().getCarryLimit())
712         def -= 0.3 * min(1.0, max(0.0, -1.0 + 2.0 * totWeight / *limit));
713       CHECK(def > 0);
714       break;
715     }
716     default:
717       break;
718   }
719   LastingEffects::modifyAttr(this, type, def);
720   return max(0, (int) def);
721 }
722 
getPoints() const723 int Creature::getPoints() const {
724   return points;
725 }
726 
onKilled(WCreature victim,optional<ExperienceType> lastDamage)727 void Creature::onKilled(WCreature victim, optional<ExperienceType> lastDamage) {
728   double attackDiff = victim->highestAttackValueEver - highestAttackValueEver;
729   constexpr double maxLevelGain = 1.0;
730   constexpr double minLevelGain = 0.02;
731   constexpr double equalLevelGain = 0.2;
732   constexpr double maxLevelDiff = 10;
733   double expIncrease = max(minLevelGain, min(maxLevelGain,
734       (maxLevelGain - equalLevelGain) * attackDiff / maxLevelDiff + equalLevelGain));
735   increaseExpLevel(lastDamage.value_or(ExperienceType::MELEE), expIncrease);
736   int difficulty = victim->getDifficultyPoints();
737   CHECK(difficulty >=0 && difficulty < 100000) << difficulty << " " << victim->getName().bare();
738   points += difficulty;
739   kills.insert(victim);
740 }
741 
getTribe()742 Tribe* Creature::getTribe() {
743   return getGame()->getTribe(tribe);
744 }
745 
getTribe() const746 const Tribe* Creature::getTribe() const {
747   return getGame()->getTribe(tribe);
748 }
749 
getTribeId() const750 TribeId Creature::getTribeId() const {
751   return tribe;
752 }
753 
setTribe(TribeId t)754 void Creature::setTribe(TribeId t) {
755   tribe = t;
756 }
757 
isFriend(WConstCreature c) const758 bool Creature::isFriend(WConstCreature c) const {
759   return !isEnemy(c);
760 }
761 
isEnemy(WConstCreature c) const762 bool Creature::isEnemy(WConstCreature c) const {
763   if (c == this)
764     return false;
765   if (isAffected(LastingEffect::INSANITY))
766     return c != this;
767   return getTribe()->isEnemy(c) || c->getTribe()->isEnemy(this) ||
768     privateEnemies.contains(c) || c->privateEnemies.contains(this);
769 }
770 
getGold(int num) const771 vector<WItem> Creature::getGold(int num) const {
772   vector<WItem> ret;
773   for (WItem item : equipment->getItems([](WConstItem it) { return it->getClass() == ItemClass::GOLD; })) {
774     ret.push_back(item);
775     if (ret.size() == num)
776       return ret;
777   }
778   return ret;
779 }
780 
setPosition(Position pos)781 void Creature::setPosition(Position pos) {
782   if (!pos.isSameLevel(position)) {
783     modViewObject().clearMovementInfo();
784   }
785   if (shortestPath && shortestPath->getLevel() != pos.getLevel())
786     shortestPath.reset();
787   position = pos;
788 }
789 
getLocalTime() const790 double Creature::getLocalTime() const {
791   if (WModel m = position.getModel())
792     return m->getLocalTime(this);
793   else
794     return 0;
795 }
796 
getGlobalTime() const797 double Creature::getGlobalTime() const {
798   if (WGame g = getGame())
799     return g->getGlobalTime();
800   else
801     return 1;
802 }
803 
tick()804 void Creature::tick() {
805   vision->update(this);
806   if (Random.roll(5))
807     getDifficultyPoints();
808   vector<WItem> discarded;
809   for (auto item : equipment->getItems()) {
810     item->tick(position);
811     if (item->isDiscarded())
812       discarded.push_back(item);
813   }
814   for (auto item : discarded)
815     equipment->removeItem(item, this);
816   double globalTime = getGlobalTime();
817   for (LastingEffect effect : ENUM_ALL(LastingEffect)) {
818     if (attributes->considerTimeout(effect, globalTime))
819       LastingEffects::onTimedOut(this, effect, true);
820     if (isAffected(effect) && LastingEffects::tick(this, effect))
821       return;
822   }
823   updateViewObject();
824   if (getBody().tick(this)) {
825     dieWithAttacker(lastAttacker);
826     return;
827   }
828 }
829 
getAttackParam(AttackType type)830 static string getAttackParam(AttackType type) {
831   switch (type) {
832     case AttackType::CUT: return "cut";
833     case AttackType::STAB: return "stab";
834     case AttackType::CRUSH: return "crush";
835     case AttackType::PUNCH: return "punch";
836     case AttackType::EAT:
837     case AttackType::BITE: return "bite";
838     case AttackType::HIT: return "hit";
839     case AttackType::SHOOT: return "shot";
840     case AttackType::SPELL: return "spell";
841     case AttackType::POSSESS: return "touch";
842   }
843 }
844 
getAttackMsg(AttackType type,bool weapon,AttackLevel level)845 static MsgType getAttackMsg(AttackType type, bool weapon, AttackLevel level) {
846   if (weapon)
847     return type == AttackType::STAB ? MsgType::THRUST_WEAPON : MsgType::SWING_WEAPON;
848   switch (type) {
849     case AttackType::EAT:
850     case AttackType::BITE: return MsgType::BITE;
851     case AttackType::PUNCH: return level == AttackLevel::LOW ? MsgType::KICK : MsgType::PUNCH;
852     case AttackType::HIT: return MsgType::HIT;
853     case AttackType::POSSESS: return MsgType::TOUCH;
854     default: FATAL << "Unhandled barehanded attack: " << int(type);
855   }
856   return MsgType(0);
857 }
858 
dropWeapon()859 void Creature::dropWeapon() {
860   if (auto weapon = getWeapon()) {
861     you(MsgType::DROP_WEAPON, weapon->getName());
862     getPosition().dropItem(equipment->removeItem(weapon, this));
863   }
864 }
865 
execute(WCreature c) const866 CreatureAction Creature::execute(WCreature c) const {
867   if (c->getPosition().dist8(getPosition()) > 1)
868     return CreatureAction();
869   return CreatureAction(this, [=] (WCreature self) {
870     self->secondPerson("You execute " + c->getName().the());
871     self->thirdPerson(self->getName().the() + " executes " + c->getName().the());
872     c->dieWithAttacker(self);
873   });
874 }
875 
attack(WCreature other,optional<AttackParams> attackParams) const876 CreatureAction Creature::attack(WCreature other, optional<AttackParams> attackParams) const {
877   CHECK(!other->isDead());
878   if (!position.isSameLevel(other->getPosition()))
879     return CreatureAction();
880   Vec2 dir = getPosition().getDir(other->getPosition());
881   if (dir.length8() != 1)
882     return CreatureAction();
883   return CreatureAction(this, [=] (WCreature self) {
884   INFO << getName().the() << " attacking " << other->getName().the();
885   auto weapon = getWeapon();
886   auto damageAttr = weapon ? weapon->getMeleeAttackAttr() : AttrType::DAMAGE;
887   int damage = getAttr(damageAttr);
888   double timeSpent = 1;
889   vector<string> attackAdjective;
890   if (attackParams && attackParams->mod)
891     switch (*attackParams->mod) {
892       case AttackParams::WILD:
893         damage *= 1.2;
894         timeSpent *= 1.5;
895         attackAdjective.push_back("wildly");
896         break;
897       case AttackParams::SWIFT:
898         damage *= 0.8;
899         timeSpent *= 0.7;
900         attackAdjective.push_back("swiftly");
901         break;
902     }
903   AttackLevel attackLevel = Random.choose(getBody().getAttackLevels());
904   if (attackParams && attackParams->level)
905     attackLevel = *attackParams->level;
906   Attack attack(self, attackLevel, attributes->getAttackType(getWeapon()), damage, damageAttr,
907       getWeapon() ? getWeapon()->getAttackEffect() : attributes->getAttackEffect());
908   const string enemyName = other->getController()->getMessageGenerator().getEnemyName(other);
909   if (getWeapon()) {
910     attackAdjective.push_front(getWeapon()->getName());
911     attackAdjective.push_back("at " + enemyName);
912     you(getAttackMsg(attack.type, true, attack.level), attackAdjective);
913     if (!canSee(other))
914       you(MsgType::HIT, "something");
915   } else {
916     attackAdjective.push_front(enemyName);
917     you(getAttackMsg(attack.type, false, attack.level), attackAdjective);
918   }
919   other->takeDamage(attack);
920   double oldTime = getLocalTime();
921   self->spendTime(timeSpent);
922   self->addMovementInfo({dir, oldTime, getLocalTime(), MovementInfo::ATTACK});
923   });
924 }
925 
onAttackedBy(WCreature attacker)926 void Creature::onAttackedBy(WCreature attacker) {
927   if (!canSee(attacker))
928     unknownAttackers.insert(attacker);
929   if (attacker->tribe != tribe)
930     privateEnemies.insert(attacker);
931   lastAttacker = attacker;
932 }
933 
getDamage(double damageRatio)934 constexpr double getDamage(double damageRatio) {
935   constexpr double minRatio = 0.66;  // the ratio at which the damage drops to 0
936   constexpr double maxRatio = 2;     // the ratio at which the damage reaches 1
937   constexpr double damageAtOne = 0.2;// damage dealt at a ratio of 1
938   if (damageRatio <= minRatio)
939     return 0;
940   else if (damageRatio <= 1)
941     return damageAtOne * (damageRatio - minRatio) / (1.0 - minRatio);
942   else if (damageRatio <= maxRatio)
943     return damageAtOne + (1.0 - damageAtOne) * (damageRatio - 1.0) / (maxRatio - 1.0);
944   else
945     return 1.0;
946 }
947 
takeDamage(const Attack & attack)948 bool Creature::takeDamage(const Attack& attack) {
949   if (WCreature attacker = attack.attacker) {
950     onAttackedBy(attacker);
951     if (!attacker->getAttributes().getSkills().hasDiscrete(SkillId::STEALTH))
952       for (Position p : visibleCreatures)
953         if (p.dist8(position) < 10 && p.getCreature() && !p.getCreature()->isDead())
954           p.getCreature()->removeEffect(LastingEffect::SLEEP);
955     if (attack.type == AttackType::POSSESS) {
956       you(MsgType::ARE, "possessed by " + attacker->getName().the());
957       attacker->dieNoReason(Creature::DropType::NOTHING);
958       addEffect(LastingEffect::INSANITY, 10);
959       return false;
960     }
961     lastDamageType = getExperienceType(attack.damageType);
962   }
963   double defense = getAttr(AttrType::DEFENSE);
964   for (LastingEffect effect : ENUM_ALL(LastingEffect))
965     if (isAffected(effect))
966       defense = LastingEffects::modifyCreatureDefense(effect, defense, attack.damageType);
967   double damage = getDamage((double) attack.strength / defense);
968   if (auto sound = attributes->getAttackSound(attack.type, damage > 0))
969     addSound(*sound);
970   if (damage > 0) {
971     if (attributes->getBody().takeDamage(attack, this, damage))
972       return true;
973   } else
974     you(MsgType::GET_HIT_NODAMAGE, getAttackParam(attack.type));
975   if (attack.effect)
976     attack.effect->applyToCreature(this, attack.attacker);
977   for (LastingEffect effect : ENUM_ALL(LastingEffect))
978     if (isAffected(effect))
979       LastingEffects::afterCreatureDamage(this, effect);
980   return false;
981 }
982 
extractNames(const vector<AdjectiveInfo> & adjectives)983 static vector<string> extractNames(const vector<AdjectiveInfo>& adjectives) {
984   return adjectives.transform([] (const AdjectiveInfo& e) -> string { return e.name; });
985 }
986 
updateViewObject()987 void Creature::updateViewObject() {
988   modViewObject().setCreatureAttributes(ViewObject::CreatureAttributes([this](AttrType t) { return getAttr(t);}));
989   modViewObject().setAttribute(ViewObject::Attribute::MORALE, getMorale());
990   modViewObject().setModifier(ViewObject::Modifier::DRAW_MORALE);
991   modViewObject().setGoodAdjectives(combine(extractNames(getGoodAdjectives()), true));
992   modViewObject().setBadAdjectives(combine(extractNames(getBadAdjectives()), true));
993   getBody().updateViewObject(modViewObject());
994   modViewObject().setDescription(getName().title());
995   getPosition().setNeedsRenderUpdate(true);
996 }
997 
getMorale() const998 double Creature::getMorale() const {
999   return min(1.0, max(-1.0, morale + LastingEffects::getMoraleIncrease(this)));
1000 }
1001 
addMorale(double val)1002 void Creature::addMorale(double val) {
1003   morale = min(1.0, max(-1.0, morale + val));
1004 }
1005 
attrStr(bool strong,bool agile,bool fast)1006 string attrStr(bool strong, bool agile, bool fast) {
1007   vector<string> good;
1008   vector<string> bad;
1009   if (strong)
1010     good.push_back("strong");
1011   else
1012     bad.push_back("weak");
1013   if (agile)
1014     good.push_back("agile");
1015   else
1016     bad.push_back("clumsy");
1017   if (fast)
1018     good.push_back("fast");
1019   else
1020     bad.push_back("slow");
1021   string p1 = combine(good);
1022   string p2 = combine(bad);
1023   if (p1.size() > 0 && p2.size() > 0)
1024     p1.append(", but ");
1025   p1.append(p2);
1026   return p1;
1027 }
1028 
heal(double amount)1029 void Creature::heal(double amount) {
1030   if (getBody().heal(this, amount))
1031     clearLastAttacker();
1032   updateViewObject();
1033 }
1034 
affectByFire(double amount)1035 void Creature::affectByFire(double amount) {
1036   if (!isAffected(LastingEffect::FIRE_RESISTANT) &&
1037       getBody().affectByFire(this, amount)) {
1038     thirdPerson(getName().the() + " burns to death");
1039     secondPerson("You burn to death");
1040     dieWithReason("burnt to death");
1041   }
1042 }
1043 
affectBySilver()1044 void Creature::affectBySilver() {
1045   if (getBody().affectBySilver(this)) {
1046     you(MsgType::DIE_OF, "silver damage");
1047     dieWithAttacker(lastAttacker);
1048   }
1049 }
1050 
affectByAcid()1051 void Creature::affectByAcid() {
1052   if (getBody().affectByAcid(this)) {
1053     you(MsgType::ARE, "dissolved by acid");
1054     dieWithReason("dissolved by acid");
1055   }
1056 }
1057 
poisonWithGas(double amount)1058 void Creature::poisonWithGas(double amount) {
1059   if (getBody().affectByPoisonGas(this, amount)) {
1060     you(MsgType::DIE_OF, "gas poisoning");
1061     dieWithReason("poisoned with gas");
1062   }
1063 }
1064 
setHeld(WCreature c)1065 void Creature::setHeld(WCreature c) {
1066   holding = c->getUniqueId();
1067 }
1068 
getHoldingCreature() const1069 WCreature Creature::getHoldingCreature() const {
1070   if (holding)
1071     for (auto pos : getPosition().neighbors8())
1072       if (auto c = pos.getCreature())
1073         if (c->getUniqueId() == *holding)
1074           return c;
1075   return nullptr;
1076 }
1077 
take(vector<PItem> items)1078 void Creature::take(vector<PItem> items) {
1079   for (PItem& elem : items)
1080     take(std::move(elem));
1081 }
1082 
take(PItem item)1083 void Creature::take(PItem item) {
1084   WItem ref = item.get();
1085   equipment->addItem(std::move(item));
1086   if (auto action = equip(ref))
1087     action.perform(this);
1088 }
1089 
dieWithReason(const string & reason,DropType drops)1090 void Creature::dieWithReason(const string& reason, DropType drops) {
1091   deathReason = reason;
1092   dieNoReason(drops);
1093 }
1094 
dieWithLastAttacker(DropType drops)1095 void Creature::dieWithLastAttacker(DropType drops) {
1096   dieWithAttacker(lastAttacker, drops);
1097 }
1098 
dieWithAttacker(WCreature attacker,DropType drops)1099 void Creature::dieWithAttacker(WCreature attacker, DropType drops) {
1100   CHECK(!isDead()) << getName().bare() << " is already dead. " << getDeathReason().value_or("");
1101   deathTime = getGlobalTime();
1102   lastAttacker = attacker;
1103   INFO << getName().the() << " dies. Killed by " << (attacker ? attacker->getName().bare() : "");
1104   getController()->onKilled(attacker);
1105   if (drops == DropType::EVERYTHING || drops == DropType::ONLY_INVENTORY)
1106     for (PItem& item : equipment->removeAllItems(this))
1107       getPosition().dropItem(std::move(item));
1108   if (drops == DropType::EVERYTHING) {
1109     getPosition().dropItems(getBody().getCorpseItem(getName().bare(), getUniqueId()));
1110     if (auto sound = getBody().getDeathSound())
1111       addSound(*sound);
1112   }
1113   if (attributes->isInnocent())
1114     getGame()->getStatistics().add(StatId::INNOCENT_KILLED);
1115   getGame()->getStatistics().add(StatId::DEATH);
1116   if (attacker)
1117     attacker->onKilled(this, lastDamageType);
1118   getGame()->addEvent(EventInfo::CreatureKilled{this, attacker});
1119   getTribe()->onMemberKilled(this, attacker);
1120   getLevel()->killCreature(this);
1121   setController(makeOwner<DoNothingController>(this));
1122 }
1123 
dieNoReason(DropType drops)1124 void Creature::dieNoReason(DropType drops) {
1125   dieWithAttacker(nullptr, drops);
1126 }
1127 
flyAway() const1128 CreatureAction Creature::flyAway() const {
1129   if (!isAffected(LastingEffect::FLYING) || getPosition().isCovered())
1130     return CreatureAction();
1131   return CreatureAction(this, [=](WCreature self) {
1132     INFO << getName().the() << " fly away";
1133     thirdPerson(getName().the() + " flies away.");
1134     self->dieNoReason(Creature::DropType::NOTHING);
1135   });
1136 }
1137 
disappear() const1138 CreatureAction Creature::disappear() const {
1139   return CreatureAction(this, [=](WCreature self) {
1140     INFO << getName().the() << " disappears";
1141     thirdPerson(getName().the() + " disappears.");
1142     self->dieNoReason(Creature::DropType::NOTHING);
1143   });
1144 }
1145 
torture(WCreature other) const1146 CreatureAction Creature::torture(WCreature other) const {
1147   if (!other->hasCondition(CreatureCondition::RESTRICTED_MOVEMENT) || other->getPosition().dist8(getPosition()) != 1)
1148     return CreatureAction();
1149   return CreatureAction(this, [=](WCreature self) {
1150     thirdPerson(getName().the() + " tortures " + other->getName().the());
1151     secondPerson("You torture " + other->getName().the());
1152     if (Random.roll(4)) {
1153       other->thirdPerson(other->getName().the() + " screams!");
1154       other->getPosition().unseenMessage("You hear a horrible scream");
1155     }
1156     other->getBody().affectByTorture(other);
1157     getGame()->addEvent(EventInfo::CreatureTortured{other, self});
1158     self->spendTime(1);
1159   });
1160 }
1161 
surrender(WCreature to)1162 void Creature::surrender(WCreature to) {
1163   getGame()->addEvent(EventInfo::CreatureSurrendered{this, to});
1164 }
1165 
retire()1166 void Creature::retire() {
1167   if (auto id = attributes->getRetiredViewId())
1168     modViewObject().setId(*id);
1169 }
1170 
increaseExpLevel(ExperienceType type,double increase)1171 void Creature::increaseExpLevel(ExperienceType type, double increase) {
1172   int curLevel = (int)getAttributes().getExpLevel(type);
1173   getAttributes().increaseExpLevel(type, increase);
1174   int newLevel = (int)getAttributes().getExpLevel(type);
1175   if (curLevel != newLevel) {
1176     you(MsgType::ARE, "more experienced");
1177     addPersonalEvent(getName().a() + " reaches " + ::getNameLowerCase(type) + " training level " + toString(newLevel));
1178   }
1179   if (type == ExperienceType::SPELL)
1180     getAttributes().getSpellMap().onExpLevelReached(this, getAttributes().getExpLevel(type));
1181 }
1182 
getBestAttack() const1183 BestAttack Creature::getBestAttack() const {
1184   return BestAttack(this);
1185 }
1186 
give(WCreature whom,vector<WItem> items) const1187 CreatureAction Creature::give(WCreature whom, vector<WItem> items) const {
1188   if (!getBody().isHumanoid() || !whom->canTakeItems(items))
1189     return CreatureAction(whom->getName().the() + (items.size() == 1 ? " can't take this item."
1190         : " can't take these items."));
1191   return CreatureAction(this, [=](WCreature self) {
1192     for (auto stack : stackItems(items)) {
1193       thirdPerson(getName().the() + " gives " + whom->getController()->getMessageGenerator().getEnemyName(whom) + " "
1194           + getPluralAName(stack[0], (int) stack.size()));
1195       secondPerson("You give " + getPluralTheName(stack[0], (int) stack.size()) + " to " +
1196           whom->getName().the());
1197     }
1198     whom->takeItems(self->equipment->removeItems(items, self), self);
1199     self->spendTime(1);
1200   });
1201 }
1202 
payFor(const vector<WItem> & items) const1203 CreatureAction Creature::payFor(const vector<WItem>& items) const {
1204   int totalPrice = std::accumulate(items.begin(), items.end(), 0,
1205       [](int sum, WConstItem it) { return sum + it->getPrice(); });
1206   return give(items[0]->getShopkeeper(this), getGold(totalPrice))
1207       .append([=](WCreature) { for (auto it : items) it->setShopkeeper(nullptr); });
1208 }
1209 
fire(Vec2 direction) const1210 CreatureAction Creature::fire(Vec2 direction) const {
1211   CHECK(direction.length8() == 1);
1212   if (getEquipment().getItems(ItemIndex::RANGED_WEAPON).empty())
1213     return CreatureAction("You need a ranged weapon.");
1214   if (getEquipment().getSlotItems(EquipmentSlot::RANGED_WEAPON).empty())
1215     return CreatureAction("You need to equip your ranged weapon.");
1216   if (getBody().numGood(BodyPart::ARM) < 2)
1217     return CreatureAction("You need two hands to shoot a bow.");
1218   return CreatureAction(this, [=](WCreature self) {
1219     auto& weapon = *self->getEquipment().getSlotItems(EquipmentSlot::RANGED_WEAPON).getOnlyElement()
1220         ->getRangedWeapon();
1221     weapon.fire(self, direction);
1222     self->spendTime(1);
1223   });
1224 }
1225 
addMovementInfo(const MovementInfo & info)1226 void Creature::addMovementInfo(const MovementInfo& info) {
1227   modViewObject().addMovementInfo(info);
1228   getPosition().setNeedsRenderUpdate(true);
1229 }
1230 
whip(const Position & pos) const1231 CreatureAction Creature::whip(const Position& pos) const {
1232   WCreature whipped = pos.getCreature();
1233   if (pos.dist8(position) > 1 || !whipped)
1234     return CreatureAction();
1235   return CreatureAction(this, [=](WCreature self) {
1236     thirdPerson(PlayerMessage(getName().the() + " whips " + whipped->getName().the()));
1237     double oldTime = getLocalTime();
1238     self->spendTime(1);
1239     if (Random.roll(3)) {
1240       addSound(SoundId::WHIP);
1241       self->addMovementInfo({position.getDir(pos), oldTime, getLocalTime(), MovementInfo::ATTACK});
1242     }
1243     if (Random.roll(5)) {
1244       whipped->thirdPerson(whipped->getName().the() + " screams!");
1245       whipped->getPosition().unseenMessage("You hear a horrible scream!");
1246     }
1247     if (Random.roll(10)) {
1248       whipped->addMorale(0.05);
1249       whipped->you(MsgType::FEEL, "happier");
1250     }
1251   });
1252 }
1253 
addSound(const Sound & sound1) const1254 void Creature::addSound(const Sound& sound1) const {
1255   Sound sound(sound1);
1256   sound.setPosition(getPosition());
1257   getGame()->getView()->addSound(sound);
1258 }
1259 
construct(Vec2 direction,FurnitureType type) const1260 CreatureAction Creature::construct(Vec2 direction, FurnitureType type) const {
1261   if (getPosition().plus(direction).canConstruct(type) && canConstruct(type))
1262     return CreatureAction(this, [=](WCreature self) {
1263         addSound(Sound(SoundId::DIGGING).setPitch(0.5));
1264         getPosition().plus(direction).construct(type, self);
1265         self->spendTime(1);
1266       });
1267   return CreatureAction();
1268 }
1269 
canConstruct(FurnitureType) const1270 bool Creature::canConstruct(FurnitureType) const {
1271   return attributes->getSkills().hasDiscrete(SkillId::CONSTRUCTION);
1272 }
1273 
eat(WItem item) const1274 CreatureAction Creature::eat(WItem item) const {
1275   return CreatureAction(this, [=](WCreature self) {
1276     thirdPerson(getName().the() + " eats " + item->getAName());
1277     secondPerson("You eat " + item->getAName());
1278     self->addEffect(LastingEffect::SATIATED, 500);
1279     self->getPosition().removeItem(item);
1280     self->spendTime(3);
1281   });
1282 }
1283 
destroyImpl(Vec2 direction,const DestroyAction & action)1284 void Creature::destroyImpl(Vec2 direction, const DestroyAction& action) {
1285   auto pos = getPosition().plus(direction);
1286   if (auto furniture = pos.modFurniture(FurnitureLayer::MIDDLE)) {
1287     string name = furniture->getName();
1288     secondPerson("You "_s + action.getVerbSecondPerson() + " the " + name);
1289     thirdPerson(getName().the() + " " + action.getVerbThirdPerson() + " the " + name);
1290     pos.unseenMessage(action.getSoundText());
1291     furniture->tryToDestroyBy(pos, this, action);
1292   }
1293 }
1294 
destroy(Vec2 direction,const DestroyAction & action) const1295 CreatureAction Creature::destroy(Vec2 direction, const DestroyAction& action) const {
1296   auto pos = getPosition().plus(direction);
1297   if (auto furniture = pos.getFurniture(FurnitureLayer::MIDDLE))
1298     if (direction.length8() <= 1 && furniture->canDestroy(getMovementType(), action))
1299       return CreatureAction(this, [=](WCreature self) {
1300         self->destroyImpl(direction, action);
1301         double oldTime = getLocalTime();
1302         self->spendTime(1);
1303         if (direction.length8() == 1)
1304           self->addMovementInfo({getPosition().getDir(pos), oldTime, min(oldTime + 1, getLocalTime()),
1305               MovementInfo::ATTACK});
1306       });
1307   return CreatureAction();
1308 }
1309 
canCopulateWith(WConstCreature c) const1310 bool Creature::canCopulateWith(WConstCreature c) const {
1311   return attributes->getSkills().hasDiscrete(SkillId::COPULATION) &&
1312       c->getBody().canCopulateWith() &&
1313       c->attributes->getGender() != attributes->getGender() &&
1314       c->isAffected(LastingEffect::SLEEP);
1315 }
1316 
canConsume(WConstCreature c) const1317 bool Creature::canConsume(WConstCreature c) const {
1318   return c->getBody().canConsume() && attributes->getSkills().hasDiscrete(SkillId::CONSUMPTION) && isFriend(c);
1319 }
1320 
copulate(Vec2 direction) const1321 CreatureAction Creature::copulate(Vec2 direction) const {
1322   WConstCreature other = getPosition().plus(direction).getCreature();
1323   if (!other || !canCopulateWith(other))
1324     return CreatureAction();
1325   return CreatureAction(this, [=](WCreature self) {
1326       INFO << getName().bare() << " copulate with " << other->getName().bare();
1327       you(MsgType::COPULATE, "with " + other->getName().the());
1328       self->spendTime(2);
1329     });
1330 }
1331 
addPersonalEvent(const string & s)1332 void Creature::addPersonalEvent(const string& s) {
1333   if (WModel m = position.getModel())
1334     m->addEvent(EventInfo::CreatureEvent{this, s});
1335 }
1336 
consume(WCreature other) const1337 CreatureAction Creature::consume(WCreature other) const {
1338   if (!other || !canConsume(other) || other->getPosition().dist8(getPosition()) > 1)
1339     return CreatureAction();
1340   return CreatureAction(this, [=] (WCreature self) {
1341     self->attributes->consume(self, *other->attributes);
1342     other->dieWithAttacker(self, Creature::DropType::ONLY_INVENTORY);
1343     self->spendTime(2);
1344   });
1345 }
1346 
getWeapon() const1347 WItem Creature::getWeapon() const {
1348   vector<WItem> it = equipment->getSlotItems(EquipmentSlot::WEAPON);
1349   if (it.empty())
1350     return nullptr;
1351   else
1352     return it.getOnlyElement();
1353 }
1354 
applyItem(WItem item) const1355 CreatureAction Creature::applyItem(WItem item) const {
1356   if (!contains({ItemClass::TOOL, ItemClass::POTION, ItemClass::FOOD, ItemClass::BOOK, ItemClass::SCROLL},
1357       item->getClass()) || !getBody().isHumanoid())
1358     return CreatureAction("You can't apply this item");
1359   if (getBody().numGood(BodyPart::ARM) == 0)
1360     return CreatureAction("You have no healthy arms!");
1361   return CreatureAction(this, [=] (WCreature self) {
1362       double time = item->getApplyTime();
1363       secondPerson("You " + item->getApplyMsgFirstPerson(self));
1364       thirdPerson(getName().the() + " " + item->getApplyMsgThirdPerson(self));
1365       position.unseenMessage(item->getNoSeeApplyMsg());
1366       item->apply(self);
1367       if (item->isDiscarded()) {
1368         self->equipment->removeItem(item, self);
1369       }
1370       self->spendTime(time);
1371   });
1372 }
1373 
throwItem(WItem item,Vec2 direction) const1374 CreatureAction Creature::throwItem(WItem item, Vec2 direction) const {
1375   if (!getBody().numGood(BodyPart::ARM) || !getBody().isHumanoid())
1376     return CreatureAction("You can't throw anything!");
1377   else if (item->getWeight() > 20)
1378     return CreatureAction(item->getTheName() + " is too heavy!");
1379   int dist = 0;
1380   int str = 20;
1381   if (item->getWeight() <= 0.5)
1382     dist = 10 * str / 15;
1383   else if (item->getWeight() <= 5)
1384     dist = 5 * str / 15;
1385   else if (item->getWeight() <= 20)
1386     dist = 2 * str / 15;
1387   else
1388     FATAL << "Item too heavy.";
1389   int damage = getAttr(AttrType::RANGED_DAMAGE) + item->getModifier(AttrType::RANGED_DAMAGE);
1390   return CreatureAction(this, [=](WCreature self) {
1391     Attack attack(self, Random.choose(getBody().getAttackLevels()), item->getAttackType(), damage, AttrType::DAMAGE);
1392     secondPerson("You throw " + item->getAName(false, this));
1393     thirdPerson(getName().the() + " throws " + item->getAName());
1394     self->getPosition().throwItem(self->equipment->removeItem(item, self), attack, dist, direction, getVision().getId());
1395     self->spendTime(1);
1396   });
1397 }
1398 
canSeeOutsidePosition(WConstCreature c) const1399 bool Creature::canSeeOutsidePosition(WConstCreature c) const {
1400   return LastingEffects::canSee(this, c);
1401 }
1402 
canSeeInPosition(WConstCreature c) const1403 bool Creature::canSeeInPosition(WConstCreature c) const {
1404   if (!c->getPosition().isSameLevel(position))
1405     return false;
1406   return !isAffected(LastingEffect::BLIND) && (!c->isAffected(LastingEffect::INVISIBLE) || isFriend(c)) &&
1407       (!c->isHidden() || c->knowsHiding(this));
1408 }
1409 
canSee(WConstCreature c) const1410 bool Creature::canSee(WConstCreature c) const {
1411   return canSeeInPosition(c) && c->getPosition().isVisibleBy(this);
1412 }
1413 
canSee(Position pos) const1414 bool Creature::canSee(Position pos) const {
1415   return !isAffected(LastingEffect::BLIND) && pos.isVisibleBy(this);
1416 }
1417 
canSee(Vec2 pos) const1418 bool Creature::canSee(Vec2 pos) const {
1419   return !isAffected(LastingEffect::BLIND) && position.withCoord(pos).isVisibleBy(this);
1420 }
1421 
isPlayer() const1422 bool Creature::isPlayer() const {
1423   return getController()->isPlayer();
1424 }
1425 
getName() const1426 const CreatureName& Creature::getName() const {
1427   return attributes->getName();
1428 }
1429 
getName()1430 CreatureName& Creature::getName() {
1431   return attributes->getName();
1432 }
1433 
identify() const1434 const char* Creature::identify() const {
1435   return getName().bare().c_str();
1436 }
1437 
getFriendlyTribes() const1438 TribeSet Creature::getFriendlyTribes() const {
1439   if (WGame game = getGame())
1440     return game->getTribe(tribe)->getFriendlyTribes();
1441   else
1442     return TribeSet().insert(tribe);
1443 }
1444 
getMovementType() const1445 MovementType Creature::getMovementType() const {
1446   return MovementType(getFriendlyTribes(), {
1447       true,
1448       isAffected(LastingEffect::FLYING),
1449       attributes->getSkills().hasDiscrete(SkillId::SWIMMING),
1450       getBody().canWade()})
1451     .setDestroyActions(EnumSet<DestroyAction::Type>([this](auto t) { return DestroyAction(t).canNavigate(this); }))
1452     .setForced(isAffected(LastingEffect::BLIND) || getHoldingCreature() || forceMovement)
1453     .setFireResistant(isAffected(LastingEffect::FIRE_RESISTANT))
1454     .setSunlightVulnerable(isAffected(LastingEffect::SUNLIGHT_VULNERABLE) && !isAffected(LastingEffect::DARKNESS_SOURCE)
1455         && (!getGame() || getGame()->getSunlightInfo().getState() == SunlightState::DAY));
1456 }
1457 
getDifficultyPoints() const1458 int Creature::getDifficultyPoints() const {
1459   difficultyPoints = max(difficultyPoints,
1460       getAttr(AttrType::SPELL_DAMAGE) +
1461       getAttr(AttrType::DEFENSE) + getAttr(AttrType::DAMAGE) + getAttr(AttrType::SPEED) / 10);
1462   return difficultyPoints;
1463 }
1464 
continueMoving()1465 CreatureAction Creature::continueMoving() {
1466   if (shortestPath && shortestPath->isReachable(getPosition()))
1467     return move(shortestPath->getNextMove(getPosition()));
1468   else
1469     return CreatureAction();
1470 }
1471 
stayIn(WLevel level,Rectangle area)1472 CreatureAction Creature::stayIn(WLevel level, Rectangle area) {
1473   if (level != getLevel() || !getPosition().getCoord().inRectangle(area)) {
1474     if (level == getLevel())
1475       for (Position v : getPosition().neighbors8(Random))
1476         if (v.getCoord().inRectangle(area))
1477           if (auto action = move(v))
1478             return action;
1479     return moveTowards(Position(area.middle(), getLevel()));
1480   }
1481   return CreatureAction();
1482 }
1483 
moveTowards(Position pos,NavigationFlags flags)1484 CreatureAction Creature::moveTowards(Position pos, NavigationFlags flags) {
1485   if (!pos.isValid())
1486     return CreatureAction();
1487   if (pos.isSameLevel(position))
1488     return moveTowards(pos, false, flags);
1489   else if (auto stairs = position.getStairsTo(pos)) {
1490     if (stairs == position)
1491       return applySquare(position);
1492     else
1493       return moveTowards(*stairs, false, flags.requireStepOnTile());
1494   } else
1495     return CreatureAction();
1496 }
1497 
canNavigateTo(Position pos) const1498 bool Creature::canNavigateTo(Position pos) const {
1499   MovementType movement = getMovementType();
1500   for (Position v : pos.neighbors8())
1501     if (v.isConnectedTo(position, movement))
1502       return true;
1503   return false;
1504 }
1505 
moveTowards(Position pos,bool away,NavigationFlags flags)1506 CreatureAction Creature::moveTowards(Position pos, bool away, NavigationFlags flags) {
1507   CHECK(pos.isSameLevel(position));
1508   if (flags.stepOnTile && !pos.canEnterEmpty(this))
1509     return CreatureAction();
1510   MEASURE(
1511   if (!away && !canNavigateTo(pos))
1512     return CreatureAction();
1513   , "Creature Sector checking " + getName().bare() + " from " + toString(position) + " to " + toString(pos));
1514   bool newPath = false;
1515   bool targetChanged = shortestPath && shortestPath->getTarget().dist8(pos) > getPosition().dist8(pos) / 10;
1516   if (!shortestPath || targetChanged || shortestPath->isReversed() != away) {
1517     newPath = true;
1518     if (!away)
1519       shortestPath.reset(new LevelShortestPath(this, pos, position));
1520     else
1521       shortestPath.reset(new LevelShortestPath(this, pos, position, -1.5));
1522   }
1523   CHECK(shortestPath);
1524   if (shortestPath->isReachable(position))
1525     if (auto action = move(shortestPath->getNextMove(position)))
1526       return action;
1527   /*if (newPath)
1528     return CreatureAction();*/
1529   INFO << "Reconstructing shortest path.";
1530   if (!away)
1531     shortestPath.reset(new LevelShortestPath(this, pos, position));
1532   else
1533     shortestPath.reset(new LevelShortestPath(this, pos, position, -1.5));
1534   if (shortestPath->isReachable(position)) {
1535     Position pos2 = shortestPath->getNextMove(position);
1536     if (auto action = move(pos2))
1537       return action;
1538     else {
1539       if (!pos2.canEnterEmpty(this) && flags.destroy)
1540         if (auto destroyAction = pos2.getBestDestroyAction(getMovementType()))
1541             if (auto action = destroy(getPosition().getDir(pos2), *destroyAction))
1542             return action;
1543       return CreatureAction();
1544     }
1545   } else {
1546     //INFO << "Cannot move toward " << pos.getCoord();
1547     return CreatureAction();
1548   }
1549 }
1550 
moveAway(Position pos,bool pathfinding)1551 CreatureAction Creature::moveAway(Position pos, bool pathfinding) {
1552   CHECK(pos.isSameLevel(position));
1553   if (pos.dist8(getPosition()) <= 5 && pathfinding)
1554     if (auto action = moveTowards(pos, true, NavigationFlags().noDestroying()))
1555       return action;
1556   pair<Vec2, Vec2> dirs = pos.getDir(getPosition()).approxL1();
1557   vector<CreatureAction> moves;
1558   if (auto action = move(dirs.first))
1559     moves.push_back(action);
1560   if (auto action = move(dirs.second))
1561     moves.push_back(action);
1562   if (moves.size() > 0)
1563     return moves[Random.get(moves.size())];
1564   return CreatureAction();
1565 }
1566 
atTarget() const1567 bool Creature::atTarget() const {
1568   return shortestPath && getPosition() == shortestPath->getTarget();
1569 }
1570 
youHit(BodyPart part,AttackType type) const1571 void Creature::youHit(BodyPart part, AttackType type) const {
1572   switch (part) {
1573     case BodyPart::BACK:
1574         switch (type) {
1575           case AttackType::SHOOT: you(MsgType::ARE, "shot in the back!"); break;
1576           case AttackType::BITE: you(MsgType::ARE, "bitten in the neck!"); break;
1577           case AttackType::CUT: you(MsgType::YOUR, "throat is cut!"); break;
1578           case AttackType::CRUSH: you(MsgType::YOUR, "spine is crushed!"); break;
1579           case AttackType::PUNCH: you(MsgType::YOUR, "neck is broken!"); break;
1580           case AttackType::HIT: you(MsgType::ARE, "hit in the back of the head!"); break;
1581           case AttackType::STAB: you(MsgType::ARE, "stabbed in the "_s +
1582                                      Random.choose("back"_s, "neck"_s)); break;
1583           case AttackType::SPELL: you(MsgType::ARE, "ripped to pieces!"); break;
1584           default: FATAL << "Unhandled attack type " << int(type);
1585         }
1586         break;
1587     case BodyPart::HEAD:
1588         switch (type) {
1589           case AttackType::SHOOT: you(MsgType::ARE, "shot in the " +
1590                                       Random.choose("eye"_s, "neck"_s, "forehead"_s) + "!"); break;
1591           case AttackType::BITE: you(MsgType::YOUR, "head is bitten off!"); break;
1592           case AttackType::CUT: you(MsgType::YOUR, "head is chopped off!"); break;
1593           case AttackType::CRUSH: you(MsgType::YOUR, "skull is shattered!"); break;
1594           case AttackType::PUNCH: you(MsgType::YOUR, "neck is broken!"); break;
1595           case AttackType::HIT: you(MsgType::ARE, "hit in the head!"); break;
1596           case AttackType::STAB: you(MsgType::ARE, "stabbed in the eye!"); break;
1597           case AttackType::SPELL: you(MsgType::YOUR, "head is ripped to pieces!"); break;
1598           default: FATAL << "Unhandled attack type " << int(type);
1599         }
1600         break;
1601     case BodyPart::TORSO:
1602         switch (type) {
1603           case AttackType::SHOOT: you(MsgType::YOUR, "shot in the heart!"); break;
1604           case AttackType::BITE: you(MsgType::YOUR, "internal organs are ripped out!"); break;
1605           case AttackType::CUT: you(MsgType::ARE, "cut in half!"); break;
1606           case AttackType::STAB: you(MsgType::ARE, "stabbed in the " +
1607                                      Random.choose("stomach"_s, "heart"_s) + "!"); break;
1608           case AttackType::CRUSH: you(MsgType::YOUR, "ribs and internal organs are crushed!"); break;
1609           case AttackType::HIT: you(MsgType::ARE, "hit in the chest!"); break;
1610           case AttackType::PUNCH: you(MsgType::YOUR, "stomach receives a deadly blow!"); break;
1611           case AttackType::SPELL: you(MsgType::ARE, "ripped to pieces!"); break;
1612           default: FATAL << "Unhandled attack type " << int(type);
1613         }
1614         break;
1615     case BodyPart::ARM:
1616         switch (type) {
1617           case AttackType::SHOOT: you(MsgType::YOUR, "shot in the arm!"); break;
1618           case AttackType::BITE: you(MsgType::YOUR, "arm is bitten off!"); break;
1619           case AttackType::CUT: you(MsgType::YOUR, "arm is chopped off!"); break;
1620           case AttackType::STAB: you(MsgType::ARE, "stabbed in the arm!"); break;
1621           case AttackType::CRUSH: you(MsgType::YOUR, "arm is smashed!"); break;
1622           case AttackType::HIT: you(MsgType::ARE, "hit in the arm!"); break;
1623           case AttackType::PUNCH: you(MsgType::YOUR, "arm is broken!"); break;
1624           case AttackType::SPELL: you(MsgType::YOUR, "arm is ripped to pieces!"); break;
1625           default: FATAL << "Unhandled attack type " << int(type);
1626         }
1627         break;
1628     case BodyPart::WING:
1629         switch (type) {
1630           case AttackType::SHOOT: you(MsgType::YOUR, "shot in the wing!"); break;
1631           case AttackType::BITE: you(MsgType::YOUR, "wing is bitten off!"); break;
1632           case AttackType::CUT: you(MsgType::YOUR, "wing is chopped off!"); break;
1633           case AttackType::STAB: you(MsgType::ARE, "stabbed in the wing!"); break;
1634           case AttackType::CRUSH: you(MsgType::YOUR, "wing is smashed!"); break;
1635           case AttackType::HIT: you(MsgType::ARE, "hit in the wing!"); break;
1636           case AttackType::PUNCH: you(MsgType::YOUR, "wing is broken!"); break;
1637           case AttackType::SPELL: you(MsgType::YOUR, "wing is ripped to pieces!"); break;
1638           default: FATAL << "Unhandled attack type " << int(type);
1639         }
1640         break;
1641     case BodyPart::LEG:
1642         switch (type) {
1643           case AttackType::SHOOT: you(MsgType::YOUR, "shot in the leg!"); break;
1644           case AttackType::BITE: you(MsgType::YOUR, "leg is bitten off!"); break;
1645           case AttackType::CUT: you(MsgType::YOUR, "leg is cut off!"); break;
1646           case AttackType::STAB: you(MsgType::YOUR, "stabbed in the leg!"); break;
1647           case AttackType::CRUSH: you(MsgType::YOUR, "knee is crushed!"); break;
1648           case AttackType::HIT: you(MsgType::ARE, "hit in the leg!"); break;
1649           case AttackType::PUNCH: you(MsgType::YOUR, "leg is broken!"); break;
1650           case AttackType::SPELL: you(MsgType::YOUR, "leg is ripped to pieces!"); break;
1651           default: FATAL << "Unhandled attack type " << int(type);
1652         }
1653         break;
1654   }
1655 }
1656 
isUnknownAttacker(WConstCreature c) const1657 bool Creature::isUnknownAttacker(WConstCreature c) const {
1658   return unknownAttackers.contains(c);
1659 }
1660 
getVision() const1661 const Vision& Creature::getVision() const {
1662   return *vision;
1663 }
1664 
getDebt() const1665 const CreatureDebt& Creature::getDebt() const {
1666   return *debt;
1667 }
1668 
getDebt()1669 CreatureDebt& Creature::getDebt() {
1670   return *debt;
1671 }
1672 
updateVisibleCreatures()1673 void Creature::updateVisibleCreatures() {
1674   int range = FieldOfView::sightRange;
1675   visibleEnemies.clear();
1676   visibleCreatures.clear();
1677   for (WCreature c : position.getAllCreatures(range))
1678     if (canSee(c) || isUnknownAttacker(c)) {
1679       visibleCreatures.push_back(c->getPosition());
1680       if (isEnemy(c))
1681         visibleEnemies.push_back(c->getPosition());
1682     }
1683 }
1684 
getVisibleEnemies() const1685 vector<WCreature> Creature::getVisibleEnemies() const {
1686   vector<WCreature> ret;
1687   for (Position p : visibleEnemies)
1688     if (WCreature c = p.getCreature())
1689       if (!c->isDead())
1690         ret.push_back(c);
1691   return ret;
1692 }
1693 
getVisibleCreatures() const1694 vector<WCreature> Creature::getVisibleCreatures() const {
1695   vector<WCreature> ret;
1696   for (Position p : visibleCreatures)
1697     if (WCreature c = p.getCreature())
1698       if (!c->isDead())
1699         ret.push_back(c);
1700   return ret;
1701 }
1702 
getVisibleTiles() const1703 vector<Position> Creature::getVisibleTiles() const {
1704   if (isAffected(LastingEffect::BLIND))
1705     return {};
1706   else
1707     return getPosition().getVisibleTiles(getVision());
1708 }
1709 
getMoraleText(double morale)1710 const char* getMoraleText(double morale) {
1711   if (morale >= 0.7)
1712     return "Ecstatic";
1713   if (morale >= 0.2)
1714     return "Merry";
1715   if (morale < -0.7)
1716     return "Depressed";
1717   if (morale < -0.2)
1718     return "Unhappy";
1719   return nullptr;
1720 }
1721 
getGoodAdjectives() const1722 vector<AdjectiveInfo> Creature::getGoodAdjectives() const {
1723   vector<AdjectiveInfo> ret;
1724   if (!!attributes->getMoraleSpeedIncrease())
1725     ret.push_back({"Morale affects speed", ""});
1726   for (LastingEffect effect : ENUM_ALL(LastingEffect))
1727     if (isAffected(effect))
1728       if (const char* name = LastingEffects::getGoodAdjective(effect)) {
1729         ret.push_back({ name, LastingEffects::getDescription(effect) });
1730         if (!attributes->isAffectedPermanently(effect))
1731           ret.back().name += attributes->getRemainingString(effect, getGlobalTime());
1732       }
1733   if (getBody().isUndead())
1734     ret.push_back({"Undead",
1735         "Undead creatures don't take regular damage and need to be killed by chopping up or using fire."});
1736   auto morale = getMorale();
1737   if (morale > 0)
1738     if (auto text = getMoraleText(morale))
1739       ret.push_back({text, "Morale affects minion's productivity and chances of fleeing from battle."});
1740   return ret;
1741 }
1742 
getBadAdjectives() const1743 vector<AdjectiveInfo> Creature::getBadAdjectives() const {
1744   vector<AdjectiveInfo> ret;
1745   getBody().getBadAdjectives(ret);
1746   for (LastingEffect effect : ENUM_ALL(LastingEffect))
1747     if (isAffected(effect))
1748       if (const char* name = LastingEffects::getBadAdjective(effect)) {
1749         ret.push_back({ name, LastingEffects::getDescription(effect) });
1750         if (!attributes->isAffectedPermanently(effect))
1751           ret.back().name += attributes->getRemainingString(effect, getGlobalTime());
1752       }
1753   auto morale = getMorale();
1754   if (morale < 0)
1755     if (auto text = getMoraleText(morale))
1756       ret.push_back({text, "Morale affects minion's productivity and chances of fleeing from battle."});
1757   return ret;
1758 }
1759 
isSameSector(Position pos) const1760 bool Creature::isSameSector(Position pos) const {
1761   return pos.isConnectedTo(position, getMovementType());
1762 }
1763 
setInCombat()1764 void Creature::setInCombat() {
1765   lastCombatTime = getGame()->getGlobalTime();
1766 }
1767 
wasInCombat(double numLastTurns) const1768 bool Creature::wasInCombat(double numLastTurns) const {
1769   return lastCombatTime && *lastCombatTime >= getGame()->getGlobalTime() - numLastTurns;
1770 }
1771 
1772