/
utils.c
1556 lines (1339 loc) · 45.7 KB
/
utils.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
/**
* @file utils.c
* Various utility functions used within the core mud code.
*
* Part of the core tbaMUD source code distribution, which is a derivative
* of, and continuation of, CircleMUD.
*
* All rights reserved. See license for complete information.
* Copyright (C) 1993, 94 by the Trustees of the Johns Hopkins University
* CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991.
*/
#include "conf.h"
#include "sysdep.h"
#include "structs.h"
#include "utils.h"
#include "db.h"
#include "comm.h"
#include "modify.h"
#include "screen.h"
#include "spells.h"
#include "handler.h"
#include "interpreter.h"
#include "class.h"
/** Aportable random number function.
* @param from The lower bounds of the random number.
* @param to The upper bounds of the random number. */
int rand_number(int from, int to)
{
/* error checking in case people call this incorrectly */
if (from > to) {
int tmp = from;
from = to;
to = tmp;
log("SYSERR: rand_number() should be called with lowest, then highest. (%d, %d), not (%d, %d).", from, to, to, from);
}
/* This should always be of the form: ((float)(to - from + 1) * rand() /
* (float)(RAND_MAX + from) + from); If you are using rand() due to historical
* non-randomness of the lower bits in older implementations. We always use
* circle_random() though, which shouldn't have that problem. Mean and
* standard deviation of both are identical (within the realm of statistical
* identity) if the rand() implementation is non-broken. */
return ((circle_random() % (to - from + 1)) + from);
}
/** Simulates a single dice roll from one to many of a certain sized die.
* @param num The number of dice to roll.
* @param size The number of sides each die has, and hence the number range
* of the die. */
int dice(int num, int size)
{
int sum = 0;
if (size <= 0 || num <= 0)
return (0);
while (num-- > 0)
sum += rand_number(1, size);
return (sum);
}
/** Return the smaller number. Original note: Be wary of sign issues with this.
* @param a The first number.
* @param b The second number. */
int MIN(int a, int b)
{
return (a < b ? a : b);
}
/** Return the larger number. Original note: Be wary of sign issues with this.
* @param a The first number.
* @param b The second number. */
int MAX(int a, int b)
{
return (a > b ? a : b);
}
/** Used to capitalize a string. Will not change any mud specific color codes.
* @param txt The string to capitalize. */
char *CAP(char *txt)
{
char *p = txt;
/* Skip all preceeding color codes and ANSI codes */
while ((*p == '\t' && *(p+1)) || (*p == '\x1B' && *(p+1) == '[')) {
if (*p == '\t') p += 2; /* Skip \t sign and color letter/number */
else {
p += 2; /* Skip the CSI section of the ANSI code */
while (*p && !isalpha(*p)) p++; /* Skip until a 'letter' is found */
if (*p) p++; /* Skip the letter */
}
}
if (*p)
*p = UPPER(*p);
return (txt);
}
#if !defined(HAVE_STRLCPY)
/** A 'strlcpy' function in the same fashion as 'strdup' below. This copies up
* to totalsize - 1 bytes from the source string, placing them and a trailing
* NUL into the destination string. Returns the total length of the string it
* tried to copy, not including the trailing NUL. So a '>= totalsize' test
* says it was truncated. (Note that you may have _expected_ truncation
* because you only wanted a few characters from the source string.) Portable
* function, in case your system does not have strlcpy. */
size_t strlcpy(char *dest, const char *source, size_t totalsize)
{
strncpy(dest, source, totalsize - 1); /* strncpy: OK (we must assume 'totalsize' is correct) */
dest[totalsize - 1] = '\0';
return strlen(source);
}
#endif
#if !defined(HAVE_STRDUP)
/** Create a duplicate of a string function. Portable. */
char *strdup(const char *source)
{
char *new_z;
CREATE(new_z, char, strlen(source) + 1);
return (strcpy(new_z, source)); /* strcpy: OK */
}
#endif
/** Strips "\\r\\n" from just the end of a string. Will not remove internal
* "\\r\\n" values to the string.
* @post Replaces any "\\r\\n" values at the end of the string with null.
* @param txt The writable string to prune. */
void prune_crlf(char *txt)
{
int i = strlen(txt) - 1;
while (txt[i] == '\n' || txt[i] == '\r')
txt[i--] = '\0';
}
#ifndef str_cmp
/** a portable, case-insensitive version of strcmp(). Returns: 0 if equal, > 0
* if arg1 > arg2, or < 0 if arg1 < arg2. Scan until strings are found
* different or we reach the end of both. */
int str_cmp(const char *arg1, const char *arg2)
{
int chk, i;
if (arg1 == NULL || arg2 == NULL) {
log("SYSERR: str_cmp() passed a NULL pointer, %p or %p.", (void *)arg1, (void *)arg2);
return (0);
}
for (i = 0; arg1[i] || arg2[i]; i++)
if ((chk = LOWER(arg1[i]) - LOWER(arg2[i])) != 0)
return (chk); /* not equal */
return (0);
}
#endif
#ifndef strn_cmp
/** a portable, case-insensitive version of strncmp(). Returns: 0 if equal, > 0
* if arg1 > arg2, or < 0 if arg1 < arg2. Scan until strings are found
* different, the end of both, or n is reached. */
int strn_cmp(const char *arg1, const char *arg2, int n)
{
int chk, i;
if (arg1 == NULL || arg2 == NULL) {
log("SYSERR: strn_cmp() passed a NULL pointer, %p or %p.", (void *)arg1, (void *)arg2);
return (0);
}
for (i = 0; (arg1[i] || arg2[i]) && (n > 0); i++, n--)
if ((chk = LOWER(arg1[i]) - LOWER(arg2[i])) != 0)
return (chk); /* not equal */
return (0);
}
#endif
/** New variable argument log() function; logs messages to disk.
* Works the same as the old for previously written code but is very nice
* if new code wishes to implment printf style log messages without the need
* to make prior sprintf calls.
* @param format The message to log. Standard printf formatting and variable
* arguments are allowed.
* @param args The comma delimited, variable substitutions to make in str. */
void basic_mud_vlog(const char *format, va_list args)
{
time_t ct = time(0);
char timestr[21];
int i;
if (logfile == NULL) {
puts("SYSERR: Using log() before stream was initialized!");
return;
}
if (format == NULL)
format = "SYSERR: log() received a NULL format.";
for (i=0;i<21;i++) timestr[i]=0;
strftime(timestr, sizeof(timestr), "%b %d %H:%M:%S %Y", localtime(&ct));
fprintf(logfile, "%-20.20s :: ", timestr);
vfprintf(logfile, format, args);
fputc('\n', logfile);
fflush(logfile);
}
/** Log messages directly to syslog on disk, no display to in game immortals.
* Supports variable string modification arguments, a la printf. Most likely
* any calls to plain old log() have been redirected, via macro, to this
* function.
* @param format The message to log. Standard printf formatting and variable
* arguments are allowed.
* @param ... The comma delimited, variable substitutions to make in str. */
void basic_mud_log(const char *format, ...)
{
va_list args;
va_start(args, format);
basic_mud_vlog(format, args);
va_end(args);
}
/** Essentially the touch command. Create an empty file or update the modified
* time of a file.
* @param path The filepath to "touch." This filepath is relative to the /lib
* directory relative to the root of the mud distribution. */
int touch(const char *path)
{
FILE *fl;
if (!(fl = fopen(path, "a"))) {
log("SYSERR: %s: %s", path, strerror(errno));
return (-1);
} else {
fclose(fl);
return (0);
}
}
/** Log mud messages to a file & to online imm's syslogs.
* @param type The minimum syslog level that needs be set to see this message.
* OFF, BRF, NRM and CMP are the values from lowest to highest. Using mudlog
* with type = OFF should be avoided as every imm will see the message even
* if they have syslog turned off.
* @param level Minimum character level needed to see this message.
* @param file TRUE log this to the syslog file, FALSE do not log this to disk.
* @param str The message to log. Standard printf formatting and variable
* arguments are allowed.
* @param ... The comma delimited, variable substitutions to make in str. */
void mudlog(int type, int level, int file, const char *str, ...)
{
char buf[MAX_STRING_LENGTH];
struct descriptor_data *i;
va_list args;
if (str == NULL)
return; /* eh, oh well. */
if (file) {
va_start(args, str);
basic_mud_vlog(str, args);
va_end(args);
}
if (level < 0)
return;
strcpy(buf, "[ "); /* strcpy: OK */
va_start(args, str);
vsnprintf(buf + 2, sizeof(buf) - 6, str, args);
va_end(args);
strcat(buf, " ]\r\n"); /* strcat: OK */
for (i = descriptor_list; i; i = i->next) {
if (STATE(i) != CON_PLAYING || IS_NPC(i->character)) /* switch */
continue;
if (GET_LEVEL(i->character) < level)
continue;
if (PLR_FLAGGED(i->character, PLR_WRITING))
continue;
if (type > (PRF_FLAGGED(i->character, PRF_LOG1) ? 1 : 0) + (PRF_FLAGGED(i->character, PRF_LOG2) ? 2 : 0))
continue;
send_to_char(i->character, "%s%s%s", CCGRN(i->character, C_NRM), buf, CCNRM(i->character, C_NRM));
}
}
/** Take a bitvector and return a human readable
* description of which bits are set in it.
* @pre The final element in the names array must contain a one character
* string consisting of a single newline character "\\n". Caller of function is
* responsible for creating the memory buffer for the result string.
* @param[in] bitvector The bitvector to test for set bits.
* @param[in] names An array of human readable strings describing each possible
* bit. The final element in this array must be a string made of a single
* newline character (eg "\\n").
* If you don't have a 'const' array for the names param, cast it as such.
* @param[out] result Holds the names of the set bits in bitvector. The bit
* names will be delimited by a single space.
* Caller of sprintbit is responsible for creating the buffer for result.
* Will be set to "NOBITS" if no bits are set in bitvector (ie bitvector = 0).
* @param[in] reslen The length of the available memory in the result buffer.
* Ideally, results will be large enough to hold the description of every bit
* that could possibly be set in bitvector. */
size_t sprintbit(bitvector_t bitvector, const char *names[], char *result, size_t reslen)
{
size_t len = 0;
int nlen;
long nr;
*result = '\0';
for (nr = 0; bitvector && len < reslen; bitvector >>= 1) {
if (IS_SET(bitvector, 1)) {
nlen = snprintf(result + len, reslen - len, "%s ", *names[nr] != '\n' ? names[nr] : "UNDEFINED");
if (len + nlen >= reslen || nlen < 0)
break;
len += nlen;
}
if (*names[nr] != '\n')
nr++;
}
if (!*result)
len = strlcpy(result, "NOBITS ", reslen);
return (len);
}
/** Return the human readable name of a defined type.
* @pre The final element in the names array must contain a one character
* string consisting of a single newline character "\\n". Caller of function is
* responsible for creating the memory buffer for the result string.
* @param[in] type The type number to be translated.
* @param[in] names An array of human readable strings describing each possible
* bit. The final element in this array must be a string made of a single
* newline character (eg "\\n").
* @param[out] result Holds the translated name of the type.
* Caller of sprintbit is responsible for creating the buffer for result.
* Will be set to "UNDEFINED" if the type is greater than the number of names
* available.
* @param[in] reslen The length of the available memory in the result buffer. */
size_t sprinttype(int type, const char *names[], char *result, size_t reslen)
{
int nr = 0;
while (type && *names[nr] != '\n') {
type--;
nr++;
}
return strlcpy(result, *names[nr] != '\n' ? names[nr] : "UNDEFINED", reslen);
}
/** Take a bitarray and return a human readable description of which bits are
* set in it.
* @pre The final element in the names array must contain a one character
* string consisting of a single newline character "\\n". Caller of function is
* responsible for creating the memory buffer for the result string large enough
* to hold all possible bit translations. There is no error checking for
* possible array overflow for result.
* @param[in] bitvector The bitarray in which to test for set bits.
* @param[in] names An array of human readable strings describing each possible
* bit. The final element in this array must be a string made of a single
* newline character (eg "\\n").
* If you don't have a 'const' array for the names param, cast it as such.
* @param[in] maxar The number of 'bytes' in the bitarray. This number will
* usually be pre-defined for the particular bitarray you are using.
* @param[out] result Holds the names of the set bits in bitarray. The bit
* names are delimited by a single space. Ideally, results will be large enough
* to hold the description of every bit that could possibly be set in bitvector.
* Will be set to "NOBITS" if no bits are set in bitarray (ie all bits in the
* bitarray are equal to 0).
*/
void sprintbitarray(int bitvector[], const char *names[], int maxar, char *result)
{
int nr, teller, found = FALSE;
*result = '\0';
for(teller = 0; teller < maxar && !found; teller++)
{
for (nr = 0; nr < 32 && !found; nr++)
{
if (IS_SET_AR(bitvector, (teller*32)+nr))
{
if (*names[(teller*32)+nr] != '\n')
{
if (*names[(teller*32)+nr] != '\0')
{
strcat(result, names[(teller*32)+nr]);
strcat(result, " ");
}
}
else
{
strcat(result, "UNDEFINED ");
}
}
if (*names[(teller*32)+nr] == '\n')
found = TRUE;
}
}
if (!*result)
strcpy(result, "NOBITS ");
}
/** Calculate the REAL time passed between two time invervals.
* @todo Recommend making this function foresightedly useful by calculating
* real months and years, too.
* @param t2 The later time.
* @param t1 The earlier time. */
struct time_info_data *real_time_passed(time_t t2, time_t t1)
{
long secs;
static struct time_info_data now;
secs = t2 - t1;
now.hours = (secs / SECS_PER_REAL_HOUR) % 24; /* 0..23 hours */
secs -= SECS_PER_REAL_HOUR * now.hours;
now.day = (secs / SECS_PER_REAL_DAY); /* 0..34 days */
/* secs -= SECS_PER_REAL_DAY * now.day; - Not used. */
now.month = -1;
now.year = -1;
return (&now);
}
/** Calculate the MUD time passed between two time invervals.
* @param t2 The later time.
* @param t1 The earlier time. */
struct time_info_data *mud_time_passed(time_t t2, time_t t1)
{
long secs;
static struct time_info_data now;
secs = t2 - t1;
now.hours = (secs / SECS_PER_MUD_HOUR) % 24; /* 0..23 hours */
secs -= SECS_PER_MUD_HOUR * now.hours;
now.day = (secs / SECS_PER_MUD_DAY) % 35; /* 0..34 days */
secs -= SECS_PER_MUD_DAY * now.day;
now.month = (secs / SECS_PER_MUD_MONTH) % 17; /* 0..16 months */
secs -= SECS_PER_MUD_MONTH * now.month;
now.year = (secs / SECS_PER_MUD_YEAR); /* 0..XX? years */
return (&now);
}
/** Translate the current mud time to real seconds (in type time_t).
* @param now The current mud time to translate into a real time unit. */
time_t mud_time_to_secs(struct time_info_data *now)
{
time_t when = 0;
when += now->year * SECS_PER_MUD_YEAR;
when += now->month * SECS_PER_MUD_MONTH;
when += now->day * SECS_PER_MUD_DAY;
when += now->hours * SECS_PER_MUD_HOUR;
return (time(NULL) - when);
}
/** Calculate a player's MUD age.
* @todo The minimum starting age of 17 is hardcoded in this function. Recommend
* changing the minimum age to a property (variable) external to this function.
* @param ch A valid player character. */
struct time_info_data *age(struct char_data *ch)
{
static struct time_info_data player_age;
player_age = *mud_time_passed(time(0), ch->player.time.birth);
player_age.year += 17; /* All players start at 17 */
return (&player_age);
}
/** Check if making ch follow victim will create an illegal follow loop. In
* essence, this prevents someone from following a character in a group that
* is already being lead by the character.
* @param ch The character trying to follow.
* @param victim The character being followed. */
bool circle_follow(struct char_data *ch, struct char_data *victim)
{
struct char_data *k;
for (k = victim; k; k = k->master) {
if (k == ch)
return (TRUE);
}
return (FALSE);
}
/** Call on a character (NPC or PC) to stop them from following someone and
* to break any charm affect.
* @todo Make the messages returned from the broken charm affect more
* understandable.
* @pre ch MUST be following someone, else core dump.
* @post The charm affect (AFF_CHARM) will be removed from the character and
* the character will stop following the "master" they were following.
* @param ch The character (NPC or PC) to stop from following.
* */
void stop_follower(struct char_data *ch)
{
struct follow_type *j, *k;
/* Makes sure this function is not called when it shouldn't be called. */
if (ch->master == NULL) {
core_dump();
return;
}
if (AFF_FLAGGED(ch, AFF_CHARM)) {
act("You realize that $N is a jerk!", FALSE, ch, 0, ch->master, TO_CHAR);
act("$n realizes that $N is a jerk!", FALSE, ch, 0, ch->master, TO_NOTVICT);
act("$n hates your guts!", FALSE, ch, 0, ch->master, TO_VICT);
if (affected_by_spell(ch, SPELL_CHARM))
affect_from_char(ch, SPELL_CHARM);
} else {
act("You stop following $N.", FALSE, ch, 0, ch->master, TO_CHAR);
act("$n stops following $N.", TRUE, ch, 0, ch->master, TO_NOTVICT);
if (CAN_SEE(ch->master, ch))
act("$n stops following you.", TRUE, ch, 0, ch->master, TO_VICT);
}
if (ch->master->followers->follower == ch) { /* Head of follower-list? */
k = ch->master->followers;
ch->master->followers = k->next;
free(k);
} else { /* locate follower who is not head of list */
for (k = ch->master->followers; k->next->follower != ch; k = k->next);
j = k->next;
k->next = j->next;
free(j);
}
ch->master = NULL;
REMOVE_BIT_AR(AFF_FLAGS(ch), AFF_CHARM);
}
/** Finds the number of follows that are following, and charmed by, the
* character (PC or NPC).
* @param ch The character to check for charmed followers.
*/
int num_followers_charmed(struct char_data *ch)
{
struct follow_type *lackey;
int total = 0;
for (lackey = ch->followers; lackey; lackey = lackey->next)
if (AFF_FLAGGED(lackey->follower, AFF_CHARM) && lackey->follower->master == ch)
total++;
return (total);
}
/** Called when a character that follows/is followed dies. If the character
* is the leader of a group, it stops everyone in the group from following
* them. Despite the title, this function does not actually perform the kill on
* the character passed in as the argument.
* @param ch The character (NPC or PC) to stop from following.
* */
void die_follower(struct char_data *ch)
{
struct follow_type *j, *k;
if (ch->master)
stop_follower(ch);
for (k = ch->followers; k; k = j) {
j = k->next;
stop_follower(k->follower);
}
}
/** Adds a new follower to a group.
* @todo Maybe make circle_follow an inherent part of this function?
* @pre Make sure to call circle_follow first. ch may also not already
* be following anyone, otherwise core dump.
* @param ch The character to follow.
* @param leader The character to be followed. */
void add_follower(struct char_data *ch, struct char_data *leader)
{
struct follow_type *k;
if (ch->master) {
core_dump();
return;
}
ch->master = leader;
CREATE(k, struct follow_type, 1);
k->follower = ch;
k->next = leader->followers;
leader->followers = k;
act("You now follow $N.", FALSE, ch, 0, leader, TO_CHAR);
if (CAN_SEE(leader, ch))
act("$n starts following you.", TRUE, ch, 0, leader, TO_VICT);
act("$n starts to follow $N.", TRUE, ch, 0, leader, TO_NOTVICT);
}
/** Reads the next non-blank line off of the input stream. Empty lines are
* skipped. Lines which begin with '*' are considered to be comments and are
* skipped.
* @pre Caller must allocate memory for buf.
* @post If a there is a line to be read, the newline character is removed from
* the file line ending and the string is returned. Else a null string is
* returned in buf.
* @param[in] fl The file to be read from.
* @param[out] buf The next non-blank line read from the file. Buffer given must
* be at least READ_SIZE (256) characters large. */
int get_line(FILE *fl, char *buf)
{
char temp[READ_SIZE];
int lines = 0;
int sl;
do {
if (!fgets(temp, READ_SIZE, fl))
return (0);
lines++;
} while (*temp == '*' || *temp == '\n' || *temp == '\r');
/* Last line of file doesn't always have a \n, but it should. */
sl = strlen(temp);
while (sl > 0 && (temp[sl - 1] == '\n' || temp[sl - 1] == '\r'))
temp[--sl] = '\0';
strcpy(buf, temp); /* strcpy: OK, if buf >= READ_SIZE (256) */
return (lines);
}
/** Create the full path, relative to the library path, of the player type
* file to open.
* @todo Make the return type bool.
* @pre Caller is responsible for allocating memory buffer for the created
* file name.
* @post The potential file path to open is created. This function does not
* actually open any file descriptors.
* @param[out] filename Buffer to store the full path of the file to open.
* @param[in] fbufsize The maximum size of filename, and the maximum size
* of the path that can be written to it.
* @param[in] mode What type of files can be created. Currently, recognized
* modes are CRASH_FILE, ETEXT_FILE, SCRIPT_VARS_FILE and PLR_FILE.
* @param[in] orig_name The player name to create the filepath (of type mode)
* for. */
int get_filename(char *filename, size_t fbufsize, int mode, const char *orig_name)
{
const char *prefix, *middle, *suffix;
char name[PATH_MAX], *ptr;
if (orig_name == NULL || *orig_name == '\0' || filename == NULL) {
log("SYSERR: NULL pointer or empty string passed to get_filename(), %p or %p.",
(const void *)orig_name, (void *)filename);
return (0);
}
switch (mode) {
case CRASH_FILE:
prefix = LIB_PLROBJS;
suffix = SUF_OBJS;
break;
case ETEXT_FILE:
prefix = LIB_PLRTEXT;
suffix = SUF_TEXT;
break;
case SCRIPT_VARS_FILE:
prefix = LIB_PLRVARS;
suffix = SUF_MEM;
break;
case PLR_FILE:
prefix = LIB_PLRFILES;
suffix = SUF_PLR;
break;
default:
return (0);
}
strlcpy(name, orig_name, sizeof(name));
for (ptr = name; *ptr; ptr++)
*ptr = LOWER(*ptr);
switch (LOWER(*name)) {
case 'a': case 'b': case 'c': case 'd': case 'e':
middle = "A-E";
break;
case 'f': case 'g': case 'h': case 'i': case 'j':
middle = "F-J";
break;
case 'k': case 'l': case 'm': case 'n': case 'o':
middle = "K-O";
break;
case 'p': case 'q': case 'r': case 's': case 't':
middle = "P-T";
break;
case 'u': case 'v': case 'w': case 'x': case 'y': case 'z':
middle = "U-Z";
break;
default:
middle = "ZZZ";
break;
}
snprintf(filename, fbufsize, "%s%s"SLASH"%s.%s", prefix, middle, name, suffix);
return (1);
}
/** Calculate the number of player characters (PCs) in the room. Any NPC (mob)
* is not returned in the count.
* @param room The room to check for PCs. */
int num_pc_in_room(struct room_data *room)
{
int i = 0;
struct char_data *ch;
for (ch = room->people; ch != NULL; ch = ch->next_in_room)
if (!IS_NPC(ch))
i++;
return (i);
}
/** This function (derived from basic fork() abort() idea by Erwin S Andreasen)
* causes your MUD to dump core (assuming you can) but continue running. The
* core dump will allow post-mortem debugging that is less severe than assert();
* Don't call this directly as core_dump_unix() but as simply 'core_dump()' so
* that it will be excluded from systems not supporting them. You still want to
* call abort() or exit(1) for non-recoverable errors, of course. Wonder if
* flushing streams includes sockets?
* @param who The file in which this call was made.
* @param line The line at which this call was made. */
void core_dump_real(const char *who, int line)
{
log("SYSERR: Assertion failed at %s:%d!", who, line);
#if 1 /* By default, let's not litter. */
#if defined(CIRCLE_UNIX)
/* These would be duplicated otherwise...make very sure. */
fflush(stdout);
fflush(stderr);
fflush(logfile);
/* Everything, just in case, for the systems that support it. */
fflush(NULL);
/* Kill the child so the debugger or script doesn't think the MUD crashed.
* The 'autorun' script would otherwise run it again. */
if (fork() == 0)
abort();
#endif
#endif
}
/** Count the number bytes taken up by color codes in a string that will be
* empty space once the color codes are converted and made non-printable.
* @param string The string in which to check for color codes. */
int count_color_chars(char *string)
{
int i, len;
int num = 0;
if (!string || !*string)
return 0;
len = strlen(string);
for (i = 0; i < len; i++) {
while (string[i] == '\t') {
if (string[i + 1] == '\t')
num++;
else
num += 2;
i += 2;
}
}
return num;
}
/* Not the prettiest thing I've ever written but it does the task which
* is counting all characters in a string which are not part of the
* protocol system. This is with the exception of detailed MXP codes. */
int count_non_protocol_chars(char * str)
{
int count = 0;
char *string = str;
while (*string) {
if (*string == '\r' || *string == '\n') {
string++;
continue;
}
if (*string == '@' || *string == '\t') {
string++;
if (*string != '[' && *string != '<' && *string != '>' && *string != '(' && *string != ')')
string++;
else if (*string == '[') {
while (*string && *string != ']')
string++;
string++;
} else
string++;
continue;
}
count++;
string++;
}
return count;
}
/** Tests to see if a room is dark. Rules (unless overridden by ROOM_DARK):
* Inside and City rooms are always lit. Outside rooms are dark at sunset and
* night.
* @todo Make the return value a bool.
* @param room The real room to test for. */
int room_is_dark(room_rnum room)
{
if (!VALID_ROOM_RNUM(room)) {
log("room_is_dark: Invalid room rnum %d. (0-%d)", room, top_of_world);
return (FALSE);
}
if (world[room].light)
return (FALSE);
if (ROOM_FLAGGED(room, ROOM_DARK))
return (TRUE);
if (SECT(room) == SECT_INSIDE || SECT(room) == SECT_CITY)
return (FALSE);
if (weather_info.sunlight == SUN_SET || weather_info.sunlight == SUN_DARK)
return (TRUE);
return (FALSE);
}
/** Calculates the Levenshtein distance between two strings. Currently used
* by the mud to make suggestions to the player when commands are mistyped.
* This function is most useful when an index of possible choices are available
* and the results of this function are constrained and used to help narrow
* down the possible choices. For more information about Levenshtein distance,
* recommend doing an internet or wikipedia search.
* @param s1 The input string.
* @param s2 The string to be compared to. */
int levenshtein_distance(const char *s1, const char *s2)
{
int **d, i, j;
int s1_len = strlen(s1), s2_len = strlen(s2);
CREATE(d, int *, s1_len + 1);
for (i = 0; i <= s1_len; i++) {
CREATE(d[i], int, s2_len + 1);
d[i][0] = i;
}
for (j = 0; j <= s2_len; j++)
d[0][j] = j;
for (i = 1; i <= s1_len; i++)
for (j = 1; j <= s2_len; j++)
d[i][j] = MIN(d[i - 1][j] + 1, MIN(d[i][j - 1] + 1,
d[i - 1][j - 1] + ((s1[i - 1] == s2[j - 1]) ? 0 : 1)));
i = d[s1_len][s2_len];
for (j = 0; j <= s1_len; j++)
free(d[j]);
free(d);
return i;
}
/** Removes a character from a piece of furniture. Unlike some of the other
* _from_ functions, this does not place the character into NOWHERE.
* @post ch is unattached from the furniture object.
* @param ch The character to remove from the furniture object.
*/
void char_from_furniture(struct char_data *ch)
{
struct obj_data *furniture;
struct char_data *tempch;
if (!SITTING(ch))
return;
if (!(furniture = SITTING(ch))){
log("SYSERR: No furniture for char in char_from_furniture.");
SITTING(ch) = NULL;
NEXT_SITTING(ch) = NULL;
return;
}
if (!(tempch = OBJ_SAT_IN_BY(furniture))){
log("SYSERR: Char from furniture, but no furniture!");
SITTING(ch) = NULL;
NEXT_SITTING(ch) = NULL;
GET_OBJ_VAL(furniture, 1) = 0;
return;
}
if (tempch == ch){
if (!NEXT_SITTING(ch)) {
OBJ_SAT_IN_BY(furniture) = NULL;
} else {
OBJ_SAT_IN_BY(furniture) = NEXT_SITTING(ch);
}
} else {
for (tempch = OBJ_SAT_IN_BY(furniture); tempch; tempch = NEXT_SITTING(tempch)) {
if (NEXT_SITTING(tempch) == ch) {
NEXT_SITTING(tempch) = NEXT_SITTING(ch);
}
}
}
GET_OBJ_VAL(furniture, 1) -= 1;
SITTING(ch) = NULL;
NEXT_SITTING(ch) = NULL;
if (GET_OBJ_VAL(furniture, 1) < 1){
OBJ_SAT_IN_BY(furniture) = NULL;
GET_OBJ_VAL(furniture, 1) = 0;
}
return;
}
/* column_list
The list is output in a fixed format, and only the number of columns can be adjusted
This function will output the list to the player
Vars:
ch - the player
num_cols - the desired number of columns
list - a pointer to a list of strings
list_length - So we can work with lists that don't end with /n
show_nums - when set to TRUE, it will show a number before the list entry.
*/
void column_list(struct char_data *ch, int num_cols, const char **list, int list_length, bool show_nums)
{
size_t max_len = 0, len = 0, temp_len;
int num_per_col, col_width, r, c, i, offset = 0;
char buf[MAX_STRING_LENGTH];
*buf='\0';
/* Work out the longest list item */
for (i=0; i<list_length; i++)
if (max_len < strlen(list[i]))
max_len = strlen(list[i]);
/* auto columns case */
if (num_cols == 0) {
num_cols = (IS_NPC(ch) ? 80 : GET_SCREEN_WIDTH(ch)) / (max_len + (show_nums ? 5 : 1));
}
/* Ensure that the number of columns is in the range 1-10 */
num_cols = MIN(MAX(num_cols,1), 10);
/* Calculate the width of each column */
if (IS_NPC(ch)) col_width = 80 / num_cols;
else col_width = (GET_SCREEN_WIDTH(ch)) / num_cols;
if (show_nums) col_width-=4;
if (col_width < 0 || (size_t)col_width < max_len)
log("Warning: columns too narrow for correct output to %s in simple_column_list (utils.c)", GET_NAME(ch));
/* Calculate how many list items there should be per column */
num_per_col = (list_length / num_cols) + ((list_length % num_cols) ? 1 : 0);
/* Fill 'buf' with the columnised list */
for (r=0; r<num_per_col; r++)
{
for (c=0; c<num_cols; c++)
{
offset = (c*num_per_col)+r;
if (offset < list_length)
{
if (show_nums)