Peoplemon  0.1.0
Peoplemon 3 game source documentation
Interaction.cpp
Go to the documentation of this file.
2 
3 #include <BLIB/Serialization/JSON.hpp>
9 #include <Core/Events/Item.hpp>
10 #include <Core/Files/GameSave.hpp>
11 #include <Core/Items/Item.hpp>
12 #include <Core/Properties.hpp>
13 #include <Core/Systems/Systems.hpp>
14 
15 namespace core
16 {
17 namespace system
18 {
19 namespace
20 {
21 std::string trainerName(const std::string& n) { return "tnr:" + n; }
22 
23 std::string npcName(const std::string& n) { return "npc:" + n; }
24 
25 } // namespace
26 
28 : owner(owner)
29 , interactingEntity(bl::ecs::InvalidEntity)
30 , currentConversation(owner)
31 , interactingTrainer(nullptr) {}
32 
33 void Interaction::init() { bl::event::Dispatcher::subscribe(this); }
34 
35 bool Interaction::interact(bl::ecs::Entity interactor) {
36  const bl::tmap::Position* pos =
37  owner.engine().ecs().getComponent<bl::tmap::Position>(interactor);
38  if (!pos) return false;
39 
40  const bl::ecs::Entity interacted = owner.position().search(*pos, pos->direction, 1);
41  if (interacted != bl::ecs::InvalidEntity) {
42  BL_LOG_INFO << "Entity " << interactor << " interacted with entity: " << interacted;
43 
44  if (interactor != owner.player().player() && interacted != owner.player().player()) {
45  BL_LOG_WARN << "Nonplayer entity " << interactor << " interacted with " << interacted;
46  return false;
47  }
48  const bl::ecs::Entity nonplayer =
49  interactor != owner.player().player() ? interactor : interacted;
50 
51  const component::NPC* npc = owner.engine().ecs().getComponent<component::NPC>(nonplayer);
52  if (npc) {
53  setTalked(npcName(npc->name()));
54  owner.controllable().setEntityLocked(nonplayer, true);
55  faceEntity(nonplayer, owner.player().player());
56  faceEntity(owner.player().player(), nonplayer);
57  currentConversation.setConversation(
58  npc->conversation(), nonplayer, npcTalkedTo(npc->name()));
59  interactingEntity = nonplayer;
60  processConversationNode();
61  return true;
62  }
63  else {
64  component::Trainer* trainer =
65  owner.engine().ecs().getComponent<component::Trainer>(nonplayer);
66  if (trainer) {
67  setTalked(trainerName(trainer->name()));
68  owner.controllable().setEntityLocked(nonplayer, true);
69  faceEntity(nonplayer, owner.player().player());
70  faceEntity(owner.player().player(), nonplayer);
71  if (!trainer->defeated()) {
72  interactingTrainer = trainer;
73  currentConversation.setConversation(trainer->beforeBattleConversation(),
74  nonplayer,
75  trainerTalkedto(trainer->name()));
76  }
77  else {
78  currentConversation.setConversation(trainer->afterBattleConversation(),
79  nonplayer,
80  trainerTalkedto(trainer->name()));
81  }
82  interactingEntity = nonplayer;
83  processConversationNode();
84  return true;
85  }
86  }
87 
88  // Check for item if player
89  if (interactor == owner.player().player()) {
90  const component::Item* ic =
91  owner.engine().ecs().getComponent<component::Item>(interacted);
92  if (ic) {
93  const std::string name = item::Item::getName(ic->id());
94  BL_LOG_INFO << "Player picked up: " << static_cast<unsigned int>(ic->id()) << " ("
95  << name << ")";
96  owner.player().state().bag.addItem(ic->id(), 1);
97  bl::event::Dispatcher::dispatch<event::ItemPickedUp>({ic->id()});
98  owner.engine().ecs().destroyEntity(interacted);
99  owner.hud().displayMessage("Picked up a " + name);
100  return true;
101  }
102  }
103  }
104 
105  // if nothing else see if map has interactable events
106  return owner.world().activeMap().interact(interactor, pos->move(pos->direction));
107 }
108 
109 void Interaction::processConversationNode() {
110  if (interactingEntity == bl::ecs::InvalidEntity) return;
111  if (currentConversation.finished()) {
112  if (interactingTrainer) { startBattle(); }
113  else { owner.controllable().resetEntityLock(interactingEntity); }
114  return;
115  }
116 
118  const file::Conversation::Node& node = currentConversation.currentNode();
119  switch (node.getType()) {
120  case E::Talk:
121  owner.hud().displayMessage(node.message(), std::bind(&Interaction::continuePressed, this));
122  break;
123 
124  case E::Prompt: {
125  std::vector<std::string> choices;
126  choices.reserve(node.choices().size());
127  for (const auto& pair : node.choices()) choices.push_back(pair.first);
128  owner.hud().promptUser(node.message(),
129  choices,
130  std::bind(&Interaction::choiceMade, this, std::placeholders::_1));
131  } break;
132 
133  case E::GiveItem:
134  owner.player().state().bag.addItem(node.item().id);
135  if (node.item().afterPrompt) {
136  // TODO - add prefix to item metadata for a/the use
137  owner.hud().displayMessage("Got a " + item::Item::getName(node.item().id),
138  std::bind(&Interaction::continuePressed, this));
139  }
140  break;
141 
142  case E::TakeItem:
143  if (node.item().beforePrompt) {
144  owner.hud().promptUser(
145  "Hand over a " + item::Item::getName(node.item().id),
146  {"Yes", "No"},
147  std::bind(&Interaction::giveItemDecided, this, std::placeholders::_1));
148  }
149  else {
150  if (owner.player().state().bag.removeItem(
151  currentConversation.currentNode().item().id)) {
152  currentConversation.notifyCheckPassed();
153  processConversationNode();
154  }
155  }
156  break;
157 
158  case E::GiveMoney:
159  owner.player().state().monei += node.money();
160  owner.hud().displayMessage("Received " + std::to_string(node.money()) + " monies",
161  std::bind(&Interaction::continuePressed, this));
162  break;
163 
164  case E::TakeMoney:
165  owner.hud().promptUser(
166  "Fork over " + std::to_string(node.money()) + " monies?",
167  {"Yes", "No"},
168  std::bind(&Interaction::giveMoneyDecided, this, std::placeholders::_1));
169  break;
170 
171  default:
172  BL_LOG_ERROR << "Invalid conversation node type " << node.getType() << ", terminating";
173  owner.controllable().resetEntityLock(interactingEntity);
174  interactingEntity = bl::ecs::InvalidEntity;
175  break;
176  }
177 }
178 
179 void Interaction::continuePressed() {
180  currentConversation.notifyNext();
181  processConversationNode();
182 }
183 
184 void Interaction::failMessageFinished() {
185  currentConversation.notifyCheckFailed();
186  processConversationNode();
187 }
188 
189 void Interaction::choiceMade(const std::string& c) {
190  currentConversation.notifyChoiceMade(c);
191  processConversationNode();
192 }
193 
194 void Interaction::giveItemDecided(const std::string& c) {
195  if (c == "Yes") {
196  if (owner.player().state().bag.removeItem(currentConversation.currentNode().item().id)) {
197  currentConversation.notifyCheckPassed();
198  processConversationNode();
199  }
200  else {
201  owner.hud().displayMessage("A voice echos in your head: YOU DON'T HAVE THAT ITEM BRO",
202  std::bind(&Interaction::failMessageFinished, this));
203  }
204  }
205  else {
206  currentConversation.notifyCheckFailed();
207  processConversationNode();
208  }
209 }
210 
211 void Interaction::giveMoneyDecided(const std::string& c) {
212  if (c == "Yes") {
213  const long money = currentConversation.currentNode().money();
214  if (owner.player().state().monei >= money) {
215  owner.player().state().monei -= money;
216  currentConversation.notifyCheckPassed();
217  processConversationNode();
218  }
219  else {
220  owner.hud().displayMessage("You don't have enough monei, get a job!",
221  std::bind(&Interaction::failMessageFinished, this));
222  }
223  }
224  else {
225  currentConversation.notifyCheckFailed();
226  processConversationNode();
227  }
228 }
229 
230 void Interaction::faceEntity(bl::ecs::Entity rot, bl::ecs::Entity face) {
231  bl::tmap::Position* mpos = owner.engine().ecs().getComponent<bl::tmap::Position>(rot);
232  const bl::tmap::Position* fpos = owner.engine().ecs().getComponent<bl::tmap::Position>(face);
233  if (mpos && fpos) {
234  const bl::tmap::Direction dir = bl::tmap::Position::facePosition(*mpos, *fpos);
235  const event::EntityRotated event(rot, dir, mpos->direction);
236  mpos->direction = dir;
237  bl::event::Dispatcher::dispatch<event::EntityRotated>(event);
238  }
239 }
240 
241 bool Interaction::npcTalkedTo(const std::string& name) const {
242  const auto wit = talkedTo.find(owner.world().activeMap().name());
243  if (wit == talkedTo.end()) return false;
244  return wit->second.find("npc:" + name) != wit->second.end();
245 }
246 
247 bool Interaction::trainerTalkedto(const std::string& name) const {
248  const auto wit = talkedTo.find(owner.world().activeMap().name());
249  if (wit == talkedTo.end()) return false;
250  return wit->second.find("tnr:" + name) != wit->second.end();
251 }
252 
253 bool Interaction::flagSet(const std::string& name) const { return flags.find(name) != flags.end(); }
254 
255 void Interaction::setFlag(const std::string& name) { flags.emplace(name); }
256 
257 void Interaction::observe(const event::GameSaveInitializing& save) {
258  save.gameSave.interaction.convFlags = &flags;
259  save.gameSave.interaction.talkedto = &talkedTo;
260 }
261 
262 void Interaction::observe(const event::BattleCompleted& battle) {
263  if (interactingTrainer && battle.type == battle::Battle::Type::Trainer) {
264  if (battle.result.localPlayerWon) {
265  currentConversation.setConversation(interactingTrainer->afterBattleConversation(),
266  interactingEntity,
267  trainerTalkedto(interactingTrainer->name()));
268  processConversationNode();
269  interactingTrainer->setDefeated();
270  }
271  interactingTrainer = nullptr;
272  }
273  owner.controllable().resetEntityLock(owner.player().player());
274 }
275 
276 void Interaction::setTalked(const std::string& name) {
277  auto wit = talkedTo.find(owner.world().activeMap().name());
278  if (wit == talkedTo.end()) {
279  wit = talkedTo.try_emplace(owner.world().activeMap().name()).first;
280  }
281  wit->second.emplace(name);
282 }
283 
284 void Interaction::startBattle() {
285  BL_LOG_INFO << "Starting trainer battle";
286  std::unique_ptr<battle::Battle> battle =
288  owner.world().activeMap().getLocationName(owner.player().position()),
289  owner.player(),
291 
292  std::vector<pplmn::BattlePeoplemon> team;
293  team.reserve(interactingTrainer->team().size());
294  for (const auto& ppl : interactingTrainer->team()) {
295  team.emplace_back(const_cast<pplmn::OwnedPeoplemon*>(&ppl));
296  }
297  battle->state.enemy().init(std::move(team),
298  std::make_unique<battle::AIController>(interactingTrainer->name(),
299  interactingTrainer->items()));
300  battle->setController(std::make_unique<battle::LocalBattleController>());
301  battle->state.enemy().getSubstate().trainer = interactingTrainer;
302 
303  bl::event::Dispatcher::dispatch<event::BattleStarted>({std::move(battle)});
304 }
305 
306 } // namespace system
307 } // namespace core
Core classes and functionality for both the editor and game.
void notifyNext()
Continues to the next node on player input.
bool finished() const
Returns whether or not the conversation is finished.
void notifyCheckFailed()
Jumps to the node for a check pass (ie take item/money fail etc)
void notifyCheckPassed()
Jumps to the node for a check pass (ie take item/money success etc)
void notifyChoiceMade(const std::string &choice)
Jumps to the node for the given choice if the current node is a Prompt.
const file::Conversation::Node & currentNode() const
Returns the current node.
void setConversation(const file::Conversation &conversation, bl::ecs::Entity entity, bool talked)
Sets the wrapper to point to the given underlying. The underlying must remain in scope during the lif...
static std::unique_ptr< Battle > create(bl::engine::Engine &engine, const std::string &location, system::Player &player, Type type)
Creates the battle struct and initializes the player battler.
Definition: Battle.cpp:42
Add this component to an entity to make it give an item when interacted with. On interact the owning ...
Definition: Item.hpp:17
item::Id id() const
Returns the item id of this item entity.
Definition: Item.cpp:10
Adding this to an entity will allow it to be interacted with and talked to.
Definition: NPC.hpp:17
const file::Conversation & conversation() const
The conversation of the NPC.
Definition: NPC.cpp:23
const std::string & name() const
The name of the NPC.
Definition: NPC.cpp:21
Adding this to an entity will make it a trainer that can battle.
Definition: Trainer.hpp:18
const std::string & name() const
The name of the NPC.
Definition: Trainer.cpp:43
bool defeated() const
Returns whether or not this trainer was defeated.
Definition: Trainer.cpp:57
const file::Conversation & beforeBattleConversation() const
The conversation before battle is entered.
Definition: Trainer.cpp:45
const std::vector< item::Id > & items() const
Returns the items the trainer has.
Definition: Trainer.cpp:55
const file::Conversation & afterBattleConversation() const
The conversation used after battle if interacted again.
Definition: Trainer.cpp:47
const std::vector< pplmn::OwnedPeoplemon > & team() const
Returns the team of peoplemon the trainer has.
Definition: Trainer.cpp:53
void setDefeated()
Marks this trainer as defeated.
Definition: Trainer.cpp:59
Fired when a battle finishes.
Definition: Battle.hpp:36
const battle::Battle::Type type
The type of battle that was completed.
Definition: Battle.hpp:48
const battle::Result result
The result of the battle.
Definition: Battle.hpp:51
Fired when the game is saving or loading. Allows systems to hook in their data.
Definition: GameSave.hpp:22
file::GameSave & gameSave
Game save being initialized.
Definition: GameSave.hpp:24
std::uint32_t & money()
Returns the money requested or given, undefined behavior if not a money node.
Item & item()
Returns the item to give or take. Undefined behavior if not an item node.
Type
Represents the different effects that nodes can have.
struct core::file::GameSave::InteractDataPointers interaction
std::unordered_set< std::string > * convFlags
Definition: GameSave.hpp:34
std::unordered_map< std::string, std::unordered_set< std::string > > * talkedto
Definition: GameSave.hpp:33
static const std::string & getName(item::Id item)
Returns the name of the given item.
Definition: Item.cpp:91
const std::string & getLocationName(const bl::tmap::Position &pos) const
Returns the name of the town or route at the given position.
Definition: Map.cpp:610
bool interact(bl::ecs::Entity interactor, const bl::tmap::Position &interactPos)
Lets entities interact with the map itself. This is called by the Interaction system.
Definition: Map.cpp:454
const std::string & name() const
Returns the name of the map.
Definition: Map.cpp:171
bool removeItem(item::Id item, unsigned int qty=1)
Removes the given item from the bag.
Definition: Bag.cpp:52
void addItem(item::Id item, unsigned int qty=1)
Adds the given item to the bag.
Definition: Bag.cpp:44
player::Bag bag
Definition: State.hpp:44
bool setEntityLocked(bl::ecs::Entity entity, bool locked, bool preserve=true)
Set the give entity to be locked or unlocked.
bool resetEntityLock(bl::ecs::Entity entity)
Resets the given entity's lock state to the last remembered value.
void displayMessage(const std::string &message, const Callback &cb=[](const std::string &) {})
Displays a message in the HUD textbox. Messages are queued in order that they arrive.
Definition: HUD.cpp:92
void promptUser(const std::string &prompt, const std::vector< std::string > &choices, const Callback &cb)
Asks the player a question through the HUD.
Definition: HUD.cpp:112
void init()
Subscribes to the game event bus.
Definition: Interaction.cpp:33
Interaction(Systems &owner)
Construct a new Interaction system.
Definition: Interaction.cpp:27
bool flagSet(const std::string &flag) const
Checks if the given conversation flag has been set.
bool npcTalkedTo(const std::string &name) const
Returns whether or not the given npc has been talked to.
bool interact(bl::ecs::Entity interactor)
Performs an interation on behalf of the given entity.
Definition: Interaction.cpp:35
bool trainerTalkedto(const std::string &name) const
Returns whether or not the given trainer has been talked to.
void setFlag(const std::string &flag)
Sets the conversation flag.
player::State & state()
Returns the state of the player.
Definition: Player.cpp:154
const bl::tmap::Position & position() const
Returns the current position of the player.
Definition: Player.cpp:57
bl::ecs::Entity player() const
Returns the id of the player entity.
Definition: Player.cpp:55
bl::ecs::Entity search(const bl::tmap::Position &start, bl::tmap::Direction dir, unsigned int range)
Searches for an entity from the starting position in the given direction for the given number of spac...
Definition: Position.cpp:39
Owns all primary systems and a reference to the engine.
Definition: Systems.hpp:47
const bl::engine::Engine & engine() const
Const accessor for the Engine.
Definition: Systems.cpp:35
Player & player()
Returns the player system.
Definition: Systems.cpp:59
Position & position()
Returns a reference to the position system.
Definition: Systems.cpp:47
Controllable & controllable()
Returns the controllable entity system.
Definition: Systems.cpp:63
HUD & hud()
Returns the HUD.
Definition: Systems.cpp:69
World & world()
Modifiable accessor for the world system.
Definition: Systems.cpp:43
map::Map & activeMap()
Returns a reference to the active map.
Definition: World.cpp:84