-
Notifications
You must be signed in to change notification settings - Fork 96
/
building_gameplay.cpp
2979 lines (2758 loc) · 165 KB
/
building_gameplay.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
// 3D World - Building Gameplay Logic
// by Frank Gennari 12/29/21
#include "3DWorld.h"
#include "function_registry.h"
#include "buildings.h"
#include "openal_wrap.h"
using std::string;
float const THROW_VELOCITY = 0.0050;
float const ALERT_THRESH = 0.08; // min sound alert level for AIs
float const PLAYER_RESPAWN = 5.0; // in seconds
bool do_room_obj_pickup(0), use_last_pickup_object(0), show_bldg_pickup_crosshair(0), player_near_toilet(0), player_attracts_flies(0), player_wait_respawn(0);
bool city_action_key(0), can_do_building_action(0);
int can_pickup_bldg_obj(0), player_in_elevator(0); // player_in_elevator: 0=no, 1=in, 2=in + doors closed, 3=moving
float office_chair_rot_rate(0.0), cur_building_sound_level(0.0);
point debug_event_pos;
carried_item_t player_held_object;
bldg_obj_type_t bldg_obj_types[NUM_ROBJ_TYPES];
vector<sphere_t> cur_sounds; // radius = sound volume
extern bool camera_in_building, player_is_hiding, player_in_unlit_room, player_in_tunnel, player_in_mall, disable_blood;
extern int window_width, window_height, display_framerate, display_mode, game_mode, building_action_key, frame_counter, player_in_basement, player_in_water;
extern int animate2, camera_surf_collide;
extern float fticks, CAMERA_RADIUS;
extern double tfticks, camera_zh;
extern colorRGBA vignette_color;
extern building_params_t global_building_params;
extern building_t const *player_building;
void place_player_at_xy(float xval, float yval);
void show_key_icon(vector<colorRGBA> const &key_colors);
void show_flashlight_icon();
void show_pool_cue_icon();
bool is_shirt_model(room_object_t const &obj);
bool is_pants_model(room_object_t const &obj);
bool player_at_full_health();
bool player_is_thirsty();
void register_fly_attract(bool no_msg);
room_obj_or_custom_item_t steal_from_car(room_object_t const &car, float floor_spacing, bool do_pickup);
float get_filing_cabinet_drawers(room_object_t const &c, vect_cube_t &drawers);
colorRGBA get_bucket_liquid_info(room_object_t const &c, float &liquid_level);
void reset_creepy_sounds();
void clear_building_water_splashes();
bool in_building_gameplay_mode() {return (game_mode == GAME_MODE_BUILDINGS);} // replaces dodgeball mode
// object types/pickup
void setup_bldg_obj_types() {
static bool was_setup(0);
if (was_setup) return; // nothing to do
was_setup = 1;
// player_coll, ai_coll, rat_coll, pickup, attached, is_model, lg_sm, value, weight, name [capacity]
// pc ac rc pu at im ls value weight name
bldg_obj_types[TYPE_TABLE ] = bldg_obj_type_t(1, 1, 1, 1, 0, 0, 1, 70.0, 40.0, "table");
bldg_obj_types[TYPE_CHAIR ] = bldg_obj_type_t(0, 1, 1, 1, 0, 0, 1, 50.0, 25.0, "chair"); // skip player collisions because they can be in the way and block the path in some rooms
bldg_obj_types[TYPE_STAIR ] = bldg_obj_type_t(1, 0, 1, 0, 1, 0, 1, 0.0, 0.0, "stair");
bldg_obj_types[TYPE_STAIR_WALL] = bldg_obj_type_t(1, 1, 1, 0, 1, 0, 1, 0.0, 0.0, "stairs wall");
bldg_obj_types[TYPE_PG_WALL ] = bldg_obj_type_t(1, 1, 1, 0, 1, 0, 0, 0.0, 0.0, "parking garage wall"); // detail object
bldg_obj_types[TYPE_PG_PILLAR ] = bldg_obj_type_t(1, 1, 1, 0, 1, 0, 0, 0.0, 0.0, "support pillar"); // detail object
bldg_obj_types[TYPE_PG_BEAM ] = bldg_obj_type_t(1, 1, 1, 0, 1, 0, 0, 0.0, 0.0, "ceiling beam"); // detail object
bldg_obj_types[TYPE_ELEVATOR ] = bldg_obj_type_t(1, 1, 1, 0, 1, 0, 0, 0.0, 0.0, "elevator");
bldg_obj_types[TYPE_PARK_SPACE] = bldg_obj_type_t(0, 0, 0, 0, 1, 0, 0, 0.0, 0.0, "parking space"); // detail object
bldg_obj_types[TYPE_RAMP ] = bldg_obj_type_t(1, 1, 1, 0, 1, 0, 0, 0.0, 0.0, "ramp"); // detail object
bldg_obj_types[TYPE_LIGHT ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 0, 40.0, 5.0, "light");
bldg_obj_types[TYPE_RUG ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 1, 50.0, 20.0, "rug");
bldg_obj_types[TYPE_PICTURE ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 1, 100.0, 1.0, "picture"); // should be random value
bldg_obj_types[TYPE_WBOARD ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 1, 50.0, 25.0, "whiteboard");
bldg_obj_types[TYPE_BOOK ] = bldg_obj_type_t(0, 0, 1, 1, 0, 0, 3, 10.0, 1.0, "book");
bldg_obj_types[TYPE_BCASE ] = bldg_obj_type_t(1, 1, 1, 1, 0, 0, 3, 150.0, 100.0, "bookcase"); // Note: can't pick up until bookcase can be expanded and books taken off
bldg_obj_types[TYPE_TCAN ] = bldg_obj_type_t(0, 1, 1, 1, 0, 0, 2, 12.0, 2.0, "trashcan"); // skip player collisions because they can be in the way and block the path in some rooms
bldg_obj_types[TYPE_DESK ] = bldg_obj_type_t(1, 1, 1, 0, 0, 0, 3, 100.0, 80.0, "desk"); // drawers are small items
bldg_obj_types[TYPE_BED ] = bldg_obj_type_t(1, 1, 1, 1, 0, 0, 3, 300.0, 200.0, "bed"); // pillows are small, and the rest is large
bldg_obj_types[TYPE_WINDOW ] = bldg_obj_type_t(0, 0, 0, 0, 1, 0, 1, 0.0, 0.0, "window");
bldg_obj_types[TYPE_BLOCKER ] = bldg_obj_type_t(0, 0, 0, 0, 0, 0, 0, 0.0, 0.0, "<blocker>"); // not a drawn object; block other objects, but not the player or AI
bldg_obj_types[TYPE_COLLIDER ] = bldg_obj_type_t(1, 1, 1, 0, 0, 0, 0, 0.0, 0.0, "<collider>"); // not a drawn object; block the player and AI
bldg_obj_types[TYPE_CUBICLE ] = bldg_obj_type_t(0, 0, 1, 0, 1, 0, 1, 500.0, 250.0, "cubicle"); // skip collisions because they have their own colliders, but include rat coll
bldg_obj_types[TYPE_STALL ] = bldg_obj_type_t(1, 1, 1, 1, 1, 0, 1, 40.0, 20.0, "bathroom divider"); // can pick up short sections of bathroom stalls (urinal dividers)
bldg_obj_types[TYPE_SIGN ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 3, 10.0, 1.0, "sign");
bldg_obj_types[TYPE_COUNTER ] = bldg_obj_type_t(1, 1, 1, 0, 1, 0, 3, 0.0, 0.0, "kitchen counter");
bldg_obj_types[TYPE_CABINET ] = bldg_obj_type_t(0, 0, 0, 0, 1, 0, 3, 0.0, 0.0, "kitchen cabinet");
bldg_obj_types[TYPE_KSINK ] = bldg_obj_type_t(1, 1, 1, 0, 1, 0, 3, 0.0, 0.0, "kitchen sink");
bldg_obj_types[TYPE_BRSINK ] = bldg_obj_type_t(1, 1, 0, 0, 1, 0, 1, 0.0, 0.0, "bathroom sink"); // for office building bathrooms
bldg_obj_types[TYPE_PLANT ] = bldg_obj_type_t(1, 1, 1, 1, 0, 0, 3, 18.0, 8.0, "potted plant"); // AI collides with plants on the floor, and player does in some cases
bldg_obj_types[TYPE_DRESSER ] = bldg_obj_type_t(1, 1, 1, 0, 0, 0, 3, 120.0, 110.0, "dresser"); // Note: can't pick up until drawers can be opened and items removed from them
bldg_obj_types[TYPE_NIGHTSTAND] = bldg_obj_type_t(1, 1, 1, 1, 0, 0, 3, 60.0, 45.0, "nightstand");
bldg_obj_types[TYPE_FLOORING ] = bldg_obj_type_t(0, 0, 0, 0, 1, 0, 1, 0.0, 0.0, "flooring");
// closets can't be picked up, but they can block a pickup; marked as large because small objects are not modified; marked as is_model because closets can contain lamps
bldg_obj_types[TYPE_CLOSET ] = bldg_obj_type_t(1, 1, 1, 1, 1, 1, 1, 0.0, 0.0, "closet");
bldg_obj_types[TYPE_WALL_TRIM ] = bldg_obj_type_t(0, 0, 0, 0, 1, 0, 0, 0.0, 0.0, "wall trim"); // detail object
bldg_obj_types[TYPE_RAILING ] = bldg_obj_type_t(1, 1, 0, 0, 1, 0, 2, 0.0, 0.0, "railing"); // Note: ai_coll logic is custom, but ai_coll flag has been set for consistency
bldg_obj_types[TYPE_CRATE ] = bldg_obj_type_t(1, 1, 1, 1, 0, 0, 2, 10.0, 12.0, "crate"); // should be random value
bldg_obj_types[TYPE_BOX ] = bldg_obj_type_t(1, 1, 1, 1, 0, 0, 2, 5.0, 8.0, "box"); // should be random value
bldg_obj_types[TYPE_MIRROR ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 1, 40.0, 15.0, "mirror"); // house medicine cabinet or office building bathroom mirror
bldg_obj_types[TYPE_SHELVES ] = bldg_obj_type_t(1, 1, 1, 1, 0, 0, 2, 0.0, 0.0, "shelves");
bldg_obj_types[TYPE_KEYBOARD ] = bldg_obj_type_t(0, 0, 1, 1, 0, 0, 2, 15.0, 2.0, "keyboard");
bldg_obj_types[TYPE_SHOWER ] = bldg_obj_type_t(1, 1, 1, 0, 1, 0, 1, 0.0, 0.0, "shower"); // technically large + small, but only large objects are dynamically updated
bldg_obj_types[TYPE_RDESK ] = bldg_obj_type_t(1, 1, 1, 0, 1, 0, 1, 800.0, 300.0, "reception desk"); // mark as attached to prevent movement blocking stairs or elevators
bldg_obj_types[TYPE_BOTTLE ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 1.0, 1.0, "bottle", 1); // single use
bldg_obj_types[TYPE_WINE_RACK ] = bldg_obj_type_t(1, 1, 1, 1, 0, 0, 3, 75.0, 40.0, "wine rack");
bldg_obj_types[TYPE_COMPUTER ] = bldg_obj_type_t(0, 1, 1, 1, 0, 0, 2, 500.0, 20.0, "computer"); // rats can collide with computers
bldg_obj_types[TYPE_MWAVE ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 100.0, 50.0, "microwave oven");
bldg_obj_types[TYPE_PAPER ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 0.0, 0.0, "sheet of paper"); // will have a random value that's often 0
bldg_obj_types[TYPE_BLINDS ] = bldg_obj_type_t(0, 0, 0, 0, 1, 0, 1, 50.0, 7.0, "window blinds");
bldg_obj_types[TYPE_PEN ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 0.10, 0.02, "pen");
bldg_obj_types[TYPE_PENCIL ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 0.10, 0.02, "pencil");
bldg_obj_types[TYPE_PAINTCAN ] = bldg_obj_type_t(0, 0, 1, 1, 0, 0, 2, 12.0, 8.0, "paint can");
bldg_obj_types[TYPE_LG_BALL ] = bldg_obj_type_t(0, 0, 1, 1, 0, 0, 2, 15.0, 1.2, "ball");
bldg_obj_types[TYPE_HANGER_ROD] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 10.0, 5.0, "hanger rod");
bldg_obj_types[TYPE_DRAIN ] = bldg_obj_type_t(0, 0, 1, 0, 1, 0, 2, 0.0, 0.0, "drain pipe");
bldg_obj_types[TYPE_MONEY ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 20.0, 0.0, "pile of money"); // $20 bills
bldg_obj_types[TYPE_PHONE ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 200.0, 0.1, "cell phone");
bldg_obj_types[TYPE_TPROLL ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 0.25, 0.1, "TP roll", 200);
bldg_obj_types[TYPE_SPRAYCAN ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 2.0, 1.0, "spray paint", 5000);
bldg_obj_types[TYPE_MARKER ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 0.20, 0.05, "marker", 10000);
bldg_obj_types[TYPE_BUTTON ] = bldg_obj_type_t(0, 0, 0, 1, 1, 0, 2, 1.0, 0.05, "button");
bldg_obj_types[TYPE_CRACK ] = bldg_obj_type_t(0, 0, 0, 0, 1, 0, 2, 0.0, 0.0, "crack"); // in glass; not yet used
bldg_obj_types[TYPE_SWITCH ] = bldg_obj_type_t(0, 0, 0, 0, 1, 0, 2, 10.0, 0.1, "switch");
bldg_obj_types[TYPE_BREAKER ] = bldg_obj_type_t(0, 0, 0, 0, 1, 0, 2, 20.0, 0.1, "breaker");
bldg_obj_types[TYPE_PLATE ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 6.0, 0.25, "plate");
bldg_obj_types[TYPE_LAPTOP ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 600.0, 8.0, "laptop");
bldg_obj_types[TYPE_FPLACE ] = bldg_obj_type_t(1, 1, 1, 0, 1, 0, 1, 0.0, 2000.0,"fireplace");
bldg_obj_types[TYPE_LBASKET ] = bldg_obj_type_t(1, 1, 1, 1, 0, 0, 2, 12.0, 2.0, "laundry basket");
bldg_obj_types[TYPE_WHEATER ] = bldg_obj_type_t(1, 1, 1, 0, 1, 0, 0, 300.0, 500.0, "water heater"); // detail object
bldg_obj_types[TYPE_FURNACE ] = bldg_obj_type_t(1, 1, 1, 0, 1, 0, 0, 500.0, 200.0, "furnace"); // detail object
bldg_obj_types[TYPE_TAPE ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 2.0, 0.4, "duct tape", 1000);
bldg_obj_types[TYPE_OUTLET ] = bldg_obj_type_t(0, 0, 0, 0, 1, 0, 0, 7.0, 0.1, "outlet"); // detail object
bldg_obj_types[TYPE_PIPE ] = bldg_obj_type_t(1, 1, 1, 0, 1, 0, 0, 0.0, 0.0, "pipe"); // detail object; only vertical pipes are collidable
bldg_obj_types[TYPE_CURB ] = bldg_obj_type_t(0, 0, 1, 0, 1, 0, 0, 0.0, 100.0, "curb"); // for parking garages; only rats collide
bldg_obj_types[TYPE_BRK_PANEL ] = bldg_obj_type_t(1, 1, 1, 0, 1, 0, 2, 1000.0,100.0, "breaker panel");
bldg_obj_types[TYPE_VENT ] = bldg_obj_type_t(0, 0, 0, 0, 1, 0, 0, 20.0, 2.0, "vent"); // detail object
bldg_obj_types[TYPE_ATTIC_DOOR] = bldg_obj_type_t(1, 1, 1, 0, 1, 0, 2, 100.0, 50.0, "attic door"); // door/ladder
bldg_obj_types[TYPE_CHIMNEY ] = bldg_obj_type_t(1, 1, 1, 0, 1, 0, 2, 1000.0,1000.0,"chimney"); // interior chimney in attic
bldg_obj_types[TYPE_DUCT ] = bldg_obj_type_t(1, 1, 1, 0, 1, 0, 0, 0.0, 0.0, "duct"); // detail object
bldg_obj_types[TYPE_TOY ] = bldg_obj_type_t(0, 0, 1, 1, 0, 0, 2, 2.0, 0.1, "toy"); // plastic ring stack
bldg_obj_types[TYPE_DRESS_MIR ] = bldg_obj_type_t(0, 0, 1, 1, 0, 0, 1, 100.0, 30.0, "mirror");
bldg_obj_types[TYPE_PAN ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 15.0, 4.0, "frying pan");
bldg_obj_types[TYPE_VASE ] = bldg_obj_type_t(1, 1, 0, 1, 0, 0, 2, 20.0, 1.0, "vase"); // only large mall floor vases have collisions enabled
bldg_obj_types[TYPE_URN ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 40.0, 2.0, "urn");
bldg_obj_types[TYPE_FCABINET ] = bldg_obj_type_t(1, 1, 1, 1, 0, 0, 3, 100.0, 220.0, "filing cabinet"); // body is large, drawers and their contents are small
bldg_obj_types[TYPE_STAPLER ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 10.0, 0.6, "stapler");
bldg_obj_types[TYPE_WIND_SILL ] = bldg_obj_type_t(0, 0, 0, 0, 1, 0, 1, 0.0, 0.0, "window sill");
bldg_obj_types[TYPE_EXT_STEP ] = bldg_obj_type_t(0, 0, 0, 0, 1, 0, 1, 0.0, 0.0, "exterior step");
bldg_obj_types[TYPE_BALCONY ] = bldg_obj_type_t(1, 1, 1, 0, 1, 0, 0, 0.0, 0.0, "balcony"); // exterior object
bldg_obj_types[TYPE_SPRINKLER ] = bldg_obj_type_t(0, 0, 0, 0, 1, 0, 0, 0.0, 0.0, "fire sprinkler"); // detail object
bldg_obj_types[TYPE_FEXT_MOUNT] = bldg_obj_type_t(0, 0, 0, 0, 1, 0, 2, 0.0, 0.0, "fire extinguisher mount");
bldg_obj_types[TYPE_FEXT_SIGN ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 5.0, 0.2, "fire extinguisher sign");
bldg_obj_types[TYPE_PIZZA_BOX ] = bldg_obj_type_t(0, 0, 1, 1, 0, 0, 2, 10.0, 1.0, "box of pizza");
bldg_obj_types[TYPE_PIZZA_TOP ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 0.05, 0.01, "pizza topping"); // pepperoni, tomato, pepper, olive, etc.; may need sub-types with diff names
bldg_obj_types[TYPE_TEESHIRT ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 10.0, 0.25, "tee shirt");
bldg_obj_types[TYPE_PANTS ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 16.0, 0.50, "jeans");
bldg_obj_types[TYPE_BLANKET ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 20.0, 2.0, "blanket");
bldg_obj_types[TYPE_SERVER ] = bldg_obj_type_t(1, 1, 1, 1, 0, 0, 2, 10000, 400.00,"server"); // small because it's in a windowless room; too heavy for inventory
bldg_obj_types[TYPE_POOL_BALL ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 2.0, 0.37, "pool ball");
bldg_obj_types[TYPE_POOL_CUE ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 20.0, 1.2, "pool cue");
bldg_obj_types[TYPE_WALL_MOUNT] = bldg_obj_type_t(0, 0, 0, 0, 1, 0, 2, 0.0, 0.0, "wall mounting bracket");
bldg_obj_types[TYPE_POOL_TILE ] = bldg_obj_type_t(1, 0, 0, 0, 1, 0, 2, 0.0, 0.0, "pool tile");
bldg_obj_types[TYPE_POOL_FLOAT] = bldg_obj_type_t(1, 0, 0, 1, 0, 0, 2, 10.0, 1.0, "pool float");
bldg_obj_types[TYPE_BENCH ] = bldg_obj_type_t(1, 1, 1, 1, 0, 0, 2, 40.0, 30.0, "bench");
bldg_obj_types[TYPE_DIV_BOARD ] = bldg_obj_type_t(1, 1, 1, 0, 1, 0, 2, 0.0, 100.0, "diving board");
bldg_obj_types[TYPE_FALSE_DOOR] = bldg_obj_type_t(0, 0, 0, 0, 1, 0, 1, 0.0, 0.0, "door");
bldg_obj_types[TYPE_FLASHLIGHT] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 15.0, 1.0, "flashlight");
bldg_obj_types[TYPE_CANDLE ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 1.0, 0.4, "candle", 10000);
bldg_obj_types[TYPE_CAMERA ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 150.0, 1.5, "security camera");
bldg_obj_types[TYPE_CLOCK ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 20.0, 1.0, "clock");
bldg_obj_types[TYPE_DOWNSPOUT ] = bldg_obj_type_t(0, 0, 0, 0, 1, 0, 1, 0.0, 0.0, "downspout");
bldg_obj_types[TYPE_SHELFRACK ] = bldg_obj_type_t(1, 1, 1, 1, 1, 0, 1, 0.0, 0.0, "shelf rack");
bldg_obj_types[TYPE_CHIM_CAP ] = bldg_obj_type_t(0, 0, 0, 0, 1, 0, 1, 0.0, 0.0, "exterior step");
bldg_obj_types[TYPE_FOOD_BOX ] = bldg_obj_type_t(0, 0, 1, 1, 0, 0, 2, 8.0, 1.0, "box of food");
bldg_obj_types[TYPE_SAFE ] = bldg_obj_type_t(1, 1, 1, 0, 1, 0, 2, 250.0, 300.0, "safe");
bldg_obj_types[TYPE_LADDER ] = bldg_obj_type_t(1, 0, 0, 0, 1, 0, 1, 0.0, 0.0, "ladder");
bldg_obj_types[TYPE_CHECKOUT ] = bldg_obj_type_t(1, 1, 1, 0, 1, 0, 1, 0.0, 300.0, "checkout counter");
bldg_obj_types[TYPE_FISHTANK ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 1, 100.0, 160.0, "fish tank");
bldg_obj_types[TYPE_LAVALAMP ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 30.0, 3.0, "lava lamp");
bldg_obj_types[TYPE_SHOWERTUB ] = bldg_obj_type_t(1, 1, 1, 1, 1, 0, 1, 0.0, 0.0, "shower"); // shower part of a shower+tub combo; technically large and small; take curtains
bldg_obj_types[TYPE_TRASH ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 0.0, 0.1, "trash");
bldg_obj_types[TYPE_VALVE ] = bldg_obj_type_t(0, 0, 0, 0, 1, 0, 0, 0.0, 0.0, "valve"); // detail object
bldg_obj_types[TYPE_DBG_SHAPE ] = bldg_obj_type_t(0, 0, 0, 0, 0, 0, 1, 0.0, 0.0, "debug shape"); // small (optimization)
bldg_obj_types[TYPE_METAL_BAR ] = bldg_obj_type_t(0, 0, 0, 0, 1, 0, 2, 0.0, 0.0, "metal bar");
bldg_obj_types[TYPE_OFF_PILLAR] = bldg_obj_type_t(1, 1, 1, 0, 1, 0, 1, 0.0, 0.0, "office pillar");
bldg_obj_types[TYPE_DRINK_CAN ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 1.0, 0.75, "drink can", 1); // single use
bldg_obj_types[TYPE_CONF_TABLE] = bldg_obj_type_t(1, 1, 1, 1, 0, 0, 1, 200.0, 300.0, "conference table");
bldg_obj_types[TYPE_INT_WINDOW] = bldg_obj_type_t(1, 1, 1, 0, 1, 0, 1, 0.0, 0.0, "window"); // window in interior wall between two rooms
bldg_obj_types[TYPE_INT_LADDER] = bldg_obj_type_t(1, 1, 1, 1, 0, 0, 2, 50.0, 40.0, "ladder");
bldg_obj_types[TYPE_MACHINE ] = bldg_obj_type_t(1, 1, 1, 0, 1, 0, 2, 0.0, 0.0, "machine");
bldg_obj_types[TYPE_BUCKET ] = bldg_obj_type_t(0, 1, 1, 1, 0, 0, 3, 8.0, 1.0, "bucket"); // with transparent liquid
bldg_obj_types[TYPE_SPIWEB ] = bldg_obj_type_t(0, 0, 0, 0, 0, 0, 2, 0.0, 0.0, "spider web");
bldg_obj_types[TYPE_TREE ] = bldg_obj_type_t(1, 1, 1, 0, 1, 0, 3, 0.0, 1000.0,"tree"); // similar to plants; only in basement malls
bldg_obj_types[TYPE_STORE_GATE] = bldg_obj_type_t(1, 1, 1, 0, 1, 0, 2, 0.0, 0.0, "store gate");
bldg_obj_types[TYPE_THEFT_SENS] = bldg_obj_type_t(1, 1, 1, 1, 0, 0, 2, 100.0, 30.0, "theft sensor"); // should this be a large object to avoid updating sm_static with lights?
bldg_obj_types[TYPE_ELEC_WIRE ] = bldg_obj_type_t(0, 0, 0, 0, 1, 0, 2, 0.0, 0.0, "electrical wire");
bldg_obj_types[TYPE_ERASER ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 3.0, 0.05, "whiteboard eraser");
// player_coll, ai_coll, rat_coll, pickup, attached, is_model, lg_sm, value, weight, name [capacity]
// 3D models
bldg_obj_types[TYPE_TOILET ] = bldg_obj_type_t(1, 1, 1, 1, 1, 1, 0, 120.0, 88.0, "toilet");
bldg_obj_types[TYPE_SINK ] = bldg_obj_type_t(1, 1, 1, 1, 1, 1, 0, 80.0, 55.0, "sink");
bldg_obj_types[TYPE_TUB ] = bldg_obj_type_t(1, 1, 1, 0, 1, 1, 1, 250.0, 200.0, "bathtub"); // large object for sides
bldg_obj_types[TYPE_FRIDGE ] = bldg_obj_type_t(1, 1, 1, 1, 0, 1, 0, 700.0, 300.0, "refrigerator"); // no pickup, too large and may want to keep it for future hunger bar
bldg_obj_types[TYPE_STOVE ] = bldg_obj_type_t(1, 1, 1, 1, 0, 1, 0, 400.0, 150.0, "stove");
bldg_obj_types[TYPE_TV ] = bldg_obj_type_t(1, 1, 1, 1, 0, 1, 1, 400.0, 70.0, "TV");
bldg_obj_types[TYPE_MONITOR ] = bldg_obj_type_t(0, 0, 0, 1, 0, 1, 1, 250.0, 15.0, "computer monitor");
bldg_obj_types[TYPE_COUCH ] = bldg_obj_type_t(1, 1, 1, 1, 0, 1, 0, 600.0, 300.0, "couch");
bldg_obj_types[TYPE_OFF_CHAIR ] = bldg_obj_type_t(1, 1, 1, 1, 0, 1, 0, 150.0, 60.0, "office chair");
bldg_obj_types[TYPE_URINAL ] = bldg_obj_type_t(1, 1, 1, 1, 1, 1, 0, 100.0, 80.0, "urinal");
bldg_obj_types[TYPE_LAMP ] = bldg_obj_type_t(0, 0, 1, 1, 0, 1, 0, 25.0, 12.0, "lamp");
bldg_obj_types[TYPE_WASHER ] = bldg_obj_type_t(1, 1, 1, 1, 0, 1, 0, 300.0, 150.0, "washer");
bldg_obj_types[TYPE_DRYER ] = bldg_obj_type_t(1, 1, 1, 1, 0, 1, 0, 300.0, 160.0, "dryer");
// keys are special because they're potentially either a small object or an object model (in a drawer)
bldg_obj_types[TYPE_KEY ] = bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 0.0, 0.05, "room key"); // drawn as an object, not a model
bldg_obj_types[TYPE_HANGER ] = bldg_obj_type_t(0, 0, 0, 1, 0, 1, 0, 0.25, 0.05, "clothes hanger");
bldg_obj_types[TYPE_CLOTHES ] = bldg_obj_type_t(0, 0, 0, 1, 0, 1, 0, 10.0, 0.25, "clothes"); // teeshirt, shirt, pants, etc.
bldg_obj_types[TYPE_FESCAPE ] = bldg_obj_type_t(1, 1, 1, 0, 1, 1, 0, 10000, 4000, "fire escape"); // technically exterior, not interior
bldg_obj_types[TYPE_CUP ] = bldg_obj_type_t(0, 0, 0, 1, 0, 1, 0, 5.0, 0.2, "cup");
bldg_obj_types[TYPE_TOASTER ] = bldg_obj_type_t(0, 0, 0, 1, 0, 1, 2, 20.0, 2.5, "toaster"); // mixed model and object geom
bldg_obj_types[TYPE_HOOD ] = bldg_obj_type_t(0, 0, 1, 0, 1, 1, 0, 200.0, 40.0, "ventilation hood");
bldg_obj_types[TYPE_RCHAIR ] = bldg_obj_type_t(1, 1, 1, 1, 0, 1, 0, 120.0, 45.0, "rocking chair");
bldg_obj_types[TYPE_SILVER ] = bldg_obj_type_t(0, 0, 0, 1, 0, 1, 2, 10.0, 0.2, "silverware"); // drawn as a small static object when expanded (in a drawer)
bldg_obj_types[TYPE_TOY_MODEL ] = bldg_obj_type_t(0, 0, 1, 1, 0, 1, 0, 4.0, 0.2, "toy"); // plastic ring stack
bldg_obj_types[TYPE_CEIL_FAN ] = bldg_obj_type_t(0, 0, 0, 0, 1, 1, 0, 200.0, 25.0, "ceiling fan");
bldg_obj_types[TYPE_FIRE_EXT ] = bldg_obj_type_t(0, 0, 1, 1, 0, 1, 0, 25.0, 10.0, "fire extinguisher", 250);
bldg_obj_types[TYPE_FOLD_SHIRT] = bldg_obj_type_t(0, 0, 0, 1, 0, 1, 2, 10.0, 0.25, "folded shirt"); // drawn as a small static object when expanded (in a drawer)
bldg_obj_types[TYPE_PLANT_MODEL]= bldg_obj_type_t(0, 1, 1, 1, 0, 1, 0, 15.0, 5.0, "potted plant"); // AI collides with plants on the floor
bldg_obj_types[TYPE_POOL_TABLE] = bldg_obj_type_t(1, 1, 1, 1, 0, 1, 0, 400.0, 250.0, "pool table");
bldg_obj_types[TYPE_POOL_LAD ] = bldg_obj_type_t(0, 0, 1, 0, 1, 1, 0, 200.0, 35.0, "pool ladder");
bldg_obj_types[TYPE_BAR_STOOL ] = bldg_obj_type_t(1, 1, 1, 1, 0, 1, 0, 100.0, 40.0, "bar stool");
bldg_obj_types[TYPE_PADLOCK ] = bldg_obj_type_t(0, 0, 0, 1, 0, 1, 0, 10.0, 0.2, "padlock");
bldg_obj_types[TYPE_CASHREG ] = bldg_obj_type_t(1, 1, 1, 0, 1, 1, 0, 1000, 200, "cash register");
bldg_obj_types[TYPE_WFOUNTAIN ] = bldg_obj_type_t(1, 1, 1, 0, 1, 1, 0, 200, 80, "water fountain");
bldg_obj_types[TYPE_BANANA ] = bldg_obj_type_t(0, 0, 1, 1, 0, 1, 0, 0.25, 0.3, "banana");
bldg_obj_types[TYPE_BAN_PEEL ] = bldg_obj_type_t(1, 0, 1, 1, 0, 1, 0, 0.0, 0.05, "banana peel");
bldg_obj_types[TYPE_CONF_PHONE] = bldg_obj_type_t(0, 0, 0, 1, 0, 1, 0, 40.0, 2.0, "phone");
bldg_obj_types[TYPE_GBIKE ] = bldg_obj_type_t(1, 1, 1, 1, 0, 1, 0, 150.0, 20.0, "bike"); // garage bike
bldg_obj_types[TYPE_XFORMER ] = bldg_obj_type_t(1, 1, 1, 0, 1, 1, 0, 0.0, 0.0, "transformer");
bldg_obj_types[TYPE_US_FLAG ] = bldg_obj_type_t(0, 0, 0, 1, 0, 1, 0, 30.0, 1.0, "American Flag");
bldg_obj_types[TYPE_BLDG_FOUNT] = bldg_obj_type_t(1, 1, 1, 0, 1, 1, 0, 0.0, 0.0, "fountain"); // mall fountain
// player_coll, ai_coll, rat_coll, pickup, attached, is_model, lg_sm, value, weight, name [capacity]
// animals; not room objects
bldg_obj_types[TYPE_RAT ] = bldg_obj_type_t(0, 0, 1, 1, 0, 1, 0, 8.99, 1.0, "rat"); // can be picked up
bldg_obj_types[TYPE_ROACH ] = bldg_obj_type_t(0, 0, 0, 1, 0, 1, 0, 0.0, 0.01, "cockroach");
bldg_obj_types[TYPE_SPIDER ] = bldg_obj_type_t(0, 0, 1, 0, 0, 0, 0, 0.0, 0.1, "spider");
bldg_obj_types[TYPE_SNAKE ] = bldg_obj_type_t(0, 0, 1, 0, 0, 0, 0, 50.00, 4.0, "snake");
bldg_obj_types[TYPE_INSECT ] = bldg_obj_type_t(0, 0, 0, 0, 0, 1, 0, 0.0, 0.01, "insect"); // not actually added as an object
bldg_obj_types[TYPE_FISH ] = bldg_obj_type_t(0, 0, 0, 0, 0, 1, 0, 10.0, 0.1, "fish"); // not actually added as an object
// pc ac rc pu at im ls value weight name [capacity]
}
bldg_obj_type_t const &get_room_obj_type(room_object_t const &obj) {
assert(obj.type < NUM_ROBJ_TYPES);
return bldg_obj_types[obj.type];
}
float carried_item_t::get_remaining_capacity_ratio() const {
unsigned const capacity(get_room_obj_type(*this).capacity);
return ((capacity == 0) ? 1.0 : (1.0 - float(min(use_count, capacity))/float(capacity))); // Note: zero capacity is unlimited and ratio returned is always 1.0
}
float const mattress_weight(80.0), sheets_weight(4.0), pillow_weight(1.0);
rand_gen_t rgen_from_obj(room_object_t const &obj) {
rand_gen_t rgen;
rgen.set_state((12345*abs(obj.x1()) + obj.obj_id), 67890*abs(obj.y1()));
return rgen;
}
float get_paper_value(room_object_t const &obj) {
rand_gen_t rgen(rgen_from_obj(obj));
if (rgen.rand_float() >= (obj.is_on_floor() ? 0.05 : 0.25)) return 0.0; // only 25% of papers have some value; 5% of papers on the floor
float const val_mult((rgen.rand_float() < 0.25) ? 10.0 : 1.0); // 25% of papers have higher value
return val_mult*(2 + (rgen.rand()%10))*(1 + (rgen.rand()%10));
}
string get_pool_ball_name(unsigned number) {
assert(number < 16);
std::ostringstream oss;
if (number == 15) {oss << "cue ball";} else {oss << (number+1) << " ball";}
return oss.str();
}
bldg_obj_type_t get_taken_obj_type(room_object_t const &obj) {
room_object const otype(obj.type);
// player_coll, ai_coll, rat_coll, pickup, attached, is_model, lg_sm, value, weight, name [capacity]
if (otype == TYPE_PICTURE && obj.taken_level > 0) {return bldg_obj_type_t(0, 0, 0, 1, 0, 0, 1, 20.0, 6.0, "picture frame");} // second item to take from picture
if (otype == TYPE_TPROLL && obj.taken_level > 0) {return bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 6.0, 0.5, "toilet paper holder");} // second item to take from tproll
if (otype == TYPE_TCAN && obj.in_mall() ) {return bldg_obj_type_t(0, 1, 1, 1, 0, 0, 2, 80.0, 40.0, "large trashcan");}
if (otype == TYPE_BED) {
if (obj.taken_level > 1) {return bldg_obj_type_t(0, 0, 0, 1, 0, 0, 1, 250.0, mattress_weight, "mattress" );} // third item to take from bed
if (obj.taken_level > 0) {return bldg_obj_type_t(0, 0, 0, 1, 0, 0, 1, 80.0, sheets_weight, "bed sheets");} // second item to take from bed
return bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 20.0, pillow_weight, "pillow"); // first item to take from bed
}
if (otype == TYPE_PLANT && !(obj.flags & RO_FLAG_ADJ_BOT)) { // plant not on a table/desk
if (obj.taken_level > 1) {return bldg_obj_type_t(0, 0, 1, 1, 0, 0, 1, 10.0, 10.0, "plant pot");} // third item to take
if (obj.taken_level > 0) {return bldg_obj_type_t(0, 0, 1, 1, 0, 0, 1, 1.0, 10.0, "dirt" );} // second item to take
return bldg_obj_type_t(0, 0, 1, 1, 0, 0, 2, 25.0, 5.0, "plant"); // first item to take
}
if (otype == TYPE_TOY) { // take one ring at a time then the base (5 parts)
if (obj.taken_level < 4) {return bldg_obj_type_t(0, 0, 1, 1, 0, 0, 2, 0.5, 0.025, "toy ring");}
// else take the toy base
}
if (otype == TYPE_FISHTANK) { // glass terrarium or fishtank
if (obj.has_lid()) return bldg_obj_type_t(0, 0, 0, 1, 0, 0, 1, 10.0, 1.0, "tank lid and light"); // take the lid and light
float value(0.0), weight(0.0);
string name;
switch (obj.item_flags) {
case TYPE_FISH : value = 100.0; weight = 160.0; name = "fish tank"; break;
case TYPE_RAT : value = 50.0; weight = 30.0; name = "rat tank"; break;
case TYPE_SNAKE : value = 60.0; weight = 35.0; name = "snake terrarium"; break;
case TYPE_SPIDER: value = 40.0; weight = 25.0; name = "spider terrarium"; break;
default: assert(0); // unsupported
}
return bldg_obj_type_t(0, 0, 0, 1, 0, 0, 1, value, weight, name);
}
if (otype == TYPE_SHOWERTUB) {return bldg_obj_type_t(0, 0, 0, 1, 0, 0, 1, 20.0, 2.0, "shower curtain");} // only the curtains can be taken
if (otype == TYPE_TABLE && obj.is_broken ()) {return bldg_obj_type_t(1, 1, 1, 1, 0, 0, 1, 25.0, 40.0, "broken table");} // glass tables only
if (otype == TYPE_COMPUTER && obj.is_broken ()) {return bldg_obj_type_t(0, 0, 1, 1, 0, 0, 2, 100.0, 20.0, "old computer");}
if (otype == TYPE_BOX && obj.is_open ()) {return bldg_obj_type_t(1, 1, 1, 1, 0, 0, 2, 0.0, 0.05, "opened box" );}
if (otype == TYPE_CRATE && obj.is_open ()) {return bldg_obj_type_t(1, 1, 1, 1, 0, 0, 2, 2.0, 0.5, "opened crate");}
if (otype == TYPE_TV && obj.is_broken ()) {return bldg_obj_type_t(1, 1, 1, 1, 0, 1, 1, 20.0, 70.0, "broken TV" );}
if (otype == TYPE_MONITOR && obj.is_broken ()) {return bldg_obj_type_t(1, 1, 1, 1, 0, 1, 1, 10.0, 15.0, "broken computer monitor");}
if (otype == TYPE_LIGHT && obj.is_broken ()) {return bldg_obj_type_t(0, 0, 0, 1, 0, 0, 0, 20.0, 5.0, "flickering light");}
if (otype == TYPE_LIGHT && obj.is_broken2()) {return bldg_obj_type_t(0, 0, 0, 1, 0, 0, 0, 10.0, 5.0, "broken light");}
if (otype == TYPE_RAT && obj.is_broken ()) {return bldg_obj_type_t(0, 0, 1, 1, 0, 1, 0, 0.0, 1.0, "cooked/dead rat");}
if (otype == TYPE_ROACH && obj.is_broken ()) {return bldg_obj_type_t(0, 0, 0, 1, 0, 1, 0, 0.0, 0.01, "dead cockroach");} // same stats as live cockroach
if (otype == TYPE_FIRE_EXT && obj.is_broken ()) {return bldg_obj_type_t(0, 0, 1, 1, 0, 1, 0, 20.0, 10.0, "empty fire extinguisher");}
if (otype == TYPE_CANDLE && obj.is_used ()) {return bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 0.5, 0.4, "used candle");}
if (otype == TYPE_POOL_FLOAT&&obj.is_broken ()) {return bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 5.0, 1.0, "deflated pool float");} // half value, no player coll
if (otype == TYPE_VASE && obj.in_mall ()) {return bldg_obj_type_t(1, 1, 0, 1, 0, 0, 2, 500.0,250.0,"sculpture");}
if (otype == TYPE_BENCH && obj.in_mall ()) {return bldg_obj_type_t(1, 1, 1, 1, 0, 0, 2, 150.0,100.0,"mall bench");}
if (otype == TYPE_INSECT) { // unused
bool const is_fly(obj.is_hanging());
string const name(string(obj.is_broken() ? "dead " : "") + (is_fly ? "fly" : "cockroach"));
return bldg_obj_type_t(0, 0, 0, 1, 0, !is_fly, (is_fly ? 2 : 0), 0.0, 0.01, name);
}
if (obj.is_a_drink()) {
bool const is_bottle(otype == TYPE_BOTTLE); // else drink can
string const &name(is_bottle ? bottle_params[obj.get_bottle_type()].name : drink_can_params[obj.get_drink_can_type()].name );
float const value(is_bottle ? bottle_params[obj.get_bottle_type()].value : drink_can_params[obj.get_drink_can_type()].value);
bldg_obj_type_t type(0, 0, 0, 1, 0, 0, 2, value, (is_bottle ? 1.0 : 0.75), name);
if (obj.is_bottle_empty()) {
type.name = "empty " + type.name;
type.weight *= (is_bottle ? 0.25 : 0.05);
type.value = 0.0;
}
else if (!(obj.flags & RO_FLAG_NO_CONS)) {
type.name = "consumable " + type.name;
}
return type;
}
if (otype == TYPE_PIZZA_BOX) {
if (obj.taken_level == 1) {return bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 0.0, 0.25, "empty pizza box");} // whether or not the box is open
if (obj.is_open()) { // take the pizza
// pizza slice? what about using TYPE_PIZZA_TOP?
return bldg_obj_type_t(0, 0, 0, 1, 0, 0, 2, 10.0, 0.75, "pizza");
}
} // else take the entire box with the pizza
// default value, name may be modified below
bldg_obj_type_t type(get_room_obj_type(obj));
float wv_factor(1.0); // weight/value scale
if (otype == TYPE_LG_BALL) {
ball_type_t const &bt(obj.get_ball_type());
type.name = bt.name; // use a more specific type name
type.value = bt.value;
type.weight = bt.weight;
}
else if (otype == TYPE_CLOTHES) {
if (is_shirt_model(obj)) {type.name = "shirt";}
else if (is_pants_model(obj)) {type.name = "pants";}
}
else if (otype == TYPE_MIRROR && (obj.flags & RO_FLAG_IS_HOUSE)) {
type.name = "medicine cabinet";
}
else if (otype == TYPE_PAPER) {
float const value(get_paper_value(obj));
if (value >= 500.0) {type.name = "top secret document";}
else if (value >= 100.0) {type.name = "confidential document";}
else if (value > 0.0) {type.name = "valuable document";}
}
else if (otype == TYPE_POOL_BALL) {
type.name = get_pool_ball_name(obj.item_flags); // starts from 0; cue ball is 15
}
else if (otype == TYPE_FOOD_BOX) {
string const &food_name(obj.get_food_box_name());
if (!food_name.empty()) {type.name = food_name;}
}
else if (otype == TYPE_KEY) {
assert(obj.obj_id < NUM_LOCK_COLORS);
type.name = lock_color_names[obj.obj_id] + " " + type.name;
}
else if (otype == TYPE_TCAN) {
if (obj.color == BLUE) {type.name = "recycling bin";}
}
else if (otype == TYPE_BUCKET) {
float liquid_level(0.0);
get_bucket_liquid_info(obj, liquid_level);
type.name = ((liquid_level > 0.0) ? "bucket of unknown liquid" : "empty bucket");
type.weight += 0.1*round_fp(10*16.0*liquid_level); // 2 gallon bucket, gets heavier with more liquid; round to nearest tenth
}
else if (otype == TYPE_TABLE && obj.in_mall()) { // variable sized mall food court table
wv_factor = 0.75*obj.get_length()/obj.get_width(); // longer tables are heavier and higher value
type.name = "food court table";
}
else if (otype == TYPE_WBOARD) {
wv_factor = 0.5*obj.get_width()/obj.dz(); // longer whiteboards are heavier and higher value
}
if (wv_factor != 1.0) { // scale weight and value by this factor, rounded to the nearest pound and dollar
type.weight = int(wv_factor*type.weight);
type.value = int(wv_factor*type.value );
}
return type;
}
bool is_refillable(room_object_t const &obj) {return (obj.type == TYPE_FIRE_EXT);}
float get_obj_value(room_object_t const &obj) {
float value(get_taken_obj_type(obj).value);
if (obj.type == TYPE_CRATE || obj.type == TYPE_BOX) {value *= (1 + (rgen_from_obj(obj).rand() % 20));}
else if (obj.type == TYPE_PAPER) {value = get_paper_value(obj);}
else if (obj.type == TYPE_MONEY) {
unsigned const num_bills(round_fp(obj.dz()/(0.01*obj.get_length())));
value *= num_bills;
}
if (obj.is_used() && !is_refillable(obj)) {value = 0.01*floor(50.0*value);} // used objects have half value, rounded down to the nearest cent
return value;
}
float get_obj_weight(room_object_t const &obj) {
return get_taken_obj_type(obj).weight; // constant per object type, for now, but really should depend on object size/volume
}
bool is_consumable(room_object_t const &obj) {
if (!in_building_gameplay_mode() || !obj.is_a_drink() || obj.is_bottle_empty() || (obj.flags & RO_FLAG_NO_CONS)) return 0; // not consumable
if (obj.type == TYPE_DRINK_CAN) return 1; // always consumable; not an inventory item
unsigned const bottle_type(obj.get_bottle_type());
bool const is_drink(bottle_type == BOTTLE_TYPE_WATER || bottle_type == BOTTLE_TYPE_COKE);
if (is_drink || bottle_type == BOTTLE_TYPE_MEDS) { // healing items
if (obj.is_all_zeros()) return 1; // unsized item taken from a car, always consume
if (player_at_full_health()) { // if player is at full health, heal is not needed, so add this item to inventory rather than comsume it
if (is_drink && player_is_thirsty()) return 1; // player needs to drink
return 0;
}
}
return 1;
}
bool is_healing_food(room_object_t const &obj) {
return (obj.type == TYPE_PIZZA_BOX && obj.is_open() && obj.taken_level == 0 && in_building_gameplay_mode() && !player_at_full_health());
}
void show_weight_limit_message() {
std::ostringstream oss;
oss << "Over weight limit of " << global_building_params.player_weight_limit << " lbs";
print_text_onscreen(oss.str(), RED, 1.0, 1.5*TICKS_PER_SECOND, 0);
}
class phone_manager_t {
bool is_enabled=0, is_ringing = 0, is_on=0;
double stop_ring_time=0.0, next_ring_time=0.0, next_cycle_time=0.0, auto_off_time=0.0, next_button_time=0.0;
rand_gen_t rgen;
void schedule_next_ring() {next_ring_time = tfticks + (double)rgen.rand_uniform(30.0, 120.0)*TICKS_PER_SECOND;} // 30s to 2min
public:
bool is_phone_ringing() const {return is_ringing;}
bool is_phone_on () const {return is_on ;}
void next_frame() {
if (!is_enabled || !camera_in_building) {} // do nothing
else if (is_ringing) {
if (tfticks > stop_ring_time) {is_ringing = 0; schedule_next_ring();} // stop automatically
else if (tfticks > next_cycle_time) { // start a new ring cycle
gen_sound_thread_safe_at_player(get_sound_id_for_file("phone_ring.wav"), 1.0);
register_building_sound_at_player(1.0);
next_cycle_time += 4.2*TICKS_PER_SECOND; // 4.2s between rings
}
}
else {
if (tfticks > next_ring_time) { // start a new ring cycle
is_ringing = 1;
stop_ring_time = tfticks + (double)rgen.rand_uniform(12.0, 24.0)*TICKS_PER_SECOND; // 10-20s into the future
next_cycle_time = tfticks; // cycle begins now
}
if (is_on && tfticks > auto_off_time) {is_on = 0;} // auto off
}
}
void enable() {
if (is_enabled) return; // already enabled
is_enabled = 1;
is_ringing = is_on = 0;
schedule_next_ring();
}
void disable() {
is_enabled = is_ringing = is_on = 0;
}
void player_action() {
if (tfticks < next_button_time) return; // skip if pressed immediately after the last press (switch debouncer)
next_button_time = tfticks + 0.25*TICKS_PER_SECOND;
if (is_ringing) { // switch off
is_ringing = is_on = 0;
stop_ring_time = tfticks; // now
schedule_next_ring();
register_achievement("Spam Risk");
}
else {
is_on ^= 1;
if (is_on) {auto_off_time = tfticks + 4.0*TICKS_PER_SECOND;} // 4s auto off delay
}
gen_sound_thread_safe_at_player(SOUND_CLICK, 0.5);
}
};
phone_manager_t phone_manager;
struct tape_manager_t {
bool in_use;
float last_toggle_time;
vector<point> points;
point last_pos;
room_object_t tape;
building_t *cur_building;
tape_manager_t() : in_use(0), last_toggle_time(0.0), cur_building(nullptr) {}
void toggle_use(room_object_t const &tape_, building_t *building) {
if ((tfticks - last_toggle_time) < 0.5*TICKS_PER_SECOND) return; // don't toggle too many times per frame
last_toggle_time = tfticks;
if (in_use) {clear();} // end use
else {tape = tape_; cur_building = building; in_use = 1;} // begin use
}
void clear() {
if (cur_building != nullptr) {cur_building->maybe_update_tape(last_pos, 1);} // end_of_tape=1
cur_building = nullptr;
points.clear();
tape = room_object_t();
in_use = 0;
}
};
bool phone_is_ringing() {return phone_manager.is_phone_ringing();}
tape_manager_t tape_manager;
unsigned const NUM_ACHIEVEMENTS = 20;
class achievement_tracker_t {
// Rat Food, Fish Food, Top Secret Document, Mr. Yuck, Zombie Hunter, Royal Flush, Zombie Bashing, One More Drink, Bathroom Reader, TP Artist,
// Master Lockpick, Squeaky Clean, Sleep with the Fishes, Splat the Spider, 7 years of bad luck, Tastes Like Chicken, Spam Risk, Ball in Pocket,
// Soft Locked, Tampering with Evidence
set<string> achievements;
// some way to make this persistent, print these out somewhere, or add small screen icons?
public:
bool register_achievement(string const &achievement) {
if (!achievements.insert(achievement).second) return 0; // we already have this one
std::ostringstream msg;
msg << "You have unlocked a new achievement:\n" << achievement << " (" << achievements.size() << "/" << NUM_ACHIEVEMENTS << ")";
// Note: can set a yval of -0.005 to not block other text, but there can only be one active onscreen message at once anyway
print_text_onscreen(msg.str(), WHITE, 1.25, 3*TICKS_PER_SECOND, 20);
return 1;
}
};
achievement_tracker_t achievement_tracker;
bool register_achievement(string const &str) {return achievement_tracker.register_achievement(str);}
class player_inventory_t { // manages player inventory, health, and other stats
vector<carried_item_t> carried; // interactive items the player is currently carrying
vector<dead_person_t > dead_players;
set<unsigned> rooms_stolen_from;
float cur_value, cur_weight, tot_value, tot_weight, damage_done, best_value, player_health, drunkenness, bladder, bladder_time, oxygen, thirst;
float prev_player_zval, respawn_time=0.0;
unsigned num_doors_unlocked, has_key; // has_key is a bit mask for key colors
bool prev_in_building, has_flashlight, is_poisoned, poison_from_spider, has_pool_cue;
void register_player_death(unsigned sound_id, string const &why) {
player_wait_respawn = 1;
if (PLAYER_RESPAWN == 0.0) {player_respawn();} // respawn now
else {respawn_time = tfticks + PLAYER_RESPAWN*TICKS_PER_SECOND;} // respawn later
gen_sound_thread_safe_at_player(sound_id);
print_text_onscreen(("You Have Died" + why), RED, 2.0, 2*TICKS_PER_SECOND, 10);
clear();
}
static void print_value_and_weight(std::ostringstream &oss, float value, float weight) {
oss << ": value $";
if (value < 1.0 && value > 0.0) {oss << ((value < 0.1) ? "0.0" : "0.") << round_fp(100.0*value);} // make sure to print the leading/trailing zero for cents
else {oss << value;}
oss << " weight " << weight << " lbs";
}
void on_empty_inventory() {
phone_manager.disable(); // phones won't ring when taken out of their building, since the player can't switch to them anyway
tape_manager.clear();
rooms_stolen_from.clear();
player_attracts_flies = 0; // even if items remain in the player's inventory
}
public:
player_inventory_t() {clear_all();}
void clear() { // called on player death
max_eq(best_value, tot_value);
cur_value = cur_weight = tot_value = tot_weight = damage_done = 0.0;
drunkenness = bladder = bladder_time = prev_player_zval = 0.0;
player_health = oxygen = thirst = 1.0; // full health, oxygen, and (anti-)thirst
num_doors_unlocked = has_key = 0; // num_doors_unlocked not saved on death, but maybe should be?
prev_in_building = has_flashlight = is_poisoned = poison_from_spider = has_pool_cue = 0;
carried.clear();
on_empty_inventory();
}
void clear_all() { // called on game mode init
tot_value = best_value = 0.0;
clear();
}
void drop_all_inventory_items(point const &camera_bs, building_t &building) { // on player death
if (!building.has_room_geom()) return;
while (!carried.empty()) {
room_object_t const &obj(carried.back());
if (obj.has_dstate() || obj.type == TYPE_BOOK || obj.type == TYPE_RAT || (obj.type == TYPE_FIRE_EXT && obj.is_broken())) {
if (building.maybe_use_last_pickup_room_object(camera_bs, 1, 1)) continue; // no_time_check=1, random_dir=1
}
carried.pop_back();
} // end while
}
void player_respawn() {
point const xlate(get_camera_coord_space_xlate());
place_player_at_xy(xlate.x, xlate.y); // move back to the origin/spawn location
if (PLAYER_RESPAWN > 0.0) {print_text_onscreen("You Have Been Reborn", RED, 2.0, 2*TICKS_PER_SECOND, 10);}
player_wait_respawn = 0;
}
void take_damage(float amt, int poison_type=0) { // poison_type: 0=none, 1=spider, 2=snake
player_health -= amt*(1.0f - 0.75f*min(drunkenness, 1.0f)); // up to 75% damage reduction when drunk
if (poison_type > 0) {
if (!is_poisoned) { // first poisoning (by spider)
print_text_onscreen("You have been poisoned", DK_RED, 1.0, 2.5*TICKS_PER_SECOND, 0);
is_poisoned = 1;
}
poison_from_spider = (poison_type == 1);
}
}
void record_damage_done(float amt) {damage_done += amt; max_eq(damage_done, 0.0f);} // amt can be negative, but damage_done must remain positive
void return_object_to_building(room_object_t const &obj) {damage_done -= get_obj_value(obj);}
bool check_weight_limit(float weight) const {return ((cur_weight + weight) <= global_building_params.player_weight_limit);}
bool can_pick_up_item(room_object_t const &obj) const {return check_weight_limit(get_obj_weight(obj));}
float get_carry_weight_ratio() const {return min(1.0f, cur_weight/global_building_params.player_weight_limit);}
float get_speed_mult () const {return (1.0f - 0.4f*get_carry_weight_ratio())*((bladder > 0.9) ? 0.6 : 1.0);} // 40% reduction for heavy load, 40% reduction for full bladder
float get_drunkenness() const {return drunkenness;}
float get_oxygen () const {return oxygen;}
bool player_is_dead () const {return (player_health <= 0.0);}
unsigned player_has_key () const {return has_key;}
bool player_has_flashlight() const {return has_flashlight;}
bool player_has_pool_cue () const {return has_pool_cue;}
bool player_at_full_health() const {return (player_health == 1.0 && !is_poisoned);}
bool player_is_thirsty () const {return (thirst < 0.5);}
bool player_holding_lit_candle() const {return (!carried.empty() && carried.back().type == TYPE_CANDLE && carried.back().is_lit());}
bool was_room_stolen_from(unsigned room_id) const {return (rooms_stolen_from.find(room_id) != rooms_stolen_from.end());}
void refill_thirst() {thirst = 1.0;}
bool can_open_door(door_t const &door) { // non-const because num_doors_unlocked is modified
if (!door.check_key_mask_unlocks(has_key)) {
string str("Door is locked");
if (door.is_locked_unlockable()) {str += " and unlockable";}
else if (door.is_padlocked ()) {str += " with " + lock_color_names[door.get_padlock_color_ix()] + " padlock";}
print_text_onscreen(str, RED, 1.0, 2.0*TICKS_PER_SECOND, 0);
gen_sound_thread_safe_at_player(SOUND_CLICK, 1.0, 0.6);
return 0;
}
if (door.blocked) {
print_text_onscreen("Door is blocked", RED, 1.0, 2.0*TICKS_PER_SECOND, 0);
gen_sound_thread_safe_at_player(SOUND_DOOR_CLOSE, 1.0, 0.6);
return 0;
}
if (door.is_closed_and_locked()) {
print_text_onscreen("Door unlocked", BLUE, 1.0, 1.8*TICKS_PER_SECOND, 0);
++num_doors_unlocked;
if (num_doors_unlocked == 5) {register_achievement("Master Lockpick");} // unlock 5 doors
}
return 1;
}
void register_in_closed_bathroom_stall() const {
if (!carried.empty() && carried.back().type == TYPE_BOOK) {register_achievement("Bathroom Reader");}
}
void get_dead_players_in_building(vector<dead_person_t> &dps, building_t const &building) const {
for (dead_person_t const &p : dead_players) {
if (p.building == &building) {dps.push_back(p);}
}
}
void use_medicine() {
player_health = 1.0;
is_poisoned = 0;
print_text_onscreen("Used Medicine", CYAN, 0.8, 1.5*TICKS_PER_SECOND, 0);
gen_sound_thread_safe_at_player(SOUND_GULP, 1.0);
}
void register_reward(float value) {
assert(value > 0.0);
std::ostringstream oss;
oss << "Reward of $" << value;
print_text_onscreen(oss.str(), YELLOW, 1.0, 1.5*TICKS_PER_SECOND, 0);
cur_value += value; // doesn't count as damage
}
void switch_item(bool dir) { // Note: current item is always carried.back()
if (carried.size() <= 1) return; // no other item to switch to
if (dir) {std::rotate(carried.begin(), carried.begin()+1, carried.end());}
else {std::rotate(carried.begin(), carried.end ()-1, carried.end());}
gen_sound_thread_safe_at_player(SOUND_CLICK, 0.5);
tape_manager.clear();
}
void add_item(room_object_t const &obj) {
float health(0.0), drunk(0.0), liquid(0.0);
bool const bladder_was_full(bladder >= 0.9);
float const value(get_obj_value(obj));
room_object const type(obj.type);
// items that are low value, small, or too strange/random don't count as being stolen
if (type != TYPE_BOOK && type != TYPE_FIRE_EXT && type != TYPE_LG_BALL && type != TYPE_TRASH && type !=TYPE_BOTTLE && type != TYPE_PAPER && type != TYPE_PEN &&
type != TYPE_PENCIL && type != TYPE_HANGER_ROD && type != TYPE_TPROLL && type != TYPE_MARKER && type != TYPE_BUTTON && type != TYPE_PLATE && type != TYPE_TAPE &&
type != TYPE_FEXT_MOUNT && type != TYPE_FEXT_SIGN && type != TYPE_PIZZA_BOX && type != TYPE_PIZZA_TOP && type != TYPE_POOL_BALL && type != TYPE_DRINK_CAN &&
type != TYPE_KEY && type != TYPE_HANGER && type != TYPE_PADLOCK && type != TYPE_BANANA && type != TYPE_BAN_PEEL && type != TYPE_ELEC_WIRE && type != TYPE_ERASER)
{
rooms_stolen_from.insert(obj.room_id); // only if was_expanded?
}
if (type == TYPE_PAPER && value >= 500.0) {register_achievement("Top Secret Document");}
if ((type == TYPE_TCAN && !obj.was_expanded() && obj.color != BLUE) || // skip trashcans on shelves and recycling bins
type == TYPE_TOILET || type == TYPE_URINAL || (type == TYPE_RAT && obj.is_broken()))
{
register_fly_attract(0); // trashcans, toilets, urinals, and dead rats attract flies
}
damage_done += value;
colorRGBA text_color(GREEN);
std::ostringstream oss;
oss << get_taken_obj_type(obj).name;
if (is_consumable(obj)) { // nonempty bottle, consumable
if (type == TYPE_BOTTLE) {
// should alcohol, poison, and medicine help with thirst? I guess alcohol helps somewhat
switch (obj.get_bottle_type()) {
case BOTTLE_TYPE_WATER : health = 0.25; liquid = 1.0; break; // water
case BOTTLE_TYPE_COKE : health = 0.50; liquid = 1.0; break; // Coke
case BOTTLE_TYPE_BEER : drunk = 0.25; liquid = 0.5; break; // beer
case BOTTLE_TYPE_WINE : drunk = 0.50; liquid = 0.5; break; // wine (entire bottle)
case BOTTLE_TYPE_POISON: health = -0.50; break; // poison - take damage
case BOTTLE_TYPE_MEDS : health = 1.00; is_poisoned = 0; break; // medicine, restore full health and cure poisoning
default: assert(0);
}
}
else if (type == TYPE_DRINK_CAN) { // slightly smaller than a bottle
switch (obj.get_drink_can_type()) {
case DRINK_CAN_TYPE_COKE: health = 0.4; liquid = 0.8; break;
case DRINK_CAN_TYPE_BEER: drunk = 0.2; liquid = 0.4; break;
default: assert(0);
}
}
else {assert(0);} // invalid type
}
else if (is_healing_food(obj)) {
health = 0.50; // healing pizza
}
if (health > 0.0) { // heal
player_health = min(1.0f, (player_health + health));
oss << ": +" << round_fp(100.0*health) << "% Health";
}
if (health < 0.0) { // take damage
player_health += health;
oss << ": " << round_fp(100.0*health) << "% Health";
text_color = RED;
add_camera_filter(colorRGBA(RED, 0.25), 4, -1, CAM_FILT_DAMAGE); // 4 ticks of red damage
gen_sound_thread_safe_at_player(SOUND_DOH, 0.5);
if (player_is_dead()) {register_achievement("Mr. Yuck");}
}
if (liquid > 0.0) {
oss << ": -" << round_fp(100.0*liquid) << "% Thirst";
thirst = min(1.0f, (thirst + liquid));
}
if (type == TYPE_FLASHLIGHT) {has_flashlight = 1;} // also goes in inventory
if (type == TYPE_POOL_CUE ) {has_pool_cue = 1;} // also goes in inventory
if (type == TYPE_KEY) {
has_key |= (1 << obj.obj_id); // mark as having the key and record the color, but it doesn't go into the inventory or contribute to weight or value
}
else if (health == 0.0 && drunk == 0.0) { // print value and weight if item is not consumed
float const weight(get_obj_weight(obj));
cur_value += value;
cur_weight += weight;
if (obj.is_interactive()) {
carried.push_back(obj);
room_object_t &co(carried.back());
co.flags &= ~RO_FLAG_ON_SRACK; // no longer on/in shelf rack, floor, or closet
if (type == TYPE_BOOK) { // clear dim and dir for books
float const dx(co.dx()), dy(co.dy()), dz(co.dz());
if (dz > min(dx, dy)) { // upright book from a bookcase, put it on its side facing the player
co.y2() = co.y1() + dz;
co.x2() = co.x1() + max(dx, dy);
co.z2() = co.z1() + min(dx, dy);
}
else if (co.dim) { // swap aspect ratio to make dim=0
co.x2() = co.x1() + dy;
co.y2() = co.y1() + dx;
}
co.dim = co.dir = 0;
co.flags &= ~(RO_FLAG_RAND_ROT | RO_FLAG_OPEN); // remove the rotate and open bits
co.light_amt = 1.0; // max light for books when carrying
}
else if (type == TYPE_PHONE) {
if (co.dim) { // swap aspect ratio to make dim=0
float const dx(co.dx()), dy(co.dy());
co.x2() = co.x1() + dy;
co.y2() = co.y1() + dx;
}
co.dim = co.dir = 0; // clear dim and dir
phone_manager.enable();
}
else if (type == TYPE_BOTTLE) { // medicine bottle
float const dx(co.dx()), dy(co.dy()), dz(co.dz());
if (max(dx, dy) > dz) { // sideways bottle, make upright
co.y2() = co.y1() + dz;
co.x2() = co.x1() + dz;
co.z2() = co.z1() + max(dx, dy);
}
co.dim = co.dir = 0; // vertical
}
tape_manager.clear();
}
print_value_and_weight(oss, value, get_obj_weight(obj));
}
else { // add one drink to the bladder, 25% of capacity
bladder = min(1.0f, (bladder + 0.25f));
}
if (drunk > 0.0) {
drunkenness += drunk;
oss << ": +" << round_fp(100.0*drunk) << "% Drunkenness";
if (drunkenness > 0.99f && (drunkenness - drunk) <= 0.99f) {oss << "\nYou are drunk"; text_color = DK_GREEN;}
}
if (!bladder_was_full && bladder >= 0.9f) {oss << "\nYou need to use the bathroom"; text_color = YELLOW;}
print_text_onscreen(oss.str(), text_color, 1.0, 3*TICKS_PER_SECOND, 0);
}
void add_custom_item(custom_item_t const &item) { // Note: no support for consumables, health, drunk, or interactive items
cur_value += item.value;
cur_weight += item.weight;
damage_done += item.value;
std::ostringstream oss;
oss << item.name;
print_value_and_weight(oss, item.value, item.weight);
print_text_onscreen(oss.str(), GREEN, 1.0, 3*TICKS_PER_SECOND, 0);
}
bool take_person(uint8_t &person_has_key, float person_height) {
if (drunkenness < 1.5) { // not drunk enough
print_text_onscreen("Not drunk enough", RED, 1.0, 2.0*TICKS_PER_SECOND, 0);
return 0;
}
float const value(1000), weight((person_height > 0.025) ? 180.0 : 80.0); // always worth $1000; use height to select man vs. girl
if (!check_weight_limit(weight)) {show_weight_limit_message(); return 0;}
has_key |= person_has_key; // steal their key(s)
person_has_key = 0;
cur_value += value;
cur_weight += weight;
std::ostringstream oss;
oss << "zombie: value $" << value << " weight " << weight << " lbs";
print_text_onscreen(oss.str(), GREEN, 1.0, 4*TICKS_PER_SECOND, 0);
register_achievement("Zombie Hunter");
return 1; // success
}
bool try_use_last_item(room_object_t &obj) {
if (carried.empty()) return 0; // no interactive carried item
obj = carried.back(); // deep copy
if (!obj.has_dstate()) { // not a droppable/throwable item(ball)
if (!obj.can_use()) return 0; // should never get here?
if (obj.type == TYPE_CANDLE && player_in_water != 2 && carried.back().get_remaining_capacity_ratio() > 0.0) {carried.back().flags ^= RO_FLAG_LIT;} // toggle candle light
return 1;
}
remove_last_item(); // drop the item - remove it from our inventory
return 1;
}
bool update_last_item_use_count(int val) { // val can be positive or negative
if (val == 0) return 1; // no change
assert(!carried.empty());
carried_item_t &obj(carried.back());
unsigned const capacity(get_room_obj_type(obj).capacity);
if (capacity > 0) {
max_eq(val, -int(obj.use_count)); // can't go negative
obj.use_count += val;
if (obj.use_count >= capacity) { // remove after too many uses
if (obj.type == TYPE_FIRE_EXT) {
float const old_value(get_obj_value(obj));
obj.flags |= RO_FLAG_BROKEN; // mark as empty
float const new_value(get_obj_value(obj));
assert(new_value <= old_value); // value can't increase
cur_value -= min(cur_value, (old_value - new_value));
return 1;
}
if (obj.type == TYPE_TPROLL) {register_achievement("TP Artist");}
remove_last_item();
return 0;
}
}
// mark used last so that medicine doesn't leave us with half it's value because the amount subtracted if half if it's used;
// do other consumables with multiple uses still have this problem?
obj.flags |= RO_FLAG_USED;
return 1;
}
bool mark_last_item_used() {
return update_last_item_use_count(1);
}
void mark_last_item_broken() {
assert(!carried.empty());
carried.back().flags |= RO_FLAG_BROKEN;
}
void remove_last_item() {
assert(!carried.empty());
room_object_t const &obj(carried.back());
cur_value -= get_obj_value (obj);
cur_weight -= get_obj_weight(obj);
cur_value = 0.01*round_fp(100.0*cur_value ); // round to nearest cent
cur_weight = 0.01*round_fp(100.0*cur_weight); // round to nearest 0.01 lb
assert(cur_value > -0.01 && cur_weight > -0.01); // sanity check for math
max_eq(cur_value, 0.0f); // handle FP rounding error
max_eq(cur_weight, 0.0f);
carried.pop_back(); // Note: invalidates obj
tape_manager.clear();
}
void collect_items(bool keep_interactive) { // called when player exits a building
if (!keep_interactive) {has_key = 0;} // key only good for current building; flashlight and pool cue can be used in all buildings
on_empty_inventory();
if (carried.empty() && cur_weight == 0.0 && cur_value == 0.0) return; // nothing to add
float keep_value(0.0), keep_weight(0.0);
if (keep_interactive) { // carried items don't contribute to collected value and weight; their value and weight remain in the inventory after collection
for (auto i = carried.begin(); i != carried.end(); ++i) {
keep_value += get_obj_value (*i);
keep_weight += get_obj_weight(*i);
}
cur_value -= keep_value;
cur_weight -= keep_weight;
cur_value = 0.01*round_fp(100.0*cur_value); // round to nearest cent
}
else {
carried.clear();
}
if (cur_weight > 0.0 || cur_value > 0.0) { // we have some change in value or weight to print
std::ostringstream oss;
oss << "Added value $" << cur_value << " Added weight " << cur_weight << " lbs\n";
tot_value += cur_value;
tot_weight += cur_weight;
tot_value = 0.01*round_fp(100.0*tot_value); // round to nearest cent
oss << "Total value $" << tot_value << " Total weight " << tot_weight << " lbs";
print_text_onscreen(oss.str(), GREEN, 1.0, 4*TICKS_PER_SECOND, 0);
}
cur_value = keep_value;
cur_weight = keep_weight;
}
void show_stats() const {
if (!carried.empty()) {
player_held_object = carried.back(); // deep copy last pickup object if usable
if (player_held_object.type == TYPE_PHONE) {
if (phone_manager.is_phone_ringing()) {player_held_object.flags |= RO_FLAG_EMISSIVE;} // show ring screen
else if (phone_manager.is_phone_on()) {player_held_object.flags |= RO_FLAG_OPEN ;} // show lock screen
}
}
if (display_framerate) { // controlled by framerate toggle
float const aspect_ratio((float)window_width/(float)window_height);
if (cur_weight > 0.0 || tot_weight > 0.0 || best_value > 0.0) { // don't show stats until the player has picked something up
std::ostringstream oss;
oss << "Cur $" << cur_value << " / " << cur_weight << " lbs Total $" << tot_value << " / " << tot_weight
<< " lbs Best $" << best_value << " Damage $" << round_fp(damage_done); // print damage to nearest dollar
if (!carried.empty()) {
unsigned const capacity(get_room_obj_type(player_held_object).capacity);
oss << " [" << get_taken_obj_type(player_held_object).name; // print the name of the throwable object
if (capacity > 0 && !player_held_object.is_broken()) {oss << " " << (capacity - carried.back().use_count) << "/" << capacity;} // print use/capacity if nonempty
oss << "]";
}
draw_text(GREEN, -0.010*aspect_ratio, -0.011, -0.02, oss.str(), 0.8); // size=0.8
}
if (phone_manager.is_phone_ringing() && player_held_object.type == TYPE_PHONE) { // player is holding a ringing phone, give them a hint
draw_text(LT_BLUE, -0.001*aspect_ratio, -0.009, -0.02, "[Press space to silence phone]");
}
if (in_building_gameplay_mode()) {
float const lvl(min(cur_building_sound_level, 1.0f));
unsigned const num_bars(round_fp(20.0*lvl));
if (num_bars > 0) { // display sound meter
colorRGBA const color(lvl, (1.0 - lvl), 0.0, 1.0); // green => yellow => orange => red
draw_text(color, -0.005*aspect_ratio, -0.010, -0.02, string(num_bars, '#'));
}
// technically, the player is still hiding, even if the phone is ringing; zombies may be attracted to the sound but won't actually see the player
if (player_is_hiding && camera_in_building && !phone_manager.is_phone_ringing()) {draw_text(LT_BLUE, -0.001*aspect_ratio, -0.009, -0.02, "[Hiding]");}
}
}
if (in_building_gameplay_mode()) {
// Note: shields is used for drunkenness; values are scaled from 0-1 to 0-100; powerup values are for bladder fullness
vector<status_bar_t> extra_bars;
colorRGBA const thirst_color((thirst < 0.2 && ((int(tfticks)/8)&1)) ? MAGENTA : BLUE); // flash magenta when low