1 #include "mission.h"
2 
3 #include <algorithm>
4 #include <cstdlib>
5 #include <istream>
6 #include <iterator>
7 #include <list>
8 #include <memory>
9 #include <new>
10 #include <numeric>
11 #include <set>
12 #include <unordered_map>
13 #include <utility>
14 
15 #include "avatar.h"
16 #include "colony.h"
17 #include "creature.h"
18 #include "debug.h"
19 #include "dialogue_chatbin.h"
20 #include "enum_conversions.h"
21 #include "game.h"
22 #include "inventory.h"
23 #include "item.h"
24 #include "item_contents.h"
25 #include "item_group.h"
26 #include "item_stack.h"
27 #include "kill_tracker.h"
28 #include "map.h"
29 #include "map_iterator.h"
30 #include "monster.h"
31 #include "npc.h"
32 #include "npc_class.h"
33 #include "overmap.h"
34 #include "overmapbuffer.h"
35 #include "point.h"
36 #include "requirements.h"
37 #include "string_formatter.h"
38 #include "translations.h"
39 #include "vehicle.h"
40 #include "vpart_position.h"
41 
42 #define dbg(x) DebugLog((x),D_GAME) << __FILE__ << ":" << __LINE__ << ": "
43 
create(const character_id & npc_id) const44 mission mission_type::create( const character_id &npc_id ) const
45 {
46     mission ret;
47     ret.uid = g->assign_mission_id();
48     ret.type = this;
49     ret.npc_id = npc_id;
50     ret.item_id = item_id;
51     ret.item_count = item_count;
52     ret.value = value;
53     ret.follow_up = follow_up;
54     ret.monster_species = monster_species;
55     ret.monster_type = monster_type;
56     ret.monster_kill_goal = monster_kill_goal;
57 
58     if( deadline_low != 0_turns || deadline_high != 0_turns ) {
59         ret.deadline = calendar::turn + rng( deadline_low, deadline_high );
60     } else {
61         ret.deadline = calendar::turn_zero;
62     }
63 
64     return ret;
65 }
66 
tname() const67 std::string mission_type::tname() const
68 {
69     return name.translated();
70 }
71 
72 static std::unordered_map<int, mission> world_missions;
73 
reserve_new(const mission_type_id & type,const character_id & npc_id)74 mission *mission::reserve_new( const mission_type_id &type, const character_id &npc_id )
75 {
76     const mission tmp = mission_type::get( type )->create( npc_id );
77     // TODO: Warn about overwrite?
78     mission &miss = world_missions[tmp.uid] = tmp;
79     return &miss;
80 }
81 
find(int id)82 mission *mission::find( int id )
83 {
84     const auto iter = world_missions.find( id );
85     if( iter != world_missions.end() ) {
86         return &iter->second;
87     }
88     dbg( D_ERROR ) << "requested mission with uid " << id << " does not exist";
89     debugmsg( "requested mission with uid %d does not exist", id );
90     return nullptr;
91 }
92 
get_all_active()93 std::vector<mission *> mission::get_all_active()
94 {
95     std::vector<mission *> ret;
96     ret.reserve( world_missions.size() );
97     for( auto &pr : world_missions ) {
98         ret.push_back( &pr.second );
99     }
100 
101     return ret;
102 }
103 
add_existing(const mission & m)104 void mission::add_existing( const mission &m )
105 {
106     world_missions[ m.uid ] = m;
107 }
108 
process_all()109 void mission::process_all()
110 {
111     for( auto &e : world_missions ) {
112         e.second.process();
113     }
114 }
115 
to_ptr_vector(const std::vector<int> & vec)116 std::vector<mission *> mission::to_ptr_vector( const std::vector<int> &vec )
117 {
118     std::vector<mission *> result;
119     for( const int &id : vec ) {
120         mission *miss = find( id );
121         if( miss != nullptr ) {
122             result.push_back( miss );
123         }
124     }
125     return result;
126 }
127 
to_uid_vector(const std::vector<mission * > & vec)128 std::vector<int> mission::to_uid_vector( const std::vector<mission *> &vec )
129 {
130     std::vector<int> result;
131     result.reserve( vec.size() );
132     for( const mission *miss : vec ) {
133         result.push_back( miss->uid );
134     }
135     return result;
136 }
137 
clear_all()138 void mission::clear_all()
139 {
140     world_missions.clear();
141 }
142 
on_creature_death(Creature & poor_dead_dude)143 void mission::on_creature_death( Creature &poor_dead_dude )
144 {
145     if( poor_dead_dude.is_hallucination() ) {
146         return;
147     }
148     monster *mon = dynamic_cast<monster *>( &poor_dead_dude );
149     if( mon != nullptr ) {
150         if( mon->mission_ids.empty() ) {
151             return;
152         }
153         for( const int mission_id : mon->mission_ids ) {
154             mission *found_mission = mission::find( mission_id );
155             if( !found_mission ) {
156                 debugmsg( "invalid mission id %d", mission_id );
157                 continue;
158             }
159             const mission_type *type = found_mission->type;
160             if( type->goal == MGOAL_FIND_MONSTER ) {
161                 found_mission->fail();
162             }
163             if( type->goal == MGOAL_KILL_MONSTER ) {
164                 found_mission->step_complete( 1 );
165             }
166         }
167         return;
168     }
169     npc *p = dynamic_cast<npc *>( &poor_dead_dude );
170     if( p == nullptr ) {
171         // Must be the player
172         for( auto &miss : get_avatar().get_active_missions() ) {
173             // mission is free and can be reused
174             miss->player_id = character_id();
175         }
176         // The missions remains assigned to the (dead) character. This should not cause any problems
177         // as the character is dismissed anyway.
178         // Technically, the active missions could be moved to the failed mission section.
179         return;
180     }
181     const character_id dead_guys_id = p->getID();
182     for( auto &e : world_missions ) {
183         mission &i = e.second;
184         if( !i.in_progress() ) {
185             continue;
186         }
187         //complete the mission if you needed killing
188         if( i.type->goal == MGOAL_ASSASSINATE && i.target_npc_id == dead_guys_id ) {
189             i.step_complete( 1 );
190         }
191         //fail the mission if the mission giver dies
192         if( i.npc_id == dead_guys_id ) {
193             i.fail();
194         }
195         //fail the mission if recruit target dies
196         if( i.type->goal == MGOAL_RECRUIT_NPC && i.target_npc_id == dead_guys_id ) {
197             i.fail();
198         }
199     }
200 }
201 
on_creature_fusion(Creature & fuser,Creature & fused)202 bool mission::on_creature_fusion( Creature &fuser, Creature &fused )
203 {
204     if( fuser.is_hallucination() || fused.is_hallucination() ) {
205         return false;
206     }
207     monster *mon_fuser = dynamic_cast<monster *>( &fuser );
208     if( mon_fuser == nullptr ) {
209         debugmsg( "Unimplemented: fuser is not a monster" );
210         return false;
211     }
212     monster *mon_fused = dynamic_cast<monster *>( &fused );
213     if( mon_fused == nullptr ) {
214         debugmsg( "Unimplemented: fused is not a monster" );
215         return false;
216     }
217     bool mission_transfered = false;
218     for( const int mission_id : mon_fused->mission_ids ) {
219         const mission *const found_mission = mission::find( mission_id );
220         if( !found_mission ) {
221             debugmsg( "invalid mission id %d", mission_id );
222             continue;
223         }
224         const mission_type *const type = found_mission->type;
225         if( type->goal == MGOAL_KILL_MONSTER ) {
226             // the fuser has to be killed now!
227             mon_fuser->mission_ids.emplace( mission_id );
228             mon_fused->mission_ids.erase( mission_id );
229             mission_transfered = true;
230         }
231     }
232     return mission_transfered;
233 }
234 
on_talk_with_npc(const character_id & npc_id)235 void mission::on_talk_with_npc( const character_id &npc_id )
236 {
237     switch( type->goal ) {
238         case MGOAL_TALK_TO_NPC:
239             // If our goal is to talk to this npc, and we haven't yet completed a step for this
240             // mission, then complete a step.
241             if( npc_id == target_npc_id && step == 0 ) {
242                 step_complete( 1 );
243             }
244             break;
245         default:
246             break;
247     }
248 }
249 
reserve_random(const mission_origin origin,const tripoint_abs_omt & p,const character_id & npc_id)250 mission *mission::reserve_random( const mission_origin origin, const tripoint_abs_omt &p,
251                                   const character_id &npc_id )
252 {
253     const auto type = mission_type::get_random_id( origin, p );
254     if( type.is_null() ) {
255         return nullptr;
256     }
257     return mission::reserve_new( type, npc_id );
258 }
259 
assign(avatar & u)260 void mission::assign( avatar &u )
261 {
262     if( player_id == u.getID() ) {
263         debugmsg( "strange: player is already assigned to mission %d", uid );
264         return;
265     }
266     if( player_id.is_valid() ) {
267         debugmsg( "tried to assign mission %d to player, but mission is already assigned to %d",
268                   uid, player_id.get_value() );
269         return;
270     }
271     player_id = u.getID();
272     u.on_mission_assignment( *this );
273     if( status == mission_status::yet_to_start ) {
274         const kill_tracker &kills = g->get_kill_tracker();
275         if( type->goal == MGOAL_KILL_MONSTER_TYPE && monster_type != mtype_id::NULL_ID() ) {
276             kill_count_to_reach = kills.kill_count( monster_type ) + monster_kill_goal;
277         } else if( type->goal == MGOAL_KILL_MONSTER_SPEC ) {
278             kill_count_to_reach = kills.kill_count( monster_species ) + monster_kill_goal;
279         }
280         if( type->deadline_low != 0_turns || type->deadline_high != 0_turns ) {
281             deadline = calendar::turn + rng( type->deadline_low, type->deadline_high );
282         } else {
283             deadline = calendar::turn_zero;
284         }
285         type->start( this );
286         status = mission_status::in_progress;
287     }
288 }
289 
fail()290 void mission::fail()
291 {
292     status = mission_status::failure;
293     avatar &player_character = get_avatar();
294     if( player_character.getID() == player_id ) {
295         player_character.on_mission_finished( *this );
296     }
297 
298     type->fail( this );
299 }
300 
set_target_to_mission_giver()301 void mission::set_target_to_mission_giver()
302 {
303     const npc *giver = g->find_npc( npc_id );
304     if( giver != nullptr ) {
305         target = giver->global_omt_location();
306     } else {
307         target = overmap::invalid_tripoint;
308     }
309 }
310 
step_complete(const int _step)311 void mission::step_complete( const int _step )
312 {
313     step = _step;
314     switch( type->goal ) {
315         case MGOAL_FIND_ITEM:
316         case MGOAL_FIND_ITEM_GROUP:
317         case MGOAL_FIND_MONSTER:
318         case MGOAL_ASSASSINATE:
319         case MGOAL_KILL_MONSTER:
320         case MGOAL_COMPUTER_TOGGLE:
321         case MGOAL_TALK_TO_NPC:
322             // Go back and report.
323             set_target_to_mission_giver();
324             break;
325         default:
326             //Suppress warnings
327             break;
328     }
329 }
330 
wrap_up()331 void mission::wrap_up()
332 {
333     avatar &player_character = get_avatar();
334     if( player_character.getID() != player_id ) {
335         // This is called from npctalk.cpp, the npc should only offer the option to wrap up mission
336         // that have been assigned to the current player.
337         debugmsg( "mission::wrap_up called, player %d was assigned, but current player is %d",
338                   player_id.get_value(), player_character.getID().get_value() );
339     }
340 
341     status = mission_status::success;
342     player_character.on_mission_finished( *this );
343     std::vector<item_comp> comps;
344     switch( type->goal ) {
345         case MGOAL_FIND_ITEM_GROUP: {
346             inventory tmp_inv = player_character.crafting_inventory();
347             std::vector<item *> items = std::vector<item *>();
348             tmp_inv.dump( items );
349             item_group_id grp_type = type->group_id;
350             itype_id container = type->container_id;
351             bool specific_container_required = !container.is_null();
352             bool remove_container = type->remove_container;
353             itype_id empty_container = type->empty_container;
354 
355             std::map<itype_id, int> matches = std::map<itype_id, int>();
356             get_all_item_group_matches(
357                 items, grp_type, matches,
358                 container, itype_id( "null" ), specific_container_required );
359 
360             for( std::pair<const itype_id, int> &cnt : matches ) {
361                 comps.push_back( item_comp( cnt.first, cnt.second ) );
362 
363             }
364 
365             player_character.consume_items( comps );
366 
367             if( remove_container ) {
368                 std::vector<item_comp> container_comp = std::vector<item_comp>();
369                 if( !empty_container.is_null() ) {
370                     container_comp.push_back( item_comp( empty_container, type->item_count ) );
371                     player_character.consume_items( container_comp );
372                 } else {
373                     container_comp.push_back( item_comp( container, type->item_count ) );
374                     player_character.consume_items( container_comp );
375                 }
376             }
377         }
378         break;
379 
380         case MGOAL_FIND_ITEM: {
381             const item item_sought( type->item_id );
382             if( item_sought.is_software() ) {
383                 int consumed = 0;
384                 while( consumed < item_count ) {
385                     if( player_character.consume_software_container( type->item_id ) ) {
386                         consumed++;
387                     } else {
388                         debugmsg( "Tried to consume more software %s than available", type->item_id.c_str() );
389                         break;
390                     }
391                 }
392             } else {
393                 comps.push_back( item_comp( type->item_id, item_count ) );
394                 player_character.consume_items( comps );
395             }
396         }
397         break;
398         case MGOAL_FIND_ANY_ITEM:
399             player_character.remove_mission_items( uid );
400             break;
401         default:
402             //Suppress warnings
403             break;
404     }
405 
406     type->end( this );
407 }
408 
is_complete(const character_id & _npc_id) const409 bool mission::is_complete( const character_id &_npc_id ) const
410 {
411     if( status == mission_status::success ) {
412         return true;
413     }
414 
415     avatar &player_character = get_avatar();
416     switch( type->goal ) {
417         case MGOAL_GO_TO: {
418             const tripoint_abs_omt cur_pos = player_character.global_omt_location();
419             return ( rl_dist( cur_pos, target ) <= 1 );
420         }
421 
422         case MGOAL_GO_TO_TYPE: {
423             const auto cur_ter = overmap_buffer.ter( player_character.global_omt_location() );
424             return is_ot_match( type->target_id.str(), cur_ter, ot_match_type::type );
425         }
426 
427         case MGOAL_FIND_ITEM_GROUP: {
428             inventory tmp_inv = player_character.crafting_inventory();
429             std::vector<item *> items = std::vector<item *>();
430             tmp_inv.dump( items );
431             item_group_id grp_type = type->group_id;
432             itype_id container = type->container_id;
433             bool specific_container_required = !container.is_null();
434 
435             std::map<itype_id, int> matches = std::map<itype_id, int>();
436             get_all_item_group_matches(
437                 items, grp_type, matches,
438                 container, itype_id( "null" ), specific_container_required );
439 
440             int total_match = std::accumulate( matches.begin(), matches.end(), 0,
441             []( const std::size_t previous, const std::pair<const itype_id, std::size_t> &p ) {
442                 return static_cast<int>( previous + p.second );
443             } );
444 
445             if( total_match >= ( type->item_count ) ) {
446                 return true;
447 
448             }
449         }
450         return false;
451 
452         case MGOAL_FIND_ITEM: {
453             if( npc_id.is_valid() && npc_id != _npc_id ) {
454                 return false;
455             }
456             item item_sought( type->item_id );
457             map &here = get_map();
458             int found_quantity = 0;
459             bool charges = item_sought.count_by_charges();
460             bool software = item_sought.is_software();
461             auto count_items = [this, &found_quantity, &player_character, charges, software]( item_stack &&
462             items ) {
463                 for( const item &i : items ) {
464                     if( !i.is_owned_by( player_character, true ) ) {
465                         continue;
466                     }
467                     if( software ) {
468                         for( const item *soft : i.softwares() ) {
469                             if( soft->typeId() == type->item_id ) {
470                                 found_quantity ++;
471                             }
472                         }
473                     }
474                     if( charges ) {
475                         found_quantity += i.charges_of( type->item_id, item_count - found_quantity );
476                     } else {
477                         found_quantity += i.amount_of( type->item_id, item_count - found_quantity );
478                     }
479                 }
480             };
481             for( const tripoint &p : here.points_in_radius( player_character.pos(), 5 ) ) {
482                 if( player_character.sees( p ) ) {
483                     if( here.has_items( p ) && here.accessible_items( p ) ) {
484                         count_items( here.i_at( p ) );
485                     }
486                     if( const cata::optional<vpart_reference> vp =
487                             here.veh_at( p ).part_with_feature( "CARGO", true ) ) {
488                         count_items( vp->vehicle().get_items( vp->part_index() ) );
489                     }
490                     if( found_quantity >= item_count ) {
491                         break;
492                     }
493                 }
494             }
495             if( software ) {
496                 found_quantity += player_character.count_softwares( type->item_id );
497             }
498             if( charges ) {
499                 return player_character.charges_of( type->item_id ) + found_quantity >= item_count;
500             } else {
501                 return player_character.amount_of( type->item_id ) + found_quantity >= item_count;
502             }
503         }
504         return true;
505 
506         case MGOAL_FIND_ANY_ITEM:
507             return player_character.has_mission_item( uid ) && ( !npc_id.is_valid() || npc_id == _npc_id );
508 
509         case MGOAL_FIND_MONSTER:
510             if( npc_id.is_valid() && npc_id != _npc_id ) {
511                 return false;
512             }
513             return g->get_creature_if( [&]( const Creature & critter ) {
514                 const monster *const mon_ptr = dynamic_cast<const monster *>( &critter );
515                 return mon_ptr && mon_ptr->mission_ids.count( uid );
516             } );
517 
518         case MGOAL_RECRUIT_NPC: {
519             npc *p = g->find_npc( target_npc_id );
520             return p != nullptr && p->get_attitude() == NPCATT_FOLLOW;
521         }
522 
523         case MGOAL_RECRUIT_NPC_CLASS: {
524             const auto npcs = overmap_buffer.get_npcs_near_player( 100 );
525             for( const auto &npc : npcs ) {
526                 if( npc->myclass == recruit_class && npc->get_attitude() == NPCATT_FOLLOW ) {
527                     return true;
528                 }
529             }
530             return false;
531         }
532 
533         case MGOAL_FIND_NPC:
534             return npc_id == _npc_id;
535 
536         case MGOAL_TALK_TO_NPC:
537         case MGOAL_ASSASSINATE:
538         case MGOAL_KILL_MONSTER:
539         case MGOAL_COMPUTER_TOGGLE:
540             return step >= 1;
541 
542         case MGOAL_KILL_MONSTER_TYPE:
543             return g->get_kill_tracker().kill_count( monster_type ) >= kill_count_to_reach;
544 
545         case MGOAL_KILL_MONSTER_SPEC:
546             return g->get_kill_tracker().kill_count( monster_species ) >= kill_count_to_reach;
547 
548         case MGOAL_CONDITION: {
549             // For now, we only allow completing when talking to the mission originator.
550             if( npc_id != _npc_id ) {
551                 return false;
552             }
553 
554             npc *n = g->find_npc( _npc_id );
555             if( n == nullptr ) {
556                 return false;
557             }
558 
559             mission_goal_condition_context cc;
560             cc.alpha = get_talker_for( player_character );
561             cc.beta = get_talker_for( *n );
562 
563             for( auto &mission : n->chatbin.missions_assigned ) {
564                 if( mission->get_assigned_player_id() == player_character.getID() ) {
565                     cc.missions_assigned.push_back( mission );
566                 }
567             }
568 
569             return type->test_goal_condition( cc );
570         }
571 
572         default:
573             return false;
574     }
575 }
576 
get_all_item_group_matches(std::vector<item * > & items,item_group_id & grp_type,std::map<itype_id,int> & matches,const itype_id & required_container,const itype_id & actual_container,bool & specific_container_required)577 void mission::get_all_item_group_matches( std::vector<item *> &items,
578         item_group_id &grp_type, std::map<itype_id, int> &matches,
579         const itype_id &required_container, const itype_id &actual_container,
580         bool &specific_container_required )
581 {
582     for( item *itm : items ) {
583         bool correct_container = ( required_container == actual_container ) ||
584                                  !specific_container_required;
585 
586         bool item_in_group = item_group::group_contains_item( grp_type, itm->typeId() );
587 
588         //check whether item itself is target
589         if( item_in_group && correct_container ) {
590             std::map<itype_id, int>::iterator it = matches.find( itm->typeId() );
591             if( it != matches.end() ) {
592                 it->second = ( it->second ) + 1;
593             } else {
594                 matches.insert( std::make_pair( itm->typeId(), 1 ) );
595             }
596         }
597 
598         //recursively check item contents for target
599         if( itm->is_container() && !itm->is_container_empty() ) {
600             std::list<item *> content_list = itm->contents.all_items_top();
601             std::vector<item *> content = std::vector<item *>();
602 
603             //list of item into list item*
604             std::transform(
605                 content_list.begin(), content_list.end(),
606                 std::back_inserter( content ),
607             []( item * p ) {
608                 return p;
609             } );
610 
611             get_all_item_group_matches(
612                 content, grp_type, matches,
613                 required_container, ( itm->typeId() ), specific_container_required );
614         }
615     }
616 }
617 
has_deadline() const618 bool mission::has_deadline() const
619 {
620     return deadline != calendar::turn_zero;
621 }
622 
get_deadline() const623 time_point mission::get_deadline() const
624 {
625     return deadline;
626 }
627 
get_description() const628 std::string mission::get_description() const
629 {
630     return type->description.translated();
631 }
632 
has_target() const633 bool mission::has_target() const
634 {
635     return target != overmap::invalid_tripoint;
636 }
637 
get_target() const638 const tripoint_abs_omt &mission::get_target() const
639 {
640     return target;
641 }
642 
get_type() const643 const mission_type &mission::get_type() const
644 {
645     if( type == nullptr ) {
646         debugmsg( "Null mission type" );
647         return mission_type::get_all().front();
648     }
649 
650     return *type;
651 }
652 
has_follow_up() const653 bool mission::has_follow_up() const
654 {
655     return !follow_up.is_null();
656 }
657 
get_follow_up() const658 mission_type_id mission::get_follow_up() const
659 {
660     return follow_up;
661 }
662 
get_value() const663 int mission::get_value() const
664 {
665     return value;
666 }
667 
get_id() const668 int mission::get_id() const
669 {
670     return uid;
671 }
672 
get_item_id() const673 const itype_id &mission::get_item_id() const
674 {
675     return item_id;
676 }
677 
has_failed() const678 bool mission::has_failed() const
679 {
680     return status == mission_status::failure;
681 }
682 
in_progress() const683 bool mission::in_progress() const
684 {
685     return status == mission_status::in_progress;
686 }
687 
process()688 void mission::process()
689 {
690     if( !in_progress() ) {
691         return;
692     }
693 
694     if( has_deadline() && calendar::turn > deadline ) {
695         fail();
696     } else if( !npc_id.is_valid() && is_complete( npc_id ) ) { // No quest giver.
697         wrap_up();
698     }
699 }
700 
get_npc_id() const701 character_id mission::get_npc_id() const
702 {
703     return npc_id;
704 }
705 
get_likely_rewards() const706 const std::vector<std::pair<int, itype_id>> &mission::get_likely_rewards() const
707 {
708     return type->likely_rewards;
709 }
710 
has_generic_rewards() const711 bool mission::has_generic_rewards() const
712 {
713     return type->has_generic_rewards;
714 }
715 
set_target(const tripoint_abs_omt & p)716 void mission::set_target( const tripoint_abs_omt &p )
717 {
718     target = p;
719 }
720 
set_target_npc_id(const character_id & npc_id)721 void mission::set_target_npc_id( const character_id &npc_id )
722 {
723     target_npc_id = npc_id;
724 }
725 
is_assigned() const726 bool mission::is_assigned() const
727 {
728     return player_id.is_valid();
729 }
730 
get_assigned_player_id() const731 character_id mission::get_assigned_player_id() const
732 {
733     return player_id;
734 }
735 
name()736 std::string mission::name()
737 {
738     if( type == nullptr ) {
739         return "NULL";
740     }
741     return type->tname();
742 }
743 
mission_id()744 mission_type_id mission::mission_id()
745 {
746     if( type == nullptr ) {
747         return mission_type_id( "NULL" );
748     }
749     return type->id;
750 }
751 
dialogue_for_topic(const std::string & in_topic) const752 std::string mission::dialogue_for_topic( const std::string &in_topic ) const
753 {
754     // The internal keys are pretty ugly, it's better to translate them here than globally
755     static const std::map<std::string, std::string> topic_translation = {{
756             { "TALK_MISSION_DESCRIBE", "describe" },
757             { "TALK_MISSION_DESCRIBE_URGENT", "describe" },
758             { "TALK_MISSION_OFFER", "offer" },
759             { "TALK_MISSION_ACCEPTED", "accepted" },
760             { "TALK_MISSION_REJECTED", "rejected" },
761             { "TALK_MISSION_ADVICE", "advice" },
762             { "TALK_MISSION_INQUIRE", "inquire" },
763             { "TALK_MISSION_SUCCESS", "success" },
764             { "TALK_MISSION_SUCCESS_LIE", "success_lie" },
765             { "TALK_MISSION_FAILURE", "failure" }
766         }
767     };
768 
769     const auto &replacement = topic_translation.find( in_topic );
770     const std::string &topic = replacement != topic_translation.end() ? replacement->second : in_topic;
771 
772     const auto &response = type->dialogue.find( topic );
773     if( response != type->dialogue.end() ) {
774         return response->second.translated();
775     }
776 
777     return string_format( "Someone forgot to code this message id is %s, topic is %s!",
778                           type->id.c_str(), topic.c_str() );
779 }
780 
mission()781 mission::mission()
782     : deadline( 0 )
783 {
784     type = nullptr;
785     status = mission_status::yet_to_start;
786     value = 0;
787     uid = -1;
788     target = tripoint_abs_omt( tripoint_min );
789     item_id = itype_id::NULL_ID();
790     item_count = 1;
791     target_id = string_id<oter_type_t>::NULL_ID();
792     recruit_class = NC_NONE;
793     target_npc_id = character_id();
794     monster_type = mtype_id::NULL_ID();
795     monster_kill_goal = -1;
796     npc_id = character_id();
797     good_fac_id = -1;
798     bad_fac_id = -1;
799     step = 0;
800     player_id = character_id();
801 }
802 
803 namespace io
804 {
805 template<>
enum_to_string(mission::mission_status data)806 std::string enum_to_string<mission::mission_status>( mission::mission_status data )
807 {
808     switch( data ) {
809         // *INDENT-OFF*
810         case mission::mission_status::yet_to_start: return "yet_to_start";
811         case mission::mission_status::in_progress: return "in_progress";
812         case mission::mission_status::success: return "success";
813         case mission::mission_status::failure: return "failure";
814         // *INDENT-ON*
815         case mission::mission_status::num_mission_status:
816             break;
817 
818     }
819     debugmsg( "Invalid mission_status" );
820     abort();
821 }
822 
823 } // namespace io
824 
status_from_string(const std::string & s)825 mission::mission_status mission::status_from_string( const std::string &s )
826 {
827     return io::string_to_enum<mission::mission_status>( s );
828 }
829 
status_to_string(mission::mission_status st)830 std::string mission::status_to_string( mission::mission_status st )
831 {
832     return io::enum_to_string<mission::mission_status>( st );
833 }
834