-
Notifications
You must be signed in to change notification settings - Fork 22
/
Copy pathinvent.c
4626 lines (4301 loc) · 157 KB
/
invent.c
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
/* NetHack 3.6 invent.c $NHDT-Date: 1674864733 2023/01/28 00:12:13 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.268 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/*-Copyright (c) Derek S. Ray, 2015. */
/* NetHack may be freely redistributed. See license for details. */
#include "hack.h"
#ifndef C /* same as cmd.c */
#define C(c) (0x1f & (c))
#endif
#define NOINVSYM '#'
#define CONTAINED_SYM '>' /* designator for inside a container */
#define HANDS_SYM '-'
STATIC_DCL void FDECL(loot_classify, (Loot *, struct obj *));
STATIC_DCL char *FDECL(loot_xname, (struct obj *));
STATIC_DCL int FDECL(CFDECLSPEC sortloot_cmp, (const genericptr,
const genericptr));
STATIC_DCL void NDECL(reorder_invent);
STATIC_DCL void FDECL(noarmor, (BOOLEAN_P));
STATIC_DCL void FDECL(invdisp_nothing, (const char *, const char *));
STATIC_DCL boolean FDECL(worn_wield_only, (struct obj *));
STATIC_DCL boolean FDECL(only_here, (struct obj *));
STATIC_DCL void FDECL(compactify, (char *));
STATIC_DCL boolean FDECL(taking_off, (const char *));
STATIC_DCL boolean FDECL(putting_on, (const char *));
STATIC_PTR int FDECL(ckvalidcat, (struct obj *));
STATIC_PTR int FDECL(ckunpaid, (struct obj *));
STATIC_PTR char *FDECL(safeq_xprname, (struct obj *));
STATIC_PTR char *FDECL(safeq_shortxprname, (struct obj *));
STATIC_DCL char FDECL(display_pickinv, (const char *, const char *,
const char *, BOOLEAN_P, long *));
STATIC_DCL char FDECL(display_used_invlets, (CHAR_P));
STATIC_DCL boolean FDECL(this_type_only, (struct obj *));
STATIC_DCL void NDECL(dounpaid);
STATIC_DCL struct obj *FDECL(find_unpaid, (struct obj *, struct obj **));
STATIC_DCL void FDECL(menu_identify, (int));
STATIC_DCL boolean FDECL(tool_in_use, (struct obj *));
STATIC_DCL char FDECL(obj_to_let, (struct obj *));
static int lastinvnr = 51; /* 0 ... 51 (never saved&restored) */
/* wizards can wish for venom, which will become an invisible inventory
* item without this. putting it in inv_order would mean venom would
* suddenly become a choice for all the inventory-class commands, which
* would probably cause mass confusion. the test for inventory venom
* is only WIZARD and not wizard because the wizard can leave venom lying
* around on a bones level for normal players to find. [Note to the
* confused: 'WIZARD' used to be a compile-time conditional so this was
* guarded by #ifdef WIZARD/.../#endif.]
*/
static char venom_inv[] = { VENOM_CLASS, 0 }; /* (constant) */
/* sortloot() classification; called at most once [per sort] for each object */
STATIC_OVL void
loot_classify(sort_item, obj)
Loot *sort_item;
struct obj *obj;
{
/* we may eventually make this a settable option to always use
with sortloot instead of only when the 'sortpack' option isn't
set; it is similar to sortpack's inv_order but items most
likely to be picked up are moved to the front */
static char def_srt_order[MAXOCLASSES] = {
COIN_CLASS, AMULET_CLASS, RING_CLASS, WAND_CLASS, POTION_CLASS,
SCROLL_CLASS, SPBOOK_CLASS, GEM_CLASS, FOOD_CLASS, TOOL_CLASS,
WEAPON_CLASS, ARMOR_CLASS, ROCK_CLASS, BALL_CLASS, CHAIN_CLASS, 0,
};
static char armcat[8];
const char *classorder;
char *p;
int k, otyp = obj->otyp, oclass = obj->oclass;
boolean seen, discovered = objects[otyp].oc_name_known ? TRUE : FALSE;
/*
* For the value types assigned by this classification, sortloot()
* will put lower valued ones before higher valued ones.
*/
if (!Blind)
obj->dknown = 1; /* xname(obj) does this; we want it sooner */
seen = obj->dknown ? TRUE : FALSE,
/* class order */
classorder = flags.sortpack ? flags.inv_order : def_srt_order;
p = index(classorder, oclass);
if (p)
k = 1 + (int) (p - classorder);
else
k = 1 + (int) strlen(classorder) + (oclass != VENOM_CLASS);
sort_item->orderclass = (xchar) k;
/* subclass designation; only a few classes have subclasses
and the non-armor ones we use are fairly arbitrary */
switch (oclass) {
case ARMOR_CLASS:
if (!armcat[7]) {
/* one-time init; we use a different order than the subclass
values defined by objclass.h */
armcat[ARM_HELM] = 1; /* [2] */
armcat[ARM_GLOVES] = 2; /* [3] */
armcat[ARM_BOOTS] = 3; /* [4] */
armcat[ARM_SHIELD] = 4; /* [1] */
armcat[ARM_CLOAK] = 5; /* [5] */
armcat[ARM_SHIRT] = 6; /* [6] */
armcat[ARM_SUIT] = 7; /* [0] */
armcat[7] = 8; /* sanity protection */
}
k = objects[otyp].oc_armcat;
/* oc_armcat overloads oc_subtyp which is an 'schar' so guard
against somebody assigning something unexpected to it */
if (k < 0 || k >= 7)
k = 7;
k = armcat[k];
break;
case WEAPON_CLASS:
/* for weapons, group by ammo (arrows, bolts), launcher (bows),
missile (darts, boomerangs), stackable (daggers, knives, spears),
'other' (swords, axes, &c), polearms */
k = objects[otyp].oc_skill;
k = (k < 0) ? ((k >= -P_CROSSBOW && k <= -P_BOW) ? 1 : 3)
: ((k >= P_BOW && k <= P_CROSSBOW) ? 2
: (k == P_SPEAR || k == P_DAGGER) ? 4
: !is_pole(obj) ? 5 : 6);
break;
case TOOL_CLASS:
if (seen && discovered
&& (otyp == BAG_OF_TRICKS || otyp == HORN_OF_PLENTY))
k = 2; /* known pseudo-container */
else if (Is_container(obj))
k = 1; /* regular container or unknown bag of tricks */
else
switch (otyp) {
case FLUTE:
case MAGIC_FLUTE:
case TOOLED_HORN:
case FROST_HORN:
case FIRE_HORN:
case HARP:
case MAGIC_HARP:
case BUGLE:
case LEATHER_DRUM:
case DRUM_OF_EARTHQUAKE:
case HORN_OF_PLENTY: /* not a musical instrument */
k = 3; /* instrument or unknown horn of plenty */
break;
default:
k = 4; /* 'other' tool */
break;
}
break;
case FOOD_CLASS:
/* [what about separating "partly eaten" within each group?] */
switch (otyp) {
case SLIME_MOLD:
k = 1;
break;
default:
/* [maybe separate one-bite foods from rations and such?] */
k = obj->globby ? 6 : 2;
break;
case TIN:
k = 3;
break;
case EGG:
k = 4;
break;
case CORPSE:
k = 5;
break;
}
break;
case GEM_CLASS:
/*
* Normally subclass takes priority over discovery status, but
* that would give away information for gems (assuming we'll
* group them as valuable gems, next glass, then gray stones,
* and finally rocks once they're all fully identified).
*
* Order:
* 1) unseen gems and glass ("gem")
* 2) seen but undiscovered gems and glass ("blue gem"),
* 3) discovered gems ("sapphire"),
* 4) discovered glass ("worthless pieced of blue glass"),
* 5) unseen gray stones and rocks ("stone"),
* 6) seen but undiscovered gray stones ("gray stone"),
* 7) discovered gray stones ("touchstone"),
* 8) seen rocks ("rock").
*/
switch (objects[obj->otyp].oc_material) {
case GEMSTONE:
k = !seen ? 1 : !discovered ? 2 : 3;
break;
case GLASS:
k = !seen ? 1 : !discovered ? 2 : 4;
break;
default: /* MINERAL */
k = !seen ? 5 : (obj->otyp != ROCK) ? (!discovered ? 6 : 7) : 8;
break;
}
break;
default:
/* other classes don't have subclasses; we assign a nonzero
value because sortloot() uses 0 to mean 'not yet classified' */
k = 1; /* any non-zero would do */
break;
}
sort_item->subclass = (xchar) k;
/* discovery status */
k = !seen ? 1 /* unseen */
: (discovered || !OBJ_DESCR(objects[otyp])) ? 4
: (objects[otyp].oc_uname) ? 3 /* named (partially discovered) */
: 2; /* undiscovered */
sort_item->disco = (xchar) k;
}
/* sortloot() formatting routine; for alphabetizing, not shown to user */
STATIC_OVL char *
loot_xname(obj)
struct obj *obj;
{
struct obj saveo;
boolean save_debug;
char *res, *save_oname;
/*
* Deal with things that xname() includes as a prefix. We don't
* want such because they change alphabetical ordering. First,
* remember 'obj's current settings.
*/
saveo.odiluted = obj->odiluted;
saveo.blessed = obj->blessed, saveo.cursed = obj->cursed;
saveo.spe = obj->spe;
saveo.owt = obj->owt;
saveo.material = obj->material;
save_oname = has_oname(obj) ? ONAME(obj) : 0;
save_debug = flags.debug;
/* suppress "diluted" for potions and "holy/unholy" for water;
sortloot() will deal with them using other criteria than name */
if (obj->oclass == POTION_CLASS) {
obj->odiluted = 0;
if (obj->otyp == POT_WATER)
obj->blessed = 0, obj->cursed = 0;
}
/* make "wet towel" and "moist towel" format as "towel" so that all
three group together */
if (obj->otyp == TOWEL)
obj->spe = 0;
/* group "<size> glob of <foo>" by <foo> rather than by <size> */
if (obj->globby)
obj->owt = 200; /* 200: weight of combined glob from ten creatures
(five or fewer is "small", more than fifteen is
"large", in between has no prefix) */
/* suppress material by setting to default */
obj->material = objects[obj->otyp].oc_material;
/* suppress user-assigned name */
if (save_oname && !obj->oartifact)
ONAME(obj) = 0;
/* avoid wizard mode formatting variations */
if (wizard) { /* flags.debug */
/* paranoia: before toggling off wizard mode, guard against a
panic in xname() producing a normal mode panic save file */
program_state.something_worth_saving = 0;
flags.debug = FALSE;
}
res = cxname_singular(obj);
if (save_debug) {
flags.debug = TRUE;
program_state.something_worth_saving = 1;
}
/* restore the object */
obj->material = saveo.material;
if (obj->oclass == POTION_CLASS) {
obj->odiluted = saveo.odiluted;
if (obj->otyp == POT_WATER)
obj->blessed = saveo.blessed, obj->cursed = saveo.cursed;
}
if (obj->otyp == TOWEL) {
obj->spe = saveo.spe;
/* give "towel" a suffix that will force wet ones to come first,
moist ones next, and dry ones last regardless of whether
they've been flagged as having spe known */
Strcat(res, is_wet_towel(obj) ? ((obj->spe >= 3) ? "x" : "y") : "z");
}
if (obj->globby) {
obj->owt = saveo.owt;
/* we've suppressed the size prefix (above); there normally won't
be more than one of a given creature type because they coalesce,
but globs with different bless/curse state won't merge so it is
feasible to have multiple at the same location; add a suffix to
get such sorted by size (small first) */
Strcat(res, (obj->owt <= 100) ? "a"
: (obj->owt <= 300) ? "b"
: (obj->owt <= 500) ? "c"
: "d");
}
if (save_oname && !obj->oartifact)
ONAME(obj) = save_oname;
return res;
}
/* set by sortloot() for use by sortloot_cmp(); reset by sortloot when done */
static unsigned sortlootmode = 0;
/* qsort comparison routine for sortloot() */
STATIC_OVL int CFDECLSPEC
sortloot_cmp(vptr1, vptr2)
const genericptr vptr1;
const genericptr vptr2;
{
struct sortloot_item *sli1 = (struct sortloot_item *) vptr1,
*sli2 = (struct sortloot_item *) vptr2;
struct obj *obj1 = sli1->obj,
*obj2 = sli2->obj;
char *nam1, *nam2, *tmpstr;
const char *mat1, *mat2;
int val1, val2, c, namcmp;
/* order by object class unless we're doing by-invlet without sortpack */
if ((sortlootmode & (SORTLOOT_PACK | SORTLOOT_INVLET))
!= SORTLOOT_INVLET) {
/* Classify each object at most once no matter how many
comparisons it is involved in. */
if (!sli1->orderclass)
loot_classify(sli1, obj1);
if (!sli2->orderclass)
loot_classify(sli2, obj2);
/* Sort by class. */
val1 = sli1->orderclass;
val2 = sli2->orderclass;
if (val1 != val2)
return (int) (val1 - val2);
/* skip sub-classes when ordering by sortpack+invlet */
if ((sortlootmode & SORTLOOT_INVLET) == 0) {
/* Class matches; sort by subclass. */
val1 = sli1->subclass;
val2 = sli2->subclass;
if (val1 != val2)
return val1 - val2;
/* Class and subclass match; sort by discovery status:
* first unseen, then seen but not named or discovered,
* then named, lastly discovered.
* 1) potion
* 2) pink potion
* 3) dark green potion called confusion
* 4) potion of healing
* Multiple entries within each group will be put into
* alphabetical order below.
*/
val1 = sli1->disco;
val2 = sli2->disco;
if (val1 != val2)
return val1 - val2;
}
}
/* order by assigned inventory letter */
if ((sortlootmode & SORTLOOT_INVLET) != 0) {
c = obj1->invlet;
val1 = ('a' <= c && c <= 'z') ? (c - 'a' + 2)
: ('A' <= c && c <= 'Z') ? (c - 'A' + 2 + 26)
: (c == '$') ? 1
: (c == '#') ? 1 + 52 + 1
: 1 + 52 + 1 + 1; /* none of the above */
c = obj2->invlet;
val2 = ('a' <= c && c <= 'z') ? (c - 'a' + 2)
: ('A' <= c && c <= 'Z') ? (c - 'A' + 2 + 26)
: (c == '$') ? 1
: (c == '#') ? 1 + 52 + 1
: 1 + 52 + 1 + 1; /* none of the above */
if (val1 != val2)
return val1 - val2;
}
if ((sortlootmode & SORTLOOT_LOOT) == 0)
goto tiebreak;
/*
* Sort object names in lexicographical order, ignoring quantity.
*
* Each obj gets formatted at most once (per sort) no matter how many
* comparisons it gets subjected to.
*/
nam1 = sli1->str;
if (!nam1) {
nam1 = sli1->str = dupstr(tmpstr = loot_xname(obj1));
maybereleaseobuf(tmpstr);
}
nam2 = sli2->str;
if (!nam2) {
nam2 = sli2->str = dupstr(tmpstr = loot_xname(obj2));
maybereleaseobuf(tmpstr);
}
if ((namcmp = strcmpi(nam1, nam2)) != 0)
return namcmp;
/* Sort by BUCX. */
val1 = obj1->bknown ? (obj1->blessed ? 3 : !obj1->cursed ? 2 : 1) : 0;
val2 = obj2->bknown ? (obj2->blessed ? 3 : !obj2->cursed ? 2 : 1) : 0;
if (val1 != val2)
return val2 - val1; /* bigger is better */
/* Sort alphabetically by material. */
mat1 = (obj1->material != objects[obj1->otyp].oc_material)
? materialnm[obj1->material] : "";
mat2 = (obj2->material != objects[obj2->otyp].oc_material)
? materialnm[obj2->material] : "";
if ((namcmp = strcmpi(mat1, mat2)) != 0)
return namcmp;
/* Sort by greasing. This will put the objects in degreasing order. */
val1 = obj1->greased;
val2 = obj2->greased;
if (val1 != val2)
return val2 - val1; /* bigger is better */
/* Sort by erosion. The effective amount is what matters. */
val1 = greatest_erosion(obj1);
val2 = greatest_erosion(obj2);
if (val1 != val2)
return val1 - val2; /* bigger is WORSE */
/* Sort by erodeproofing. Map known-invulnerable to 1, and both
known-vulnerable and unknown-vulnerability to 0, because that's
how they're displayed. */
val1 = obj1->rknown && obj1->oerodeproof;
val2 = obj2->rknown && obj2->oerodeproof;
if (val1 != val2)
return val2 - val1; /* bigger is better */
/* Sort by enchantment. Map unknown to -1000, which is comfortably
below the range of obj->spe. oc_uses_known means that obj->known
matters, which usually indirectly means that obj->spe is relevant.
Lots of objects use obj->spe for some other purpose (see obj.h). */
if (objects[obj1->otyp].oc_uses_known
/* exclude eggs (laid by you) and tins (homemade, pureed, &c) */
&& obj1->oclass != FOOD_CLASS) {
val1 = obj1->known ? obj1->spe : -1000;
val2 = obj2->known ? obj2->spe : -1000;
if (val1 != val2)
return val2 - val1; /* bigger is better */
}
tiebreak:
/* They're identical, as far as we're concerned. We want
to force a deterministic order, and do so by producing a
stable sort: maintain the original order of equal items. */
return (sli1->indx - sli2->indx);
}
/*
* sortloot() - the story so far...
*
* The original implementation constructed and returned an array
* of pointers to objects in the requested order. Callers had to
* count the number of objects, allocate the array, pass one
* object at a time to the routine which populates it, traverse
* the objects via stepping through the array, then free the
* array. The ordering process used a basic insertion sort which
* is fine for short lists but inefficient for long ones.
*
* 3.6.0 (and continuing with 3.6.1) changed all that so that
* sortloot was self-contained as far as callers were concerned.
* It reordered the linked list into the requested order and then
* normal list traversal was used to process it. It also switched
* to qsort() on the assumption that the C library implementation
* put some effort into sorting efficiently. It also checked
* whether the list was already sorted as it got ready to do the
* sorting, so re-examining inventory or a pile of objects without
* having changed anything would gobble up less CPU than a full
* sort. But it had at least two problems (aside from the ordinary
* complement of bugs):
* 1) some players wanted to get the original order back when they
* changed the 'sortloot' option back to 'none', but the list
* reordering made that infeasible;
* 2) object identification giving the 'ID whole pack' result
* would call makeknown() on each newly ID'd object, that would
* call update_inventory() to update the persistent inventory
* window if one existed, the interface would call the inventory
* display routine which would call sortloot() which might change
* the order of the list being traversed by the identify code,
* possibly skipping the ID of some objects. That could have been
* avoided by suppressing 'perm_invent' during identification
* (fragile) or by avoiding sortloot() during inventory display
* (more robust).
*
* As of 3.6.2: revert to the temporary array of ordered obj pointers
* but have sortloot() do the counting and allocation. Callers
* need to use array traversal instead of linked list traversal
* and need to free the temporary array when done. And the
* array contains 'struct sortloot_item' (aka 'Loot') entries
* instead of simple 'struct obj *' entries.
*/
Loot *
sortloot(olist, mode, by_nexthere, filterfunc)
struct obj **olist; /* previous version might have changed *olist, we don't */
unsigned mode; /* flags for sortloot_cmp() */
boolean by_nexthere; /* T: traverse via obj->nexthere, F: via obj->nobj */
boolean FDECL((*filterfunc), (OBJ_P));
{
Loot *sliarray;
struct obj *o;
unsigned n, i;
boolean augment_filter;
for (n = 0, o = *olist; o; o = by_nexthere ? o->nexthere : o->nobj)
++n;
/* note: if there is a filter function, this might overallocate */
sliarray = (Loot *) alloc((n + 1) * sizeof *sliarray);
/* the 'keep cockatrice corpses' flag is overloaded with sort mode */
augment_filter = (mode & SORTLOOT_PETRIFY) ? TRUE : FALSE;
mode &= ~SORTLOOT_PETRIFY; /* remove flag, leaving mode */
/* populate aliarray[0..n-1] */
for (i = 0, o = *olist; o; o = by_nexthere ? o->nexthere : o->nobj) {
if (filterfunc && !(*filterfunc)(o)
/* caller may be asking us to override filterfunc (in order
to do a cockatrice corpse touch check during pickup even
if/when the filter rejects food class) */
&& (!augment_filter || o->otyp != CORPSE
|| !touch_petrifies(&mons[o->corpsenm])))
continue;
sliarray[i].obj = o, sliarray[i].indx = (int) i;
sliarray[i].str = (char *) 0;
sliarray[i].orderclass = sliarray[i].subclass = sliarray[i].disco = 0;
++i;
}
n = i;
/* add a terminator so that we don't have to pass 'n' back to caller */
sliarray[n].obj = (struct obj *) 0, sliarray[n].indx = -1;
sliarray[n].str = (char *) 0;
sliarray[n].orderclass = sliarray[n].subclass = sliarray[n].disco = 0;
/* do the sort; if no sorting is requested, we'll just return
a sortloot_item array reflecting the current ordering */
if (mode && n > 1) {
sortlootmode = mode; /* extra input for sortloot_cmp() */
qsort((genericptr_t) sliarray, n, sizeof *sliarray, sortloot_cmp);
sortlootmode = 0; /* reset static mode flags */
/* if sortloot_cmp formatted any objects, discard their strings now */
for (i = 0; i < n; ++i)
if (sliarray[i].str)
free((genericptr_t) sliarray[i].str), sliarray[i].str = 0;
}
return sliarray;
}
/* sortloot() callers should use this to free up memory it allocates */
void
unsortloot(loot_array_p)
Loot **loot_array_p;
{
if (*loot_array_p)
free((genericptr_t) *loot_array_p), *loot_array_p = (Loot *) 0;
}
#if 0 /* 3.6.0 'revamp' */
void
sortloot(olist, mode, by_nexthere)
struct obj **olist;
unsigned mode; /* flags for sortloot_cmp() */
boolean by_nexthere; /* T: traverse via obj->nexthere, F: via obj->nobj */
{
struct sortloot_item *sliarray, osli, nsli;
struct obj *o, **nxt_p;
unsigned n, i;
boolean already_sorted = TRUE;
sortlootmode = mode; /* extra input for sortloot_cmp() */
for (n = osli.indx = 0, osli.obj = *olist; (o = osli.obj) != 0;
osli = nsli) {
nsli.obj = by_nexthere ? o->nexthere : o->nobj;
nsli.indx = (int) ++n;
if (nsli.obj && already_sorted
&& sortloot_cmp((genericptr_t) &osli, (genericptr_t) &nsli) > 0)
already_sorted = FALSE;
}
if (n > 1 && !already_sorted) {
sliarray = (struct sortloot_item *) alloc(n * sizeof *sliarray);
for (i = 0, o = *olist; o;
++i, o = by_nexthere ? o->nexthere : o->nobj)
sliarray[i].obj = o, sliarray[i].indx = (int) i;
qsort((genericptr_t) sliarray, n, sizeof *sliarray, sortloot_cmp);
for (i = 0; i < n; ++i) {
o = sliarray[i].obj;
nxt_p = by_nexthere ? &(o->nexthere) : &(o->nobj);
*nxt_p = (i < n - 1) ? sliarray[i + 1].obj : (struct obj *) 0;
}
*olist = sliarray[0].obj;
free((genericptr_t) sliarray);
}
sortlootmode = 0;
}
#endif /*0*/
void
assigninvlet(otmp)
register struct obj *otmp;
{
boolean inuse[52];
register int i;
register struct obj *obj;
/* there should be at most one of these in inventory... */
if (otmp->oclass == COIN_CLASS) {
otmp->invlet = GOLD_SYM;
return;
}
for (i = 0; i < 52; i++)
inuse[i] = FALSE;
for (obj = invent; obj; obj = obj->nobj)
if (obj != otmp) {
i = obj->invlet;
if ('a' <= i && i <= 'z')
inuse[i - 'a'] = TRUE;
else if ('A' <= i && i <= 'Z')
inuse[i - 'A' + 26] = TRUE;
if (i == otmp->invlet)
otmp->invlet = 0;
}
if ((i = otmp->invlet)
&& (('a' <= i && i <= 'z') || ('A' <= i && i <= 'Z')))
return;
for (i = lastinvnr + 1; i != lastinvnr; i++) {
if (i == 52) {
i = -1;
continue;
}
if (!inuse[i])
break;
}
otmp->invlet =
(inuse[i] ? NOINVSYM : (i < 26) ? ('a' + i) : ('A' + i - 26));
lastinvnr = i;
}
/* note: assumes ASCII; toggling a bit puts lowercase in front of uppercase */
#define inv_rank(o) ((o)->invlet ^ 040)
/* sort the inventory; used by addinv() and doorganize() */
STATIC_OVL void
reorder_invent()
{
struct obj *otmp, *prev, *next;
boolean need_more_sorting;
do {
/*
* We expect at most one item to be out of order, so this
* isn't nearly as inefficient as it may first appear.
*/
need_more_sorting = FALSE;
for (otmp = invent, prev = 0; otmp;) {
next = otmp->nobj;
if (next && inv_rank(next) < inv_rank(otmp)) {
need_more_sorting = TRUE;
if (prev)
prev->nobj = next;
else
invent = next;
otmp->nobj = next->nobj;
next->nobj = otmp;
prev = next;
} else {
prev = otmp;
otmp = next;
}
}
} while (need_more_sorting);
}
#undef inv_rank
/* scan a list of objects to see whether another object will merge with
one of them; used in pickup.c when all 52 inventory slots are in use,
to figure out whether another object could still be picked up */
struct obj *
merge_choice(objlist, obj)
struct obj *objlist, *obj;
{
struct monst *shkp;
int save_nocharge;
if (obj->otyp == SCR_SCARE_MONSTER) /* punt on these */
return (struct obj *) 0;
/* if this is an item on the shop floor, the attributes it will
have when carried are different from what they are now; prevent
that from eliciting an incorrect result from mergable() */
save_nocharge = obj->no_charge;
if (objlist == invent && obj->where == OBJ_FLOOR
&& (shkp = shop_keeper(inside_shop(obj->ox, obj->oy))) != 0) {
if (obj->no_charge)
obj->no_charge = 0;
/* A billable object won't have its `unpaid' bit set, so would
erroneously seem to be a candidate to merge with a similar
ordinary object. That's no good, because once it's really
picked up, it won't merge after all. It might merge with
another unpaid object, but we can't check that here (depends
too much upon shk's bill) and if it doesn't merge it would
end up in the '#' overflow inventory slot, so reject it now. */
else if (inhishop(shkp))
return (struct obj *) 0;
}
while (objlist) {
if (mergable(objlist, obj))
break;
objlist = objlist->nobj;
}
obj->no_charge = save_nocharge;
return objlist;
}
/* merge obj with otmp and delete obj if types agree */
int
merged(potmp, pobj)
struct obj **potmp, **pobj;
{
register struct obj *otmp = *potmp, *obj = *pobj;
if (mergable(otmp, obj)) {
/* Approximate age: we do it this way because if we were to
* do it "accurately" (merge only when ages are identical)
* we'd wind up never merging any corpses.
* otmp->age = otmp->age*(1-proportion) + obj->age*proportion;
*
* Don't do the age manipulation if lit. We would need
* to stop the burn on both items, then merge the age,
* then restart the burn. Glob ages are averaged in the
* absorb routine, which uses weight rather than quantity
* to adjust for proportion (glob quantity is always 1).
*/
if (!obj->lamplit && !obj->globby)
otmp->age = ((otmp->age * otmp->quan) + (obj->age * obj->quan))
/ (otmp->quan + obj->quan);
if (!otmp->globby)
otmp->quan += obj->quan;
/* recalc weight for various classes that may use
different materials that could make the object
weigh less than 1 aum*/
if (otmp->oclass == WEAPON_CLASS)
otmp->owt = weight(otmp);
/* temporary special case for gold objects!!!! */
else if (otmp->oclass == COIN_CLASS)
otmp->owt = weight(otmp), otmp->bknown = 0;
/* and puddings!!!1!!one! */
else if (!Is_pudding(otmp))
otmp->owt += obj->owt;
if (!has_oname(otmp) && has_oname(obj))
otmp = *potmp = oname(otmp, ONAME(obj));
obj_extract_self(obj);
/* really should merge the timeouts */
if (obj->lamplit)
obj_merge_light_sources(obj, otmp);
if (obj->timed)
obj_stop_timers(obj); /* follows lights */
/* fixup for `#adjust' merging wielded darts, daggers, &c */
if (obj->owornmask && carried(otmp)) {
long wmask = otmp->owornmask | obj->owornmask;
/* Both the items might be worn in competing slots;
merger preference (regardless of which is which):
primary weapon + alternate weapon -> primary weapon;
primary weapon + quiver -> primary weapon;
alternate weapon + quiver -> alternate weapon.
(Prior to 3.3.0, it was not possible for the two
stacks to be worn in different slots and `obj'
didn't need to be unworn when merging.) */
if (wmask & W_WEP)
wmask = W_WEP;
else if (wmask & W_SWAPWEP)
wmask = W_SWAPWEP;
else if (wmask & W_QUIVER)
wmask = W_QUIVER;
else {
impossible("merging strangely worn items (%lx)", wmask);
wmask = otmp->owornmask;
}
if ((otmp->owornmask & ~wmask) != 0L)
setnotworn(otmp);
setworn(otmp, wmask);
setnotworn(obj);
#if 0
/* (this should not be necessary, since items
already in a monster's inventory don't ever get
merged into other objects [only vice versa]) */
} else if (obj->owornmask && mcarried(otmp)) {
if (obj == MON_WEP(otmp->ocarry)) {
MON_WEP(otmp->ocarry) = otmp;
otmp->owornmask = W_WEP;
}
#endif /*0*/
}
/* handle puddings a bit differently; absorption will free the
other object automatically so we can just return out from here */
if (obj->globby) {
pudding_merge_message(otmp, obj);
obj_absorb(potmp, pobj);
return 1;
}
obfree(obj, otmp); /* free(obj), bill->otmp */
return 1;
}
return 0;
}
/*
* Adjust hero intrinsics as if this object was being added to the hero's
* inventory. Called _before_ the object has been added to the hero's
* inventory.
*
* This is called when adding objects to the hero's inventory normally (via
* addinv) or when an object in the hero's inventory has been polymorphed
* in-place.
*
* It may be valid to merge this code with with addinv_core2().
*/
void
addinv_core1(obj)
struct obj *obj;
{
if (obj->oclass == COIN_CLASS) {
context.botl = 1;
} else if (obj->otyp == AMULET_OF_YENDOR) {
if (u.uhave.amulet)
impossible("already have amulet?");
u.uhave.amulet = 1;
/* Player will be able to discover if s/he has the real amulet */
/* by monitoring the livelog - but only when it was picked up */
/* for the first time */
if (!u.uachieve.amulet && !Role_if(PM_INFIDEL)) {
livelog_write_string(LL_ACHIEVE, "acquired the Amulet of Yendor");
u.uachieve.amulet = 1;
mkgate();
}
} else if (obj->otyp == CANDELABRUM_OF_INVOCATION) {
if (u.uhave.menorah)
impossible("already have candelabrum?");
u.uhave.menorah = 1;
if (!u.uachieve.menorah)
livelog_write_string(LL_ACHIEVE, "acquired the Candelabrum of Invocation");
u.uachieve.menorah = 1;
} else if (obj->otyp == BELL_OF_OPENING) {
if (u.uhave.bell)
impossible("already have silver bell?");
u.uhave.bell = 1;
if (!u.uachieve.bell)
livelog_write_string(LL_ACHIEVE, "acquired the Bell of Opening");
u.uachieve.bell = 1;
} else if (obj->otyp == SPE_BOOK_OF_THE_DEAD) {
if (u.uhave.book)
impossible("already have the book?");
u.uhave.book = 1;
if (!u.uachieve.book)
livelog_write_string(LL_ACHIEVE, "acquired the Book of the Dead");
u.uachieve.book = 1;
} else if (obj->oartifact) {
if (is_quest_artifact(obj)) {
if (u.uhave.questart)
impossible("already have quest artifact?");
u.uhave.questart = 1;
if (Role_if(PM_INFIDEL) && u.uachieve.amulet)
u.uhave.amulet = 1;
artitouch(obj);
}
set_artifact_intrinsic(obj, 1, W_ART);
}
/* "special achievements"; revealed in end of game disclosure and
dumplog or using #conduct, originally just recorded in XLOGFILE;
record_achieve_special overloads corpsenm which is ordinarily
initialized to NON_PM (-1) rather than to 0; any special prize
must never be a corpse, egg, tin, figurine, or statue because
their use of obj->corpsenm for monster type would conflict,
nor be a leash (corpsenm overloaded for m_id of leashed
monster) or a novel (corpsenm overloaded for novel index) */
if (is_mines_prize(obj)) {
if (!u.uachieve.mines_luckstone) /* This is spoily if player can see livelog */
livelog_write_string(LL_ACHIEVE, "acquired the luckstone from Mines' End");
u.uachieve.mines_luckstone = 1;
obj->record_achieve_special = NON_PM;
obj->nomerge = 0;
} else if (is_soko_prize(obj)) {
/* Moved this livelog announcement to src/pickup.c
if (!u.uachieve.finish_sokoban)
livelog_printf(LL_ACHIEVE, "completed Sokoban, acquiring %s", an(xname(obj))); */
u.uachieve.finish_sokoban = 1;
obj->record_achieve_special = NON_PM;
obj->nomerge = 0;
}
}
/*
* Adjust hero intrinsics as if this object was being added to the hero's
* inventory. Called _after_ the object has been added to the hero's
* inventory.
*
* This is called when adding objects to the hero's inventory normally (via
* addinv) or when an object in the hero's inventory has been polymorphed
* in-place.
*/
void
addinv_core2(obj)
struct obj *obj;
{
if (confers_luck(obj) || (obj->oprops & ITEM_EXCEL)) {
/* new luckstone must be in inventory by this point
* for correct calculation */
set_moreluck();
}
}
/*
* Add obj to the hero's inventory. Make sure the object is "free".
* Adjust hero attributes as necessary.
*/
struct obj *
addinv(obj)
struct obj *obj;
{
struct obj *otmp, *prev;
int saved_otyp = (int) obj->otyp; /* for panic */
boolean obj_was_thrown;
if (obj->where != OBJ_FREE)
panic("addinv: obj not free (%d, %d, %d)",
obj->where, obj->otyp, obj->invlet);
/* normally addtobill() clears no_charge when items in a shop are
picked up, but won't do so if the shop has become untended */
obj->no_charge = 0; /* should not be set in hero's invent */
if (Has_contents(obj))
picked_container(obj); /* clear no_charge */
obj_was_thrown = obj->was_thrown;
obj->was_thrown = 0; /* not meaningful for invent */
addinv_core1(obj);
/* merge with quiver in preference to any other inventory slot
in case quiver and wielded weapon are both eligible; adding
extra to quivered stack is more useful than to wielded one */
if (uquiver && merged(&uquiver, &obj)) {
obj = uquiver;
if (!obj)
panic("addinv: null obj after quiver merge otyp=%d", saved_otyp);
goto added;
}
/* merge if possible; find end of chain in the process */
for (prev = 0, otmp = invent; otmp; prev = otmp, otmp = otmp->nobj)
if (merged(&otmp, &obj)) {
obj = otmp;
if (!obj)
panic("addinv: null obj after merge otyp=%d", saved_otyp);
goto added;
}
/* didn't merge, so insert into chain */
assigninvlet(obj);
if (flags.invlet_constant || !prev) {
obj->nobj = invent; /* insert at beginning */
invent = obj;
if (flags.invlet_constant)
reorder_invent();
} else {
prev->nobj = obj; /* insert at end */
obj->nobj = 0;
}
obj->where = OBJ_INVENT;
/* fill empty quiver if obj was thrown */
if (obj_was_thrown && flags.pickup_thrown && !uquiver
/* if Mjollnir or Xiuhcoatl is thrown and fails to return,
we want to auto-pick it when we move to its spot, but not
into quiver because it needs to be wielded to be re-thrown;
aklys likewise because player using 'f' to throw it might
not notice that it isn't wielded until it fails to return
several times; we never auto-wield, just omit from quiver
so that player will be prompted for what to throw and
possibly realize that re-wielding is necessary */
&& obj->otyp != AKLYS
&& obj->oartifact != ART_MJOLLNIR
&& obj->oartifact != ART_XIUHCOATL
&& (throwing_weapon(obj) || is_ammo(obj)))
setuqwep(obj);
added:
addinv_core2(obj);
carry_obj_effects(obj); /* carrying affects the obj */
update_inventory();
return obj;
}
/*