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