-
Notifications
You must be signed in to change notification settings - Fork 4.2k
/
mission_companion.cpp
2174 lines (2019 loc) · 88.9 KB
/
mission_companion.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include "mission_companion.h"
#include <algorithm>
#include <cstdlib>
#include <functional>
#include <list>
#include <map>
#include <memory>
#include <set>
#include <tuple>
#include <unordered_map>
#include <utility>
#include <vector>
#include "basecamp.h"
#include "bodypart.h"
#include "calendar.h"
#include "cata_assert.h"
#include "catacharset.h"
#include "character.h"
#include "colony.h"
#include "color.h"
#include "coordinates.h"
#include "creature.h"
#include "cursesdef.h"
#include "debug.h"
#include "enums.h"
#include "faction.h"
#include "faction_camp.h"
#include "game.h"
#include "game_constants.h"
#include "input.h"
#include "inventory.h"
#include "item.h"
#include "item_group.h"
#include "item_stack.h"
#include "itype.h"
#include "line.h"
#include "map.h"
#include "map_iterator.h"
#include "mapdata.h"
#include "memory_fast.h"
#include "messages.h"
#include "monster.h"
#include "mtype.h"
#include "npc.h"
#include "optional.h"
#include "output.h"
#include "overmap.h"
#include "overmapbuffer.h"
#include "point.h"
#include "rng.h"
#include "skill.h"
#include "string_formatter.h"
#include "translations.h"
#include "ui.h"
#include "ui_manager.h"
#include "value_ptr.h"
#include "weather.h"
#include "weighted_list.h"
class character_id;
static const efftype_id effect_riding( "riding" );
static const itype_id itype_fungal_seeds( "fungal_seeds" );
static const itype_id itype_marloss_seed( "marloss_seed" );
static const skill_id skill_bashing( "bashing" );
static const skill_id skill_cutting( "cutting" );
static const skill_id skill_dodge( "dodge" );
static const skill_id skill_fabrication( "fabrication" );
static const skill_id skill_gun( "gun" );
static const skill_id skill_melee( "melee" );
static const skill_id skill_stabbing( "stabbing" );
static const skill_id skill_survival( "survival" );
static const skill_id skill_unarmed( "unarmed" );
static const trait_id trait_DEBUG_HS( "DEBUG_HS" );
static const trait_id trait_NPC_MISSION_LEV_1( "NPC_MISSION_LEV_1" );
static const trait_id trait_NPC_CONSTRUCTION_LEV_1( "NPC_CONSTRUCTION_LEV_1" );
static const trait_id trait_NPC_CONSTRUCTION_LEV_2( "NPC_CONSTRUCTION_LEV_2" );
struct comp_rank {
int industry;
int combat;
int survival;
};
mission_data::mission_data()
{
for( int tab_num = base_camps::TAB_MAIN; tab_num != base_camps::TAB_NW + 3; tab_num++ ) {
std::vector<mission_entry> k;
entries.push_back( k );
}
}
namespace talk_function
{
void scavenger_patrol( mission_data &mission_key, npc &p );
void scavenger_raid( mission_data &mission_key, npc &p );
void commune_menial( mission_data &mission_key, npc &p );
void commune_carpentry( mission_data &mission_key, npc &p );
void commune_farmfield( mission_data &mission_key, npc &p );
void commune_forage( mission_data &mission_key, npc &p );
void commune_refuge_caravan( mission_data &mission_key, npc &p );
bool handle_outpost_mission( const mission_entry &cur_key, npc &p );
} // namespace talk_function
void talk_function::companion_mission( npc &p )
{
mission_data mission_key;
std::string role_id = p.companion_mission_role_id;
const tripoint_abs_omt omt_pos = p.global_omt_location();
std::string title = _( "Outpost Missions" );
if( role_id == "SCAVENGER" ) {
title = _( "Junk Shop Missions" );
scavenger_patrol( mission_key, p );
if( p.has_trait( trait_NPC_MISSION_LEV_1 ) ) {
scavenger_raid( mission_key, p );
}
} else if( role_id == "COMMUNE CROPS" ) {
title = _( "Agricultural Missions" );
commune_farmfield( mission_key, p );
commune_forage( mission_key, p );
commune_refuge_caravan( mission_key, p );
} else if( role_id == "FOREMAN" ) {
title = _( "Construction Missions" );
commune_menial( mission_key, p );
if( p.has_trait( trait_NPC_MISSION_LEV_1 ) ) {
commune_carpentry( mission_key, p );
}
} else if( role_id == "REFUGEE MERCHANT" ) {
title = _( "Free Merchant Missions" );
commune_refuge_caravan( mission_key, p );
} else {
return;
}
if( display_and_choose_opts( mission_key, omt_pos, role_id, title ) ) {
handle_outpost_mission( mission_key.cur_key, p );
}
}
void talk_function::scavenger_patrol( mission_data &mission_key, npc &p )
{
std::string entry = _( "Profit: $25-$500\nDanger: Low\nTime: 10 hour missions\n\n"
"Assigning one of your allies to patrol the surrounding wilderness "
"and isolated buildings presents the opportunity to build survival "
"skills while engaging in relatively safe combat against isolated "
"creatures." );
mission_key.add( "Assign Scavenging Patrol", _( "Assign Scavenging Patrol" ), entry );
std::vector<npc_ptr> npc_list = companion_list( p, "_scavenging_patrol" );
if( !npc_list.empty() ) {
entry = _( "Profit: $25-$500\nDanger: Low\nTime: 10 hour missions\n\nPatrol Roster:\n" );
for( auto &elem : npc_list ) {
entry += " " + elem->name + " [" + std::to_string( to_hours<int>( calendar::turn -
elem->companion_mission_time ) ) + _( " hours]\n" );
}
entry += _( "\n\nDo you wish to bring your allies back into your party?" );
mission_key.add( "Retrieve Scavenging Patrol", _( "Retrieve Scavenging Patrol" ), entry );
}
}
void talk_function::scavenger_raid( mission_data &mission_key, npc &p )
{
std::string entry = _( "Profit: $200-$1000\nDanger: Medium\nTime: 10 hour missions\n\n"
"Scavenging raids target formerly populated areas to loot as many "
"valuable items as possible before being surrounded by the undead. "
"Combat is to be expected and assistance from the rest of the party "
"can't be guaranteed. The rewards are greater and there is a chance "
"of the companion bringing back items." );
mission_key.add( "Assign Scavenging Raid", _( "Assign Scavenging Raid" ), entry );
std::vector<npc_ptr> npc_list = companion_list( p, "_scavenging_raid" );
if( !npc_list.empty() ) {
entry = _( "Profit: $200-$1000\nDanger: Medium\nTime: 10 hour missions\n\n"
"Raid Roster:\n" );
for( auto &elem : npc_list ) {
entry += " " + elem->name + " [" + std::to_string( to_hours<int>( calendar::turn -
elem->companion_mission_time ) ) + _( " hours]\n" );
}
entry += _( "\n\nDo you wish to bring your allies back into your party?" );
mission_key.add( "Retrieve Scavenging Raid", _( "Retrieve Scavenging Raid" ), entry );
}
}
void talk_function::commune_menial( mission_data &mission_key, npc &p )
{
mission_key.add( "Assign Ally to Menial Labor", _( "Assign Ally to Menial Labor" ) );
std::vector<npc_ptr> npc_list = companion_list( p, "_labor" );
if( !npc_list.empty() ) {
std::string entry = _( "Profit: $8/hour\nDanger: Minimal\nTime: 1 hour minimum\n\n"
"Assigning one of your allies to menial labor is a safe way to teach "
"them basic skills and build reputation with the outpost. Don't expect "
"much of a reward though.\n\nLabor Roster:\n" );
for( auto &elem : npc_list ) {
entry += " " + elem->name + " [" + std::to_string( to_hours<int>( calendar::turn -
elem->companion_mission_time ) ) + _( " hours]\n" );
}
entry += _( "\n\nDo you wish to bring your allies back into your party?" );
mission_key.add( "Recover Ally from Menial Labor", _( "Recover Ally from Menial Labor" ),
entry );
}
}
void talk_function::commune_carpentry( mission_data &mission_key, npc &p )
{
std::string entry = _( "Profit: $12/hour\nDanger: Minimal\nTime: 1 hour minimum\n\n"
"Carpentry work requires more skill than menial labor while offering "
"modestly improved pay. It is unlikely that your companions will face "
"combat but there are hazards working on makeshift buildings." );
mission_key.add( "Assign Ally to Carpentry Work", _( "Assign Ally to Carpentry Work" ), entry );
std::vector<npc_ptr> npc_list = companion_list( p, "_carpenter" );
if( !npc_list.empty() ) {
entry = _( "Profit: $12/hour\nDanger: Minimal\nTime: 1 hour minimum\n\nLabor Roster:\n" );
for( auto &elem : npc_list ) {
entry += " " + elem->name + " [" + std::to_string( to_hours<int>( calendar::turn -
elem->companion_mission_time ) ) + _( " hours]\n" );
}
entry += _( "\n\nDo you wish to bring your allies back into your party?" );
mission_key.add( "Recover Ally from Carpentry Work",
_( "Recover Ally from Carpentry Work" ), entry );
}
}
void talk_function::commune_farmfield( mission_data &mission_key, npc &p )
{
if( !p.has_trait( trait_NPC_CONSTRUCTION_LEV_1 ) ) {
std::string entry = _( "Cost: $1000\n\n\n"
" .........\n" // NOLINT(cata-text-style)
" .........\n" // NOLINT(cata-text-style)
" .........\n" // NOLINT(cata-text-style)
" .........\n" // NOLINT(cata-text-style)
" .........\n" // NOLINT(cata-text-style)
" .........\n" // NOLINT(cata-text-style)
" ..#....**\n" // NOLINT(cata-text-style)
" ..#Ov..**\n" // NOLINT(cata-text-style)
" ...O|....\n\n" // NOLINT(cata-text-style)
"We're willing to let you purchase a field at a substantial "
"discount to use for your own agricultural enterprises. We'll "
"plow it for you so you know exactly what is yours… after you "
"have a field you can hire workers to plant or harvest crops for "
"you. If the crop is something we have a demand for, we'll be "
"willing to liquidate it." );
mission_key.add( "Purchase East Field", _( "Purchase East Field" ), entry );
}
if( p.has_trait( trait_NPC_CONSTRUCTION_LEV_1 ) && !p.has_trait( trait_NPC_CONSTRUCTION_LEV_2 ) ) {
std::string entry = _( "Cost: $5500\n\n"
"\n ........." // NOLINT(cata-text-style)
"\n ........." // NOLINT(cata-text-style)
"\n ........." // NOLINT(cata-text-style)
"\n ........." // NOLINT(cata-text-style)
"\n ........." // NOLINT(cata-text-style)
"\n ........." // NOLINT(cata-text-style)
"\n ..#....**" // NOLINT(cata-text-style)
"\n ..#Ov..**" // NOLINT(cata-text-style)
"\n ...O|....\n\n" // NOLINT(cata-text-style)
"Protecting your field with a sturdy picket fence will keep most "
"wildlife from nibbling your crops apart. You can expect yields to "
"increase." );
mission_key.add( "Upgrade East Field I", _( "Upgrade East Field I" ), entry );
}
if( p.has_trait( trait_NPC_CONSTRUCTION_LEV_1 ) ) {
std::string entry = _( "Cost: $3.00/plot\n\n"
"\n ........." // NOLINT(cata-text-style)
"\n ........." // NOLINT(cata-text-style)
"\n ........." // NOLINT(cata-text-style)
"\n ........." // NOLINT(cata-text-style)
"\n ........." // NOLINT(cata-text-style)
"\n ........." // NOLINT(cata-text-style)
"\n ..#....**" // NOLINT(cata-text-style)
"\n ..#Ov..**" // NOLINT(cata-text-style)
"\n ...O|....\n\n" // NOLINT(cata-text-style)
"We'll plant the field with your choice of crop if you are willing "
"to finance it. When the crop is ready to harvest you can have us "
"liquidate it or harvest it for you." );
mission_key.add( "Plant East Field", _( "Plant East Field" ), entry );
entry = _( "Cost: $2.00/plot\n\n"
"\n ........." // NOLINT(cata-text-style)
"\n ........." // NOLINT(cata-text-style)
"\n ........." // NOLINT(cata-text-style)
"\n ........." // NOLINT(cata-text-style)
"\n ........." // NOLINT(cata-text-style)
"\n ........." // NOLINT(cata-text-style)
"\n ..#....**" // NOLINT(cata-text-style)
"\n ..#Ov..**" // NOLINT(cata-text-style)
"\n ...O|....\n\n" // NOLINT(cata-text-style)
"You can either have us liquidate the crop and give you the cash or pay us to "
"harvest it for you." );
mission_key.add( "Harvest East Field", _( "Harvest East Field" ), entry );
}
}
void talk_function::commune_forage( mission_data &mission_key, npc &p )
{
std::string entry = _( "Profit: $10/hour\nDanger: Low\nTime: 4 hour minimum\n\n"
"Foraging for food involves dispatching a companion to search the "
"surrounding wilderness for wild edibles. Combat will be avoided but "
"encounters with wild animals are to be expected. The low pay is "
"supplemented with the odd item as a reward for particularly large "
"hauls." );
mission_key.add( "Assign Ally to Forage for Food", _( "Assign Ally to Forage for Food" ),
entry );
std::vector<npc_ptr> npc_list = companion_list( p, "_forage" );
if( !npc_list.empty() ) {
entry = _( "Profit: $10/hour\nDanger: Low\nTime: 4 hour minimum\n\nLabor Roster:\n" );
for( auto &elem : npc_list ) {
entry += " " + elem->name + " [" + std::to_string( to_hours<int>( calendar::turn -
elem->companion_mission_time ) ) + _( " hours]\n" );
}
entry += _( "\n\nDo you wish to bring your allies back into your party?" );
mission_key.add( "Recover Ally from Foraging", _( "Recover Ally from Foraging" ), entry );
}
}
void talk_function::commune_refuge_caravan( mission_data &mission_key, npc &p )
{
std::string entry = _( "Profit: $18/hour\nDanger: High\nTime: UNKNOWN\n\n"
"Adding companions to the caravan team increases the likelihood of "
"success. By nature, caravans are extremely tempting targets for "
"raiders or hostile groups so only a strong party is recommended. The "
"rewards are significant for those participating but are even more "
"important for the factions that profit.\n\n"
"The commune is sending food to the Free Merchants in the Refugee "
"Center as part of a tax and in exchange for skilled labor." );
mission_key.add( "Caravan Commune-Refugee Center", _( "Caravan Commune-Refugee Center" ),
entry );
std::vector<npc_ptr> npc_list = companion_list( p, "_commune_refugee_caravan" );
std::vector<npc_ptr> npc_list_aux;
if( !npc_list.empty() ) {
entry = _( "Profit: $18/hour\nDanger: High\nTime: UNKNOWN\n\n"
"\nRoster:\n" );
for( auto &elem : npc_list ) {
if( elem->companion_mission_time == calendar::before_time_starts ) {
entry += " " + elem->name + _( " [READY]\n" );
npc_list_aux.push_back( elem );
} else if( calendar::turn >= elem->companion_mission_time ) {
entry += " " + elem->name + _( " [COMPLETE]\n" );
} else {
entry += " " + elem->name + " [" + std::to_string( std::abs( to_hours<int>
( calendar::turn - elem->companion_mission_time ) ) ) + _( " Hours]\n" );
}
}
if( !npc_list_aux.empty() ) {
std::string entry_aux = _( "Profit: $18/hour\nDanger: High\nTime: UNKNOWN\n\n"
"\nRoster:\n" );
const std::string entry_suffix = _( " [READY]\n" );
for( auto &elem : npc_list_aux ) {
if( elem->companion_mission_time == calendar::before_time_starts ) {
entry_aux += " " + elem->name + entry_suffix;
}
}
entry_aux = entry_aux + _( "\n\n"
"The caravan will contain two or three additional members "
"from the commune, are you ready to depart?" );
mission_key.add( "Begin Commune-Refugee Center Run",
_( "Begin Commune-Refugee Center Run" ), entry );
}
entry += _( "\n\nDo you wish to bring your allies back into your party?" );
mission_key.add( "Recover Commune-Refugee Center", _( "Recover Commune-Refugee Center" ),
entry );
}
}
bool talk_function::display_and_choose_opts(
mission_data &mission_key, const tripoint_abs_omt &omt_pos, const std::string &role_id,
const std::string &title )
{
if( mission_key.entries.empty() ) {
popup( _( "There are no missions at this colony. Press Spacebar…" ) );
return false;
}
int TITLE_TAB_HEIGHT = 0;
if( role_id == "FACTION_CAMP" ) {
TITLE_TAB_HEIGHT = 1;
}
base_camps::tab_mode tab_mode = base_camps::TAB_MAIN;
size_t sel = 0;
// The following are for managing the right pane scrollbar.
size_t info_offset = 0;
nc_color col = c_white;
std::vector<std::string> name_text;
std::vector<std::string> mission_text;
input_context ctxt( "FACTIONS" );
ctxt.register_action( "UP", to_translation( "Move cursor up" ) );
ctxt.register_action( "DOWN", to_translation( "Move cursor down" ) );
ctxt.register_action( "NEXT_TAB" );
ctxt.register_action( "PREV_TAB" );
ctxt.register_action( "PAGE_UP" );
ctxt.register_action( "PAGE_DOWN" );
ctxt.register_action( "CONFIRM" );
ctxt.register_action( "QUIT" );
ctxt.register_action( "HELP_KEYBINDINGS" );
std::vector<mission_entry> cur_key_list;
auto reset_cur_key_list = [&]() {
cur_key_list = mission_key.entries[0];
for( const auto &k : mission_key.entries[1] ) {
bool has = false;
for( const auto &keys : cur_key_list ) {
if( k.id == keys.id ) {
has = true;
break;
}
}
if( !has ) {
cur_key_list.push_back( k );
}
}
};
reset_cur_key_list();
if( cur_key_list.empty() ) {
popup( _( "There are no missions at this colony. Press Spacebar…" ) );
return false;
}
size_t part_y = 0;
size_t part_x = 0;
size_t maxy = 0;
size_t maxx = 0;
size_t info_height = 0;
size_t info_width = 0;
catacurses::window w_list;
catacurses::window w_tabs;
catacurses::window w_info;
ui_adaptor ui;
ui.on_screen_resize( [&]( ui_adaptor & ui ) {
part_y = TERMY > FULL_SCREEN_HEIGHT ? ( TERMY - FULL_SCREEN_HEIGHT ) / 4 : 0;
part_x = TERMX > FULL_SCREEN_WIDTH ? ( TERMX - FULL_SCREEN_WIDTH ) / 4 : 0;
maxy = part_y ? TERMY - 2 * part_y : FULL_SCREEN_HEIGHT;
maxx = part_x ? TERMX - 2 * part_x : FULL_SCREEN_WIDTH;
info_height = maxy - 3;
info_width = maxx - 1 - MAX_FAC_NAME_SIZE;
w_list = catacurses::newwin( maxy, maxx,
point( part_x, part_y + TITLE_TAB_HEIGHT ) );
w_info = catacurses::newwin( info_height, info_width,
point( part_x + MAX_FAC_NAME_SIZE, part_y + TITLE_TAB_HEIGHT + 1 ) );
if( role_id == "FACTION_CAMP" ) {
w_tabs = catacurses::newwin( TITLE_TAB_HEIGHT, maxx, point( part_x, part_y ) );
ui.position( point( part_x, part_y ), point( maxx, maxy + TITLE_TAB_HEIGHT ) );
} else {
ui.position( point( part_x, part_y + TITLE_TAB_HEIGHT ), point( maxx, maxy ) );
}
} );
ui.mark_resize();
ui.on_redraw( [&]( const ui_adaptor & ) {
werase( w_list );
draw_border( w_list );
// NOLINTNEXTLINE(cata-use-named-point-constants)
mvwprintz( w_list, point( 1, 1 ), c_white, name_mission_tabs( omt_pos, role_id, title,
tab_mode ) );
std::vector<std::vector<std::string>> folded_names;
size_t folded_names_lines = 0;
for( const auto &cur_key_entry : cur_key_list ) {
std::vector<std::string> f_name = foldstring( cur_key_entry.name_display, MAX_FAC_NAME_SIZE - 5,
' ' );
folded_names_lines += f_name.size();
folded_names.emplace_back( f_name );
}
int name_offset = 0;
size_t sel_pos = 0;
// Translate from entry index to line index
for( size_t i = 0; i < sel; i++ ) {
sel_pos += folded_names[i].size();
}
calcStartPos( name_offset, sel_pos, info_height, folded_names_lines );
int name_index = 0;
// Are we so far down the list that we bump into the end?
bool last_section = folded_names_lines < info_height ||
folded_names_lines - info_height <= static_cast<size_t>( name_offset );
// Translate back from desired line index to the corresponding entry, making sure to round up
// near the end to ensure the last line gets included.
if( name_offset > 0 ) {
for( size_t i = 0; i < cur_key_list.size(); i++ ) {
name_offset -= folded_names[i].size();
if( name_offset <= 0 ) {
if( name_offset == 0 || last_section ) {
name_index++;
}
break;
}
name_index++;
}
}
size_t list_line = 2;
for( size_t current = name_index; list_line < info_height + 2 &&
current < cur_key_list.size(); current++ ) {
nc_color col = ( current == sel ? h_white : c_white );
//highlight important missions
for( const auto &k : mission_key.entries[0] ) {
if( cur_key_list[current].id == k.id ) {
col = ( current == sel ? h_white : c_yellow );
break;
}
}
//dull uncraftable items
for( const auto &k : mission_key.entries[10] ) {
if( cur_key_list[current].id == k.id ) {
col = ( current == sel ? h_white : c_dark_gray );
break;
}
}
std::vector<std::string> &name_text = folded_names[current];
if( list_line + name_text.size() > info_height + 2 ) {
break; // Skip last entry that would spill over into the bar.
}
for( size_t name_line = 0; name_line < name_text.size(); name_line++ ) {
print_colored_text( w_list, point( name_line ? 5 : 1, list_line ),
col, col, name_text[name_line] );
list_line += 1;
}
}
if( folded_names_lines > info_height ) {
scrollbar()
.offset_x( 0 )
.offset_y( 1 )
.content_size( folded_names_lines )
.viewport_pos( sel_pos )
.viewport_size( info_height )
.apply( w_list );
}
wnoutrefresh( w_list );
werase( w_info );
// Fold mission text, store it for scrolling
mission_text = foldstring( mission_key.cur_key.text, info_width - 2, ' ' );
if( info_height >= mission_text.size() ) {
info_offset = 0;
} else if( info_offset + info_height > mission_text.size() ) {
info_offset = mission_text.size() - info_height;
}
if( mission_text.size() > info_height ) {
scrollbar()
.offset_x( info_width - 1 )
.offset_y( 0 )
.content_size( mission_text.size() )
.viewport_pos( info_offset )
.viewport_size( info_height )
.apply( w_info );
}
const size_t end_line = std::min( info_height, mission_text.size() - info_offset );
// Display the current subset of the mission text.
for( size_t start_line = 0; start_line < end_line; start_line++ ) {
print_colored_text( w_info, point( 0, start_line ), col, col,
mission_text[start_line + info_offset] );
}
wnoutrefresh( w_info );
if( role_id == "FACTION_CAMP" ) {
werase( w_tabs );
draw_camp_tabs( w_tabs, tab_mode, mission_key.entries );
wnoutrefresh( w_tabs );
}
} );
while( true ) {
mission_key.cur_key = cur_key_list[sel];
ui_manager::redraw();
const std::string action = ctxt.handle_input();
if( action == "DOWN" ) {
if( sel == cur_key_list.size() - 1 ) {
sel = 0; // Wrap around
} else {
sel++;
}
info_offset = 0;
} else if( action == "UP" ) {
if( sel == 0 ) {
sel = cur_key_list.size() - 1; // Wrap around
} else {
sel--;
}
info_offset = 0;
} else if( action == "PAGE_UP" ) {
if( info_offset > 0 ) {
info_offset--;
}
} else if( action == "PAGE_DOWN" ) {
info_offset++;
} else if( action == "NEXT_TAB" && role_id == "FACTION_CAMP" ) {
sel = 0;
info_offset = 0;
do {
if( tab_mode == base_camps::TAB_NW ) {
tab_mode = base_camps::TAB_MAIN;
reset_cur_key_list();
} else {
tab_mode = static_cast<base_camps::tab_mode>( tab_mode + 1 );
cur_key_list = mission_key.entries[tab_mode + 1];
}
} while( cur_key_list.empty() );
} else if( action == "PREV_TAB" && role_id == "FACTION_CAMP" ) {
sel = 0;
info_offset = 0;
do {
if( tab_mode == base_camps::TAB_MAIN ) {
tab_mode = base_camps::TAB_NW;
} else {
tab_mode = static_cast<base_camps::tab_mode>( tab_mode - 1 );
}
if( tab_mode == base_camps::TAB_MAIN ) {
reset_cur_key_list();
} else {
cur_key_list = mission_key.entries[tab_mode + 1];
}
} while( cur_key_list.empty() );
} else if( action == "QUIT" ) {
mission_entry dud;
dud.id = "NONE";
dud.name_display = "NONE";
mission_key.cur_key = dud;
break;
} else if( action == "CONFIRM" ) {
if( mission_key.cur_key.possible ) {
break;
} else {
continue;
}
}
}
return true;
}
bool talk_function::handle_outpost_mission( const mission_entry &cur_key, npc &p )
{
if( cur_key.id == "Caravan Commune-Refugee Center" ) {
individual_mission( p, _( "joins the caravan team…" ), "_commune_refugee_caravan", true );
}
if( cur_key.id == "Begin Commune-Refugee Center Run" ) {
caravan_depart( p, "evac_center_18", "_commune_refugee_caravan" );
}
if( cur_key.id == "Recover Commune-Refugee Center" ) {
caravan_return( p, "evac_center_18", "_commune_refugee_caravan" );
}
if( cur_key.id == "Purchase East Field" ) {
field_build_1( p );
}
if( cur_key.id == "Upgrade East Field I" ) {
field_build_2( p );
}
if( cur_key.id == "Plant East Field" ) {
field_plant( p, "ranch_camp_63" );
}
if( cur_key.id == "Harvest East Field" ) {
field_harvest( p, "ranch_camp_63" );
}
if( cur_key.id == "Assign Scavenging Patrol" ) {
individual_mission( p, _( "departs on the scavenging patrol…" ), "_scavenging_patrol" );
}
if( cur_key.id == "Retrieve Scavenging Patrol" ) {
scavenging_patrol_return( p );
}
if( cur_key.id == "Assign Scavenging Raid" ) {
individual_mission( p, _( "departs on the scavenging raid…" ), "_scavenging_raid" );
}
if( cur_key.id == "Retrieve Scavenging Raid" ) {
scavenging_raid_return( p );
}
if( cur_key.id == "Assign Ally to Menial Labor" ) {
individual_mission( p, _( "departs to work as a laborer…" ), "_labor" );
}
if( cur_key.id == "Recover Ally from Menial Labor" ) {
labor_return( p );
}
if( cur_key.id == "Assign Ally to Carpentry Work" ) {
individual_mission( p, _( "departs to work as a carpenter…" ), "_carpenter" );
}
if( cur_key.id == "Recover Ally from Carpentry Work" ) {
carpenter_return( p );
}
if( cur_key.id == "Assign Ally to Forage for Food" ) {
individual_mission( p, _( "departs to forage for food…" ), "_forage" );
}
if( cur_key.id == "Recover Ally from Foraging" ) {
forage_return( p );
}
return true;
}
npc_ptr talk_function::individual_mission( npc &p, const std::string &desc,
const std::string &miss_id, bool group, const std::vector<item *> &equipment,
const std::map<skill_id, int> &required_skills )
{
const tripoint_abs_omt omt_pos = p.global_omt_location();
return individual_mission( omt_pos, p.companion_mission_role_id, desc, miss_id, group,
equipment, required_skills );
}
npc_ptr talk_function::individual_mission( const tripoint_abs_omt &omt_pos,
const std::string &role_id, const std::string &desc,
const std::string &miss_id, bool group, const std::vector<item *> &equipment,
const std::map<skill_id, int> &required_skills )
{
npc_ptr comp = companion_choose( required_skills );
if( comp == nullptr ) {
return comp;
}
// make sure, for now, that NPCs dismount their horse before going on a mission.
if( comp->has_effect( effect_riding ) ) {
comp->npc_dismount();
}
Character &player_character = get_player_character();
//Ensure we have someone to give equipment to before we lose it
for( item *i : equipment ) {
comp->companion_mission_inv.add_item( *i );
//comp->i_add(*i);
if( item::count_by_charges( i->typeId() ) ) {
player_character.use_charges( i->typeId(), i->charges );
} else {
player_character.use_amount( i->typeId(), 1 );
}
}
if( comp->in_vehicle ) {
get_map().unboard_vehicle( comp->pos() );
}
popup( "%s %s", comp->name, desc );
comp->set_companion_mission( omt_pos, role_id, miss_id );
if( group ) {
comp->companion_mission_time = calendar::before_time_starts;
} else {
comp->companion_mission_time = calendar::turn;
}
g->reload_npcs();
g->validate_npc_followers();
cata_assert( !comp->is_active() );
return comp;
}
void talk_function::caravan_depart( npc &p, const std::string &dest, const std::string &id )
{
std::vector<npc_ptr> npc_list = companion_list( p, id );
int distance = caravan_dist( dest );
time_duration time = 20_minutes + distance * 10_minutes;
popup( _( "The caravan departs with an estimated total travel time of %d hours…" ),
to_hours<int>( time ) );
for( auto &elem : npc_list ) {
if( elem->companion_mission_time == calendar::before_time_starts ) {
//Adds a 10% error in estimated travel time
elem->companion_mission_time = calendar::turn + time * rng_float( -1.1, 1.1 );
}
}
}
//Could be expanded to actually path to the site, just returns the distance
int talk_function::caravan_dist( const std::string &dest )
{
Character &player_character = get_player_character();
const tripoint_abs_omt site =
overmap_buffer.find_closest( player_character.global_omt_location(), dest, 0, false );
int distance = rl_dist( player_character.global_omt_location(), site );
return distance;
}
void talk_function::caravan_return( npc &p, const std::string &dest, const std::string &id )
{
npc_ptr comp = companion_choose_return( p, id, calendar::turn );
if( comp == nullptr ) {
return;
}
if( comp->companion_mission_time == calendar::before_time_starts ) {
popup( _( "%s returns to your party." ), comp->name );
companion_return( *comp );
return;
}
//So we have chosen to return an individual or party who went on the mission
//Everyone who was on the mission will have the same companion_mission_time
//and will simulate the mission and return together
std::vector<npc_ptr> caravan_party;
std::vector<npc_ptr> bandit_party;
std::vector<npc_ptr> npc_list = companion_list( p, id );
const int rand_caravan_size = rng( 1, 3 );
caravan_party.reserve( npc_list.size() + rand_caravan_size );
for( int i = 0; i < rand_caravan_size; i++ ) {
caravan_party.push_back( temp_npc( string_id<npc_template>( "commune_guard" ) ) );
}
for( auto &elem : npc_list ) {
if( elem->companion_mission_time == comp->companion_mission_time ) {
caravan_party.push_back( elem );
}
}
int distance = caravan_dist( dest );
int time = 200 + distance * 100;
int experience = rng( 10, time / 300 );
const int rand_bandit_size = rng( 1, 3 );
bandit_party.reserve( rand_bandit_size * 2 );
for( int i = 0; i < rand_bandit_size * 2; i++ ) {
bandit_party.push_back( temp_npc( string_id<npc_template>( "bandit" ) ) );
bandit_party.push_back( temp_npc( string_id<npc_template>( "thug" ) ) );
}
if( one_in( 3 ) ) {
if( one_in( 2 ) ) {
popup( _( "A bandit party approaches the caravan in the open!" ) );
force_on_force( caravan_party, "caravan", bandit_party, "band", 1 );
} else if( one_in( 3 ) ) {
popup( _( "A bandit party attacks the caravan while it it's camped!" ) );
force_on_force( caravan_party, "caravan", bandit_party, "band", 2 );
} else {
popup( _( "The caravan walks into a bandit ambush!" ) );
force_on_force( caravan_party, "caravan", bandit_party, "band", -1 );
}
}
int money = 0;
for( const auto &elem : caravan_party ) {
//Scrub temporary party members and the dead
if( elem->get_part_hp_cur( bodypart_id( "torso" ) ) == 0 && elem->has_companion_mission() ) {
overmap_buffer.remove_npc( comp->getID() );
money += ( time / 600 ) * 9;
} else if( elem->has_companion_mission() ) {
money += ( time / 600 ) * 18;
companion_skill_trainer( *elem, "combat", experience * 10_minutes, 10 );
companion_return( *elem );
}
}
if( money != 0 ) {
get_player_character().cash += ( 100 * money );
popup( _( "The caravan party has returned. Your share of the profits are $%d!" ), money );
} else {
popup( _( "The caravan was a disaster and your companions never made it home…" ) );
}
}
//A random NPC on one team attacks a random monster on the opposite
void talk_function::attack_random( const std::vector<npc_ptr> &attacker,
const std::vector< monster * > &group )
{
if( attacker.empty() || group.empty() ) {
return;
}
const auto att = random_entry( attacker );
monster *def = random_entry( group );
att->melee_attack_abstract( *def, false, matec_id( "" ) );
if( def->get_hp() <= 0 ) {
popup( _( "%s is wasted by %s!" ), def->type->nname(), att->name );
}
}
//A random monster on one side attacks a random NPC on the other
void talk_function::attack_random( const std::vector< monster * > &group,
const std::vector<npc_ptr> &defender )
{
if( defender.empty() || group.empty() ) {
return;
}
monster *att = random_entry( group );
const auto def = random_entry( defender );
att->melee_attack( *def );
//monster mon;
if( def->get_part_hp_cur( bodypart_id( "torso" ) ) <= 0 || def->is_dead() ) {
popup( _( "%s is wasted by %s!" ), def->name, att->type->nname() );
}
}
//A random NPC on one team attacks a random NPC on the opposite
void talk_function::attack_random( const std::vector<npc_ptr> &attacker,
const std::vector<npc_ptr> &defender )
{
if( attacker.empty() || defender.empty() ) {
return;
}
const auto att = random_entry( attacker );
const auto def = random_entry( defender );
const skill_id best = att->best_skill();
int best_score = 1;
if( best ) {
best_score = att->get_skill_level( best );
}
///\EFFECT_DODGE_NPC increases avoidance of random attacks
if( rng( -1, best_score ) >= rng( 0, def->get_skill_level( skill_dodge ) ) ) {
def->set_part_hp_cur( bodypart_id( "torso" ), 0 );
popup( _( "%s is wasted by %s!" ), def->name, att->name );
} else {
popup( _( "%s dodges %s's attack!" ), def->name, att->name );
}
}
//Used to determine when to retreat, might want to add in a random factor so that engagements aren't
//drawn out wars of attrition
int talk_function::combat_score( const std::vector<npc_ptr> &group )
{
int score = 0;
for( const auto &elem : group ) {
if( elem->get_part_hp_cur( bodypart_id( "torso" ) ) != 0 ) {
const skill_id best = elem->best_skill();
if( best ) {
score += elem->get_skill_level( best );
} else {
score += 1;
}
}
}
return score;
}
int talk_function::combat_score( const std::vector< monster * > &group )
{
int score = 0;
for( const auto &elem : group ) {
if( elem->get_hp() > 0 ) {
score += elem->type->difficulty;
}
}
return score;
}
npc_ptr talk_function::temp_npc( const string_id<npc_template> &type )
{
npc_ptr temp = make_shared_fast<npc>();
temp->normalize();
temp->load_npc_template( type );
return temp;
}
//The field is designed as more of a convenience than a profit opportunity.
void talk_function::field_build_1( npc &p )
{
Character &player_character = get_player_character();
if( player_character.cash < 100000 ) {
popup( _( "I'm sorry, you don't have enough money." ) );
return;
}
p.set_mutation( trait_NPC_CONSTRUCTION_LEV_1 );
player_character.cash += -100000;
const tripoint_abs_omt site =
overmap_buffer.find_closest( player_character.global_omt_location(), "ranch_camp_63", 20,
false );
tinymap bay;
bay.load( project_to<coords::sm>( site ), false );
bay.draw_square_ter( t_dirt, point( 5, 4 ), point( 15, 14 ) );
bay.draw_square_ter( t_dirtmound, point( 6, 5 ), point( 6, 13 ) );
bay.draw_square_ter( t_dirtmound, point( 8, 5 ), point( 8, 13 ) );
bay.draw_square_ter( t_dirtmound, point( 10, 5 ), point( 10, 13 ) );
bay.draw_square_ter( t_dirtmound, point( 12, 5 ), point( 12, 13 ) );
bay.draw_square_ter( t_dirtmound, point( 14, 5 ), point( 14, 13 ) );
bay.save();
popup( _( "%s jots your name down on a ledger and yells out to nearby laborers to begin "
"plowing your new field." ), p.name );
}
//Really expensive, but that is so you can't tear down the fence and sell the wood for a profit!
void talk_function::field_build_2( npc &p )
{
Character &player_character = get_player_character();
if( player_character.cash < 550000 ) {
popup( _( "I'm sorry, you don't have enough money." ) );
return;
}
p.set_mutation( trait_NPC_CONSTRUCTION_LEV_2 );
player_character.cash += -550000;
const tripoint_abs_omt site =
overmap_buffer.find_closest( player_character.global_omt_location(), "ranch_camp_63", 20,
false );
tinymap bay;
bay.load( project_to<coords::sm>( site ), false );
bay.draw_square_ter( t_fence, point( 4, 3 ), point( 16, 3 ) );
bay.draw_square_ter( t_fence, point( 4, 15 ), point( 16, 15 ) );
bay.draw_square_ter( t_fence, point( 4, 3 ), point( 4, 15 ) );
bay.draw_square_ter( t_fence, point( 16, 3 ), point( 16, 15 ) );
bay.draw_square_ter( t_fencegate_c, point( 10, 3 ), point( 10, 3 ) );
bay.draw_square_ter( t_fencegate_c, point( 10, 15 ), point( 10, 15 ) );
bay.draw_square_ter( t_fencegate_c, point( 4, 9 ), point( 4, 9 ) );
bay.save();
popup( _( "After counting your money %s directs a nearby laborer to begin constructing a "
"fence around your plot…" ), p.name );
}
void talk_function::field_plant( npc &p, const std::string &place )
{
Character &player_character = get_player_character();