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 "player.h"
19 #include "level.h"
20 #include "ranged_weapon.h"
21 #include "name_generator.h"
22 #include "game.h"
23 #include "options.h"
24 #include "creature.h"
25 #include "item_factory.h"
26 #include "effect.h"
27 #include "view_id.h"
28 #include "map_memory.h"
29 #include "player_message.h"
30 #include "item_action.h"
31 #include "game_info.h"
32 #include "equipment.h"
33 #include "spell.h"
34 #include "creature_name.h"
35 #include "view.h"
36 #include "view_index.h"
37 #include "music.h"
38 #include "model.h"
39 #include "collective.h"
40 #include "territory.h"
41 #include "creature_attributes.h"
42 #include "attack_level.h"
43 #include "villain_type.h"
44 #include "visibility_map.h"
45 #include "collective_name.h"
46 #include "view_object.h"
47 #include "body.h"
48 #include "furniture_usage.h"
49 #include "furniture.h"
50 #include "creature_debt.h"
51 #include "tutorial.h"
52 #include "message_generator.h"
53 #include "message_buffer.h"
54 #include "pretty_printing.h"
55 #include "item_type.h"
56 #include "creature_factory.h"
57 
58 template <class Archive>
serialize(Archive & ar,const unsigned int)59 void Player::serialize(Archive& ar, const unsigned int) {
60   ar& SUBCLASS(Controller) & SUBCLASS(EventListener);
61   ar(travelling, travelDir, target, displayGreeting, levelMemory, messageBuffer);
62   ar(adventurer, visibilityMap, tutorial);
63 }
64 
65 SERIALIZABLE(Player)
66 
SERIALIZATION_CONSTRUCTOR_IMPL(Player)67 SERIALIZATION_CONSTRUCTOR_IMPL(Player)
68 
69 Player::Player(WCreature c, bool adv, SMapMemory memory, SMessageBuffer buf, SVisibilityMap v, STutorial t) :
70     Controller(c), levelMemory(memory), adventurer(adv), displayGreeting(adventurer), messageBuffer(buf),
71     visibilityMap(v), tutorial(t) {
72   visibilityMap->update(c, c->getVisibleTiles());
73 }
74 
~Player()75 Player::~Player() {
76 }
77 
onEvent(const GameEvent & event)78 void Player::onEvent(const GameEvent& event) {
79   using namespace EventInfo;
80   event.visit(
81       [&](const CreatureMoved& info) {
82         if (info.creature == getCreature())
83           visibilityMap->update(getCreature(), getCreature()->getVisibleTiles());
84       },
85       [&](const Projectile& info) {
86         if (getCreature()->canSee(info.begin) || getCreature()->canSee(info.end))
87           getView()->animateObject(info.begin.getCoord(), info.end.getCoord(), info.viewId);
88       },
89       [&](const Explosion& info) {
90         if (getCreature()->getPosition().isSameLevel(info.pos)) {
91           if (getCreature()->canSee(info.pos))
92             getView()->animation(info.pos.getCoord(), AnimationId::EXPLOSION);
93           else
94             privateMessage("BOOM!");
95         }
96       },
97       [&](const Alarm& info) {
98         Position pos = info.pos;
99         Position myPos = getCreature()->getPosition();
100         if (pos == myPos)
101           privateMessage("An alarm sounds near you.");
102         else if (pos.isSameLevel(myPos))
103           privateMessage("An alarm sounds in the " +
104               getCardinalName(myPos.getDir(pos).getBearing().getCardinalDir()));
105       },
106       [&](const ConqueredEnemy& info) {
107         if (adventurer) {
108           if (auto& name = info.collective->getName())
109             privateMessage(PlayerMessage("The tribe of " + name->full + " is destroyed.",
110                   MessagePriority::CRITICAL));
111           else
112             privateMessage(PlayerMessage("An unnamed tribe is destroyed.", MessagePriority::CRITICAL));
113         }
114       },
115       [&](const WonGame&) {
116         if (adventurer)
117           getGame()->conquered(*getCreature()->getName().first(), getCreature()->getKills().getSize(),
118               getCreature()->getPoints());
119       },
120       [&](const auto&) {}
121   );
122 }
123 
getSlotSuffix(EquipmentSlot slot)124 static string getSlotSuffix(EquipmentSlot slot) {
125   return "(equipped)";
126 }
127 
getInventoryItemName(WConstItem item,bool plural) const128 string Player::getInventoryItemName(WConstItem item, bool plural) const {
129   if (getCreature()->getEquipment().isEquipped(item))
130     return item->getNameAndModifiers(plural, getCreature()) + " "
131       + getSlotSuffix(item->getEquipmentSlot());
132   else
133     return item->getNameAndModifiers(plural, getCreature());
134 }
135 
getItemNames(vector<WItem> items,vector<ListElem> & names,vector<vector<WItem>> & groups,ItemPredicate predicate)136 void Player::getItemNames(vector<WItem> items, vector<ListElem>& names, vector<vector<WItem> >& groups,
137     ItemPredicate predicate) {
138   map<string, vector<WItem> > ret = groupBy<WItem, string>(items,
139       [this] (WItem const& item) { return getInventoryItemName(item, false); });
140   for (auto elem : ret) {
141     if (elem.second.size() == 1)
142       names.push_back(ListElem(getInventoryItemName(elem.second[0], false),
143           predicate(elem.second[0]) ? ListElem::NORMAL : ListElem::INACTIVE).setTip(elem.second[0]->getDescription()));
144     else
145       names.push_back(ListElem(toString<int>(elem.second.size()) + " "
146             + getInventoryItemName(elem.second[0], true),
147           predicate(elem.second[0]) ? ListElem::NORMAL : ListElem::INACTIVE).setTip(elem.second[0]->getDescription()));
148     groups.push_back(elem.second);
149   }
150 }
151 
pickUpItemAction(int numStack,bool multi)152 void Player::pickUpItemAction(int numStack, bool multi) {
153   CHECK(numStack >= 0);
154   auto stacks = getCreature()->stackItems(getCreature()->getPickUpOptions());
155   if (getUsableUsageType()) {
156     --numStack;
157     if (numStack == -1) {
158       getCreature()->applySquare(getCreature()->getPosition()).perform(getCreature());
159       return;
160     }
161   }
162   if (numStack < stacks.size()) {
163     vector<WItem> items = stacks[numStack];
164     if (multi && items.size() > 1) {
165       auto num = getView()->getNumber("Pick up how many " + items[0]->getName(true) + "?", 1, items.size());
166       if (!num)
167         return;
168       items = getPrefix(items, *num);
169     }
170     tryToPerform(getCreature()->pickUp(items));
171   }
172 }
173 
tryToPerform(CreatureAction action)174 bool Player::tryToPerform(CreatureAction action) {
175   if (action)
176     action.perform(getCreature());
177   else
178     privateMessage(action.getFailedReason());
179   return !!action;
180 }
181 
182 ItemClass typeDisplayOrder[] {
183   ItemClass::WEAPON,
184     ItemClass::RANGED_WEAPON,
185     ItemClass::ARMOR,
186     ItemClass::POTION,
187     ItemClass::SCROLL,
188     ItemClass::FOOD,
189     ItemClass::BOOK,
190     ItemClass::AMULET,
191     ItemClass::RING,
192     ItemClass::TOOL,
193     ItemClass::CORPSE,
194     ItemClass::OTHER,
195     ItemClass::GOLD
196 };
197 
getText(ItemClass type)198 static string getText(ItemClass type) {
199   switch (type) {
200     case ItemClass::WEAPON: return "Weapons";
201     case ItemClass::RANGED_WEAPON: return "Ranged weapons";
202     case ItemClass::AMULET: return "Amulets";
203     case ItemClass::RING: return "Rings";
204     case ItemClass::ARMOR: return "Armor";
205     case ItemClass::SCROLL: return "Scrolls";
206     case ItemClass::POTION: return "Potions";
207     case ItemClass::FOOD: return "Comestibles";
208     case ItemClass::BOOK: return "Books";
209     case ItemClass::TOOL: return "Tools";
210     case ItemClass::CORPSE: return "Corpses";
211     case ItemClass::OTHER: return "Other";
212     case ItemClass::GOLD: return "Gold";
213   }
214   FATAL << int(type);
215   return "";
216 }
217 
218 
chooseItem(const string & text,ItemPredicate predicate,optional<UserInputId> exitAction)219 vector<WItem> Player::chooseItem(const string& text, ItemPredicate predicate, optional<UserInputId> exitAction) {
220   map<ItemClass, vector<WItem> > typeGroups = groupBy<WItem, ItemClass>(
221       getCreature()->getEquipment().getItems(), [](WItem const& item) { return item->getClass();});
222   vector<ListElem> names;
223   vector<vector<WItem> > groups;
224   for (auto elem : typeDisplayOrder)
225     if (typeGroups[elem].size() > 0) {
226       names.push_back(ListElem(getText(elem), ListElem::TITLE));
227       getItemNames(typeGroups[elem], names, groups, predicate);
228     }
229   optional<int> index = getView()->chooseFromList(text, names, 0, MenuType::NORMAL, nullptr, exitAction);
230   if (index)
231     return groups[*index];
232   return vector<WItem>();
233 }
234 
applyItem(vector<WItem> items)235 void Player::applyItem(vector<WItem> items) {
236   if (getCreature()->isAffected(LastingEffect::BLIND) &&
237       contains({ItemClass::SCROLL, ItemClass::BOOK}, items[0]->getClass())) {
238     privateMessage("You can't read while blind!");
239     return;
240   }
241   if (items[0]->getApplyTime() > 1) {
242     for (WConstCreature c : getCreature()->getVisibleEnemies())
243       if (getCreature()->getPosition().dist8(c->getPosition()) < 3) {
244         if (!getView()->yesOrNoPrompt("Applying " + items[0]->getAName() + " takes " +
245             toString(items[0]->getApplyTime()) + " turns. Are you sure you want to continue?"))
246           return;
247         else
248           break;
249       }
250   }
251   tryToPerform(getCreature()->applyItem(items[0]));
252 }
253 
chooseDirection(const string & s)254 optional<Vec2> Player::chooseDirection(const string& s) {
255   return getView()->chooseDirection(getCreature()->getPosition().getCoord(), s);
256 }
257 
throwItem(vector<WItem> items,optional<Vec2> dir)258 void Player::throwItem(vector<WItem> items, optional<Vec2> dir) {
259   if (!dir) {
260     auto cDir = chooseDirection("Which direction do you want to throw?");
261     if (!cDir)
262       return;
263     dir = *cDir;
264   }
265   tryToPerform(getCreature()->throwItem(items[0], *dir));
266 }
267 
getItemActions(const vector<WItem> & item) const268 vector<ItemAction> Player::getItemActions(const vector<WItem>& item) const {
269   vector<ItemAction> actions;
270   if (getCreature()->equip(item[0]))
271     actions.push_back(ItemAction::EQUIP);
272   if (getCreature()->applyItem(item[0]))
273     actions.push_back(ItemAction::APPLY);
274   if (getCreature()->unequip(item[0]))
275     actions.push_back(ItemAction::UNEQUIP);
276   else {
277     actions.push_back(ItemAction::THROW);
278     actions.push_back(ItemAction::DROP);
279     if (item.size() > 1)
280       actions.push_back(ItemAction::DROP_MULTI);
281     if (item[0]->getShopkeeper(getCreature()))
282       actions.push_back(ItemAction::PAY);
283     for (Position v : getCreature()->getPosition().neighbors8())
284       if (WCreature c = v.getCreature())
285         if (getCreature()->isFriend(c)/* && c->canTakeItems(item)*/) {
286           actions.push_back(ItemAction::GIVE);
287           break;
288         }
289   }
290   actions.push_back(ItemAction::NAME);
291   return actions;
292 }
293 
handleItems(const EntitySet<Item> & itemIds,ItemAction action)294 void Player::handleItems(const EntitySet<Item>& itemIds, ItemAction action) {
295   vector<WItem> items = getCreature()->getEquipment().getItems(
296       [&](WConstItem it) { return itemIds.contains(it);});
297   //CHECK(items.size() == itemIds.size()) << int(items.size()) << " " << int(itemIds.size());
298   // the above assertion fails for unknown reason, so just fail this softly.
299   if (items.empty() || (items.size() == 1 && action == ItemAction::DROP_MULTI))
300     return;
301   switch (action) {
302     case ItemAction::DROP: tryToPerform(getCreature()->drop(items)); break;
303     case ItemAction::DROP_MULTI:
304       if (auto num = getView()->getNumber("Drop how many " + items[0]->getName(true) + "?", 1, items.size()))
305         tryToPerform(getCreature()->drop(getPrefix(items, *num)));
306       break;
307     case ItemAction::THROW: throwItem(items); break;
308     case ItemAction::APPLY: applyItem(items); break;
309     case ItemAction::UNEQUIP: tryToPerform(getCreature()->unequip(items[0])); break;
310     case ItemAction::GIVE: giveAction(items); break;
311     case ItemAction::PAY: payForItemAction(items); break;
312     case ItemAction::EQUIP: tryToPerform(getCreature()->equip(items[0])); break;
313     case ItemAction::NAME:
314       if (auto name = getView()->getText("Enter a name for " + items[0]->getTheName(),
315           items[0]->getArtifactName().value_or(""), 14))
316         items[0]->setArtifactName(*name);
317       break;
318     default: FATAL << "Unhandled item action " << int(action);
319   }
320 }
321 
hideAction()322 void Player::hideAction() {
323   tryToPerform(getCreature()->hide());
324 }
325 
interruptedByEnemy()326 bool Player::interruptedByEnemy() {
327   vector<WCreature> enemies = getCreature()->getVisibleEnemies();
328   vector<string> ignoreCreatures { "a boar" ,"a deer", "a fox", "a vulture", "a rat", "a jackal", "a boulder" };
329   if (enemies.size() > 0) {
330     for (WCreature c : enemies)
331       if (!ignoreCreatures.contains(c->getName().a())) {
332         privateMessage("You notice " + c->getName().a());
333         return true;
334       }
335   }
336   return false;
337 }
338 
travelAction()339 void Player::travelAction() {
340 /*  updateView = true;
341   if (!getCreature()->move(travelDir) || getView()->travelInterrupt() || interruptedByEnemy()) {
342     travelling = false;
343     return;
344   }
345   tryToPerform(getCreature()->move(travelDir));
346   const Location* currentLocation = getCreature()->getPosition().getLocation();
347   if (lastLocation != currentLocation && currentLocation != nullptr && currentLocation->getName()) {
348     privateMessage("You arrive at " + addAParticle(*currentLocation->getName()));
349     travelling = false;
350     return;
351   }
352   vector<Vec2> squareDirs = getCreature()->getPosition().getTravelDir();
353   if (squareDirs.size() != 2) {
354     travelling = false;
355     INFO << "Stopped by multiple routes";
356     return;
357   }
358   optional<int> myIndex = findElement(squareDirs, -travelDir);
359   if (!myIndex) { // This was an assertion but was failing
360     travelling = false;
361     INFO << "Stopped by bad travel data";
362     return;
363   }
364   travelDir = squareDirs[(*myIndex + 1) % 2];*/
365 }
366 
targetAction()367 void Player::targetAction() {
368   updateView = true;
369   CHECK(target);
370   if (getCreature()->getPosition() == *target || getView()->travelInterrupt()) {
371     target = none;
372     return;
373   }
374   if (auto action = getCreature()->moveTowards(*target, Creature::NavigationFlags().noDestroying()))
375     action.perform(getCreature());
376   else
377     target = none;
378   if (interruptedByEnemy() || !isTravelEnabled())
379     target = none;
380 }
381 
payForItemAction(const vector<WItem> & items)382 void Player::payForItemAction(const vector<WItem>& items) {
383   int totalPrice = (int) items.size() * items[0]->getPrice();
384   for (auto item : items) {
385     CHECK(item->getShopkeeper(getCreature()));
386     CHECK(item->getPrice() == items[0]->getPrice());
387   }
388   vector<WItem> gold = getCreature()->getGold(totalPrice);
389   int canPayFor = (int) gold.size() / items[0]->getPrice();
390   if (canPayFor == 0)
391     privateMessage("You don't have enough gold to pay.");
392   else if (canPayFor == items.size() || getView()->yesOrNoPrompt("You only have enough gold for " +
393       toString(canPayFor) + " " + items[0]->getName(canPayFor > 1, getCreature()) + ". Still pay?"))
394     tryToPerform(getCreature()->payFor(getPrefix(items, canPayFor)));
395 }
396 
payForAllItemsAction()397 void Player::payForAllItemsAction() {
398   if (int totalDebt = getCreature()->getDebt().getTotal()) {
399     auto gold = getCreature()->getGold(totalDebt);
400     if (gold.size() < totalDebt)
401       privateMessage("You don't have enough gold to pay for everything.");
402     else {
403       for (auto creatureId : getCreature()->getDebt().getCreditors())
404         for (WCreature c : getCreature()->getVisibleCreatures())
405           if (c->getUniqueId() == creatureId &&
406               getView()->yesOrNoPrompt("Give " + c->getName().the() + " " + toString(gold.size()) + " gold?")) {
407               if (tryToPerform(getCreature()->give(c, gold)))
408                 for (auto item : getCreature()->getEquipment().getItems())
409                   item->setShopkeeper(nullptr);
410       }
411     }
412   }
413 }
414 
giveAction(vector<WItem> items)415 void Player::giveAction(vector<WItem> items) {
416   if (items.size() > 1) {
417     if (auto num = getView()->getNumber("Give how many " + items[0]->getName(true) + "?", 1, items.size()))
418       items = getPrefix(items, *num);
419     else
420       return;
421   }
422   vector<WCreature> creatures;
423   for (Position pos : getCreature()->getPosition().neighbors8())
424     if (WCreature c = pos.getCreature())
425       creatures.push_back(c);
426   if (creatures.size() == 1 && getView()->yesOrNoPrompt("Give " + items[0]->getTheName(items.size() > 1) +
427         " to " + creatures[0]->getName().the() + "?"))
428     tryToPerform(getCreature()->give(creatures[0], items));
429   else if (auto dir = chooseDirection("Give whom?"))
430     if (WCreature whom = getCreature()->getPosition().plus(*dir).getCreature())
431       tryToPerform(getCreature()->give(whom, items));
432 }
433 
chatAction(optional<Vec2> dir)434 void Player::chatAction(optional<Vec2> dir) {
435   vector<WCreature> creatures;
436   for (Position pos : getCreature()->getPosition().neighbors8())
437     if (WCreature c = pos.getCreature())
438       creatures.push_back(c);
439   if (creatures.size() == 1 && !dir) {
440     tryToPerform(getCreature()->chatTo(creatures[0]));
441   } else
442   if (creatures.size() > 1 || dir) {
443     if (!dir)
444       dir = chooseDirection("Which direction?");
445     if (!dir)
446       return;
447     if (WCreature c = getCreature()->getPosition().plus(*dir).getCreature())
448       tryToPerform(getCreature()->chatTo(c));
449   }
450 }
451 
fireAction()452 void Player::fireAction() {
453   if (auto testAction = getCreature()->fire(Vec2(1, 0))) {
454     if (auto dir = chooseDirection("Fire which direction?"))
455       fireAction(*dir);
456   } else
457     privateMessage(testAction.getFailedReason());
458 }
459 
fireAction(Vec2 dir)460 void Player::fireAction(Vec2 dir) {
461   tryToPerform(getCreature()->fire(dir));
462 }
463 
spellAction(SpellId id)464 void Player::spellAction(SpellId id) {
465   Spell* spell = Spell::get(id);
466   if (!spell->isDirected())
467     tryToPerform(getCreature()->castSpell(spell));
468   else if (auto dir = chooseDirection("Which direction?"))
469     tryToPerform(getCreature()->castSpell(spell, *dir));
470 }
471 
getMemory() const472 const MapMemory& Player::getMemory() const {
473   return *levelMemory;
474 }
475 
sleeping()476 void Player::sleeping() {
477   if (getCreature()->isAffected(LastingEffect::HALLU))
478     ViewObject::setHallu(true);
479   else
480     ViewObject::setHallu(false);
481   MEASURE(
482       getView()->updateView(this, false),
483       "level render time");
484 }
485 
486 static bool displayTravelInfo = true;
487 
creatureAction(Creature::Id id)488 void Player::creatureAction(Creature::Id id) {
489   if (getCreature()->getUniqueId() == id)
490     tryToPerform(getCreature()->wait());
491   else
492     for (WCreature c : getCreature()->getVisibleCreatures())
493       if (c->getUniqueId() == id) {
494         if (!getCreature()->isEnemy(c) || !tryToPerform(getCreature()->attack(c)))
495           tryToPerform(getCreature()->moveTowards(c->getPosition()));
496       }
497 }
498 
extendedAttackAction(Creature::Id id)499 void Player::extendedAttackAction(Creature::Id id) {
500   for (Position pos : getCreature()->getPosition().neighbors8())
501     if (WCreature c = pos.getCreature())
502       if (c->getUniqueId() == id) {
503         extendedAttackAction(c);
504         return;
505       }
506 }
507 
extendedAttackAction(WCreature other)508 void Player::extendedAttackAction(WCreature other) {
509   vector<ListElem> elems;
510   vector<AttackLevel> levels = getCreature()->getBody().getAttackLevels();
511   for (auto level : levels)
512     switch (level) {
513       case AttackLevel::LOW: elems.push_back(ListElem("Low").setTip("Aim at lower parts of the body.")); break;
514       case AttackLevel::MIDDLE: elems.push_back(ListElem("Middle").setTip("Aim at middle parts of the body.")); break;
515       case AttackLevel::HIGH: elems.push_back(ListElem("High").setTip("Aim at higher parts of the body.")); break;
516     }
517   elems.push_back(ListElem("Wild").setTip("+20\% damage, -20\% accuracy, +50\% time spent."));
518   elems.push_back(ListElem("Swift").setTip("-20\% damage, +20\% accuracy, -30\% time spent."));
519   if (auto ind = getView()->chooseFromList("Choose attack parameters:", elems)) {
520     if (*ind < levels.size())
521       getCreature()->attack(other, CONSTRUCT(Creature::AttackParams, c.level = levels[*ind];)).perform(getCreature());
522     else
523       getCreature()->attack(other, CONSTRUCT(Creature::AttackParams, c.mod = Creature::AttackParams::Mod(*ind - levels.size());)).perform(getCreature());
524   }
525 }
526 
retireMessages()527 void Player::retireMessages() {
528   auto& messages = messageBuffer->current;
529   if (!messages.empty()) {
530     messages = {messages.back()};
531     messages[0].setFreshness(0);
532   }
533 }
534 
getCommands() const535 vector<Player::CommandInfo> Player::getCommands() const {
536   bool canChat = false;
537   for (Position pos : getCreature()->getPosition().neighbors8())
538     if (WCreature c = pos.getCreature())
539       canChat = true;
540   return {
541     {PlayerInfo::CommandInfo{"Fire ranged weapon", 'f', "", true},
542       [] (Player* player) { player->fireAction(); }, false},
543     {PlayerInfo::CommandInfo{"Wait", ' ', "Skip this turn.", true},
544       [] (Player* player) { player->tryToPerform(player->getCreature()->wait()); }, false},
545     {PlayerInfo::CommandInfo{"Travel", 't', "Travel to another site.", !getGame()->isSingleModel()},
546       [] (Player* player) { player->getGame()->transferAction(player->getTeam()); }, false},
547     {PlayerInfo::CommandInfo{"Chat", 'c', "Chat with someone.", canChat},
548       [] (Player* player) { player->chatAction(); }, false},
549     {PlayerInfo::CommandInfo{"Hide", 'h', "Hide behind or under a terrain feature or piece of furniture.",
550         !!getCreature()->hide()},
551       [] (Player* player) { player->hideAction(); }, false},
552     /*{PlayerInfo::CommandInfo{"Pay for all items", 'p', "Pay debt to a shopkeeper.", true},
553       [] (Player* player) { player->payForAllItemsAction();}, false},*/
554     {PlayerInfo::CommandInfo{"Drop everything", none, "Drop all items in possession.", !getCreature()->getEquipment().isEmpty()},
555       [] (Player* player) { auto c = player->getCreature(); player->tryToPerform(c->drop(c->getEquipment().getItems())); }, false},
556     {PlayerInfo::CommandInfo{"Message history", 'm', "Show message history.", true},
557       [] (Player* player) { player->showHistory(); }, false},
558   };
559 }
560 
makeMove()561 void Player::makeMove() {
562   if (!isSubscribed())
563     subscribeTo(getCreature()->getPosition().getModel());
564   if (adventurer)
565     considerAdventurerMusic();
566   if (getCreature()->isAffected(LastingEffect::HALLU))
567     ViewObject::setHallu(true);
568   else
569     ViewObject::setHallu(false);
570   //if (updateView) { Check disabled so that we update in every frame to avoid some square refreshing issues.
571     updateView = false;
572     for (Position pos : getCreature()->getVisibleTiles()) {
573       ViewIndex index;
574       pos.getViewIndex(index, getCreature());
575       levelMemory->update(pos, index);
576     }
577     MEASURE(
578         getView()->updateView(this, false),
579         "level render time");
580   //}
581   getView()->refreshView();
582   /*if (displayTravelInfo && getCreature()->getPosition().getName() == "road"
583       && getGame()->getOptions()->getBoolValue(OptionId::HINTS)) {
584     getView()->presentText("", "Use ctrl + arrows to travel quickly on roads and corridors.");
585     displayTravelInfo = false;
586   }*/
587   if (displayGreeting && getGame()->getOptions()->getBoolValue(OptionId::HINTS)) {
588     CHECK(getCreature()->getName().first());
589     getView()->updateView(this, true);
590     getView()->presentText("", "Dear " + *getCreature()->getName().first() +
591         ",\n \n \tIf you are reading this letter, then you have arrived in the valley of " +
592         getGame()->getWorldName() + ". This land is swarming with evil, and you need to destroy all of it, "
593         "like in a bad RPG game. Humans, dwarves and elves are your allies. "
594         "Find them, talk to them, they will help you. Let your sword guide you.\n \n \nYours, " +
595         NameGenerator::get(Random.choose(NameGeneratorId::FIRST_MALE, NameGeneratorId::FIRST_FEMALE))->getNext() +
596         "\n \nPS.: Beware the orcs!");
597     displayGreeting = false;
598     getView()->updateView(this, false);
599   }
600   UserInput action = getView()->getAction();
601   if (travelling && action.getId() == UserInputId::IDLE)
602     travelAction();
603   else if (target && action.getId() == UserInputId::IDLE)
604     targetAction();
605   else {
606     INFO << "Action " << int(action.getId());
607   vector<Vec2> direction;
608   bool travel = false;
609   bool wasJustTravelling = travelling || !!target;
610   if (action.getId() != UserInputId::IDLE) {
611     if (action.getId() != UserInputId::REFRESH)
612       retireMessages();
613     if (action.getId() == UserInputId::TILE_CLICK) {
614       travelling = false;
615       if (target)
616         target = none;
617       getView()->resetCenter();
618     }
619     updateView = true;
620   }
621   switch (action.getId()) {
622     case UserInputId::FIRE: fireAction(action.get<Vec2>()); break;
623     case UserInputId::TRAVEL: travel = true;
624       FALLTHROUGH;
625     case UserInputId::MOVE: direction.push_back(action.get<Vec2>()); break;
626     case UserInputId::TILE_CLICK: {
627       Position newPos = getCreature()->getPosition().withCoord(action.get<Vec2>());
628       if (newPos.dist8(getCreature()->getPosition()) == 1) {
629         Vec2 dir = getCreature()->getPosition().getDir(newPos);
630         if (WCreature c = newPos.getCreature()) {
631           creatureAction(c->getUniqueId());
632           break;
633         }
634         direction.push_back(dir);
635       } else
636       if (newPos != getCreature()->getPosition() && !wasJustTravelling)
637         target = newPos;
638       break;
639     }
640     case UserInputId::INVENTORY_ITEM:
641       handleItems(action.get<InventoryItemInfo>().items, action.get<InventoryItemInfo>().action); break;
642     case UserInputId::PICK_UP_ITEM: pickUpItemAction(action.get<int>()); break;
643     case UserInputId::PICK_UP_ITEM_MULTI: pickUpItemAction(action.get<int>(), true); break;
644     case UserInputId::CAST_SPELL: spellAction(action.get<SpellId>()); break;
645     case UserInputId::DRAW_LEVEL_MAP: getView()->drawLevelMap(this); break;
646     case UserInputId::CREATURE_BUTTON: creatureAction(action.get<Creature::Id>()); break;
647     case UserInputId::CREATURE_BUTTON2: extendedAttackAction(action.get<Creature::Id>()); break;
648     case UserInputId::EXIT: getGame()->exitAction(); return;
649     case UserInputId::APPLY_EFFECT:
650       if (auto effect = PrettyPrinting::parseObject<Effect>(action.get<string>()))
651         effect->applyToCreature(getCreature(), nullptr);
652       else
653         getView()->presentText("Sorry", "Couldn't parse \"" + action.get<string>() + "\"");
654       break;
655     case UserInputId::CREATE_ITEM:
656       if (auto itemType = PrettyPrinting::parseObject<ItemType>(action.get<string>()))
657         getCreature()->take(itemType->get());
658       else
659         getView()->presentText("Sorry", "Couldn't parse \"" + action.get<string>() + "\"");
660       break;
661     case UserInputId::SUMMON_ENEMY:
662       if (auto id = PrettyPrinting::parseObject<CreatureId>(action.get<string>())) {
663         auto factory = CreatureFactory::singleCreature(TribeId::getMonster(), *id);
664         Effect::summon(getCreature()->getPosition(), factory, 1, 1000, 3);
665       } else
666         getView()->presentText("Sorry", "Couldn't parse \"" + action.get<string>() + "\"");
667       break;
668     case UserInputId::PLAYER_COMMAND: {
669         int index = action.get<int>();
670         auto commands = getCommands();
671         if (index >= 0 && index < commands.size()) {
672           commands[index].perform(this);
673           if (commands[index].actionKillsController)
674             return;
675         }
676       }
677       break;
678     case UserInputId::PAY_DEBT:
679         payForAllItemsAction();
680       break;
681     case UserInputId::TUTORIAL_CONTINUE:
682         if (tutorial)
683           tutorial->continueTutorial(getGame());
684         break;
685     case UserInputId::TUTORIAL_GO_BACK:
686         if (tutorial)
687           tutorial->goBack();
688         break;
689 #ifndef RELEASE
690     case UserInputId::CHEAT_ATTRIBUTES:
691       getCreature()->getAttributes().setBaseAttr(AttrType::DAMAGE, 80);
692       getCreature()->getAttributes().setBaseAttr(AttrType::DEFENSE, 80);
693       getCreature()->getAttributes().setBaseAttr(AttrType::SPELL_DAMAGE, 80);
694       getCreature()->getAttributes().setBaseAttr(AttrType::SPEED, 200);
695       getCreature()->addPermanentEffect(LastingEffect::FLYING, true);
696       break;
697 #endif
698     default: break;
699   }
700   if (getCreature()->isAffected(LastingEffect::SLEEP)) {
701     onFellAsleep();
702     return;
703   }
704   for (Vec2 dir : direction)
705     if (travel) {
706       if (WCreature other = getCreature()->getPosition().plus(dir).getCreature())
707         extendedAttackAction(other);
708       else {
709 /*        vector<Vec2> squareDirs = getCreature()->getPosition().getTravelDir();
710         if (findElement(squareDirs, dir)) {
711           travelDir = dir;
712           lastLocation = getCreature()->getPosition().getLocation();
713           travelling = true;
714           travelAction();
715         }*/
716       }
717     } else {
718       moveAction(dir);
719       break;
720     }
721   }
722 }
723 
showHistory()724 void Player::showHistory() {
725   PlayerMessage::presentMessages(getView(), messageBuffer->history);
726 }
727 
getForceMovementQuestion(Position pos,WConstCreature creature)728 static string getForceMovementQuestion(Position pos, WConstCreature creature) {
729   if (pos.canEnterEmpty(creature))
730     return "";
731   else if (pos.isBurning())
732     return "Walk into the fire?";
733   else if (pos.canEnterEmpty(MovementTrait::SWIM))
734     return "The water is very deep, are you sure?";
735   else if (pos.sunlightBurns() && creature->getMovementType().isSunlightVulnerable())
736     return "Walk into the sunlight?";
737   else if (pos.isTribeForbidden(creature->getTribeId()))
738     return "Walk into the forbidden zone?";
739   else
740     return "Walk into the " + pos.getName() + "?";
741 }
742 
moveAction(Vec2 dir)743 void Player::moveAction(Vec2 dir) {
744   if (tryToPerform(getCreature()->move(dir)))
745     return;
746   if (auto action = getCreature()->forceMove(dir)) {
747     string nextQuestion = getForceMovementQuestion(getCreature()->getPosition().plus(dir), getCreature());
748     string hereQuestion = getForceMovementQuestion(getCreature()->getPosition(), getCreature());
749     if (hereQuestion == nextQuestion || getView()->yesOrNoPrompt(nextQuestion, true))
750       action.perform(getCreature());
751   } else if (auto action = getCreature()->bumpInto(dir))
752     action.perform(getCreature());
753   else if (!getCreature()->getPosition().plus(dir).canEnterEmpty(getCreature()))
754     tryToPerform(getCreature()->destroy(dir, DestroyAction::Type::BASH));
755 }
756 
isPlayer() const757 bool Player::isPlayer() const {
758   return true;
759 }
760 
privateMessage(const PlayerMessage & message)761 void Player::privateMessage(const PlayerMessage& message) {
762   if (View* view = getView()) {
763     if (message.getText().size() < 2)
764       return;
765     if (auto title = message.getAnnouncementTitle())
766       view->presentText(*title, message.getText());
767     else {
768       messageBuffer->history.push_back(message);
769       auto& messages = messageBuffer->current;
770       if (!messages.empty() && messages.back().getFreshness() < 1)
771         messages.clear();
772       messages.emplace_back(message);
773       if (message.getPriority() == MessagePriority::CRITICAL)
774         view->presentText("Important!", message.getText());
775     }
776   }
777 }
778 
getLevel() const779 WLevel Player::getLevel() const {
780   return getCreature()->getLevel();
781 }
782 
getGame() const783 WGame Player::getGame() const {
784   if (auto creature = getCreature())
785     return creature->getGame();
786   else
787     return nullptr;
788 }
789 
getView() const790 View* Player::getView() const {
791   if (WGame game = getGame())
792     return game->getView();
793   else
794     return nullptr;
795 }
796 
getPosition() const797 Vec2 Player::getPosition() const {
798   return getCreature()->getPosition().getCoord();
799 }
800 
801 static MessageGenerator messageGenerator(MessageGenerator::SECOND_PERSON);
802 
getMessageGenerator() const803 MessageGenerator& Player::getMessageGenerator() const {
804   return messageGenerator;
805 }
806 
onStartedControl()807 void Player::onStartedControl() {
808   getGame()->addPlayer(getCreature());
809 }
810 
onEndedControl()811 void Player::onEndedControl() {
812   if (auto game = getGame()) // if the whole Game is being destructed then we get null here
813     game->removePlayer(getCreature());
814 }
815 
getViewIndex(Vec2 pos,ViewIndex & index) const816 void Player::getViewIndex(Vec2 pos, ViewIndex& index) const {
817   bool canSee = visibilityMap->isVisible(Position(pos, getLevel())) ||
818       getGame()->getOptions()->getBoolValue(OptionId::SHOW_MAP);
819   Position position = getCreature()->getPosition().withCoord(pos);
820   if (canSee)
821     position.getViewIndex(index, getCreature());
822   else
823     index.setHiddenId(position.getViewObject().id());
824   if (!canSee)
825     if (auto memIndex = getMemory().getViewIndex(position))
826       index.mergeFromMemory(*memIndex);
827   if (position.isTribeForbidden(getCreature()->getTribeId()))
828     index.setHighlight(HighlightType::FORBIDDEN_ZONE);
829   if (WConstCreature c = position.getCreature()) {
830     if ((canSee && getCreature()->canSeeInPosition(c)) || c == getCreature() ||
831         getCreature()->canSeeOutsidePosition(c)) {
832       index.insert(c->getViewObjectFor(getCreature()->getTribe()));
833       if (c == getCreature())
834         index.getObject(ViewLayer::CREATURE).setModifier(ViewObject::Modifier::PLAYER);
835       if (getTeam().contains(c))
836         index.getObject(ViewLayer::CREATURE).setModifier(ViewObject::Modifier::TEAM_HIGHLIGHT);
837       if (getCreature()->isEnemy(c))
838         index.getObject(ViewLayer::CREATURE).setModifier(ViewObject::Modifier::HOSTILE);
839     } else if (getCreature()->isUnknownAttacker(c))
840       index.insert(copyOf(ViewObject::unknownMonster()));
841   }
842  /* if (pos == getCreature()->getPosition() && index.hasObject(ViewLayer::CREATURE))
843       index.getObject(ViewLayer::CREATURE).setModifier(ViewObject::Modifier::TEAM_LEADER_HIGHLIGHT);*/
844 
845 }
846 
onKilled(WConstCreature attacker)847 void Player::onKilled(WConstCreature attacker) {
848   unsubscribe();
849   getView()->updateView(this, false);
850   if (getGame()->getPlayerCreatures().size() == 1 && getView()->yesOrNoPrompt("Display message history?"))
851     showHistory();
852   if (adventurer)
853     getGame()->gameOver(getCreature(), getCreature()->getKills().getSize(), "monsters", getCreature()->getPoints());
854 }
855 
onFellAsleep()856 void Player::onFellAsleep() {
857 }
858 
getTeam() const859 vector<WCreature> Player::getTeam() const {
860   return {getCreature()};
861 }
862 
isTravelEnabled() const863 bool Player::isTravelEnabled() const {
864   return true;
865 }
866 
getUsableUsageType() const867 optional<FurnitureUsageType> Player::getUsableUsageType() const {
868   if (auto furniture = getCreature()->getPosition().getFurniture(FurnitureLayer::MIDDLE))
869     if (furniture->canUse(getCreature()))
870       if (auto usageType = furniture->getUsageType())
871         if (!FurnitureUsage::getUsageQuestion(*usageType, getCreature()->getPosition().getName()).empty())
872           return usageType;
873   return none;
874 }
875 
refreshGameInfo(GameInfo & gameInfo) const876 void Player::refreshGameInfo(GameInfo& gameInfo) const {
877   gameInfo.messageBuffer = messageBuffer->current;
878   gameInfo.singleModel = getGame()->isSingleModel();
879   gameInfo.infoType = GameInfo::InfoType::PLAYER;
880   SunlightInfo sunlightInfo = getGame()->getSunlightInfo();
881   gameInfo.sunlightInfo.description = sunlightInfo.getText();
882   gameInfo.sunlightInfo.timeRemaining = (int) sunlightInfo.getTimeRemaining();
883   gameInfo.time = (int) getCreature()->getGame()->getGlobalTime();
884   gameInfo.playerInfo = PlayerInfo(getCreature());
885   auto& info = *gameInfo.playerInfo.getReferenceMaybe<PlayerInfo>();
886   info.team.clear();
887   auto team = getTeam();
888   auto leader = team[0];
889   if (team.size() > 1 && team[1]->isPlayer()) {
890     auto timeCmp = [](WConstCreature c1, WConstCreature c2) { return c1->getLocalTime() < c2->getLocalTime();};
891     sort(team.begin(), team.end(), timeCmp);
892   }
893   for (WConstCreature c : team)
894     info.team.push_back({c->getViewObject().id(), (int) c->getBestAttack().value, c->isPlayer(), c == leader});
895   info.levelName = getLevel()->getName();
896   info.lyingItems.clear();
897   if (auto usageType = getUsableUsageType()) {
898     string question = FurnitureUsage::getUsageQuestion(*usageType, getCreature()->getPosition().getName());
899     info.lyingItems.push_back(getFurnitureUsageInfo(question, getCreature()->getPosition().getViewObject().id()));
900   }
901   for (auto stack : getCreature()->stackItems(getCreature()->getPickUpOptions()))
902     info.lyingItems.push_back(getItemInfo(stack));
903   info.inventory.clear();
904   map<ItemClass, vector<WItem> > typeGroups = groupBy<WItem, ItemClass>(
905       getCreature()->getEquipment().getItems(), [](WItem const& item) { return item->getClass();});
906   info.debt = getCreature()->getDebt().getTotal();
907   for (auto elem : typeDisplayOrder)
908     if (typeGroups[elem].size() > 0)
909       append(info.inventory, getItemInfos(typeGroups[elem]));
910   info.commands = getCommands().transform([](const CommandInfo& info) -> PlayerInfo::CommandInfo { return info.commandInfo;});
911   if (tutorial)
912     tutorial->refreshInfo(getGame(), gameInfo.tutorial);
913 }
914 
getFurnitureUsageInfo(const string & question,ViewId viewId) const915 ItemInfo Player::getFurnitureUsageInfo(const string& question, ViewId viewId) const {
916   return CONSTRUCT(ItemInfo,
917     c.name = question;
918     c.fullName = c.name;
919     c.description = "Click to " + c.name;
920     c.number = 1;
921     c.viewId = viewId;);
922 }
923 
getItemInfo(const vector<WItem> & stack) const924 ItemInfo Player::getItemInfo(const vector<WItem>& stack) const {
925   return CONSTRUCT(ItemInfo,
926     c.name = stack[0]->getShortName(getCreature());
927     c.fullName = stack[0]->getNameAndModifiers(false, getCreature());
928     c.description = getCreature()->isAffected(LastingEffect::BLIND) ? "" : stack[0]->getDescription();
929     c.number = stack.size();
930     c.viewId = stack[0]->getViewObject().id();
931     for (auto it : stack)
932       c.ids.insert(it->getUniqueId());
933     c.actions = getItemActions(stack);
934     c.equiped = getCreature()->getEquipment().isEquipped(stack[0]);
935     c.weight = stack[0]->getWeight();
936     if (stack[0]->getShopkeeper(getCreature()))
937       c.price = make_pair(ViewId::GOLD, stack[0]->getPrice());
938   );
939 }
940 
getItemInfos(const vector<WItem> & items) const941 vector<ItemInfo> Player::getItemInfos(const vector<WItem>& items) const {
942   map<string, vector<WItem> > stacks = groupBy<WItem, string>(items,
943       [this] (WItem const& item) { return getInventoryItemName(item, false); });
944   vector<ItemInfo> ret;
945   for (auto elem : stacks)
946     ret.push_back(getItemInfo(elem.second));
947   return ret;
948 }
949 
getVisibleEnemies() const950 vector<Vec2> Player::getVisibleEnemies() const {
951   return getCreature()->getVisibleEnemies().transform(
952       [](WConstCreature c) { return c->getPosition().getCoord(); });
953 }
954 
getLocalTime() const955 double Player::getLocalTime() const {
956   return getCreature()->getLocalTime();
957 }
958 
getCenterType() const959 Player::CenterType Player::getCenterType() const {
960   return CenterType::FOLLOW;
961 }
962 
getUnknownLocations(WConstLevel level) const963 vector<Vec2> Player::getUnknownLocations(WConstLevel level) const {
964   vector<Vec2> ret;
965   for (auto col : getCreature()->getPosition().getModel()->getCollectives())
966     if (col->getLevel() == getLevel())
967       if (auto& pos = col->getTerritory().getCentralPoint())
968         if (!getMemory().getViewIndex(*pos))
969           ret.push_back(pos->getCoord());
970   return ret;
971 }
972 
considerAdventurerMusic()973 void Player::considerAdventurerMusic() {
974   for (WCollective col : getCreature()->getPosition().getModel()->getCollectives())
975     if (col->getVillainType() == VillainType::MAIN && !col->isConquered() &&
976         col->getTerritory().contains(getCreature()->getPosition())) {
977       getGame()->setCurrentMusic(MusicType::ADV_BATTLE, true);
978       return;
979     }
980   getGame()->setCurrentMusic(MusicType::ADV_PEACEFUL, true);
981 }
982 
983 REGISTER_TYPE(ListenerTemplate<Player>)
984