1
2;; init.scm -- contains lots of common scheme utilities
3(load "init.scm")
4
5;; Result codes (these belong here because they are tied to kernel values, see
6;; result.h)
7(define result-ok          0)
8(define result-no-target   1)
9(define result-no-effect   2)
10(define result-no-hostiles 3)
11(define result-lacks-skill 4)
12(define result-failed      5)
13(define result-not-here    6)
14(define result-critical-fail 7)
15(define result-not-now     8)
16
17;; Test if a result code indicates that the action was not completed
18(define (abortive-result? result)
19  (or (eq? result result-no-target)
20      (eq? result result-not-here)))
21
22;; Override the default error hook to warn the loader when the interpreter
23;; encounters any errors during evaluation.
24(define (my-error-hook . x)
25  (kern-interp-error x)
26  (apply throw x))
27(define *error-hook* my-error-hook)
28
29;; kern-load -- loads a file but also tells the kernel to make a note of it so
30;; that saved sessions will know to use the file, too.
31(define (kern-load fname)
32  (kern-include fname)
33  (load fname))
34
35(define nil '())
36
37;; safe-eval -- evaluates an expression; bad expressions evaluate to '()
38;; instead of causing an interpreter error
39(define (safe-eval expr)
40  (cond ((null? expr) '())
41        ((symbol? expr)
42         (if (defined? expr)
43             (eval expr)
44             '()))
45        (eval expr)))
46
47;; filter -- filter-in elements from a list
48(define (filter pred seq)
49  (cond ((null? seq) nil)
50        ((pred (car seq))
51         (cons (car seq)
52               (filter pred (cdr seq))))
53        (else (filter pred (cdr seq)))))
54
55;; simple, non-prioritized, generic search (not very efficient, so don't try
56;; it on large search spaces)
57(define (search here? next start maxdepth)
58  (define (do-search queue visited depth)
59    (if (or (= depth 0) (null? queue)) nil
60        (let ((loc (car queue)))
61          (if (here? loc) loc
62              (do-search (append (cdr queue)
63                                 (filter (lambda (v) (not (member v visited)))
64                                         (next loc)))
65                         (append (list loc) visited)
66                         (- depth 1))))))
67  (do-search (list start) nil maxdepth))
68
69;; similar to search, but a) it's exhaustive and b) it runs a procedure on
70;; every entry (warning: not sure if or how well this works)
71(define (bfs-apply start next proc)
72  (define (do-search queue visited)
73    (if (null? queue) nil
74        (let ((loc (car queue)))
75          (proc loc)
76          (do-search (append (cdr queue)
77                             (filter (lambda (v) (not (member v visited)))
78                                     (next loc)))
79                     (append (list loc) visited)))))
80  (do-search (list start) nil))
81
82;; Set element k of list x to val (zero-indexed)
83(define (list-set-ref! x k val)
84  (if (zero? k)
85      (set-car! x val)
86      (list-set-ref! (cdr x) (- k 1) val)))
87
88;; Check if a list contains an element.
89(define (in-list? elem lst)
90  (foldr (lambda (a b) (or a (eqv? b elem)))
91         #f
92         lst))
93
94(define (in-text-list? elem lst)
95  (foldr (lambda (a b) (or a (equal? b elem)))
96         #f
97         lst))
98
99;; Check if a location is passable to a character
100(define (passable? loc kobj)
101  (kern-place-is-passable loc kobj))
102
103(define (obj-is-char? kobj) (kern-obj-is-being? kobj))
104(define (is-being? kobj) (kern-obj-is-being? kobj))
105
106;; Check if a location is occupied by a character or party
107(define (occupied? loc)
108  (foldr (lambda (val kobj) (or val (obj-is-char? kobj)))
109         #f
110         (kern-get-objects-at loc)))
111
112(define (get-beings-at loc)
113  (filter kern-obj-is-being?
114          (kern-get-objects-at loc)))
115
116;; Given a starting location, search outward for a passable, unoccupied place
117;; to put a character.
118(define (pick-loc origin char)
119  (search (lambda (loc) (and (kern-is-valid-location? loc)
120									(passable? loc char)
121									(not (occupied? loc))))
122          neighbors
123          origin
124          10))
125
126;; Generic proc to summon other beings. Used by spells and some special
127;; effects.
128(define (summon origin mk-critter faction count)
129  (define (run-loop n)
130    (if (= n 0) nil
131        (let* ((critter (kern-obj-set-temporary (kern-being-set-base-faction
132                                                 (mk-critter)
133                                                 faction)
134                                                #t))
135               (loc (pick-loc origin critter)))
136          (cond ((null? loc) nil)
137                (else
138                 (kern-obj-put-at critter loc)
139                 (run-loop (- n 1)))))))
140  (run-loop count))
141
142;; Like summon but the beings are permanent, not temporary.
143(define (psummon origin mk-critter count)
144  ;;;(display "psummon")(newline)
145  (define (run-loop n)
146    (if (= n 0) nil
147        (let* ((critter (kern-obj-inc-ref (mk-critter)))
148               (loc (pick-loc origin critter)))
149          (cond ((null? loc) (kern-obj-dec-ref critter))
150               (else
151                (kern-obj-put-at critter loc)
152                (kern-obj-dec-ref critter)
153                (run-loop (- n 1)))))))
154  (run-loop count))
155
156;; check if klooker can see kobj
157(define (can-see? klooker kobj)
158  (let ((from (kern-obj-get-location klooker))
159        (to (kern-obj-get-location kobj)))
160    (and (kern-in-los? from to)
161         (<= (kern-get-distance from to)
162             (kern-obj-get-vision-radius klooker))
163         (kern-obj-is-visible? kobj))))
164
165;; check if klooker can can see anything in the list kobs
166(define (can-see-any? klooker kobjs)
167  (if (null? kobjs)
168      #f
169      (or (can-see? klooker (car kobjs))
170          (can-see-any? klooker (cdr kobjs)))))
171
172;; check if knpc can see any of the player party members
173(define (any-player-party-member-visible? knpc)
174  (can-see-any? knpc
175                (kern-party-get-members (kern-get-player))))
176
177;; gets location of player character (not party- ie 'works' in temporary map)
178(define (player-member-loc)
179  (let ((loc (kern-obj-get-location (car (kern-party-get-members (kern-get-player))))))
180    (if (null? loc)
181        nil
182        (loc-place loc))))
183
184(define (num-player-party-members)
185  ;;(display "num-player-party-members")(newline)
186  (length (kern-party-get-members (kern-get-player))))
187
188(define (is-only-living-party-member? kchar)
189  (and (is-alive? kchar)
190       (is-player-party-member? kchar)
191       (not (foldr (lambda (found kchar2)
192                     (println  found " " (kern-obj-get-name kchar2))
193                     (or found
194                         (and (not (eqv? kchar kchar2))
195                              (is-alive? kchar2))))
196                   #f
197                   (kern-party-get-members (kern-get-player))))
198       ))
199
200;; Check if an object is hostile toward a character
201(define (is-hostile? kbeing kobj)
202  (and (is-being? kobj)
203       (kern-being-is-hostile? kbeing kobj)))
204
205;; Check if an object is allied with a character
206(define (is-ally? kbeing kobj)
207  (kern-being-is-ally? kbeing kobj))
208
209;; Find all characters hostile to the given character
210(define (all-hostiles kchar)
211  (filter (lambda (kobj) (is-hostile? kchar kobj))
212          (kern-place-get-beings (loc-place (kern-obj-get-location kchar)))))
213
214;; Find all friendlies
215(define (all-allies kchar)
216  (filter (lambda (kobj) (is-ally? kchar kobj))
217          (kern-place-get-beings (loc-place (kern-obj-get-location kchar)))))
218
219
220;; Count the number of hostiles
221(define (num-hostiles kchar)
222  (length (all-hostiles kchar)))
223
224;; Count the number of friendlies
225(define (num-allies kchar)
226  (length (all-allies kchar)))
227
228;; Find all beings hostile
229(define (all-visible-hostiles kbeing)
230  (kern-being-get-visible-hostiles kbeing))
231
232(define (any-visible-hostiles? kchar)
233  (> (length (all-visible-hostiles kchar)) 0))
234
235(define (nearest-visible-hostile kchar)
236  (nearest-obj kchar (all-visible-hostiles kchar)))
237
238;; Find all allies
239(define (all-visible-allies kbeing)
240  (kern-being-get-visible-allies kbeing))
241
242;; Count the number of visible friendlies
243(define (num-visible-allies kchar)
244  (length (all-visible-allies kchar)))
245
246;; Count the number of hostiles
247(define (num-visible-hostiles kchar)
248  (length (all-visible-hostiles kchar)))
249
250
251;; Find all the characters in a place
252(define (all-chars kplace)
253  (kern-place-get-beings kplace))
254
255;; Check if an object is in the given range of the origin point
256(define (in-range? origin radius kobj)
257  (<= (kern-get-distance origin
258                         (kern-obj-get-location kobj))
259      radius))
260
261;; Check if a character's target is in range
262(define (can-hit? kchar ktarg range)
263  ;;(println "can-hit: " range)
264  (in-range? (kern-obj-get-location kchar)
265             range
266             ktarg))
267
268;; Filter objects out of range
269(define (all-in-range origin radius objlst)
270  (filter (lambda (kobj)
271            (<= (kern-get-distance origin
272                                   (kern-obj-get-location kobj))
273                radius))
274          objlst))
275
276;; Return a list of all hostiles in range of the given location
277(define (get-hostiles-in-range-of-loc kchar range loc)
278  (all-in-range loc
279                range
280                (kern-being-get-visible-hostiles kchar)))
281
282;; Return a list of all hostiles in range of the kchar's current location
283(define (get-hostiles-in-range kchar range)
284  (get-hostiles-in-range-of-loc kchar
285                                range
286                                (kern-obj-get-location kchar)))
287
288;; Return a list of beings within the given range
289(define (get-beings-in-range kobj range)
290  (let ((loc (kern-obj-get-location kobj)))
291  (all-in-range loc
292                range
293                (kern-place-get-beings (loc-place loc)))))
294
295;; Convenience proc for rolling dtables by hand
296(define (dtable-row . cols) cols)
297
298(define (distance kobj-a kobj-b)
299  (let ((loc-a (kern-obj-get-location kobj-a))
300        (loc-b (kern-obj-get-location kobj-b)))
301  (kern-get-distance loc-a loc-b)))
302
303;; Inefficient code to find nearest obj from a list
304(define (nearest-obj kobj klist)
305  (if (null? klist) nil
306      (foldr (lambda (a b) (if (< (distance kobj a) (distance kobj b)) a b))
307             (car klist) (cdr klist))))
308
309;; Inefficient code to find nearest location from a list
310(define (nearest-loc kobj klist)
311  (println "nearest-loc: " klist)
312  (if (null? klist)
313      nil
314      (let ((kloc (kern-obj-get-location kobj)))
315        (foldr (lambda (a b)
316                 (if (< (loc-city-block-distance kloc a)
317                        (loc-city-block-distance kloc b))
318                     a
319                     b))
320               (car klist)
321               (cdr klist)))))
322
323;; Move an object one step along a path to a destination.
324(define (old-pathfind kobj dest)
325  ;;;;(display "pathfind")(newline)
326  (define (follow-path path)
327    (if (not (null? path))
328        (let ((coords (car path))
329              (origin (kern-obj-get-location kobj)))
330          ;;;;(display "pathfind:coords=");;(display coords)(newline)
331          (let ((dx (- (car coords) (loc-x origin)))
332                (dy (- (cdr coords) (loc-y origin))))
333            (kern-obj-move kobj dx dy)))))
334  (let ((path (kern-obj-find-path kobj dest)))
335    ;;;;(display "pathfind:path=");;(display path)(newline)
336    (if (not (null? path))
337        ;; skip the first location in the path
338        (follow-path (cdr path)))))
339
340;; pathfind - use the built-in kernel call that uses cached paths and tries to
341;; handle blocking mechanisms
342(define (pathfind kobj kdest)
343  ;;(println "pathfind(" (kern-obj-get-name kobj) "," kdest ")")
344  (and (kern-obj-is-being? kobj)
345       (kern-being-pathfind-to kobj kdest)))
346
347(define (can-pathfind? kobj dest)
348  (println "can-pathfind?")
349  (or (loc-8-adjacent? dest
350                     (kern-obj-get-location kobj))
351      (not (null? (kern-obj-find-path kobj dest)))))
352
353(define (notnull? val) (not (null? val)))
354
355(define (being-at? loc)
356  (not (null? (filter kern-obj-is-being? (kern-get-objects-at loc)))))
357
358(define (get-being-at loc)
359  (let ((beings (filter kern-obj-is-being? (kern-get-objects-at loc))))
360    (if (null? beings)
361        nil
362        (car beings))))
363
364(define (is-dead? kchar)
365  (kern-char-is-dead? kchar))
366
367(define (is-alive? kchar)
368  (not (is-dead? kchar)))
369
370(define (has-ap? kobj)
371  (> (kern-obj-get-ap kobj) 0))
372
373(define (has-ap-debt? kobj)
374  (< (kern-obj-get-ap kobj) 0))
375
376(define (has-skill? kchar kskill)
377  (in-list? kskill
378            (kern-char-get-skills kchar)))
379
380(define (flee kchar)
381  ;;;(display "flee")(newline)
382  (kern-char-set-fleeing kchar #t))
383
384(define (wander kchar)
385  (kern-obj-wander kchar))
386
387(define (weakest kchar-a kchar-b)
388  (if (< (kern-char-get-hp kchar-a)
389         (kern-char-get-hp kchar-b))
390      a
391      b))
392
393(define (join-player kchar)
394  (kern-char-join-player kchar))
395
396(define (random-select list)
397  (if (or (null? list)
398          (= 0 (length list)))
399      nil
400      (list-ref list (modulo (random-next) (length list)))))
401
402(define (taunt kchar ktarg taunts)
403  (say kchar (random-select taunts)))
404
405;; ----------------------------------------------------------------------------
406;; search-rect -- apply a procedure to every location in a rectangular region
407;; and return a list of its non-nil results.
408;; ----------------------------------------------------------------------------
409(define (search-rect kplace x y w h proc)
410  (filter notnull? (map proc (loc-enum-rect kplace x y w h))))
411
412;; ----------------------------------------------------------------------------
413;; foldr-rect -- similar to search-rect above, but the procedure must
414;; accumulate its own results. Faster because it doesn't have to run the
415;; filter.
416;; ----------------------------------------------------------------------------
417(define (foldr-rect kplace x y w h proc ival)
418  (foldr proc ival (loc-enum-rect kplace x y w h)))
419
420;;----------------------------------------------------------------------------
421;; Return a list of locations with matching terrain
422;;----------------------------------------------------------------------------
423(define (find-terrain kplace x y w h kter)
424  (define (check loc)
425    (if (eqv? (kern-place-get-terrain loc) kter)
426        loc
427        nil))
428  (search-rect kplace x y w h check))
429
430(define (on-terrain? kobj kter)
431  (eqv? kter (kern-place-get-terrain (kern-obj-get-location kobj))))
432
433(define (all-visible-terrain-of-type kobj kter)
434  (filter (lambda (x)
435            (eqv? kter
436                  (kern-place-get-terrain x)))
437          (kern-being-get-visible-tiles kobj)))
438
439(define (find-nearest-visible-terrain-of-type kobj kter)
440  (nearest-loc kobj (all-visible-terrain-of-type kobj kter)))
441
442(define (hidden? kchar)
443  ;;(println "hidden?")
444  ;; Just check if the 8 neighbors are all los-blocking
445  (let ((loc (kern-obj-get-location kchar)))
446    (foldr-rect (loc-place loc)
447                (- (loc-x loc) 1) (- (loc-y loc) 1)
448                3 3
449               (lambda (val neighbor)
450                 ;;(println neighbor " neighbor? " (equal? neighbor loc) " blocks? " (kern-place-blocks-los? neighbor))
451                 (and val
452                      (or (eq? neighbor loc)
453                          (kern-place-blocks-los? neighbor))))
454                #t
455                )))
456
457;; kobj-is-type -- check if the object is of the given type
458(define (kobj-is-type? kobj ktype)
459  (eqv? (kern-obj-get-type kobj)
460        ktype))
461
462;; kplace-get-objects-of-type -- return a list of all objects of the given type
463;; in the given place
464(define (kplace-get-objects-of-type kplace ktype)
465  (filter (lambda (kobj) (kobj-is-type? kobj ktype))
466          (kern-place-get-objects kplace)))
467
468;;----------------------------------------------------------------------------
469;; find-objects -- return a list of locations with the given object on them
470;;----------------------------------------------------------------------------
471(define (find-objects kplace x y w h ktype)
472  (define (check loc)
473    (define (scanobjlst lst)
474      (foldr (lambda (a b)
475               (or a (kobj-is-type? b ktype)))
476             #f
477             lst))
478    (if (scanobjlst (kern-get-objects-at loc))
479        loc
480        nil))
481  (search-rect kplace x y w h check))
482
483(define (in-inventory? kchar ktype)
484  ;;(println (kern-type-get-name ktype))
485  (define (hasit? item inv)
486    (cond ((null? inv) #f)
487          ((eqv? item (car (car inv))) #t)
488          (else
489           ;;(println " " (kern-type-get-name (car (car inv))))
490           (hasit? item (cdr inv)))))
491  (hasit? ktype (kern-char-get-inventory kchar)))
492
493(define (num-in-inventory kchar ktype)
494  (define (count-em item inv)
495    ;;;(display "inv: ");;(display inv)(newline)
496    (cond ((null? inv) 0)
497          ((eqv? item (car (car inv))) (cdr (car inv)))
498          (else (count-em item (cdr inv)))))
499  (count-em ktype (kern-char-get-inventory kchar)))
500
501(define (any-in-inventory? kchar lst)
502  (foldr (lambda (v k)
503           (or v
504               (in-inventory? kchar k)))
505         #f
506         lst))
507
508(define (all-in-inventory? kchar lst)
509  (foldr (lambda (v k)
510           (and v
511               (in-inventory? kchar k)))
512         #t
513         lst))
514
515;; Note: I commented out the remove-from-inventory call because the things
516;; should remove themselves (eg, potions do)
517(define (use-item-from-inventory-on-self kchar ktype)
518  ;;(kern-obj-remove-from-inventory kchar ktype 1)
519  ;;;(display "using")(newline)
520  (apply (kern-type-get-gifc ktype) (list 'use ktype kchar))
521  (kern-log-msg (kern-obj-get-name kchar)
522                " uses 1 "
523                (kern-type-get-name ktype))
524  #t)
525
526;;============================================================================
527;; Modulo system procedures -- useful on wrapping maps
528;;============================================================================
529(define (madd a b R) (modulo (+ a b) R))
530(define (msub a b R) (modulo (- a b) R))
531(define (minc a R) (modulo (+ a 1) R))
532(define (mdec a R) (modulo (- a 1) R))
533
534;;----------------------------------------------------------------------------
535;; mdist - find the distance between two numbers in a modulo system. There are
536;; always 2 distances (additive and subtractive). This picks the shortest
537;; distance..
538;;----------------------------------------------------------------------------
539(define (mdist a b R) (min (msub a b R) (msub b a R)))
540
541;; ----------------------------------------------------------------------------
542;; Turn on/off verbose scheme garbage collection. Useful if you think scheme is
543;; gc'ing some of your code behind your back.
544;; ----------------------------------------------------------------------------
545(gc-verbose #t)
546
547(define (profile proc . args)
548  (let ((t (kern-get-ticks))
549        (result (apply proc args)))
550    ;;(display "*** TIME: ");;(display (- (kern-get-ticks) t)) ;;(display " ms")
551    (newline)
552    result))
553
554;; ----------------------------------------------------------------------------
555;; find-object-types-at -- return a list of objects of the given type which can
556;; be found at the given location
557;; ----------------------------------------------------------------------------
558(define (find-object-types-at loc ktype)
559  (filter (lambda (a) (kobj-is-type? a ktype))
560          (kern-get-objects-at loc)))
561
562;; ----------------------------------------------------------------------------
563;; is-object-type-at? -- check for an object (by type) at a location
564;; ----------------------------------------------------------------------------
565(define (is-object-type-at? loc ktype)
566  (foldr (lambda (a b) (or a (kobj-is-type? b ktype)))
567         #f
568         (kern-get-objects-at loc)))
569
570;; ----------------------------------------------------------------------------
571;; any-object-types-at? -- returns #t iff one or more objects at loc is of one
572;; of the given types
573;; ----------------------------------------------------------------------------
574(define (any-object-types-at? loc ktypes)
575  (foldr (lambda (a b) (or a (is-object-type-at? loc b)))
576         #f
577         ktypes))
578
579;; is-player-party-member? -- #t iff kchar is in player party
580(define (is-player-party-member? kchar)
581  (in-list? kchar
582            (kern-party-get-members (kern-get-player))))
583
584;; ----------------------------------------------------------------------------
585;; kobj-get -- remove an object from the map and put it into another object
586;; ----------------------------------------------------------------------------
587(define (kobj-get kobj kchar)
588  (if (kern-obj-put-into kobj kchar)
589      (begin
590        (if (not (is-player-party-member? kchar))
591            (kern-log-msg (kern-obj-get-name kchar)
592                          " gets "
593                          (kern-obj-get-name kobj)))
594        (kern-obj-inc-ref kobj)
595        (kern-obj-remove kobj)
596        (kern-obj-dec-ref kobj)
597        (kern-obj-dec-ap kchar (/ norm 5))
598        (kern-map-repaint))))
599
600;; ----------------------------------------------------------------------------
601;; kobj-get-at -- get an object of a specific type from the location
602;; ----------------------------------------------------------------------------
603(define (kobj-get-at kchar loc ktype)
604  (let ((objs (find-object-types-at loc ktype)))
605    (if (notnull? objs)
606        (kobj-get (car objs) kchar))))
607
608;; ----------------------------------------------------------------------------
609;; place-random-corner -- randomly select a corner and return it as a location
610;; ----------------------------------------------------------------------------
611(define (place-random-corner kplace)
612  (case (kern-dice-roll "1d4")
613    ((1) (mk-loc kplace  0  0))
614    ((2) (mk-loc kplace  0  (- (kern-place-get-width kplace 1))))
615    ((3) (mk-loc kplace  (- (kern-place-get-height kplace) 1) 0))
616    ((4) (mk-loc kplace
617                 (- (kern-place-get-height kplace) 1)
618                 (- (kern-place-get-width kplace) 1)))))
619
620;; ----------------------------------------------------------------------------
621;; do-or-goto -- if the location is adjacent then the proc, otherwise have
622;; the char pathfind to it
623;; ----------------------------------------------------------------------------
624(define (do-or-goto kchar coords proc)
625  ;;;(display "do-or-goto")(newline)
626  (if (or (loc-4-adjacent? (kern-obj-get-location kchar) coords)
627          (eq? coords (kern-obj-get-location kchar)))
628      (proc kchar coords)
629      (pathfind kchar coords)))
630
631;; ----------------------------------------------------------------------------
632;; evade -- simple alg for evading melee foes
633;;
634;; Simple approach: each foe's coordinates forms a vector to the char's
635;; coordinates. Take the sum of these coordinates to get the evasion
636;; vector. "Normalize" the vector components by rounding them to the nearest 0,
637;; 1 or -1. This is the dx/dy to move. If the terrain is impassable in the
638;; preferred direction then try zeroing out the non-zero components and
639;; moving. This will give two backup vectors to try.
640;;
641;; ADDENDUM: I don't want to allow diagonal evasion, so the "normalized" vector
642;; must be skipped if it's a diagonal, thus causing us to try the fallbak
643;; vector(s).
644;;
645;; Now allowing diagonals, since that factor has changed
646;;
647;; TODO: probably shouldnt flee over dangerous terrains
648;;
649;; ----------------------------------------------------------------------------
650(define (evade kchar foes)
651  (let* ((tloc (kern-obj-get-location kchar))
652         (v (loc-canonical
653				(foldr
654					(lambda (accum thisfoe)
655						(loc-sum accum
656							(loc-diff (kern-obj-get-location thisfoe) tloc)
657						))
658					(mk-loc (loc-place tloc) 0 0)
659					foes)
660				))
661			)
662		(define (move dx dy)
663			(if (kern-place-is-passable
664					(loc-sum
665						(mk-loc (loc-place tloc) dx dy)
666						tloc)
667					kchar)
668				(kern-obj-move kchar dx dy)
669				#f))
670		(define (evade-on-normal)
671				(move (loc-x v) (loc-y v)))
672
673		(or (evade-on-normal)
674			(and (not (eq? 0 (loc-y v)))
675				(move (loc-x v) 0))
676			(and (not (eq? 0 (loc-x v)))
677				(move 0 (loc-y v))))
678		))
679
680
681;; ----------------------------------------------------------------------------
682;; closest-obj -- given an origin and a list of objects, return the object from
683;; the list that is closest (in city-block distance) to the origin
684;; ----------------------------------------------------------------------------
685(define (closest-obj origin lst)
686  (if (null? lst) nil
687      (foldr (lambda (a b)
688               (if (loc-closer? (kern-obj-get-location a)
689                                (kern-obj-get-location b)
690                                origin)
691                   a
692                   b))
693               (car lst)
694               (cdr lst))))
695
696;; ----------------------------------------------------------------------------
697;; blit-maps -- blit multiple maps to a single target map
698;; ---------------------------------------------------------------------------
699(define (blit-maps kmap . blits)
700  (define (blit dstx dsty srcmap srcx srcy w h)
701    (kern-blit-map kmap dstx dsty srcmap srcx srcy w h))
702  (foldr (lambda (a b) (apply blit b))
703         kmap
704         blits))
705
706(define (fill-terrain-prob kter kplace ox oy ow oh prob)
707  (define (fill x y w h)
708    (if (> h 0)
709        (if (> w 0)
710            (begin
711              (if (<= (modulo (random-next)
712                              100)
713                      prob)
714                  (kern-place-set-terrain (list kplace x y) kter))
715              (fill (+ x 1) y (- w 1) h))
716            (fill ox (+ y 1) ow (- h 1)))))
717  (fill ox oy ow oh))
718
719(define (fill-terrain kter kplace ox oy ow oh)
720  (fill-terrain-prob kter kplace ox oy ow oh 100))
721
722;;============================================================================
723;; rect
724;;============================================================================
725(define (mk-rect x y w h) (list x y w h))
726(define (rect-x r) (car r))
727(define (rect-y r) (cadr r))
728(define (rect-w r) (caddr r))
729(define (rect-h r) (cadddr r))
730(define (rect-ex r) (+ (rect-x r) (rect-w r)))
731(define (rect-ey r) (+ (rect-y r) (rect-h r)))
732(define (x-in-rect? x r)
733  (and (>= x (rect-x r))
734       (< x (rect-ex r))))
735(define (y-in-rect? y r)
736  (and (>= y (rect-y r))
737       (< y (rect-ey r))))
738(define (xy-in-rect? x y r)
739  (and (x-in-rect? x r)
740       (y-in-rect? y r)))
741(define (rect-in-rect? a b)
742  (and (xy-in-rect? (rect-x a) (rect-y a) b)
743       (xy-in-rect? (rect-ex a) (rect-ey a) b)))
744(define (loc-in-rect? loc rect)
745  (xy-in-rect? (loc-x loc)
746               (loc-y loc)
747               rect))
748(define (rect-random rect)
749  (list (+ (rect-x rect) (modulo (random-next) (rect-w rect)))
750        (+ (rect-y rect) (modulo (random-next) (rect-h rect)))))
751
752;;;; (define original-load load)
753;;;; (define (load file)
754;;;;    (display (kern-get-ticks))
755;;;;    (display " loading ")
756;;;;    (display file)(newline)
757;;;;    (original-load file))
758
759(define (put obj x y) (list obj x y))
760
761;; lookup-spell-by-handler -- find a spell in the list of all spells
762(define (lookup-spell handler)
763  (define (search-spells slist)
764    (if (null? slist)
765        nil
766        (let ((spell (car slist)))
767          (if (eqv? (spell-handler spell)
768                    handler)
769              spell
770              (search-spells (cdr slist))))))
771  (search-spells spells))
772
773;; generic lookup
774(define (lookup this? slist)
775  (if (null? slist)
776      nil
777      (if (this? (car slist))
778          (car slist)
779          (lookup this? (cdr slist)))))
780
781;; can-cast -- check if a char has enough mana to cast a spell
782(define (can-cast? kchar handler)
783  (let ((spell (lookup-spell handler)))
784    (if (null? spell)
785        #f
786        (and (>= (kern-char-get-mana kchar)
787                 (spell-cost spell))
788             (>= (kern-char-get-level kchar)
789                 (spell-level spell))))))
790
791;; cast0 - cast a spell which requires no args if possible, assumes kchar has
792;; enough mana
793(define (cast0 kchar spell)
794  (apply (spell-handler spell) (list kchar))
795  (kern-char-dec-mana kchar (spell-cost spell))
796  (kern-obj-dec-ap kchar (spell-ap spell))
797  (kern-log-msg (kern-obj-get-name kchar)
798                " casts "
799                (spell-name spell)))
800
801;; cast1 - cast a spell which requires one arg if possible, assumes kchar has
802;; enough mana
803(define (cast1 kchar spell ktarg)
804  ;;;(display "cast1: ");;(display spell)(newline)
805  (apply (spell-handler spell) (list kchar ktarg))
806  (kern-char-dec-mana kchar (spell-cost spell))
807  (kern-obj-dec-ap kchar (spell-ap spell))
808  (kern-log-msg (kern-obj-get-name kchar)
809                " casts "
810                (spell-name spell)
811                " on "
812                (kern-obj-get-name ktarg)
813                "!"))
814
815;; ----------------------------------------------------------------------------
816;; terrain-ok-for-field? -- check if the terrain at a given location will allow
817;; a field to be dropped on it. Terrains with passability class equivalent to
818;; Grass, trees and forest are ok, everything else is not.
819;; ----------------------------------------------------------------------------
820(define (terrain-ok-for-field? loc)
821  (let ((kter (kern-place-get-terrain loc)))
822    (println "kter: " kter)
823    (if (null? kter)
824        #f
825        (let ((pclass (kern-terrain-get-pclass kter)))
826          (foldr (lambda (a b) (or a (= pclass b)))
827                 #f
828                 (list pclass-grass pclass-trees pclass-forest))))))
829
830(define (get-8-neighboring-tiles loc)
831  (let ((kplace (loc-place loc))
832        (x (loc-x loc))
833        (y (loc-y loc)))
834    (filter kern-is-valid-location?
835            (map (lambda (offset) (mk-loc kplace
836                                          (+ (car offset) x)
837                                          (+ (cdr offset) y)))
838                 (list (cons -1 -1)
839                       (cons  0 -1)
840                       (cons  1 -1)
841                       (cons -1  0)
842                       (cons  1  0)
843                       (cons -1  1)
844                       (cons  0  1)
845                       (cons  1  1))))))
846
847(define (get-4-neighboring-tiles loc)
848  (let ((kplace (loc-place loc))
849        (x (loc-x loc))
850        (y (loc-y loc)))
851    (filter kern-is-valid-location?
852            (map (lambda (offset) (mk-loc kplace
853                                          (+ (car offset) x)
854                                          (+ (cdr offset) y)))
855                 (list (cons  0 -1)
856                       (cons -1  0)
857                       (cons  1  0)
858                       (cons  0  1)
859                       )))))
860
861(define (shake-map dur)
862  (if (> dur 0)
863      (begin
864        (kern-map-set-jitter #t)
865        (kern-map-repaint)
866        (shake-map (- dur 1)))
867      (begin
868        (kern-map-set-jitter #f)
869        (kern-map-repaint))))
870
871(define (random-vdir)
872  (random-select (list (cons -1 0)
873                       (cons 1 0)
874                       (cons 0 -1)
875                       (cons 0 1))))
876
877(define (random-neighbor-loc kobj)
878  (let ((vdir (random-vdir)))
879    (loc-sum (kern-obj-get-location kobj)
880             (mk-loc nil (car vdir) (cdr vdir)))))
881
882(define (push kobj dx dy dist)
883  (let* ((loc (loc-sum (kern-obj-get-location kobj)
884                       (mk-loc nil dx dy))))
885    (if (and (kern-place-is-passable loc kobj)
886             (not (occupied? loc)))
887        (begin
888          (kern-obj-relocate kobj loc nil)
889          #t)
890        #f)))
891
892(define (stagger kchar)
893  (let ((vdir (random-vdir)))
894    (push kchar (car vdir) (cdr vdir) 1)))
895
896(define (end-turn kobj)(kern-obj-set-ap kobj 0))
897
898(define (add-effect-multiple kobj keff fgob q)
899  (if (> q 0)
900      (begin
901        (kern-obj-add-effect kobj keff fgob)
902        (add-effect-multiple kobj keff fgob (- q 1)))))
903
904;; time procs for use with return value from kern-get-time:
905(define (time-mk yr mo we da hr mi)
906  (list yr mo we da hr mi))
907(define (time-year time) (list-ref time 0))
908(define (time-month time) (list-ref time 1))
909(define (time-week time) (list-ref time 2))
910(define (time-day time) (list-ref time 3))
911(define (time-hour time) (list-ref time 4))
912(define (time-minute time) (list-ref time 5))
913
914;; wants-healing? -- check if a char is <= 50% max hp
915(define (wants-healing? kchar)
916  (<= (kern-char-get-hp kchar)
917      (/ (kern-char-get-max-hp kchar) 2)))
918
919;; wants-healing? -- check if a char is <= 25% max hp
920(define (wants-great-healing? kchar)
921  (<= (kern-char-get-hp kchar)
922      (/ (kern-char-get-max-hp kchar) 4)))
923
924;; wants-mana? -- check if a char is <= 50% max mana
925(define (wants-mana? kchar)
926  (<= (kern-char-get-mana kchar)
927      (/ (kern-char-get-max-mana kchar) 2)))
928
929;; has-mana-potion? -- check if a char has a mana potion in inventory
930(define (has-mana-potion? kchar)
931  (in-inventory? kchar t_mana_potion))
932
933;; drink-mana-potion -- use a mana potion from inventory
934(define (drink-mana-potion kchar)
935  (use-item-from-inventory-on-self kchar t_mana_potion))
936
937;; has-heal-potion? -- check if a char has a heal potion in inventory
938(define (has-heal-potion? kchar)
939  (in-inventory? kchar t_heal_potion))
940
941;; drink-heal-potion -- use a heal potion from inventory
942(define (drink-heal-potion kchar)
943  (use-item-from-inventory-on-self kchar t_heal_potion))
944
945(define (set-max-hp kchar)
946  (kern-char-set-hp kchar
947                    (kern-char-get-max-hp kchar)))
948
949;; max-hp -- calc max hp given species, level and occ
950(define (max-hp sp occ lvl mod mult)
951  (+ (kern-species-get-hp-mod sp)
952     (if (null? occ) 0 (kern-occ-get-hp-mod occ))
953     mod
954     (* lvl
955        (+ (kern-species-get-hp-mult sp)
956           (if (null? occ) 0 (kern-occ-get-hp-mult occ))
957           mult))))
958
959;; max-mp -- calc max mp given species, level and occ
960(define (max-mp sp occ lvl mod mult)
961  (+ (kern-species-get-mp-mod sp)
962     (if (null? occ) 0 (kern-occ-get-mp-mod occ))
963     mod
964     (* lvl
965        (+ (kern-species-get-mp-mult sp)
966           (if (null? occ) 0 (kern-occ-get-mp-mult occ))
967           mult))))
968
969
970;; set-level -- set character to level and max out hp and mana (intended for
971;; new npc creation)
972(define (set-level kchar lvl)
973  (kern-char-set-level kchar lvl))
974
975;; use-potion? -- use potion on self if desired and available
976(define (use-potion? kchar)
977  (or (and (wants-healing? kchar)
978           (has-heal-potion? kchar)
979           (drink-heal-potion kchar))
980      (and (wants-mana? kchar)
981           (has-mana-potion? kchar)
982           (drink-mana-potion kchar))))
983
984(define (use-heal-spell-on-self? kchar)
985  ;;;;(display "use-heal-spell-on-self?")(newline)
986  (and (wants-healing? kchar)
987       (can-use-ability? heal-ability kchar)
988       (use-ability heal-ability kchar kchar)))
989
990(define (use-great-heal-spell-on-self? kchar)
991  ;;;;(display "use-great-heal-spell-on-self?")(newline)
992  (and (wants-great-healing? kchar)
993       (can-use-ability? great-heal-ability kchar)
994       (use-ability great-heal-ability kchar kchar)))
995
996(define (use-spell-on-self? kchar)
997  ;;;;(display "use-spell-on-self?")(newline)
998  (or (use-great-heal-spell-on-self? kchar)
999      (use-heal-spell-on-self? kchar)))
1000
1001(define (avoid-melee? kchar)
1002  ;;;;(display "avoid-melee? kchar")(newline)
1003  (let ((nearby-foes (get-hostiles-in-range kchar 1)))
1004    (if (null? nearby-foes)
1005        #f
1006        (evade kchar nearby-foes))))
1007
1008(define (dump-char kchar)
1009  (if (null? kchar)
1010      (println "nil")
1011      (begin
1012        (println "npc: " (kern-obj-get-name kchar)
1013                 "[" (kern-char-get-level kchar) "]"
1014                 " hp=" (kern-char-get-hp kchar) "/" (kern-char-get-max-hp kchar)
1015                 " mp=" (kern-char-get-mana kchar) "/" (kern-char-get-max-mana kchar)
1016                 " @[" (loc-x (kern-obj-get-location kchar))
1017                 "," (loc-y (kern-obj-get-location kchar)) "]"
1018                 ))))
1019
1020
1021(define (get-nearest-patient kchar)
1022  (let ((kloc (kern-obj-get-location kchar)))
1023    (foldr (lambda (kpatient ktarg)
1024             ;;(display "  checking ")(dump-char ktarg)
1025             (if (and (wants-healing? ktarg)
1026                      (or (null? kpatient)
1027                          (< (kern-get-distance kloc
1028                                                (kern-obj-get-location ktarg))
1029                             (kern-get-distance kloc
1030                                                (kern-obj-get-location kpatient)))))
1031                 ktarg
1032                 kpatient))
1033           nil
1034           (all-visible-allies kchar))))
1035
1036;; This is for medics. A patient is an ally that needs healing. If a patient is
1037;; less than 2 tiles away then do nothing. If a patient is more than 2 tiles
1038;; away then pathfind toward it.
1039(define (move-toward-patient? kchar)
1040  (let ((patient (get-nearest-patient kchar)))
1041    (if (null? patient)
1042        #f
1043        (begin
1044          ;;(display "selected ")(dump-char patient)
1045          (if (in-range? (kern-obj-get-location kchar)
1046                         2
1047                         patient)
1048              #f
1049              (pathfind kchar (kern-obj-get-location patient)))))))
1050
1051(define (prompt-for-key)
1052  (kern-log-msg "<Hit any key to continue>")
1053  (kern-ui-waitkey))
1054
1055(define (ship-at? loc) (not (null? (kern-place-get-vehicle loc))))
1056
1057(define (take-player-gold q)
1058  (kern-player-set-gold (- (kern-player-get-gold) q)))
1059
1060(define (give-player-gold q)
1061  (kern-player-set-gold (+ (kern-player-get-gold) q)))
1062
1063(define (player-has-gold? q)
1064  (>= (kern-player-get-gold) q))
1065
1066;; services -- used with trade-service below
1067(define (svc-mk name price proc) (list name price proc))
1068(define (svc-name svc) (car svc))
1069(define (svc-price svc) (cadr svc))
1070(define (svc-proc svc) (caddr svc))
1071
1072;; some standard healer services
1073(define (heal-service kchar knpc)
1074  ;;(display "heal-service")(newline)
1075  (let ((hp (- (kern-char-get-max-hp kchar)
1076               (kern-char-get-hp kchar))))
1077    (if (> hp 0)
1078        (begin
1079          (say knpc "VAS MANI! Be healed, "
1080               (kern-obj-get-name kchar))
1081          (kern-map-flash hp)
1082          (kern-obj-heal kchar hp)
1083          #t)
1084        (begin
1085          (say knpc (kern-obj-get-name kchar)
1086               " is not wounded!")
1087          (prompt-for-key)
1088          #f))))
1089
1090(define (cure-service kchar knpc)
1091  ;;(display "cure-service")(newline)
1092  (if (is-poisoned? kchar)
1093      (begin
1094        (say knpc "AN NOX! You are cured, "
1095             (kern-obj-get-name kchar))
1096        (kern-map-flash 1)
1097        (kern-obj-remove-effect kchar ef_poison))
1098      (begin
1099        (say knpc (kern-obj-get-name kchar)
1100             " is not poisoned!")
1101        (prompt-for-key)
1102        #f)))
1103
1104(define (resurrect-service kchar knpc)
1105  ;;(display "resurrect-service")(newline)
1106  (if (is-dead? kchar)
1107      (begin
1108       (say knpc "IN MANI CORP! Arise, "
1109            (kern-obj-get-name kchar))
1110       (kern-map-flash 500)
1111       (resurrect kchar)
1112       (kern-obj-heal kchar 10))
1113      (begin
1114        (say knpc (kern-obj-get-name kchar)
1115             " is not dead!")
1116        (prompt-for-key)
1117        #f)))
1118
1119;; trade-services -- take a list of services which operate on a party member
1120;; and prompt the player, check prices, and otherwise handle the transaction
1121(define (trade-services knpc kpc services)
1122
1123  (define (list-services)
1124    (map (lambda (svc)
1125           (string-append (svc-name svc)
1126                          "..."
1127                          (number->string (svc-price svc))
1128                          " gold"))
1129         services))
1130
1131  ;; line-name - convert a string like "Heal...30 gold" to "Heal"
1132  (define (line-name line)
1133    (define (extract l)
1134      (if (null? l)
1135          nil
1136          (if (char=? (car l) #\.)
1137              nil
1138              (cons (car l) (extract (cdr l))))))
1139    (if (null? line)
1140        nil
1141        (list->string (extract (string->list line)))))
1142
1143  (define (lookup-svc line)
1144    (let ((name (line-name line)))
1145      (if (null? name)
1146          nil
1147          (lookup (lambda (svc)
1148                    (string=? name
1149                              (svc-name svc)))
1150                  services))))
1151
1152  (define (choose-svc)
1153    (lookup-svc (apply kern-ui-select-from-list (list-services))))
1154
1155  (let ((svc (choose-svc)))
1156
1157    (define (can-pay?)
1158      (if (player-has-gold? (svc-price svc))
1159          #t
1160          (begin
1161            (say knpc "You don't have enough gold!")
1162            #f)))
1163
1164    (define (apply-svc)
1165      (let ((kchar (kern-ui-select-party-member)))
1166        (if (null? kchar)
1167            #f
1168            (if (apply (svc-proc svc) (list kchar knpc))
1169                (begin
1170                  (take-player-gold (svc-price svc))
1171                  #t)))))
1172
1173    (and (not (null? svc))
1174         (can-pay?)
1175         (apply-svc))))
1176
1177;; player-out-of-sight -- no LOS between kobj and any party member
1178(define (player-out-of-sight? kobj)
1179  (define (can-see? members)
1180    (if (null? members)
1181        #f
1182        (or (kern-in-los? (kern-obj-get-location (car members))
1183                          (kern-obj-get-location kobj))
1184            (can-see? (cdr members)))))
1185  (not (can-see? (kern-party-get-members (kern-get-player)))))
1186
1187(define (improve-relations kb1 kb2)
1188  (kern-dtable-inc (kern-being-get-current-faction kb1)
1189                   (kern-being-get-current-faction kb2)))
1190
1191(define (harm-relations kb1 kb2)
1192  (kern-dtable-dec (kern-being-get-current-faction kb1)
1193                   (kern-being-get-current-faction kb2)))
1194
1195(define (make-enemies kb1 kb2)
1196  (harm-relations kb1 kb2)
1197  (harm-relations kb1 kb2)
1198  (harm-relations kb1 kb2)
1199  (harm-relations kb1 kb2)
1200  )
1201
1202(define (make-allies kb1 kb2)
1203  (improve-relations kb1 kb2)
1204  (improve-relations kb1 kb2)
1205  (improve-relations kb1 kb2)
1206  (improve-relations kb1 kb2)
1207  )
1208
1209(define (is-bad-terrain-at? loc)
1210  (is-bad-terrain? (kern-place-get-terrain loc)))
1211
1212;; put-random-stuff -- randomly generate locations within the given rectangle
1213;; and, if pred? is satisfied, pass the loc to ctor.
1214(define (put-random-stuff place rect pred? ctor n)
1215  (if (> n 0)
1216      (let ((loc (cons place (rect-random rect))))
1217        (if (pred? loc)
1218            (begin
1219              (ctor loc)
1220              (put-random-stuff place rect pred? ctor (- n 1)))
1221            (put-random-stuff place rect pred? ctor n)))))
1222
1223(define (drop-random-corpses kplace n)
1224  (put-random-stuff kplace
1225                    (mk-rect 0 0
1226                             (kern-place-get-width kplace)
1227                             (kern-place-get-height kplace))
1228                    (lambda (loc)
1229                      (eqv? (kern-place-get-terrain loc)
1230                            t_grass))
1231                    (lambda (loc)
1232                      (kern-obj-put-at (mk-corpse-with-loot)
1233                                       loc))
1234                    n))
1235
1236(define (webify kplace x y w h)
1237  (define (drop-web x loc)
1238    (let ((kter (kern-place-get-terrain loc)))
1239      (if (or (eqv? kter t_grass)
1240              (eqv? kter t_boulder))
1241          (kern-obj-put-at (kern-mk-obj F_web_perm 1)
1242                           loc))))
1243  (foldr-rect kplace x y w h drop-web nil))
1244
1245;; Fill the rectangle with objects of the given type. If pred? is not null use
1246;; it to filter out unsuitable locations.
1247(define (rect-fill-with-npc kplace x y w h npct pred?)
1248  (define (drop-obj x loc)
1249    (if (or (null? pred?)
1250            (pred? loc))
1251        (kern-obj-put-at (kern-mk-obj ktype 1)
1252                         loc)))
1253(foldr-rect kplace x y w h drop-obj #f))
1254
1255;; on-entry-to-dungeon-room -- generic place on-enty procedure for dungeon
1256;; rooms. When the player enters (or re-enters) a dungeon this looks for a
1257;; monster manager object and triggers it.
1258(define (on-entry-to-dungeon-room kplace kplayer)
1259  ;;(println "on-entry-to-dungeon-room")
1260  (map (lambda (kmm)
1261         ;;(println " signal")
1262         (signal-kobj kmm 'on kmm nil))
1263       (kplace-get-objects-of-type kplace t_monman))
1264  )
1265
1266;; trigger anything with an 'on-entry' ifc
1267(define (on-entry-trigger-all kplace kplayer)
1268  (map (lambda (kobj)
1269         (signal-kobj kobj 'on-entry kobj))
1270       (kern-place-get-objects kplace))
1271  )
1272
1273
1274;; mk-dungeon-room -- make a 19x19 dungeon room (simplified form of
1275;; kern-mk-place)
1276(define (mk-dungeon-room tag name terrain . objects)
1277  (kern-mk-place tag
1278                 name
1279                 nil     ; sprite
1280                 (kern-mk-map nil 19 19 pal_expanded terrain)
1281                 #f      ; wraps
1282                 #t      ; underground
1283                 #f      ; large-scale (wilderness)
1284                 #f      ; tmp combat place
1285                 nil     ; subplaces
1286                 nil     ; neighbors
1287
1288                 ;; objects -- automatically add a monster manager
1289                 (cons (put (mk-monman) 0 0)
1290                       objects)
1291                 (list 'on-entry-to-dungeon-room) ; hooks
1292                 nil     ; edge entrances
1293                 ))
1294
1295(define (mk-combat-map tag . terrain)
1296  (kern-mk-map tag 19 19 pal_expanded terrain))
1297
1298(define (mk-tower tag name terrain entrances . objects)
1299  (kern-mk-place tag
1300                 name
1301                 s_keep     ; sprite
1302                 (kern-mk-map nil 19 19 pal_expanded terrain)
1303                 #f      ; wraps
1304                 #f      ; underground
1305                 #f      ; large-scale (wilderness)
1306                 #f      ; tmp combat place
1307                 nil     ; subplaces
1308                 nil     ; neighbors
1309
1310                 ;; objects -- automatically add a monster manager
1311                 (cons (put (mk-monman) 0 0)
1312                       objects)
1313                 (list 'on-entry-to-dungeon-room) ; hooks
1314                 entrances     ; edge entrances
1315                 ))
1316
1317;; Just like mk-tower but make the sprite configurable
1318(define (mk-19x19-town tag name sprite terrain entrances . objects)
1319  (kern-mk-place tag
1320                 name
1321                 sprite
1322                 (kern-mk-map nil 19 19 pal_expanded terrain)
1323                 #f      ; wraps
1324                 #f      ; underground
1325                 #f      ; large-scale (wilderness)
1326                 #f      ; tmp combat place
1327                 nil     ; subplaces
1328                 nil     ; neighbors
1329                 ;; objects -- automatically add a monster manager
1330                 (cons (put (mk-monman) 0 0) objects)
1331                 (list 'on-entry-to-dungeon-room 'on-entry-trigger-all) ; hooks
1332                 entrances     ; edge entrances
1333                 ))
1334
1335
1336;; mk-dungeon-level -- given a 2d list of rooms, connect them up as neighbors
1337(define (mk-dungeon-level . rooms)
1338  (define (bind-dir r1 r2 dir)
1339    (if (and (not (null? r1))
1340             (not (null? r2)))
1341        (kern-place-set-neighbor dir r1 r2)))
1342  (define (bind-row top bot)
1343    (if (not (null? top))
1344        (begin
1345          (if (not (null? (cdr top)))
1346              (bind-dir (car top) (cadr top) east))
1347          (if (null? bot)
1348              (bind-row (cdr top) nil)
1349              (begin
1350                (bind-dir (car top) (car bot) south)
1351                (if (not (null? (cdr bot)))
1352                    (bind-dir (car top) (cadr bot) southeast))
1353                (if (not (null? (cdr top)))
1354                    (bind-dir (cadr top) (car bot) southwest))
1355                (bind-row (cdr top) (cdr bot)))))))
1356  (define (bind-rooms rooms)
1357    (if (not (null? rooms))
1358        (begin
1359          (bind-row (car rooms)
1360                    (if (null? (cdr rooms))
1361                        nil
1362                        (cadr rooms)))
1363          (bind-rooms (cdr rooms)))))
1364  (bind-rooms rooms))
1365
1366
1367(define (println . args)
1368  (map display args)
1369  (newline))
1370
1371
1372(define (is-bad-field-at? kchar loc)
1373  (define (is-bad-field? val ktype)
1374    (or val
1375        (and (is-field? ktype)
1376             (not (is-immune-to-field? kchar ktype)))))
1377  (foldr is-bad-field?
1378         #f
1379         (kern-get-objects-at loc)))
1380
1381(define (is-bad-loc? kchar loc)
1382  (or
1383   (is-bad-terrain-at? loc)
1384   (is-bad-field-at? kchar loc)
1385   ))
1386
1387(define (is-good-loc? kchar loc)
1388  ;;(println "is-good-loc?")
1389  (and (passable? loc kchar)
1390       (not (occupied? loc))
1391       (not (is-bad-loc? kchar loc))))
1392
1393(define (get-off-bad-tile? kchar)
1394  ;;(println "get-off-bad-tile")
1395
1396  (define (choose-good-tile tiles)
1397    ;;(display "choose-good-tile")(newline)
1398    (if (null? tiles)
1399        nil
1400        (if (is-good-loc? kchar (car tiles))
1401            (car tiles)
1402            (choose-good-tile (cdr tiles)))))
1403
1404  (define (move-to-good-tile)
1405    ;;(display "move-to-good-tile")(newline)
1406    (let* ((curloc (kern-obj-get-location kchar))
1407           (tiles (get-4-neighboring-tiles curloc))
1408           (newloc (choose-good-tile tiles)))
1409      (if (null? newloc)
1410          #f
1411          (begin
1412            ;;(display "moving")(newline)
1413            (kern-obj-move kchar
1414                           (- (loc-x newloc) (loc-x curloc))
1415                           (- (loc-y newloc) (loc-y curloc)))
1416            #t))))
1417
1418  (and
1419   (is-bad-loc? kchar (kern-obj-get-location kchar))
1420   (move-to-good-tile)))
1421
1422(define (move-away-from-foes? kchar)
1423  ;;(println "move-away-from-foes?")
1424  (evade kchar (all-visible-hostiles kchar)))
1425
1426;; random-loc -- choose a random location
1427(define (random-loc kplace x y w h)
1428  (mk-loc kplace
1429          (+ x (modulo (random-next) w))
1430          (+ y (modulo (random-next) h))))
1431
1432;; random-loc -- choose a random location anywhere in the given place
1433(define (random-loc-in-place kplace)
1434  (random-loc kplace
1435              0
1436              0
1437              (kern-place-get-width kplace)
1438              (kern-place-get-height kplace)))
1439
1440;; random-loc-place-iter -- try up to n times to find a random location which
1441;; satisfies pred?
1442(define (random-loc-place-iter kplace pred? n)
1443  (if (<= n 0)
1444      nil
1445      (let ((loc (random-loc-in-place kplace)))
1446        (if (pred? loc)
1447            loc
1448            (random-loc-place-iter kplace pred? (- n 1))))))
1449
1450(define (is-floor? loc)
1451  (let ((kter (kern-place-get-terrain loc)))
1452    (or (eqv? kter t_flagstones)
1453        (eqv? kter t_cobblestone))))
1454
1455(define (loc-is-empty? loc)
1456  (null? (kern-get-objects-at loc)))
1457
1458(define (mean-player-party-level)
1459  (let ((members (kern-party-get-members (kern-get-player))))
1460    (if (= 0 (length members))
1461        1
1462        (/ (foldr (lambda (sum kchar)
1463                    ;;(println "level:" (kern-char-get-level kchar))
1464                    (+ sum (kern-char-get-level kchar)))
1465                  0
1466                  members)
1467           (length members)))))
1468
1469(define (calc-level)
1470  (max 1
1471       (+ (mean-player-party-level)
1472          (num-player-party-members)
1473          (kern-dice-roll "1d5-3"))))
1474
1475(define (get-mech-at loc)
1476  (let ((mechs (filter kern-obj-is-mech?
1477                       (kern-get-objects-at loc))))
1478    (if (null? mechs)
1479        nil
1480        (car mechs))))
1481
1482(define (handle-mech-at loc kchar)
1483  (let ((kmech (get-mech-at loc)))
1484    (if (null? kmech)
1485        #f
1486        (signal-kobj kmech 'handle kmech kchar))))
1487
1488(define (get-place kobj)
1489  (loc-place (kern-obj-get-location kobj)))
1490
1491;; xp to reach the given level
1492(define (power base exp)
1493  (if (= 0 exp)
1494      1
1495      (* base (power base (- exp 1)))))
1496
1497(define (lvl-xp lvl)
1498  (power 2 (+ 5 lvl)))
1499
1500(define (random-faction)
1501  (modulo (random-next) faction-num))
1502
1503(define (get-target-loc caster range)
1504  (kern-ui-target (kern-obj-get-location caster)
1505                  range))
1506
1507;;----------------------------------------------------------------------------
1508;; code for opening a moongate, warping in a monster, and re-closing it
1509(define (open-moongate loc)
1510  (let ((kgate (mk-moongate nil)))
1511    (kern-obj-relocate kgate loc nil)
1512    (moongate-animate kgate moongate-stages)
1513    kgate))
1514(define (close-moongate kgate)
1515  (moongate-animate kgate (reverse moongate-stages))
1516  (moongate-destroy kgate))
1517(define (warp-in kchar loc dir faction)
1518  (display "warp-in")(newline)
1519  (kern-char-set-schedule kchar nil)
1520  (kern-obj-inc-ref kchar)
1521  (kern-obj-remove kchar)
1522  (kern-obj-relocate kchar loc nil)
1523  (kern-obj-dec-ref kchar)
1524  (kern-map-repaint)
1525  (kern-sleep 250)
1526  (kern-obj-relocate kchar (loc-offset loc dir) nil)
1527  (kern-being-set-base-faction kchar faction)
1528  (kern-map-repaint))
1529
1530;;-----------------------------------------------------------------------------
1531;; re-mk-composite-sprite -- combine all the sprites into one layered sprite,
1532;; cloning ALL BUT the first sprite. Useful for re-decorating base sprites that
1533;; have already been cloned.
1534(define (re-mk-composite-sprite sprites)
1535  (foldr (lambda (s1 s2) (kern-sprite-append-decoration s1 s2))
1536         (car sprites)
1537         (cdr sprites)))
1538
1539;;-----------------------------------------------------------------------------
1540;; mk-composite-sprite -- combine all the sprites into one composite sprite,
1541;; cloning all the sprites in the list.
1542(define (mk-composite-sprite sprites)
1543  (re-mk-composite-sprite (cons (kern-sprite-clone (car sprites)
1544                                                   nil)
1545                                (cdr sprites))))
1546
1547;   (foldr (lambda (s1 s2) (kern-sprite-append-decoration s1 s2))
1548;          (kern-sprite-clone (car sprites) nil)
1549;          (cdr sprites)))
1550
1551(define (kchar-in-vehicle? kchar)
1552  (let ((kparty (kern-char-get-party kchar)))
1553    (if (null? kparty)
1554        #f
1555        (not (null? (kern-party-get-vehicle kparty))))))
1556
1557;; is-in-darkness? -- #t iff light on this object's tile is less than the
1558;; threshold for "dark"
1559(define (is-in-darkness? kobj)
1560  (< (kern-place-get-light (kern-obj-get-location kobj))
1561     64))
1562
1563;; Convenience wrapper for kern-obj-add-to-inventory
1564(define (give kpc ktype quantity)
1565  (kern-obj-add-to-inventory kpc ktype quantity))
1566
1567;; Convenience wrapper for kern-obj-remove-from-inventory
1568(define (take kobj ktype quantity)
1569  (kern-obj-remove-from-inventory kobj ktype quantity))
1570
1571;; Return #t iff object has at least that many in inventory
1572(define (has? kobj ktype quantity)
1573  (>= (num-in-inventory kobj ktype) quantity))
1574
1575;; Safely if a character is in the player party. char-tag should be the
1576;; character's quoted scheme variable name, for example 'ch_dude.
1577(define (in-player-party? kchar-tag)
1578  (println "in-player-party? " kchar-tag)
1579  (and (defined? kchar-tag)
1580       (let ((kchar (eval kchar-tag)))
1581         (and (is-alive? kchar)
1582              (is-player-party-member? kchar)))))
1583
1584(define (set-wind-north)
1585  (println "set-wind-north")
1586  (kern-set-wind north 10))
1587
1588;; block-teleporting takes a place and a list of strings that looks
1589;; suspiciously like a terrain map, and uses the map to apply blocking
1590;; mechanisms to the place. Every "x#" entry in the map will cause a blocking
1591;; mechanism to be placed on that location. All other entries are ignored. The
1592;; blocking mechanisms prevent spells like blink from letting the player break
1593;; the fiction of a simulated multi-story place.
1594(define (block-teleporting kplace map)
1595  (define (doline y lines)
1596    (define (docol x tokens)
1597      (cond ((null? tokens) nil)
1598            (else
1599             (if (and (char=? #\x (car tokens))
1600                       (char=? #\# (cadr tokens)))
1601                 (begin
1602                   (kern-obj-put-at (mk-blocker) (list kplace x y))
1603                 ))
1604             (docol (+ x 1) (cdddr tokens)))))
1605    (cond ((null? lines) nil)
1606          (else
1607           (docol 0 (string->list (car lines)))
1608           (doline (+ y 1) (cdr lines)))))
1609    (doline 0 map))
1610
1611;; Find the visible object of the given type nearest to the kchar.
1612(define (find-nearest kchar ktype)
1613  (let ((objects (filter (lambda (kobj)
1614                           (and (kobj-is-type? kobj ktype)
1615                                (can-see? kchar kobj)))
1616                         (kern-place-get-objects (loc-place (kern-obj-get-location kchar))))))
1617    (cond ((null? objects) nil)
1618          (else
1619           (nearest-obj kchar objects)))))
1620
1621;; Return an integer describing the sign of x
1622(define (sgn x)
1623  (cond ((> x 0) 1)
1624        ((< x 0) -1)
1625        (else 0)))
1626
1627;; Return a list of (x . y) pairs that constitute a line between two
1628;; points. Uses Bresenhaum's line-drawing algorithm.
1629(define (line x1 y1 x2 y2)
1630  (let* ((dx (- x2 x1))
1631         (dy (- y2 y1))
1632         (adx (abs dx))
1633         (ady (abs dy))
1634         (sdx (sgn dx))
1635         (sdy (sgn dy))
1636         (x (/ ady 2))
1637         (y (/ adx 2))
1638         (px x1)
1639         (py y1))
1640    (define (f1 i)
1641      ;;(println "f1 i=" i " px=" px " py=" py)
1642      (cond ((>= i adx)
1643             nil)
1644            (else
1645             (set! y (+ y ady))
1646             (cond ((>= y adx)
1647                    (set! y (- y adx))
1648                    (set! py (+ py sdy))))
1649             (set! px (+ px sdx))
1650             (cons (cons px py)
1651                   (f1 (+ 1 i))))))
1652    (define (f2 i)
1653      ;;(println "f2 i=" i " px=" px " py=" py)
1654      (cond ((>= i ady)
1655             nil)
1656            (else
1657             (set! x (+ x adx))
1658             (cond ((>= x ady)
1659                    (set! x (- x ady))
1660                    (set! px (+ px sdx))))
1661             (set! py (+ py sdy))
1662             (cons (cons px py)
1663                   (f2 (+ 1 i))))))
1664    (cond ((>= adx ady)
1665           (cons (cons x1 y1) (f1 0)))
1666          (else
1667           (cons (cons x1 y1) (f2 0))))))
1668
1669;; Utility for generating dice from numbers easily
1670;;
1671(define (mkdice dice size)
1672	(let ((numstr (if (number? dice)
1673						(number->string dice)
1674						dice))
1675			(sizestr (if (number? size)
1676						(number->string size)
1677						size)))
1678			(string-append numstr "d" sizestr)))
1679
1680;; output for effects that should only be noted if visible
1681
1682(define (msg-log-visible loc . args)
1683	(if (kern-place-is-visible? loc)
1684		(apply kern-log-msg args)
1685		)
1686	)
1687
1688;; Print dots across the console (similar to the u4 shrine meditation)
1689(define (log-dots n delay)
1690  (define (dots n)
1691    (cond ((> n 0)
1692           (kern-log-continue ".")
1693           (kern-log-flush)
1694           (kern-sleep delay)
1695           (dots (- n 1)))))
1696  (kern-log-begin)
1697  (dots n)
1698  (kern-log-end)
1699  )
1700
1701(define (find-first fn? lst)
1702  (if (null? lst)
1703      nil
1704      (if (fn? (car lst))
1705          (car lst)
1706          (find-first fn? (cdr lst)))))
1707
1708(define (append! lst val)
1709  (cond ((null? lst) nil)
1710        ((null? (cdr lst)) (set-cdr! lst val))
1711        (else (append! (cdr lst) val))))
1712
1713(define (repeat fn n)
1714  (if (> n 0)
1715      (begin
1716        (fn)
1717        (repeat fn (- n 1)))))
1718
1719(define (string-lower str)
1720  (list->string (map char-downcase (string->list str))))
1721
1722(define (!= a b)
1723  (not (= a b)))
1724
1725(define (rect-x r) (car r))
1726(define (rect-y r) (cadr r))
1727(define (rect-w r) (caddr r))
1728(define (rect-h r) (cadddr r))
1729
1730(define (rect-down r v)
1731  (list (rect-x r) (+ v (rect-y r)) (rect-w r) (rect-h r)))
1732
1733(define (rect-crop-down r v)
1734  (list (rect-x r) (+ v (rect-y r)) (rect-w r) (- (rect-h r) v)))
1735
1736(define (rect-offset r x y)
1737  (list (+ x (rect-x r)) (+ y (rect-y r)) (rect-w r) (rect-h r)))
1738
1739(define (rect-crop-offset r x y)
1740  (list (+ x (rect-x r)) (+ y (rect-y r)) (- (rect-w r) x) (- (rect-h r) y)))
1741
1742(define (1- x) (- x 1))
1743(define (1+ x) (+ x 1))
1744
1745;; Standard dc vs 1d20 + bonus, with a perfect roll granting automatic success.
1746(define (check-roll dc bonus)
1747  (let ((roll (kern-dice-roll "1d20")))
1748    (or (= 20 roll)
1749        (> (+ roll bonus) dc))))
1750
1751