/
read.c
3760 lines (3509 loc) · 127 KB
/
read.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.7 read.c $NHDT-Date: 1615760296 2021/03/14 22:18:16 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.220 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/*-Copyright (c) Robert Patrick Rankin, 2012. */
/* NetHack may be freely redistributed. See license for details. */
#include "hack.h"
#define Your_Own_Role(mndx) \
((mndx) == g.urole.malenum \
|| (g.urole.femalenum != NON_PM && (mndx) == g.urole.femalenum))
#define Your_Own_Race(mndx) \
((mndx) == g.urace.malenum \
|| (g.urace.femalenum != NON_PM && (mndx) == g.urace.femalenum))
/* For create_critters and demonology... */
#define MAKE_EM_NATURAL 0 /* Create monsters... */
#define MAKE_EM_HOSTILE 1 /* Create hostile monsters... */
#define MAKE_EM_PEACEFUL 2 /* Create peaceful monsters... */
#define MAKE_EM_TAME 3 /* Create tamed monsters... */
static boolean learnscrolltyp(short);
static void cap_spe(struct obj *);
static char *erode_obj_text(struct obj *, char *);
static char *hawaiian_design(struct obj *, char *);
static int read_ok(struct obj *);
static void stripspe(struct obj *);
static void p_glow1(struct obj *);
static void p_glow2(struct obj *, const char *);
static void set_dark(int,int,genericptr_t);
static void randomize(int *, int);
static void forget_single_object(int);
static void specified_id(void);
static int maybe_tame(struct monst *, struct obj *);
static boolean get_valid_stinking_cloud_pos(int, int);
static boolean is_valid_stinking_cloud_pos(int, int, boolean);
static void display_stinking_cloud_positions(int);
static void seffect_enchant_armor(struct obj **);
static void seffect_destroy_armor(struct obj **);
static void seffect_confuse_monster(struct obj **);
static void seffect_scare_monster(struct obj **);
static void seffect_remove_curse(struct obj **);
static void seffect_create_monster(struct obj **);
static void seffect_elementalism(struct obj **);
static void seffect_enchant_weapon(struct obj **);
static void seffect_taming(struct obj **);
static void seffect_genocide(struct obj **);
static void seffect_light(struct obj **);
static void seffect_time(struct obj **);
static void seffect_air(struct obj **);
static void seffect_charging(struct obj **);
static void seffect_amnesia(struct obj **);
static void seffect_fire(struct obj **);
static void seffect_earth(struct obj **);
static void seffect_punishment(struct obj **);
static void seffect_stinking_cloud(struct obj **);
static void seffect_blank_paper(struct obj **);
static void seffect_teleportation(struct obj **);
static void seffect_gold_detection(struct obj **);
static void seffect_food_detection(struct obj **);
static void seffect_identify(struct obj **);
static void seffect_magic_mapping(struct obj **);
static void seffect_change_material(struct obj **);
static void seffect_cloning(struct obj **);
#ifdef MAIL_STRUCTURES
static void seffect_mail(struct obj **);
#endif /* MAIL_STRUCTURES */
static void set_lit(int, int, genericptr);
static void do_class_genocide(void);
static boolean create_particular_parse(char *,
struct _create_particular_data *);
static boolean create_particular_creation(struct _create_particular_data *);
static boolean
learnscrolltyp(short scrolltyp)
{
if (!objects[scrolltyp].oc_name_known) {
makeknown(scrolltyp);
more_experienced(0, 10);
return TRUE;
} else
return FALSE;
}
/* also called from teleport.c for scroll of teleportation */
void
learnscroll(struct obj* sobj)
{
/* it's implied that sobj->dknown is set;
we couldn't be reading this scroll otherwise */
if (sobj->oclass != SPBOOK_CLASS)
(void) learnscrolltyp(sobj->otyp);
}
/* max spe is +99, min is -99 */
static void
cap_spe(struct obj* obj)
{
if (obj) {
if (abs(obj->spe) > SPE_LIM)
obj->spe = sgn(obj->spe) * SPE_LIM;
}
}
static char *
erode_obj_text(struct obj* otmp, char* buf)
{
int erosion = greatest_erosion(otmp);
if (erosion)
wipeout_text(buf, (int) (strlen(buf) * erosion / (2 * MAX_ERODE)),
otmp->o_id ^ (unsigned) ubirthday);
return buf;
}
char *
tshirt_text(struct obj* tshirt, char* buf)
{
static const char *shirt_msgs[] = {
/* Scott Bigham */
"I explored the Dungeons of Doom and all I got was this lousy T-shirt!",
"Is that Mjollnir in your pocket or are you just happy to see me?",
"It's not the size of your sword, it's how #enhance'd you are with it.",
"Madame Elvira's House O' Succubi Lifetime Customer",
"Madame Elvira's House O' Succubi Employee of the Month",
"Ludios Vault Guards Do It In Small, Dark Rooms",
"Yendor Military Soldiers Do It In Large Groups",
"I survived Yendor Military Boot Camp",
"Ludios Accounting School Intra-Mural Lacrosse Team",
"Oracle(TM) Fountains 10th Annual Wet T-Shirt Contest",
"Hey, black dragon! Disintegrate THIS!",
"I'm With Stupid -->",
"Don't blame me, I voted for Izchak!",
"Don't Panic", /* HHGTTG */
"Furinkan High School Athletic Dept.", /* Ranma 1/2 */
"Hel-LOOO, Nurse!", /* Animaniacs */
"=^.^=",
"100% goblin hair - do not wash",
"Aberzombie and Fitch",
"cK -- Cockatrice touches the Kop",
"Don't ask me, I only adventure here",
"Down with pants!",
"d, your dog or a killer?",
"FREE PUG AND NEWT!",
"Go team ant!",
"Got newt?",
"Hello, my darlings!", /* Charlie Drake */
"Hey! Nymphs! Steal This T-Shirt!",
"I <3 Dungeon of Doom",
"I <3 Maud",
/* note: there is a similarly worded apron (alchemy smock) slogan */
"I am a Valkyrie. If you see me running, try to keep up.",
"I am not a pack rat - I am a collector",
"I bounced off a rubber tree", /* Monkey Island */
"Plunder Island Brimstone Beach Club", /* Monkey Island */
"If you can read this, I can hit you with my polearm",
"I'm confused!",
"I scored with the princess",
"I want to live forever or die in the attempt.",
"Lichen Park",
"LOST IN THOUGHT - please send search party",
"Meat is Mordor",
"Minetown Better Business Bureau",
"Minetown Watch",
/* Discworld riff; unfortunately long */
"Ms. Palm's House of Negotiable Affection--A Very Reputable House Of Disrepute",
"Protection Racketeer",
"Real men love Crom",
"Somebody stole my Mojo!",
"The Hellhound Gang",
"The Werewolves",
"They Might Be Storm Giants",
"Weapons don't kill people, I kill people",
"White Zombie",
"You're killing me!",
"Anhur State University - Home of the Fighting Fire Ants!",
"FREE HUGS",
"Serial Ascender",
"Real men are valkyries",
"Young Men's Cavedigging Association",
"Occupy Fort Ludios",
"I couldn't afford this T-shirt so I stole it!",
"Mind flayers suck",
"I'm not wearing any pants",
"Down with the living!",
"Pudding farmer",
"Vegetarian",
"Hello, I'm War!",
"It is better to light a candle than to curse the darkness",
"It is easier to curse the darkness than to light a candle",
/* expanded "rock--paper--scissors" featured in TV show "Big Bang
Theory" although they didn't create it (and an actual T-shirt
with pentagonal diagram showing which choices defeat which) */
"rock--paper--scissors--lizard--Spock!",
/* "All men must die -- all men must serve" challange and response
from book series _A_Song_of_Ice_and_Fire_ by George R.R. Martin,
TV show "Game of Thrones" (probably an actual T-shirt too...) */
"/Valar morghulis/ -- /Valar dohaeris/",
/* Splice Shirts */
"SAVE THE ZRUTYS",
"SpliceHack Beta Tester",
"#NerfTheLoTF",
"Got ASCII?",
"Don't @ me!",
"I <3 Sokoban",
"No Artifacts. Valkyrie Only. Astral Plane.",
"Livelog THIS!"
};
Strcpy(buf, shirt_msgs[tshirt->o_id % SIZE(shirt_msgs)]);
return erode_obj_text(tshirt, buf);
}
char *
hawaiian_motif(struct obj *shirt, char *buf)
{
static const char *hawaiian_motifs[] = {
/* birds */
"flamingo",
"parrot",
"toucan",
"bird of paradise", /* could be a bird or a flower */
/* sea creatures */
"sea turtle",
"tropical fish",
"jellyfish",
"giant eel",
"water nymph",
/* plants */
"plumeria",
"orchid",
"hibiscus flower",
"palm tree",
/* other */
"hula dancer",
"sailboat",
"ukulele",
};
/* a tourist's starting shirt always has the same o_id; we need some
additional randomness or else its design will never differ */
unsigned motif = shirt->o_id ^ (unsigned) ubirthday;
Strcpy(buf, hawaiian_motifs[motif % SIZE(hawaiian_motifs)]);
return buf;
}
static char *
hawaiian_design(struct obj *shirt, char *buf)
{
static const char *hawaiian_bgs[] = {
/* solid colors */
"purple",
"yellow",
"red",
"blue",
"orange",
"black",
"green",
/* adjectives */
"abstract",
"geometric",
"patterned",
"naturalistic",
};
/* This hash method is slightly different than the one in hawaiian_motif;
using the same formula in both cases may lead to some shirt combos
never appearing, if the sizes of the two lists have common factors. */
unsigned bg = shirt->o_id ^ (unsigned) ~ubirthday;
Sprintf(buf, "%s on %s background",
makeplural(hawaiian_motif(shirt, buf)),
an(hawaiian_bgs[bg % SIZE(hawaiian_bgs)]));
return buf;
}
char *
apron_text(struct obj* apron, char* buf)
{
static const char *apron_msgs[] = {
"Kiss the cook",
"I'm making SCIENCE!",
"Don't mess with the chef",
"Don't make me poison you",
"Gehennom's Kitchen",
"Rat: The other white meat",
"If you can't stand the heat, get out of Gehennom!",
"If we weren't meant to eat animals, why are they made out of meat?",
"If you don't like the food, I'll stab you",
/* In the movie "The Sum of All Fears", a Russian worker in a weapons
facility wears a T-shirt that a translator says reads, "I am a
bomb technician, if you see me running ... try to catch up."
In nethack, the quote is far more suitable to an alchemy smock
(particularly since so many of these others are about cooking)
than a T-shirt and is paraphrased to simplify/shorten it.
[later... turns out that this is already a T-shirt message:
"I am a Valkyrie. If you see me running, try to keep up."
so this one has been revised a little: added alchemist prefix,
changed "keep up" to original source's "catch up"] */
"I am an alchemist; if you see me running, try to catch up...",
};
Strcpy(buf, apron_msgs[apron->o_id % SIZE(apron_msgs)]);
return erode_obj_text(apron, buf);
}
static const char *candy_wrappers[] = {
"", /* (none -- should never happen) */
"Apollo", /* Lost */
"Moon Crunchy", /* South Park */
"Snacky Cake", "Chocolate Nuggie", "The Small Bar",
"Crispy Yum Yum", "Nilla Crunchie", "Berry Bar",
"Choco Nummer", "Om-nom", /* Cat Macro */
"Fruity Oaty", /* Serenity */
"Wonka Bar", /* Charlie and the Chocolate Factory */
};
/* return the text of a candy bar's wrapper */
const char *
candy_wrapper_text(struct obj* obj)
{
/* modulo operation is just bullet proofing; 'spe' is already in range */
return candy_wrappers[obj->spe % SIZE(candy_wrappers)];
}
/* assign a wrapper to a candy bar stack */
void
assign_candy_wrapper(struct obj* obj)
{
if (obj->otyp == CANDY_BAR) {
/* skips candy_wrappers[0] */
obj->spe = 1 + rn2(SIZE(candy_wrappers) - 1);
}
return;
}
/* getobj callback for object to read */
static int
read_ok(struct obj* obj)
{
if (!obj)
return GETOBJ_EXCLUDE;
if (obj->oclass == SCROLL_CLASS || obj->oclass == SPBOOK_CLASS)
return GETOBJ_SUGGEST;
return GETOBJ_DOWNPLAY;
}
/* the 'r' command; read a scroll or spell book or various other things */
int
doread(void)
{
static const char find_any_braille[] = "feel any Braille writing.";
register struct obj *scroll;
boolean confused, nodisappear;
int otyp;
/*
* Reading while blind is allowed in most cases, including the
* Book of the Dead but not regular spellbooks. For scrolls, the
* description has to have been seen or magically learned (so only
* when scroll->dknown is true): hero recites the label while
* holding the unfurled scroll. We deliberately don't require
* free hands because that would cripple scroll of remove curse,
* but we ought to be requiring hands or at least limbs. The
* recitation could be sub-vocal; actual speech isn't required.
*
* Reading while confused is allowed and can produce alternate
* outcome.
*
* Reading while stunned is currently allowed but probably should
* be prevented....
*/
g.known = FALSE;
if (check_capacity((char *) 0))
return 0;
scroll = getobj("read", read_ok, GETOBJ_PROMPT);
if (!scroll)
return 0;
otyp = scroll->otyp;
/* outrumor has its own blindness check */
if (otyp == FORTUNE_COOKIE) {
if (flags.verbose)
You("break up the cookie and throw away the pieces.");
outrumor(bcsign(scroll), BY_COOKIE);
if (!Blind)
if (!u.uconduct.literate++)
livelog_write_string(LL_CONDUCT,
"became literate by reading a fortune cookie");
useup(scroll);
return 1;
} else if (otyp == T_SHIRT || otyp == ALCHEMY_SMOCK
|| otyp == HAWAIIAN_SHIRT) {
char buf[BUFSZ], *mesg;
const char *endpunct;
if (Blind) {
You_cant(find_any_braille);
return 0;
}
/* can't read shirt worn under suit (under cloak is ok though) */
if ((otyp == T_SHIRT || otyp == HAWAIIAN_SHIRT) && uarm
&& scroll == uarmu) {
pline("%s shirt is obscured by %s%s.",
scroll->unpaid ? "That" : "Your", shk_your(buf, uarm),
suit_simple_name(uarm));
return 0;
}
if (otyp == HAWAIIAN_SHIRT) {
pline("%s features %s.", flags.verbose ? "The design" : "It",
hawaiian_design(scroll, buf));
return 1;
}
if (!u.uconduct.literate++)
livelog_printf(LL_CONDUCT, "became literate by reading %s",
(scroll->otyp == T_SHIRT) ? "a T-shirt" : "an apron");
/* populate 'buf[]' */
mesg = (otyp == T_SHIRT) ? tshirt_text(scroll, buf)
: apron_text(scroll, buf);
endpunct = "";
if (flags.verbose) {
int ln = (int) strlen(mesg);
/* we will be displaying a sentence; need ending punctuation */
if (ln > 0 && !index(".!?", mesg[ln - 1]))
endpunct = ".";
pline("It reads:");
}
pline("\"%s\"%s", mesg, endpunct);
return 1;
} else if ((otyp == DUNCE_CAP || otyp == CORNUTHAUM)
/* note: "DUNCE" isn't directly connected to tourists but
if everyone could read it, they would always be able to
trivially distinguish between the two types of conical hat;
limiting this to tourists is better than rejecting it */
&& Role_if(PM_TOURIST)) {
/* another note: the misspelling, "wizzard", is correct;
that's what is written on Rincewind's pointy hat from
Pratchett's Discworld series, along with a lot of stars;
rather than inked on or painted on, treat them as stitched
or even separate pieces of fabric which have been attached
(don't recall whether the books mention anything like that...) */
const char *cap_text = (otyp == DUNCE_CAP) ? "DUNCE" : "WIZZARD";
if (scroll->o_id % 3) {
/* no need to vary this when blind; "on this ___" is important
because it suggests that there might be something on others */
You_cant("find anything to read on this %s.",
simpleonames(scroll));
return 0;
}
pline("%s on the %s. It reads: %s.",
!Blind ? "There is writing" : "You feel lettering",
simpleonames(scroll), cap_text);
if (!u.uconduct.literate++)
livelog_printf(LL_CONDUCT, "became literate by reading %s",
otyp == DUNCE_CAP ? "a dunce cap" : "a cornuthaum");
/* yet another note: despite the fact that player will recognize
the object type, don't make it become a discovery for hero */
if (!objects[otyp].oc_name_known && !objects[otyp].oc_uname)
docall(scroll);
return 1;
} else if (otyp == CREDIT_CARD) {
static const char *card_msgs[] = {
"Leprechaun Gold Tru$t - Shamrock Card",
"Magic Memory Vault Charge Card",
"Larn National Bank", /* Larn */
"First Bank of Omega", /* Omega */
"Bank of Zork - Frobozz Magic Card", /* Zork */
"Ankh-Morpork Merchant's Guild Barter Card",
"Ankh-Morpork Thieves' Guild Unlimited Transaction Card",
"Ransmannsby Moneylenders Association",
"Bank of Gehennom - 99% Interest Card",
"Yendorian Express - Copper Card",
"Yendorian Express - Silver Card",
"Yendorian Express - Gold Card",
"Yendorian Express - Mithril Card",
"Yendorian Express - Platinum Card", /* must be last */
};
/* Takeoffs of banned or broken from tcgs. */
static const char *tcg_msgs[] = {
/* MTG */
"Draw three cards, or force a monster to draw three cards.",
"Turn any monster into an 3/3 newt.",
"Opponent loses next turn."
/* YGO */
"Select and control one hostile monster.",
"Draw two cards from your deck.",
/* Pkmn */
"Birthday surprise!",
/* HVL */
"You gain 3 energy."
};
if (Blind) {
You("feel the embossed numbers:");
} else {
if (flags.verbose)
pline("It reads:");
if (Role_if(PM_CARTOMANCER))
pline("\"%s\"",
scroll->oartifact
? tcg_msgs[SIZE(tcg_msgs) - 1]
: tcg_msgs[scroll->o_id % (SIZE(tcg_msgs) - 1)]);
else
pline("\"%s\"",
scroll->oartifact
? card_msgs[SIZE(card_msgs) - 1]
: card_msgs[scroll->o_id % (SIZE(card_msgs) - 1)]);
}
/* Make a credit card number */
pline("\"%d0%d %ld%d1 0%d%d0\"%s",
(((int) scroll->o_id % 89) + 10),
((int) scroll->o_id % 4),
((((long) scroll->o_id * 499L) % 899999L) + 100000L),
((int) scroll->o_id % 10),
(!((int) scroll->o_id % 3)),
(((int) scroll->o_id * 7) % 10),
(flags.verbose || Blind) ? "." : "");
if (!u.uconduct.literate++)
livelog_write_string(LL_CONDUCT,
"became literate by reading a credit card");
return 1;
} else if (otyp == CAN_OF_GREASE) {
pline("This %s has no label.", singular(scroll, xname));
return 0;
} else if (otyp == MAGIC_MARKER) {
if (Blind) {
You_cant(find_any_braille);
return 0;
}
if (flags.verbose)
pline("It reads:");
pline("\"Magic Marker(TM) Red Ink Marker Pen. Water Soluble.\"");
if (!u.uconduct.literate++)
livelog_write_string(LL_CONDUCT,
"became literate by reading a magic marker");
return 1;
} else if (scroll->oclass == COIN_CLASS) {
if (Blind)
You("feel the embossed words:");
else if (flags.verbose)
You("read:");
pline("\"1 Zorkmid. 857 GUE. In Frobs We Trust.\"");
if (!u.uconduct.literate++)
livelog_write_string(LL_CONDUCT,
"became literate by reading a coin's engravings");
return 1;
} else if (scroll->oartifact == ART_ORB_OF_FATE) {
if (Blind)
You("feel the engraved signature:");
else
pline("It is signed:");
pline("\"Odin.\"");
if (!u.uconduct.literate++)
livelog_write_string(LL_CONDUCT,
"became literate by reading the divine signature of Odin");
return 1;
} else if (otyp == CANDY_BAR) {
const char *wrapper = candy_wrapper_text(scroll);
if (Blind) {
You_cant(find_any_braille);
return 0;
}
if (!*wrapper) {
pline("The candy bar's wrapper is blank.");
return 0;
}
pline("The wrapper reads: \"%s\".", wrapper);
if (!u.uconduct.literate++)
livelog_write_string(LL_CONDUCT,
"became literate by reading a candy bar wrapper");
return 1;
} else if (scroll->oclass != SCROLL_CLASS
&& scroll->oclass != SPBOOK_CLASS) {
pline(silly_thing_to, "read");
return 0;
} else if (Blind && otyp != SPE_BOOK_OF_THE_DEAD) {
const char *what = 0;
if (otyp == SPE_NOVEL)
/* unseen novels are already distinguishable from unseen
spellbooks so this isn't revealing any extra information */
what = "words";
else if (scroll->oclass == SPBOOK_CLASS)
what = "mystic runes";
else if (!scroll->dknown)
what = "formula on the scroll";
if (what) {
pline("Being blind, you cannot read the %s.", what);
return 0;
}
}
confused = (Confusion != 0);
#ifdef MAIL_STRUCTURES
if (otyp == SCR_MAIL) {
confused = FALSE; /* override */
/* reading mail is a convenience for the player and takes
place outside the game, so shouldn't affect gameplay;
on the other hand, it starts by explicitly making the
hero actively read something, which is pretty hard
to simply ignore; as a compromise, if the player has
maintained illiterate conduct so far, and this mail
scroll didn't come from bones, ask for confirmation */
if (!u.uconduct.literate) {
if (!scroll->spe && yn(
"Reading mail will violate \"illiterate\" conduct. Read anyway?"
) != 'y')
return 0;
}
}
#endif
/* Actions required to win the game aren't counted towards conduct */
/* Novel conduct is handled in read_tribute so exclude it too */
if (otyp != SPE_BOOK_OF_THE_DEAD && otyp != SPE_NOVEL
&& otyp != SPE_BLANK_PAPER && otyp != SCR_BLANK_PAPER)
if (!u.uconduct.literate++)
livelog_printf(LL_CONDUCT, "became literate by reading %s",
scroll->oclass == SPBOOK_CLASS ? "a book" :
scroll->oclass == SCROLL_CLASS ? "a scroll" : "something");
if (scroll->oclass == SPBOOK_CLASS) {
return study_book(scroll);
}
scroll->in_use = TRUE; /* scroll, not spellbook, now being read */
if (scroll->oartifact) {
if(Blind) {
pline("Being blind, you cannot see the %s.", the(xname(scroll)));
return 0;
}
pline("You examine %s.", the(xname(scroll)));
} else if (otyp != SCR_BLANK_PAPER) {
boolean silently = !can_chant(&g.youmonst);
/* a few scroll feedback messages describe something happening
to the scroll itself, so avoid "it disappears" for those */
nodisappear = (otyp == SCR_FIRE
|| (otyp == SCR_REMOVE_CURSE && scroll->cursed));
if (Blind)
pline(nodisappear
? "You %s the formula on the scroll."
: "As you %s the formula on it, the scroll disappears.",
silently ? "cogitate" : "pronounce");
else
pline(nodisappear ? "You read the scroll."
: "As you read the scroll, it disappears.");
if (confused) {
if (Hallucination)
pline("Being so trippy, you screw up...");
else {
if (Role_if(PM_CARTOMANCER))
pline("Being confused, you %s the rules text...",
silently ? "misunderstand" : "misread");
else
pline("Being confused, you %s the magic words...",
silently ? "misunderstand" : "mispronounce");
}
}
}
if (!seffects(scroll)) {
if (!objects[otyp].oc_name_known) {
if (g.known)
learnscroll(scroll);
else if (!objects[otyp].oc_uname)
docall(scroll);
}
scroll->in_use = FALSE;
if (otyp != SCR_BLANK_PAPER && !scroll->oartifact)
useup(scroll);
}
return 1;
}
static void
stripspe(register struct obj* obj)
{
if (obj->blessed || obj->spe <= 0) {
pline1(nothing_happens);
} else {
/* order matters: message, shop handling, actual transformation */
pline("%s briefly.", Yobjnam2(obj, "vibrate"));
costly_alteration(obj, COST_UNCHRG);
obj->spe = 0;
if (obj->otyp == OIL_LAMP || obj->otyp == LANTERN)
obj->age = 0;
}
}
static void
p_glow1(register struct obj* otmp)
{
pline("%s briefly.", Yobjnam2(otmp, Blind ? "vibrate" : "glow"));
}
static void
p_glow2(register struct obj* otmp, register const char* color)
{
pline("%s%s%s for a moment.", Yobjnam2(otmp, Blind ? "vibrate" : "glow"),
Blind ? "" : " ", Blind ? "" : hcolor(color));
}
/* getobj callback for object to charge */
int
charge_ok(struct obj* obj)
{
if (!obj)
return GETOBJ_EXCLUDE;
if (obj->oclass == WAND_CLASS)
return GETOBJ_SUGGEST;
if (obj->oclass == RING_CLASS && objects[obj->otyp].oc_charged
&& obj->dknown && objects[obj->otyp].oc_name_known)
return GETOBJ_SUGGEST;
if (is_weptool(obj)) /* specific check before general tools */
return GETOBJ_EXCLUDE;
if (obj->oclass == TOOL_CLASS) {
/* suggest tools that aren't oc_charged but can still be recharged */
if (obj->otyp == LANTERN
|| (obj->otyp == OIL_LAMP)
/* only list magic lamps if they are not identified yet */
|| (obj->otyp == MAGIC_LAMP
&& !objects[MAGIC_LAMP].oc_name_known)) {
return GETOBJ_SUGGEST;
}
return objects[obj->otyp].oc_charged ? GETOBJ_SUGGEST : GETOBJ_EXCLUDE;
}
/* why are weapons/armor considered charged anyway?
* make them selectable even so for "feeling of loss" message */
return GETOBJ_EXCLUDE_SELECTABLE;
}
/* recharge an object; curse_bless is -1 if the recharging implement
was cursed, +1 if blessed, 0 otherwise. */
void
recharge(struct obj* obj, int curse_bless)
{
register int n;
boolean is_cursed, is_blessed;
is_cursed = curse_bless < 0;
is_blessed = curse_bless > 0;
if (obj->oclass == WAND_CLASS) {
int lim = (obj->otyp == WAN_WISHING)
? 3
: (objects[obj->otyp].oc_dir != NODIR) ? 8 : 15;
/* undo any prior cancellation, even when is_cursed */
if (obj->spe == -1)
obj->spe = 0;
/*
* Recharging might cause wands to explode.
* v = number of previous recharges
* v = percentage chance to explode on this attempt
* v = cumulative odds for exploding
* 0 : 0 0
* 1 : 0.29 0.29
* 2 : 2.33 2.62
* 3 : 7.87 10.28
* 4 : 18.66 27.02
* 5 : 36.44 53.62
* 6 : 62.97 82.83
* 7 : 100 100
*/
n = (int) obj->recharged;
if (obj->otyp == WAN_WISHING
|| (n > 0 && (n * n * n > rn2(7 * 7 * 7)))) { /* recharge_limit */
wand_explode(obj, rnd(lim));
return;
}
/* didn't explode, so increment the recharge count */
obj->recharged = (unsigned) (n + 1);
/* now handle the actual recharging */
if (is_cursed) {
stripspe(obj);
} else {
n = (lim == 3) ? 3 : rn1(5, lim + 1 - 5);
if (!is_blessed)
n = rnd(n);
if (obj->spe < n)
obj->spe = n;
else
obj->spe++;
if (obj->spe >= lim)
p_glow2(obj, NH_BLUE);
else
p_glow1(obj);
#if 0 /*[shop price doesn't vary by charge count]*/
/* update shop bill to reflect new higher price */
if (obj->unpaid)
alter_cost(obj, 0L);
#endif
}
} else if (obj->oclass == RING_CLASS && objects[obj->otyp].oc_charged) {
/* charging does not affect ring's curse/bless status */
int s = is_blessed ? rnd(3) : is_cursed ? -rnd(2) : 1;
boolean is_on = (obj == uleft || obj == uright);
/* destruction depends on current state, not adjustment */
if (obj->spe > rn2(7) || obj->spe <= -5) {
pline("%s momentarily, then %s!", Yobjnam2(obj, "pulsate"),
otense(obj, "explode"));
if (is_on)
Ring_gone(obj);
s = rnd(3 * abs(obj->spe)); /* amount of damage */
useup(obj);
losehp(Maybe_Half_Phys(s), "exploding ring", KILLED_BY_AN);
} else {
long mask = is_on ? (obj == uleft ? LEFT_RING : RIGHT_RING) : 0L;
pline("%s spins %sclockwise for a moment.", Yname2(obj),
s < 0 ? "counter" : "");
if (s < 0)
costly_alteration(obj, COST_DECHNT);
/* cause attributes and/or properties to be updated */
if (is_on)
Ring_off(obj);
obj->spe += s; /* update the ring while it's off */
if (is_on)
setworn(obj, mask), Ring_on(obj);
/* oartifact: if a touch-sensitive artifact ring is
ever created the above will need to be revised */
/* update shop bill to reflect new higher price */
if (s > 0 && obj->unpaid)
alter_cost(obj, 0L);
}
} else if (obj->oclass == TOOL_CLASS) {
int rechrg = (int) obj->recharged;
if (objects[obj->otyp].oc_charged) {
/* tools don't have a limit, but the counter used does */
if (rechrg < 7) /* recharge_limit */
obj->recharged++;
}
switch (obj->otyp) {
case BELL_OF_OPENING:
if (is_cursed)
stripspe(obj);
else if (is_blessed)
obj->spe += rnd(3);
else
obj->spe += 1;
if (obj->spe > 5)
obj->spe = 5;
break;
case MAGIC_MARKER:
case TINNING_KIT:
case EXPENSIVE_CAMERA:
if (is_cursed) {
stripspe(obj);
} else if (rechrg && obj->otyp == MAGIC_MARKER) {
/* previously recharged */
obj->recharged = 1; /* override increment done above */
if (obj->spe < 3)
Your("marker seems permanently dried out.");
else
pline1(nothing_happens);
} else if (is_blessed) {
n = rn1(16, 15); /* 15..30 */
if (obj->spe + n <= 50)
obj->spe = 50;
else if (obj->spe + n <= 75)
obj->spe = 75;
else {
int chrg = (int) obj->spe;
if ((chrg + n) > 127)
obj->spe = 127;
else
obj->spe += n;
}
p_glow2(obj, NH_BLUE);
} else {
n = rn1(11, 10); /* 10..20 */
if (obj->spe + n <= 50)
obj->spe = 50;
else {
int chrg = (int) obj->spe;
if (chrg + n > SPE_LIM)
obj->spe = SPE_LIM;
else
obj->spe += n;
}
p_glow2(obj, NH_WHITE);
}
break;
case OIL_LAMP:
case LANTERN:
if (is_cursed) {
stripspe(obj);
if (obj->lamplit) {
if (!Blind)
pline("%s out!", Tobjnam(obj, "go"));
end_burn(obj, TRUE);
}
} else if (is_blessed) {
obj->spe = 1;
obj->age = 1500;
p_glow2(obj, NH_BLUE);
} else {
obj->spe = 1;
obj->age += 750;
if (obj->age > 1500)
obj->age = 1500;
p_glow1(obj);
}
break;
case CRYSTAL_BALL:
if (obj->spe == -1) /* like wands, first uncancel */
obj->spe = 0;
if (is_cursed) {
/* cursed scroll removes charges and curses ball */
/*stripspe(obj); -- doesn't do quite what we want...*/
if (!obj->cursed) {
p_glow2(obj, NH_BLACK);
curse(obj);
} else {
pline("%s briefly.", Yobjnam2(obj, "vibrate"));
}
if (obj->spe > 0)
costly_alteration(obj, COST_UNCHRG);
obj->spe = 0;
} else if (is_blessed) {
/* blessed scroll sets charges to max and blesses ball */
obj->spe = 7;
p_glow2(obj, !obj->blessed ? NH_LIGHT_BLUE : NH_BLUE);
if (!obj->blessed)
bless(obj);
/* [shop price stays the same regardless of charges or BUC] */
} else {
/* uncursed scroll increments charges and uncurses ball */
if (obj->spe < 7 || obj->cursed) {
n = rnd(2);
obj->spe = min(obj->spe + n, 7);
if (!obj->cursed) {
p_glow1(obj);
} else {
p_glow2(obj, NH_AMBER);
uncurse(obj);
}
} else {
/* charges at max and ball not being uncursed */
pline1(nothing_happens);
}
}
break;
case HORN_OF_PLENTY:
case BAG_OF_TRICKS:
case BAG_OF_RATS:
case CAN_OF_GREASE:
if (is_cursed) {
stripspe(obj);
} else if (is_blessed) {
if (obj->spe <= 10)
obj->spe += rn1(10, 6);
else
obj->spe += rn1(5, 6);
if (obj->spe > 50)
obj->spe = 50;
p_glow2(obj, NH_BLUE);
} else {
obj->spe += rn1(5, 2);
if (obj->spe > 50)
obj->spe = 50;
p_glow1(obj);
}
break;
case MAGIC_FLUTE:
case MAGIC_HARP:
case FROST_HORN:
case FIRE_HORN:
case HORN_OF_BLASTING:
case DRUM_OF_EARTHQUAKE:
if (is_cursed) {
stripspe(obj);
} else if (is_blessed) {
obj->spe += d(2, 4);
if (obj->spe > 20)
obj->spe = 20;
p_glow2(obj, NH_BLUE);
} else {
obj->spe += rnd(4);
if (obj->spe > 20)
obj->spe = 20;