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