forked from NetHack/NetHack
-
Notifications
You must be signed in to change notification settings - Fork 0
/
potion.c
2807 lines (2598 loc) · 91.5 KB
/
potion.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 potion.c $NHDT-Date: 1685135014 2023/05/26 21:03:34 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.238 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/*-Copyright (c) Robert Patrick Rankin, 2013. */
/* NetHack may be freely redistributed. See license for details. */
#include "hack.h"
static long itimeout(long);
static long itimeout_incr(long, int);
static void ghost_from_bottle(void);
static int drink_ok(struct obj *);
static void peffect_restore_ability(struct obj *);
static void peffect_hallucination(struct obj *);
static void peffect_water(struct obj *);
static void peffect_booze(struct obj *);
static void peffect_enlightenment(struct obj *);
static void peffect_invisibility(struct obj *);
static void peffect_see_invisible(struct obj *);
static void peffect_paralysis(struct obj *);
static void peffect_sleeping(struct obj *);
static int peffect_monster_detection(struct obj *);
static int peffect_object_detection(struct obj *);
static void peffect_sickness(struct obj *);
static void peffect_confusion(struct obj *);
static void peffect_gain_ability(struct obj *);
static void peffect_speed(struct obj *);
static void peffect_blindness(struct obj *);
static void peffect_gain_level(struct obj *);
static void peffect_healing(struct obj *);
static void peffect_extra_healing(struct obj *);
static void peffect_full_healing(struct obj *);
static void peffect_levitation(struct obj *);
static void peffect_gain_energy(struct obj *);
static void peffect_oil(struct obj *);
static void peffect_acid(struct obj *);
static void peffect_polymorph(struct obj *);
static boolean H2Opotion_dip(struct obj *, struct obj *, boolean,
const char *);
static short mixtype(struct obj *, struct obj *);
static int dip_ok(struct obj *);
static void hold_potion(struct obj *, const char *, const char *,
const char *);
static int potion_dip(struct obj *obj, struct obj *potion);
/* used to indicate whether quaff or dip has skipped an opportunity to
use a fountain or such, in order to vary the feedback if hero lacks
any potions [reinitialized every time it's used so does not need to
be placed in struct instance_globals gd] */
static int drink_ok_extra = 0;
/* force `val' to be within valid range for intrinsic timeout value */
static long
itimeout(long val)
{
if (val >= TIMEOUT)
val = TIMEOUT;
else if (val < 1L)
val = 0L;
return val;
}
/* increment `old' by `incr' and force result to be valid intrinsic timeout */
static long
itimeout_incr(long old, int incr)
{
return itimeout((old & TIMEOUT) + (long) incr);
}
/* set the timeout field of intrinsic `which' */
void
set_itimeout(long *which, long val)
{
*which &= ~TIMEOUT;
*which |= itimeout(val);
}
/* increment the timeout field of intrinsic `which' */
void
incr_itimeout(long *which, int incr)
{
set_itimeout(which, itimeout_incr(*which, incr));
}
void
make_confused(long xtime, boolean talk)
{
long old = HConfusion;
if (Unaware)
talk = FALSE;
if (!xtime && old) {
if (talk)
You_feel("less %s now.", Hallucination ? "trippy" : "confused");
}
if ((xtime && !old) || (!xtime && old))
gc.context.botl = TRUE;
set_itimeout(&HConfusion, xtime);
}
void
make_stunned(long xtime, boolean talk)
{
long old = HStun;
if (Unaware)
talk = FALSE;
if (!xtime && old) {
if (talk)
You_feel("%s now.",
Hallucination ? "less wobbly" : "a bit steadier");
}
if (xtime && !old) {
if (talk) {
if (u.usteed)
You("wobble in the saddle.");
else
You("%s...", stagger(gy.youmonst.data, "stagger"));
}
}
if ((!xtime && old) || (xtime && !old))
gc.context.botl = TRUE;
set_itimeout(&HStun, xtime);
}
/* Sick is overloaded with both fatal illness and food poisoning (via
u.usick_type bit mask), but delayed killer can only support one or
the other at a time. They should become separate intrinsics.... */
void
make_sick(long xtime,
const char *cause, /* sickness cause */
boolean talk,
int type)
{
struct kinfo *kptr;
long old = Sick;
#if 0 /* tell player even if hero is unconscious */
if (Unaware)
talk = FALSE;
#endif
if (xtime > 0L) {
if (Sick_resistance)
return;
if (!old) {
/* newly sick */
You_feel("deathly sick.");
} else {
/* already sick */
if (talk)
You_feel("%s worse.", xtime <= Sick / 2L ? "much" : "even");
}
set_itimeout(&Sick, xtime);
u.usick_type |= type;
gc.context.botl = TRUE;
} else if (old && (type & u.usick_type)) {
/* was sick, now not */
u.usick_type &= ~type;
if (u.usick_type) { /* only partly cured */
if (talk)
You_feel("somewhat better.");
set_itimeout(&Sick, Sick * 2); /* approximation */
} else {
if (talk)
You_feel("cured. What a relief!");
Sick = 0L; /* set_itimeout(&Sick, 0L) */
}
gc.context.botl = TRUE;
}
kptr = find_delayed_killer(SICK);
if (Sick) {
exercise(A_CON, FALSE);
/* setting delayed_killer used to be unconditional, but that's
not right when make_sick(0) is called to cure food poisoning
if hero was also fatally ill; this is only approximate */
if (xtime || !old || !kptr) {
int kpfx = ((cause && !strcmp(cause, "#wizintrinsic"))
? KILLED_BY : KILLED_BY_AN);
delayed_killer(SICK, kpfx, cause);
}
} else
dealloc_killer(kptr);
}
void
make_slimed(long xtime, const char *msg)
{
long old = Slimed;
#if 0 /* tell player even if hero is unconscious */
if (Unaware)
msg = 0;
#endif
set_itimeout(&Slimed, xtime);
if ((xtime != 0L) ^ (old != 0L)) {
gc.context.botl = TRUE;
if (msg)
pline("%s", msg);
}
if (!Slimed) {
dealloc_killer(find_delayed_killer(SLIMED));
/* fake appearance is set late in turn-to-slime countdown */
if (U_AP_TYPE == M_AP_MONSTER
&& gy.youmonst.mappearance == PM_GREEN_SLIME) {
gy.youmonst.m_ap_type = M_AP_NOTHING;
gy.youmonst.mappearance = 0;
}
}
}
/* start or stop petrification */
void
make_stoned(long xtime, const char *msg, int killedby, const char *killername)
{
long old = Stoned;
#if 0 /* tell player even if hero is unconscious */
if (Unaware)
msg = 0;
#endif
set_itimeout(&Stoned, xtime);
if ((xtime != 0L) ^ (old != 0L)) {
gc.context.botl = TRUE;
if (msg)
pline("%s", msg);
}
if (!Stoned)
dealloc_killer(find_delayed_killer(STONED));
else if (!old)
delayed_killer(STONED, killedby, killername);
}
void
make_vomiting(long xtime, boolean talk)
{
long old = Vomiting;
if (Unaware)
talk = FALSE;
set_itimeout(&Vomiting, xtime);
gc.context.botl = TRUE;
if (!xtime && old)
if (talk)
You_feel("much less nauseated now.");
}
static const char vismsg[] = "vision seems to %s for a moment but is %s now.";
static const char eyemsg[] = "%s momentarily %s.";
void
make_blinded(long xtime, boolean talk)
{
long old = BlindedTimeout;
boolean u_could_see, can_see_now;
const char *eyes;
/* we probe ahead in case the Eyes of the Overworld
are or will be overriding blindness */
u_could_see = !Blind;
set_itimeout(&HBlinded, xtime ? 1L : 0L);
can_see_now = !Blind;
set_itimeout(&HBlinded, old);
if (Unaware)
talk = FALSE;
if (can_see_now && !u_could_see) { /* regaining sight */
if (talk) {
if (Hallucination)
pline("Far out! Everything is all cosmic again!");
else
You("can see again.");
}
} else if (old && !xtime) {
/* clearing temporary blindness without toggling blindness */
if (talk) {
if (!haseyes(gy.youmonst.data) || PermaBlind) {
strange_feeling((struct obj *) 0, (char *) 0);
} else if (Blindfolded) {
eyes = body_part(EYE);
if (eyecount(gy.youmonst.data) != 1)
eyes = makeplural(eyes);
Your(eyemsg, eyes, vtense(eyes, "itch"));
} else { /* Eyes of the Overworld */
Your(vismsg, "brighten", Hallucination ? "sadder" : "normal");
}
}
}
if (u_could_see && !can_see_now) { /* losing sight */
if (talk) {
if (Hallucination)
pline("Oh, bummer! Everything is dark! Help!");
else
pline("A cloud of darkness falls upon you.");
}
/* Before the hero goes blind, set the ball&chain variables. */
if (Punished)
set_bc(0);
} else if (!old && xtime) {
/* setting temporary blindness without toggling blindness */
if (talk) {
if (!haseyes(gy.youmonst.data) || PermaBlind) {
strange_feeling((struct obj *) 0, (char *) 0);
} else if (Blindfolded) {
eyes = body_part(EYE);
if (eyecount(gy.youmonst.data) != 1)
eyes = makeplural(eyes);
Your(eyemsg, eyes, vtense(eyes, "twitch"));
} else { /* Eyes of the Overworld */
Your(vismsg, "dim", Hallucination ? "happier" : "normal");
}
}
}
set_itimeout(&HBlinded, xtime);
if (u_could_see ^ can_see_now) { /* one or the other but not both */
toggle_blindness();
}
}
/* blindness has just started or just ended--caller enforces that;
called by Blindf_on(), Blindf_off(), and make_blinded() */
void
toggle_blindness(void)
{
boolean Stinging = (uwep && (EWarn_of_mon & W_WEP) != 0L);
/* blindness has just been toggled */
gc.context.botl = TRUE; /* status conditions need update */
gv.vision_full_recalc = 1; /* vision has changed */
/* this vision recalculation used to be deferred until moveloop(),
but that made it possible for vision irregularities to occur
(cited case was force bolt hitting an adjacent potion of blindness
and then a secret door; hero was blinded by vapors but then got the
message "a door appears in the wall" because wall spot was IN_SIGHT) */
vision_recalc(0);
if (Blind_telepat || Infravision || Stinging)
see_monsters(); /* also counts EWarn_of_mon monsters */
/*
* Avoid either of the sequences
* "Sting starts glowing", [become blind], "Sting stops quivering" or
* "Sting starts quivering", [regain sight], "Sting stops glowing"
* by giving "Sting is quivering" when becoming blind or
* "Sting is glowing" when regaining sight so that the eventual
* "stops" message matches the most recent "Sting is ..." one.
*/
if (Stinging)
Sting_effects(-1);
/* update dknown flag for inventory picked up while blind */
if (!Blind)
learn_unseen_invent();
}
DISABLE_WARNING_FORMAT_NONLITERAL
boolean
make_hallucinated(
long xtime, /* nonzero if this is an attempt to turn on hallucination */
boolean talk,
long mask) /* nonzero if resistance status should change by mask */
{
long old = HHallucination;
boolean changed = 0;
const char *message, *verb;
if (Unaware)
talk = FALSE;
message = (!xtime) ? "Everything %s SO boring now."
: "Oh wow! Everything %s so cosmic!";
verb = (!Blind) ? "looks" : "feels";
if (mask) {
if (HHallucination)
changed = TRUE;
if (!xtime)
EHalluc_resistance |= mask;
else
EHalluc_resistance &= ~mask;
} else {
if (!EHalluc_resistance && (!!HHallucination != !!xtime))
changed = TRUE;
set_itimeout(&HHallucination, xtime);
/* clearing temporary hallucination without toggling vision */
if (!changed && !HHallucination && old && talk) {
if (!haseyes(gy.youmonst.data)) {
strange_feeling((struct obj *) 0, (char *) 0);
} else if (Blind) {
const char *eyes = body_part(EYE);
if (eyecount(gy.youmonst.data) != 1)
eyes = makeplural(eyes);
Your(eyemsg, eyes, vtense(eyes, "itch"));
} else { /* Grayswandir */
Your(vismsg, "flatten", "normal");
}
}
}
if (changed) {
/* in case we're mimicking an orange (hallucinatory form
of mimicking gold) update the mimicking's-over message */
if (!Hallucination)
eatmupdate();
if (u.uswallow) {
swallowed(0); /* redraw swallow display */
} else {
/* The see_* routines should be called *before* the pline. */
see_monsters();
see_objects();
see_traps();
}
/* for perm_inv and anything similar
(eg. Qt windowport's equipped items display) */
update_inventory();
gc.context.botl = TRUE;
if (talk)
pline(message, verb);
}
return changed;
}
RESTORE_WARNING_FORMAT_NONLITERAL
void
make_deaf(long xtime, boolean talk)
{
long old = HDeaf;
if (Unaware)
talk = FALSE;
set_itimeout(&HDeaf, xtime);
if ((xtime != 0L) ^ (old != 0L)) {
gc.context.botl = TRUE;
if (talk)
You(old && !Deaf ? "can hear again."
: "are unable to hear anything.");
}
}
/* set or clear "slippery fingers" */
void
make_glib(int xtime)
{
gc.context.botl |= (!Glib ^ !!xtime);
set_itimeout(&Glib, xtime);
/* may change "(being worn)" to "(being worn; slippery)" or vice versa */
if (uarmg)
update_inventory();
}
void
self_invis_message(void)
{
pline("%s %s.",
Hallucination ? "Far out, man! You"
: "Gee! All of a sudden, you",
See_invisible ? "can see right through yourself"
: "can't see yourself");
}
static void
ghost_from_bottle(void)
{
struct monst *mtmp = makemon(&mons[PM_GHOST], u.ux, u.uy, MM_NOMSG);
if (!mtmp) {
pline("This bottle turns out to be empty.");
return;
}
if (Blind) {
pline("As you open the bottle, %s emerges.", something);
return;
}
pline("As you open the bottle, an enormous %s emerges!",
Hallucination ? rndmonnam(NULL) : (const char *) "ghost");
if (Verbose(3, ghost_from_bottle))
You("are frightened to death, and unable to move.");
nomul(-3);
gm.multi_reason = "being frightened to death";
gn.nomovemsg = "You regain your composure.";
}
/* getobj callback for object to drink from, which also does double duty as
the callback for dipping into (both just allow potions). */
static int
drink_ok(struct obj *obj)
{
/* getobj()'s callback to test whether hands/self is a valid "item" to
pick is used here to communicate the fact that player has already
passed up an opportunity to perform the action (drink or dip) on a
non-inventory dungeon feature, so if there are no potions in invent
the message will be "you have nothing /else/ to {drink | dip into}";
if player used 'm' prefix to bypass dungeon features, drink_ok_extra
will be 0 and the potential "else" will be omitted */
if (!obj)
return drink_ok_extra ? GETOBJ_EXCLUDE_NONINVENT : GETOBJ_EXCLUDE;
if (obj->oclass == POTION_CLASS)
return GETOBJ_SUGGEST;
return GETOBJ_EXCLUDE;
}
/* "Quaffing is like drinking, except you spill more." - Terry Pratchett */
/* the #quaff command */
int
dodrink(void)
{
struct obj *otmp;
if (Strangled) {
pline("If you can't breathe air, how can you drink liquid?");
return ECMD_OK;
}
drink_ok_extra = 0;
/* preceding 'q'/#quaff with 'm' skips the possibility of drinking
from fountains, sinks, and surrounding water plus the prompting
which those entail; optional for interactive use, essential for
context-sensitive inventory item action 'quaff' */
if (!iflags.menu_requested) {
/* Is there a fountain to drink from here? */
if (IS_FOUNTAIN(levl[u.ux][u.uy].typ)
/* not as low as floor level but similar restrictions apply */
&& can_reach_floor(FALSE)) {
if (y_n("Drink from the fountain?") == 'y') {
drinkfountain();
return ECMD_TIME;
}
++drink_ok_extra;
}
/* Or a kitchen sink? */
if (IS_SINK(levl[u.ux][u.uy].typ)
/* not as low as floor level but similar restrictions apply */
&& can_reach_floor(FALSE)) {
if (y_n("Drink from the sink?") == 'y') {
drinksink();
return ECMD_TIME;
}
++drink_ok_extra;
}
/* Or are you surrounded by water? */
if (Underwater && !u.uswallow) {
if (y_n("Drink the water around you?") == 'y') {
pline("Do you know what lives in this water?");
return ECMD_TIME;
}
++drink_ok_extra;
}
}
otmp = getobj("drink", drink_ok, GETOBJ_NOFLAGS);
if (!otmp)
return ECMD_CANCEL;
/* quan > 1 used to be left to useup(), but we need to force
the current potion to be unworn, and don't want to do
that for the entire stack when starting with more than 1.
[Drinking a wielded potion of polymorph can trigger a shape
change which causes hero's weapon to be dropped. In 3.4.x,
that led to an "object lost" panic since subsequent useup()
was no longer dealing with an inventory item. Unwearing
the current potion is intended to keep it in inventory.] */
if (otmp->quan > 1L) {
otmp = splitobj(otmp, 1L);
otmp->owornmask = 0L; /* rest of original stuck unaffected */
} else if (otmp->owornmask) {
remove_worn_item(otmp, FALSE);
}
otmp->in_use = TRUE; /* you've opened the stopper */
if (objdescr_is(otmp, "milky")
&& !(gm.mvitals[PM_GHOST].mvflags & G_GONE)
&& !rn2(POTION_OCCUPANT_CHANCE(gm.mvitals[PM_GHOST].born))) {
ghost_from_bottle();
useup(otmp);
return ECMD_TIME;
} else if (objdescr_is(otmp, "smoky")
&& !(gm.mvitals[PM_DJINNI].mvflags & G_GONE)
&& !rn2(POTION_OCCUPANT_CHANCE(gm.mvitals[PM_DJINNI].born))) {
djinni_from_bottle(otmp);
useup(otmp);
return ECMD_TIME;
}
return dopotion(otmp);
}
int
dopotion(struct obj *otmp)
{
int retval;
otmp->in_use = TRUE;
gp.potion_nothing = gp.potion_unkn = 0;
if ((retval = peffects(otmp)) >= 0)
return retval ? ECMD_TIME : ECMD_OK;
if (gp.potion_nothing) {
gp.potion_unkn++;
You("have a %s feeling for a moment, then it passes.",
Hallucination ? "normal" : "peculiar");
}
if (otmp->dknown && !objects[otmp->otyp].oc_name_known) {
if (!gp.potion_unkn) {
makeknown(otmp->otyp);
more_experienced(0, 10);
} else
trycall(otmp);
}
useup(otmp);
return ECMD_TIME;
}
/* potion or spell of restore ability; for spell, otmp is a temporary
spellbook object that will be blessed if hero is skilled in healing */
static void
peffect_restore_ability(struct obj *otmp)
{
gp.potion_unkn++;
if (otmp->cursed) {
pline("Ulch! This makes you feel mediocre!");
return;
} else {
int i, ii;
/* unlike unicorn horn, overrides Fixed_abil;
does not recover temporary strength loss due to hunger
or temporary dexterity loss due to wounded legs */
pline("Wow! This makes you feel %s!",
(!otmp->blessed) ? "good"
: unfixable_trouble_count(FALSE) ? "better"
: "great");
i = rn2(A_MAX); /* start at a random point */
for (ii = 0; ii < A_MAX; ii++) {
int lim = AMAX(i);
/* this used to adjust 'lim' for A_STR when u.uhs was
WEAK or worse, but that's handled via ATEMP(A_STR) now */
if (ABASE(i) < lim) {
ABASE(i) = lim;
gc.context.botl = 1;
/* only first found if not blessed */
if (!otmp->blessed)
break;
}
if (++i >= A_MAX)
i = 0;
}
/* when using the potion (not the spell) also restore lost levels,
to make the potion more worth keeping around for players with
the spell or with a unihorn; this is better than full healing
in that it can restore all of them, not just half, and a
blessed potion restores them all at once */
if (otmp->otyp == POT_RESTORE_ABILITY && u.ulevel < u.ulevelmax) {
do {
pluslvl(FALSE);
} while (u.ulevel < u.ulevelmax && otmp->blessed);
}
}
}
static void
peffect_hallucination(struct obj *otmp)
{
if (Halluc_resistance) {
gp.potion_nothing++;
return;
} else if (Hallucination) {
gp.potion_nothing++;
}
(void) make_hallucinated(itimeout_incr(HHallucination,
rn1(200, 600 - 300 * bcsign(otmp))),
TRUE, 0L);
if ((otmp->blessed && !rn2(3)) || (!otmp->cursed && !rn2(6))) {
You("perceive yourself...");
display_nhwindow(WIN_MESSAGE, FALSE);
enlightenment(MAGICENLIGHTENMENT, ENL_GAMEINPROGRESS);
Your("awareness re-normalizes.");
exercise(A_WIS, TRUE);
}
}
static void
peffect_water(struct obj *otmp)
{
if (!otmp->blessed && !otmp->cursed) {
pline("This tastes like %s.", hliquid("water"));
u.uhunger += rnd(10);
newuhs(FALSE);
return;
}
gp.potion_unkn++;
if (mon_hates_blessings(&gy.youmonst) /* undead or demon */
|| u.ualign.type == A_CHAOTIC) {
if (otmp->blessed) {
pline("This burns like %s!", hliquid("acid"));
exercise(A_CON, FALSE);
if (u.ulycn >= LOW_PM) {
Your("affinity to %s disappears!",
makeplural(mons[u.ulycn].pmnames[NEUTRAL]));
if (gy.youmonst.data == &mons[u.ulycn])
you_unwere(FALSE);
set_ulycn(NON_PM); /* cure lycanthropy */
}
losehp(Maybe_Half_Phys(d(2, 6)), "potion of holy water",
KILLED_BY_AN);
} else if (otmp->cursed) {
You_feel("quite proud of yourself.");
healup(d(2, 6), 0, 0, 0);
if (u.ulycn >= LOW_PM && !Upolyd)
you_were();
exercise(A_CON, TRUE);
}
} else {
if (otmp->blessed) {
You_feel("full of awe.");
make_sick(0L, (char *) 0, TRUE, SICK_ALL);
exercise(A_WIS, TRUE);
exercise(A_CON, TRUE);
if (u.ulycn >= LOW_PM)
you_unwere(TRUE); /* "Purified" */
/* make_confused(0L, TRUE); */
} else {
if (u.ualign.type == A_LAWFUL) {
pline("This burns like %s!", hliquid("acid"));
losehp(Maybe_Half_Phys(d(2, 6)), "potion of unholy water",
KILLED_BY_AN);
} else
You_feel("full of dread.");
if (u.ulycn >= LOW_PM && !Upolyd)
you_were();
exercise(A_CON, FALSE);
}
}
}
static void
peffect_booze(struct obj *otmp)
{
gp.potion_unkn++;
pline("Ooph! This tastes like %s%s!",
otmp->odiluted ? "watered down " : "",
Hallucination ? "dandelion wine" : "liquid fire");
if (!otmp->blessed) {
/* booze hits harder if drinking on an empty stomach */
make_confused(itimeout_incr(HConfusion, d(2 + u.uhs, 8)), FALSE);
}
/* the whiskey makes us feel better */
if (!otmp->odiluted)
healup(1, 0, FALSE, FALSE);
u.uhunger += 10 * (2 + bcsign(otmp));
newuhs(FALSE);
exercise(A_WIS, FALSE);
if (otmp->cursed) {
You("pass out.");
gm.multi = -rnd(15);
gn.nomovemsg = "You awake with a headache.";
}
}
static void
peffect_enlightenment(struct obj *otmp)
{
if (otmp->cursed) {
gp.potion_unkn++;
You("have an uneasy feeling...");
exercise(A_WIS, FALSE);
} else {
if (otmp->blessed) {
(void) adjattrib(A_INT, 1, FALSE);
(void) adjattrib(A_WIS, 1, FALSE);
}
do_enlightenment_effect();
}
}
static void
peffect_invisibility(struct obj *otmp)
{
boolean is_spell = (otmp->oclass == SPBOOK_CLASS);
/* spell cannot penetrate mummy wrapping */
if (is_spell && BInvis && uarmc->otyp == MUMMY_WRAPPING) {
You_feel("rather itchy under %s.", yname(uarmc));
return;
}
if (Invis || Blind || BInvis) {
gp.potion_nothing++;
} else {
self_invis_message();
}
if (otmp->blessed && !rn2(HInvis ? 15 : 30))
HInvis |= FROMOUTSIDE;
else
incr_itimeout(&HInvis, d(6 - 3 * bcsign(otmp), 100) + 100);
newsym(u.ux, u.uy); /* update position */
if (otmp->cursed) {
pline("For some reason, you feel your presence is known.");
aggravate();
}
}
static void
peffect_see_invisible(struct obj *otmp)
{
int msg = Invisible && !Blind;
gp.potion_unkn++;
if (otmp->cursed)
pline("Yecch! This tastes %s.",
Hallucination ? "overripe" : "rotten");
else
pline(
Hallucination
? "This tastes like 10%% real %s%s all-natural beverage."
: "This tastes like %s%s.",
otmp->odiluted ? "reconstituted " : "", fruitname(TRUE));
if (otmp->otyp == POT_FRUIT_JUICE) {
u.uhunger += (otmp->odiluted ? 5 : 10) * (2 + bcsign(otmp));
newuhs(FALSE);
return;
}
if (!otmp->cursed) {
/* Tell them they can see again immediately, which
* will help them identify the potion...
*/
make_blinded(0L, TRUE);
}
if (otmp->blessed)
HSee_invisible |= FROMOUTSIDE;
else
incr_itimeout(&HSee_invisible, rn1(100, 750));
set_mimic_blocking(); /* do special mimic handling */
see_monsters(); /* see invisible monsters */
newsym(u.ux, u.uy); /* see yourself! */
if (msg && !Blind) { /* Blind possible if polymorphed */
You("can see through yourself, but you are visible!");
gp.potion_unkn--;
}
}
static void
peffect_paralysis(struct obj *otmp)
{
if (Free_action) {
You("stiffen momentarily.");
} else {
if (Levitation || Is_airlevel(&u.uz) || Is_waterlevel(&u.uz))
You("are motionlessly suspended.");
else if (u.usteed)
You("are frozen in place!");
else
Your("%s are frozen to the %s!", makeplural(body_part(FOOT)),
surface(u.ux, u.uy));
nomul(-(rn1(10, 25 - 12 * bcsign(otmp))));
gm.multi_reason = "frozen by a potion";
gn.nomovemsg = You_can_move_again;
exercise(A_DEX, FALSE);
}
}
static void
peffect_sleeping(struct obj *otmp)
{
if (Sleep_resistance || Free_action) {
monstseesu(M_SEEN_SLEEP);
You("yawn.");
} else {
You("suddenly fall asleep!");
monstunseesu(M_SEEN_SLEEP);
fall_asleep(-rn1(10, 25 - 12 * bcsign(otmp)), TRUE);
}
}
static int
peffect_monster_detection(struct obj *otmp)
{
if (otmp->blessed) {
int i, x, y;
if (Detect_monsters)
gp.potion_nothing++;
gp.potion_unkn++;
/* after a while, repeated uses become less effective */
if ((HDetect_monsters & TIMEOUT) >= 300L)
i = 1;
else if (otmp->oclass == SPBOOK_CLASS)
i = rn1(40, 21);
else /* potion */
i = rn2(100) + 100;
incr_itimeout(&HDetect_monsters, i);
for (x = 1; x < COLNO; x++) {
for (y = 0; y < ROWNO; y++) {
if (levl[x][y].glyph == GLYPH_INVISIBLE) {
unmap_object(x, y);
newsym(x, y);
}
if (MON_AT(x, y))
gp.potion_unkn = 0;
}
}
/* if swallowed or underwater, fall through to uncursed case */
if (!u.uswallow && !Underwater) {
see_monsters();
if (gp.potion_unkn)
You_feel("lonely.");
return 0;
}
}
if (monster_detect(otmp, 0))
return 1; /* nothing detected */
exercise(A_WIS, TRUE);
return 0;
}
static int
peffect_object_detection(struct obj *otmp)
{
if (object_detect(otmp, 0))
return 1; /* nothing detected */
exercise(A_WIS, TRUE);
return 0;
}
static void
peffect_sickness(struct obj *otmp)
{
pline("Yecch! This stuff tastes like poison.");
if (otmp->blessed) {
pline("(But in fact it was mildly stale %s.)", fruitname(TRUE));
if (!Role_if(PM_HEALER)) {
/* NB: blessed otmp->fromsink is not possible */
losehp(1, "mildly contaminated potion", KILLED_BY_AN);
}
} else {
if (Poison_resistance)
pline("(But in fact it was biologically contaminated %s.)",
fruitname(TRUE));
if (Role_if(PM_HEALER)) {
pline("Fortunately, you have been immunized.");
} else {
char contaminant[BUFSZ];
int typ = rn2(A_MAX);
Sprintf(contaminant, "%s%s",
(Poison_resistance) ? "mildly " : "",
(otmp->fromsink) ? "contaminated tap water"
: "contaminated potion");
if (!Fixed_abil) {
poisontell(typ, FALSE);
(void) adjattrib(typ, Poison_resistance ? -1 : -rn1(4, 3),
1);
}
if (!Poison_resistance) {
if (otmp->fromsink)
losehp(rnd(10) + 5 * !!(otmp->cursed), contaminant,
KILLED_BY);
else
losehp(rnd(10) + 5 * !!(otmp->cursed), contaminant,
KILLED_BY_AN);
} else {
/* rnd loss is so that unblessed poorer than blessed */
losehp(1 + rn2(2), contaminant,
(otmp->fromsink) ? KILLED_BY : KILLED_BY_AN);
}
exercise(A_CON, FALSE);
}
}
if (Hallucination) {
You("are shocked back to your senses!");
(void) make_hallucinated(0L, FALSE, 0L);
}
}
static void
peffect_confusion(struct obj *otmp)
{
if (!Confusion) {
if (Hallucination) {
pline("What a trippy feeling!");
gp.potion_unkn++;
} else
pline("Huh, What? Where am I?");