1 |
2 | /*
3 | * $Log: lib.c,v $
4 | * Revision 1.21 2003/09/14 08:25:13 millis
5 | * Fix bug 225
6 | *
7 | * Revision 1.20 2003/08/25 14:39:36 millis
8 | * Fixed bug 220
9 | *
10 | * Revision 1.19 2003/07/23 00:11:43 millis
11 | * Bug 192
12 | *
13 | * Revision 1.18 2003/07/19 14:04:27 millis
14 | * fixed bug in address handling
15 | *
16 | * Revision 1.17 2003/07/17 00:01:29 millis
17 | * Use MailOut to send emails
18 | *
19 | * Revision 1.16 2003/07/15 22:47:06 millis
20 | * Fix Bug 185 (call smail for each email individually)
21 | *
22 | * Revision 1.15 2003/06/11 15:48:52 millis
23 | * Remove the 'unit' text
24 | *
25 | * Revision 1.14 2003/02/18 14:05:28 millis
26 | * Added display of new Cavalry and Artillery units
27 | *
28 | * Revision 1.13 2003/01/15 14:12:04 millis
29 | * Merged from ustv
30 | *
31 | * Revision 1.12 2002/12/28 00:02:54 millis
32 | * Fixed bug 77, adding wrap_char() function
33 | *
34 | * Revision 1.11 2002/08/27 22:27:52 millis
35 | * Updated for automake/autoconf functionality
36 | *
37 | * Revision 1.10 2002/04/15 12:55:43 miller
38 | * Multiple changes for blind & Colonial & setup from USTV
39 | *
40 | * Revision 1.9 2001/07/15 09:15:46 greg
41 | * added support for game directories in a sub directory
42 | * /.
43 | *
44 | * Revision 1.8 2001/07/08 22:55:37 miller
45 | * Use CUSTODIAN name in better order
46 | *
47 | * Revision 1.7 2001/07/01 23:19:29 miller
48 | * Add InformCustodians func
49 | *
50 | * Revision 1.6 2001/06/24 05:35:03 nzmb
51 | * Reset dipent.dedapplied to 0 when new deadline is calculated.
52 | *
53 | * Revision 1.2 2000/11/14 14:27:37 miller
54 | * Lots of changes, including
55 | * - get_die_magic() Get DIE_MAGIC value from .magic.dat (or cerate if not found)
56 | *
57 | * Revision 1.1 1998/02/28 17:49:42 david
58 | * Initial revision
59 | *
60 | * Revision 1.2 1996/11/05 23:09:52 rpaar
61 | * USIT changes to fix minor bugs
62 | */
63 |
64 | /* lib.c
65 | * Copyright 1987, Lowe.
66 | *
67 | * Diplomacy is a trademark of the Avalon Hill Game Company, Baltimore,
68 | * Maryland, all rights reserved; used with permission.
69 | *
70 | * Redistribution and use in source and binary forms are permitted
71 | * provided that it is for non-profit purposes, that this and the
72 | * above notices are preserved and that due credit is given to Mr.
73 | * Lowe.
74 | *
75 | * Version History
76 | *
77 | * Version Author Date Comments
78 | * ------------------------------------------------------------------------
79 | * 1 Ken Lowe 1987 Original Code
80 | * 2 David Norman 19/05/96 Unobfuscate get_prov
81 | * Fix get_prov to cope with a province
82 | * "New York" and an abbreviation "New"
83 | * for a different province
84 | * Fix get_prov to differ between "Laos"
85 | * and "Lao s"
86 | */
87 |
88 | #include <stdlib.h>
89 | #include <string.h>
90 | #include <time.h>
91 | #include <unistd.h>
92 |
93 | #include "config.h"
94 | #include "dip.h"
95 | #include "functions.h"
96 | #include "porder.h"
97 | /* .magic.h no longer used */
98 | /*#include ".magic.h"*/
99 | #include "mail.h"
100 |
101 | /* TODO make this static to the function, rather than global */
102 | char nowString[20];
103 | static long DIE_MAGIC;
104 | /****************************************************************************/
105 |
106 | void archive(char *file, char *subject)
107 | {
108 |
109 | /*
110 | * Archive will copy the specified file to the archive file for this
111 | * particular game.
112 | */
113 |
114 | long now;
115 | char name[60];
116 | FILE *ofp, *ifp;
117 |
118 | sprintf(name, "%s%s/archive", GAME_DIR, dipent.name);
119 | if (!(ofp = fopen(name, "a"))) {
120 | perror(name);
121 | fprintf(log_fp, "Unable to append to archive file for '%s'.\n", dipent.name);
122 | bailout(1);
123 | }
124 | if (!(ifp = fopen(file, "r"))) {
125 | perror(file);
126 | fprintf(log_fp, "Unable to archive %s to %s.\n", file, name);
127 | bailout(1);
128 | }
129 | time(&now);
130 | fprintf(ofp, "Date: %24.24s (%ld)\n", ctime(&now), now);
131 | fprintf(ofp, "Subject: %s\n\n", subject);
132 |
133 | while (fgets(line, sizeof(line), ifp)) {
134 | if (strncmp(line, "::", 2) != 0)
135 | fputs(line, ofp);
136 | }
137 |
138 | fputc('\n', ofp);
139 |
140 | fclose(ifp);
141 | fclose(ofp);
142 |
143 | }
144 |
145 | /***********************************************************************/
146 |
147 |
148 | int die(int n, int s)
149 | {
150 |
151 | /*
152 | * Generate the results of "n" dice with "s" sides each.
153 | */
154 |
155 | register int r = 0;
156 |
157 | while (n-- > 0)
158 | r += ((rand() >> 1) % s) + 1;
159 | return r;
160 |
161 | }
162 | /****** */
163 | long get_die_magic(void)
164 | {
165 | /*
166 | * This function will see if a file called .magic.dat exists
167 | * (assumed previosuly created). If so, it will set DIE_MAGIC to
168 | * the value inside. If not, it will write a randon six-digit value
169 | * to this file
170 | */
171 |
172 | /* First, make sure value is generated only once */
173 | static int first_pass = 0;
174 | FILE *rand_ptr = NULL;
175 | int n = 0;
176 |
177 | if (!first_pass) {
178 |
179 | first_pass = 1;
180 |
181 | rand_ptr = fopen("./.magic.dat", "r");
182 | if (rand_ptr) {
183 | n = fscanf(rand_ptr, "%ld", &DIE_MAGIC);
184 | fclose(rand_ptr);
185 | }
186 |
187 | if (!rand_ptr || n != 1) {
188 | /* read errors, generate and write a random number */
189 | rand_ptr = fopen("./.magic.dat", "w");
190 | if (rand_ptr) {
191 | DIE_MAGIC = rand() % 1000000;
192 | n = fprintf(rand_ptr, "%6.6ld\n", DIE_MAGIC);
193 | fclose(rand_ptr);
194 | }
195 | if (!rand_ptr || n != 7) {
196 | fprintf(log_fp, "Unable to write magic data to ./.magic.dat.\n");
197 | bailout(1);
198 | }
199 | }
200 | }
201 | return DIE_MAGIC;
202 | }
203 |
204 |
205 |
206 |
207 | /***********************************************************************/
208 |
209 | void die_rolls(int seed)
210 | {
211 |
212 | /*
213 | * Establish a starting seed for random number generation. The seed
214 | * is set to a predictable value to allow deterministic die rolls for
215 | * replayability. The "DIE_MAGIC" value is defined outside and can be
216 | * any number as long as it is a constant and kept secret. The purpose
217 | * of it is so the players can't predict (by writing a little program
218 | * at home) what the die rolls are going to be.
219 | */
220 |
221 | char *s;
222 | extern char *rflg;
223 |
224 | for (s = rflg ? rflg : dipent.name; *s; s++)
225 | seed += *s;
226 |
227 | for (s = dipent.phase; *s; s++)
228 | seed += *s;
229 |
230 | srand(seed + get_die_magic());
231 |
232 | }
233 |
234 | /****************************************************************************/
235 |
236 | int execute(char *command)
237 | {
238 |
239 | /*
240 | * Execute the command and verify that it really happened -- a bug
241 | * somewhere was causing "system()" to return without actually
242 | * executing anything so outgoing mail got lost. The executed command
243 | * must remove the "dip.xfail" file so that we know it did its thing.
244 | */
245 |
246 | int i, status;
247 | FILE *fp;
248 | long now;
249 |
250 | for (i = 0;; i++) {
251 | time(&now);
252 | fprintf(log_fp, "%12.12s: Xcute: %s\n", ctime(&now) + 4, command);
253 |
254 | if (!(fp = fopen("dip.xfail", "w"))) {
255 | fprintf(log_fp, "Unable to create dip.xfail.\n");
256 | perror("dip.xfail");
257 | bailout(E_FATAL);
258 | }
259 | fclose(fp);
260 |
261 | if ((status = system(command))) {
262 | fprintf(log_fp, "System error %x executing: %s\n", status, command);
263 | if (i < 3) {
264 | fprintf(log_fp, "Will try again...\n");
265 | sleep(30);
266 | continue;
267 | } else {
268 | bailout(E_FATAL);
269 | }
270 | }
271 | break;
272 | }
273 |
274 | if ((fp = fopen("dip.xfail", "r"))) {
275 | fprintf(log_fp, "dip.xfail not deleted properly.\n");
276 | bailout(E_FATAL);
277 | }
278 | return 0;
279 |
280 | }
281 |
282 | /****************************************************************************/
283 |
284 | void ferrck(FILE * fp, int n)
285 | {
286 |
287 | /*
288 | * Check for an error status on an output file before renaming over
289 | * the top of the master copy of the file. This prevents disk full
290 | * and such from destroying all our data.
291 | */
292 |
293 | char *fmt;
294 |
295 | fmt = "Disk full error number %d. Bailing out!\n";
296 |
297 | if (ferror(fp)) {
298 | fprintf(log_fp, fmt, n);
299 | fprintf(stderr, fmt, n);
300 | bailout(E_FATAL);
301 | }
302 | }
303 |
304 | /****************************************************************************/
305 |
306 | static char *words[] =
307 | {"0", "(north coast)", "(nc)", "/north coast", "/nc",
308 | "(east coast)", "(ec)", "/east coast", "/ec",
309 | "(west coast)", "(wc)", "/west coast", "/wc",
310 | "(south coast)", "(sc)", "/south coast", "/sc"};
311 |
312 | static int coasts[] =
313 | {0, NC, NC, NC, NC, EC, EC, EC, EC,
314 | WC, WC, WC, WC, SC, SC, SC, SC};
315 |
316 | /* Get Coast called to return coast index for passed string */
317 |
318 | char *get_coast(coast_string, coast)
319 | char *coast_string;
320 | int *coast;
321 | {
322 | int ret_coast = XC; /* default coast */
323 | char c_string[256];
324 | char *t = coast_string;
325 | char *s = c_string;
326 |
327 | while (isspace(*t) && *t) t++; /* Strip out leading blanks */
328 |
329 | while (isprint(*t) && *t) {
330 | *s = *t; t++; s++;
331 | }
332 | *s = '\0';
333 |
334 | /* Todo, work backwards, consuming non-prints until first printable char */
335 |
336 | lookfor(c_string, words, nentry(words), &ret_coast);
337 |
338 | /* If there is a coast, decode its coast code */
339 |
340 | if (ret_coast)
341 | ret_coast = coasts[ret_coast];
342 | else
343 | ret_coast = XC;
344 |
345 | *coast = ret_coast;
346 |
347 | return t;
348 | }
349 |
350 | /****************************************************************************/
351 |
352 | char *get_prov(search_province, province_number, coast)
353 | char *search_province; /* The province to search for */
354 | int *province_number; /* OUTPUT: The number of the province */
355 | int *coast; /* OUTPUT: The specified coast */
356 |
357 | {
358 |
359 | char *heap_walker; /* Pointer to walk the province name heap */
360 | char *search_province_walker; /* Pointer to walk the search province string */
361 |
362 | char *search_province_remains = search_province;
363 | /* The part of the search province which was not used */
364 | int heap_white_space; /* Flag to indicate white space in the heap */
365 | int province_white_space; /* Flag to indicate white space in the search province */
366 |
367 |
368 | /*
369 | * Scan the input line for a suitable province name.
370 | *
371 | * Exit: province_number = 0 if invalid province, otherwise province ordinal
372 | * coast = coast specification if present.
373 | * Return = the part of the search province which was not used
374 | */
375 |
376 | /* Initialise the province number to 0 */
377 |
378 | *province_number = 0;
379 |
380 | /* Initialise the heap walker to the beginning of the heap */
381 |
382 | heap_walker = (char *) heap;
383 |
384 | /* While we have not found the end of the heap */
385 |
386 | while (*heap_walker) {
387 | /* Initialise the search province walker to the start of the search province string */
388 |
389 | search_province_walker = search_province;
390 |
391 | /* Initialise the white space flags */
392 |
393 | heap_white_space = 0;
394 | province_white_space = 0;
395 |
396 | /* Skip any spaces at the start of the search province */
397 |
398 | while (isspace(*search_province_walker))
399 | search_province_walker++;
400 |
401 | /*
402 | * While the characters in the search province and the heap match,
403 | * and any spaces in the search province and the heap match up in
404 | * locn, if not number or type, and the end of the heap is not
405 | * found
406 | */
407 |
408 | while (*heap_walker
409 | && heap_white_space == province_white_space
410 | && toupper(*search_province_walker) == toupper(*heap_walker)) {
411 | /* Reset the white space flags */
412 |
413 | heap_white_space = 0;
414 | province_white_space = 0;
415 |
416 | /* Skip any space characters or .'s in the search province */
417 |
418 | while (*++search_province_walker == '.' || isspace(*search_province_walker)) {
419 | /* Set the province white space flag */
420 |
421 | province_white_space = 1;
422 | }
423 |
424 | /* Skip and space characters or .'s in the heap */
425 |
426 | while (*++heap_walker == '.' || isspace(*heap_walker)) {
427 | /* Set the heap white space flag */
428 |
429 | heap_white_space = 1;
430 | }
431 | }
432 |
433 | /* If we stopped due to finding the end of the name in the heap, and at the time, the next
434 | character in the search province was one of ( ) - / ; , \n or a space character */
435 |
436 | if (!*heap_walker && (!*search_province_walker || strchr("(-/);,\n", *search_province_walker) || isspace(*(search_province_walker - 1)))) {
437 | /* We have a match */
438 |
439 | /* If it is the best match so far (best = longest) */
440 | /*
441 | * It does not matter that we have not yet considered coasts in
442 | * search_province_walker, as if a coast was found in the best
443 | * match so far, then it will always be a better match anyway
444 | */
445 |
446 | if (search_province_walker > search_province_remains) {
447 | /* Read the province number out of the heap */
448 |
449 | *province_number = *((unsigned char *) ++heap_walker);
450 |
451 | /* Check if the next part of the search province is a coast, and if so, pick up which one */
452 |
453 | search_province_walker = lookfor(search_province_walker, words, nentry(words), coast);
454 |
455 | /* If there is a coast, decode its coast code */
456 |
457 | if (*coast)
458 | *coast = coasts[*coast];
459 |
460 | /* Store a pointer to the unused part of the search province string */
461 |
462 | search_province_remains = search_province_walker;
463 | }
464 | }
465 | /* Skip any NULs in the heap */
466 |
467 | while (*heap_walker++);
468 |
469 | /* Step on one more character */
470 |
471 | heap_walker++;
472 | }
473 |
474 | if (*province_number) {
475 | /* province found: check if allowed gateway or railway */
476 | if (pr[*province_number].type == 'r' && !(dipent.x2flags && X2F_RAILWAYS) && dipent.name[0]) {
477 | *province_number = 0;
478 | search_province_remains = search_province;
479 | }
480 | if (pr[*province_number].type == 'g' && !(dipent.x2flags && X2F_GATEWAYS) && dipent.name[0]) {
481 | *province_number = 0;
482 | search_province_remains = search_province;
483 | }
484 | }
485 | /* Return the part of the search string which was not used */
486 |
487 | return search_province_remains;
488 | }
489 |
490 | char *lookfor(char *l, char *w[], int len, int *n)
491 | {
492 | return lookforv(l, w, len, n, 0);
493 | }
494 |
495 | char *lookforv(char *l, char *w[], int len, int *n, int exact_word)
496 | {
497 |
498 | /*
499 | * Look for a phrase in a table of phrases. The phrases are lower case
500 | * with a single blank where blanks are allowed or an octothorp (#)
501 | * where blanks (or parenthesis) are required.
502 | *
503 | * Entry: len = number of phrases
504 | * Exit: n = index into phrases, or zero if not found.
505 | */
506 |
507 | int i;
508 | char *t, *s;
509 |
510 | for (i = 1; i < len; i++) {
511 | t = w[i];
512 | s = l;
513 | while (isspace(*s))
514 | s++;
515 | while (*t && *t == tolower(*s)) {
516 | t++;
517 | s++;
518 | if (*t == ' ') {
519 | while (isspace(*s))
520 | s++;
521 | t++;
522 | } else if (*t == '#') {
523 | if (!isspace(*s) && !isdigit(*s) && !strchr("(;/,)", *s))
524 | break;
525 | while (isspace(*s))
526 | s++;
527 | t++;
528 | }
529 | }
530 |
531 | if (!*t && (!exact_word || ((!*s || isspace(*s) || strchr("(;/,)",*s)))))
532 | {
533 | *n = i;
534 | while (isspace(*s))
535 | s++;
536 | return s;
537 | }
538 | }
539 | *n = 0;
540 | return l;
541 | }
542 |
543 | int power(char c)
544 | {
545 |
546 | /*
547 | * Return the ordinal of the specified power character.
548 | */
549 |
550 | int i;
551 |
552 | if (islower(c))
553 | c = toupper(c);
554 | for (i = 1; i < MASTER + 1; i++)
555 | if (c == dipent.pl[i])
556 | return (i);
557 |
558 | return 0;
559 | }
560 |
561 | char *astype(char c, char cu)
562 | {
563 | if (c == 'x')
564 | if (cu == 'A')
565 | return "an";
566 | else
567 | return "a";
568 | else if (c == 'c')
569 | return ("a citizen's militia");
570 | else if (c == 'm')
571 | return ("an elite mercenary");
572 | else if (c == 'p')
573 | return ("an elite professional");
574 | else
575 | return ("an unknown");
576 | }
577 |
578 | char *autype(char c)
579 | {
580 | if (c == 'A')
581 | return ("an army");
582 | else if (c == 'F')
583 | return ("a fleet");
584 | else if (c == 'G')
585 | return ("a garrison");
586 | else if (c == 'W')
587 | return ("a wing");
588 | else if (c == 'T')
589 | return ("an army/fleet");
590 | else if (c == 'S')
591 | return ("a spy");
592 | else if (c == 'C')
593 | return ("a cavalry");
594 | else if (c == 'R')
595 | return ("an artillery");
596 | else if (c ==' ')
597 | return "";
598 | else
599 | return ("a unit");
600 | }
601 |
602 | char *bstype(char c)
603 | {
604 | if (c == 'x')
605 | return ("");
606 | else if (c == 'c')
607 | return ("citizen's militia ");
608 | else if (c == 'm')
609 | return ("elite mercenary ");
610 | else if (c == 'p')
611 | return ("elite professional ");
612 | else
613 | return ("unknown ");
614 | }
615 |
616 | char *Stype(char c)
617 | {
618 | if (c == 'x')
619 | return ("");
620 | else if (c == 'c')
621 | return ("Citizen's Militia ");
622 | else if (c == 'm')
623 | return ("Elite Mercenary ");
624 | else if (c == 'p')
625 | return ("Elite Professional ");
626 | else
627 | return ("Unknown ");
628 | }
629 |
630 | char *utype(char c)
631 | {
632 | if (c == 'A')
633 | return ("army");
634 | else if (c == 'F')
635 | return ("fleet");
636 | else if (c == 'G')
637 | return ("garrison");
638 | else if (c == 'W')
639 | return ("wing");
640 | else if (c == 'T')
641 | return ("army/fleet");
642 | else if (c == 'S')
643 | return ("spy");
644 | else if (c == 'C')
645 | return ("cavalry");
646 | else if (c == 'R')
647 | return ("artillery");
648 | else if (c == ' ')
649 | return "";
650 | else
651 | return ("unit");
652 | }
653 |
654 | /****************************************************************************/
655 |
656 | char *Utype(c)
657 | char c;
658 | {
659 | if (c == 'A')
660 | return ("Army");
661 | else if (c == 'F')
662 | return ("Fleet");
663 | else if (c == 'G')
664 | return ("Garrison");
665 | else if (c == 'W')
666 | return ("Wing");
667 | else if (c == 'T')
668 | return ("Army/Fleet");
669 | else if (c == 'S')
670 | return ("Spy");
671 | else if (c == 'C')
672 | return ("Cavalry");
673 | else if (c == 'R')
674 | return ("Artillery");
675 | else if (c == ' ')
676 | return "";
677 | else
678 | return ("Unit");
679 | }
680 |
681 | /*
682 | ** mov_type - return a string proper for unit type/province type
683 | ** i.e. A Wing is 'over Spain' or 'over the Baltic'
684 | ** A fleet in 'in Spain' or 'in the Baltic'
685 | ** an army is 'in Spain' only
686 | */
687 |
688 | char *mov_type( int prov_index, int unit_index)
689 | {
690 | static char line[20];
691 | char prov_type;
692 | char unit_type;
693 |
694 | line[0] = '\0';
695 | prov_type = pr[prov_index].type;
696 | unit_type = unit[unit_index].type;
697 |
698 | if (unit_type == 'W' ) {
699 | strcpy(line, "over");
700 | if (water(prov_index) ) {
701 | strcat(line, " the");
702 | }
703 | } else {
704 | if (water(prov_index) ) {
705 | strcpy(line, "in the");
706 | } else {
707 | strcpy(line, "in");
708 | }
709 | }
710 | return line;
711 | }
712 |
713 |
714 | void wrap(FILE * fp, char *buf, int pos, int indent)
715 | {
716 |
717 | char *s, *t;
718 |
719 | for (t = s = buf; *s; s++, pos++) {
720 | if (pos > 78) {
721 | while (*--s != ' ');
722 | *s = '\0';
723 | fputs(t, fp);
724 | fprintf(fp, "\n%*s", indent, "");
725 | *s++ = ' ';
726 | t = s;
727 | pos = indent;
728 | }
729 | }
730 | fputs(t, fp);
731 |
732 | }
733 | /* Do a wrap bu around the passed character */
734 | void wrap_char(FILE * fp, char *buf, int pos, int indent, char w_char)
735 | {
736 |
737 | char *s, *t;
738 |
739 | for (t = s = buf; *s; s++, pos++) {
740 | if (pos > 78) {
741 | while (*--s != w_char);
742 | /* Will wrap at the NEXT space character or line-end */
743 | while (*++s != ' ' && *s != '\0');
744 | *s = '\0';
745 | fputs(t, fp);
746 | fprintf(fp, "\n%*s", indent, "");
747 | *s++ = ' ';
748 | t = s;
749 | pos = indent;
750 | }
751 | }
752 | fputs(t, fp);
753 |
754 | }
755 |
756 | /*
757 | * Capitalize a string.
758 | */
759 |
760 | char *strcap(char *string)
761 | {
762 | char *copy;
763 | int i;
764 |
765 | /*
766 | * Allocate a string buffer. It's the CALLER's responsibility to
767 | * free() the string when he's finished with it.
768 | */
769 |
770 | copy = (char *) malloc(strlen(string) + 1);
771 |
772 | /*
773 | * Copy the original string to it, capitalizing all lower-case letters
774 | * that (1) are the first character; (2) follow a whitespace character;
775 | * or (3) follow punctuation. DON'T use 'toupper()', as that changes
776 | * the character in the original string.
777 | */
778 |
779 | if (copy) {
780 | for (i = 0; i < strlen(string); ++i) {
781 | if (islower(string[i]) && ((i == 0) || isspace(string[i - 1]) ||
782 | ispunct(string[i - 1]))) {
783 | copy[i] = string[i] - 'a' + 'A';
784 | } else {
785 | copy[i] = string[i];
786 | }
787 | }
788 | copy[i] = '\0';
789 | }
790 | return copy;
791 | }
792 |
793 | char *NowString(void)
794 | {
795 | long now;
796 |
797 | time(&now);
798 |
799 | sprintf(nowString, "%12.12s: ", ctime(&now) + 4);
800 | return (nowString);
801 | }
802 |
803 | int FindPower(int power_index)
804 | {
805 | int i;
806 | for (i = 0; i < dipent.n; i++)
807 | {
808 | if (dipent.players[i].power == power_index) return i;
809 | }
810 | return i;
811 | }
812 |
813 |
814 | int IsBlank(char *text)
815 | {
816 | char *t = text;
817 | int non_blank = 0;
818 | while (*t != '\0' && !non_blank) {
819 | if (0 == isspace(*t)) non_blank = 1;
820 | t++;
821 | }
822 | return !non_blank;
823 | }
824 |
825 | int GetOneWord(char *text)
826 | {
827 | char *t = text;
828 | int non_blank = 0;
829 | while (*t != '\0') {
830 | if (non_blank == 1 && 0 != isspace(*t)) *t = '\0'; /*terminate at 1st non-blank*/
831 | if (0 == isspace(*t)) non_blank = 1;
832 | t++;
833 | }
834 | return non_blank;
835 | }
836 |
837 | int deadline_recursive( sequence *seq, int new, int *rec_count);
838 |
839 | /****************************************************************************/
840 | /* Look and see if any requested absences */
841 | /* and adjust deadline parameter accordingly if so */
842 |
843 | int absence_adjust(long *deadline)
844 | {
845 | int ret = 0; /* Set to 1 if adjusted times */
846 | int i,j;
847 |
848 | for (i =0; i < dipent.n; i++ )
849 | {
850 | for ( j=0; j < MAX_ABSENCES; j++)
851 | {
852 | if (dipent.players[i].absence_start[j] < *deadline ) {
853 | if (dipent.players[i].absence_end[j] > *deadline ) {
854 | /* Check if a player has a pending move and (no press or strictwait)
855 | or game is quiet
856 | or is the master before adjusting the deadline */
857 | if ((dipent.players[i].status & SF_MOVE &&
858 | (dipent.flags & F_STRWAIT || DIPENT_NO_PRESS)) ||
859 | (!(dipent.flags & F_STRWAIT) && !DIPENT_NO_PRESS) ||
860 | dipent.flags & F_QUIET ||
861 | dipent.players[i].power == MASTER)
862 | {
863 | /* OK, we're in a requested deadline period */
864 | /* so boot up the deadline accordingly */
865 | *deadline = dipent.players[i].absence_end[j];
866 | ret = 1; /* Changed deadline */
867 | }
868 | }
869 | }
870 | }
871 | }
872 | /* OK, now delete passed deadlines */
873 | for (i =0; i < dipent.n; i++ )
874 | {
875 | for ( j=0; j < MAX_ABSENCES; j++)
876 | {
877 | if (dipent.players[i].absence_start[j] != 0L ) {
878 |
879 | if (dipent.players[i].absence_start[j] <= *deadline ) {
880 | if (dipent.players[i].absence_end[j] <= *deadline ) {
881 | /* OK, This deadline is no longer needed */
882 | /* but bump up total anyway */
883 | dipent.players[i].absence_total +=
884 | (dipent.players[i].absence_end[j] -
885 | dipent.players[i].absence_end[j]);
886 | dipent.players[i].absence_start[j] = 0L;
887 | dipent.players[i].absence_end[j] = 0L;
888 | dipent.players[i].absence_count--;
889 | }
890 | }
891 | }
892 | }
893 | }
894 | return ret;
895 | }
896 |
897 | /*************************************************************************/
898 |
899 | int deadline(sequence *seq, int new)
900 | {
901 | int rec_count = 0;
902 | return deadline_recursive(seq, new, &rec_count);
903 | }
904 |
905 |
906 | /****************************************************************************/
907 |
908 |
909 | int deadline_recursive(sequence * seq, int new, int *rec_count)
910 | {
911 |
912 | /*
913 | * Compute a new deadline for this dip entry.
914 | */
915 |
916 | int i, k;
917 | long now, temp;
918 | struct tm *tm, *localtime();
919 | int ret = 0;
920 |
921 | (*rec_count)++;
922 | if (*rec_count > 50) return 0; /* safety code */
923 |
924 | time(&now);
925 | if (dipent.phase[6] == 'X') {
926 | dipent.process = now + 168 * HRS2SECS;
927 | return 0;
928 | }
929 | if (!seq) {
930 | if (!(seq = dipent.phase[5] == 'M' ? &dipent.movement :
931 | dipent.phase[5] == 'R' ? &dipent.retreat :
932 | dipent.phase[5] == 'B' ? &dipent.builds : NULL)) {
933 | fprintf(stderr, "Invalid phase [%s] in deadline for '%s'.\n",
934 | dipent.phase, dipent.name);
935 | fprintf(log_fp, "Invalid phase [%s] in deadline for '%s'.\n",
936 | dipent.phase, dipent.name);
937 | return E_FATAL;
938 | }
939 | }
940 | /*
941 | ** If the new flag is indicated, we assume a move has just completed
942 | ** and we establish the next deadline. We don't want to set the new
943 | ** deadline earlier than the old one though.
944 | */
945 |
946 | if (new) {
947 | temp = now + (int) (seq->next * HRS2SECS);
948 |
949 | dipent.dedapplied = 0;
950 |
951 | if (temp < dipent.deadline)
952 | temp = dipent.deadline;
953 | /* OK, adjust the time requested according to pending absences */
954 | ret = absence_adjust(&temp);
955 |
956 | if (seq->clock >= 0) {
957 | tm = localtime(&temp);
958 | i = seq->clock * 60 - ((tm->tm_hour * 60 + tm->tm_min) * 60 + tm->tm_sec);
959 | if (i < 0)
960 | i += 24 * 60 * 60;
961 | temp += i;
962 | }
963 | for (k = 0; k < 8; k++) {
964 | tm = localtime(&temp);
965 | if (seq->days[tm->tm_wday] == '-')
966 | temp += 24 * 60 * 60;
967 | else if (islower(seq->days[tm->tm_wday]) && tm->tm_hour < 12)
968 | temp += (12 - tm->tm_hour) * 60 * 60 - tm->tm_min * 60 - tm->tm_sec;
969 | else
970 | break;
971 | }
972 |
973 | /* If old deadline was huge, keep it */
974 | dipent.deadline = max(temp, dipent.deadline) ;
975 | dipent.process = temp;
976 | temp += (int) (seq->grace * HRS2SECS);
977 | if (dipent.flags & F_GRACEDAYS) {
978 | for (k = 0; k < 8; k++) {
979 | tm = localtime(&temp);
980 | if (seq->days[tm->tm_wday] == '-')
981 | temp += 24 * 60 * 60;
982 | else if (islower(seq->days[tm->tm_wday]) && tm->tm_hour < 12)
983 | temp += (12 - tm->tm_hour) * 60 * 60 - tm->tm_min * 60 - tm->tm_sec;
984 | else
985 | break;
986 | }
987 | }
988 | /* If old grace was huge, keep it */
989 | dipent.grace = max(temp, dipent.grace);
990 | dipent.start = now + (int) (seq->mint * HRS2SECS);
991 | }
992 | /*
993 | ** Figure out if we can bump up the process time.
994 | */
995 |
996 | temp = now + (int) (seq->delay * HRS2SECS);
997 | for (i = 0; i < dipent.n; i++) {
998 | if (dipent.players[i].power < 0)
999 | continue;
1000 |
1001 | if (dipent.players[i].status & SF_PROCESS) {
1002 | temp = now - 1 * HRS2SECS;
1003 | dipent.players[i].status &= ~SF_PROCESS;
1004 | }
1005 | if (dipent.players[i].status & SF_WAIT && now < dipent.deadline) {
1006 | temp = dipent.deadline;
1007 | break;
1008 | }
1009 | if (WAITING(dipent.players[i].status)) {
1010 | temp = now < dipent.deadline ? dipent.deadline :
1011 | now > dipent.grace && dipent.flags & F_NONMR ? dipent.process :
1012 | dipent.grace;
1013 | break;
1014 | }
1015 | }
1016 |
1017 | /*
1018 | * We don't want to advance the process time beyond the deadline
1019 | * here. That will be done in 'process' when a reminder is sent
1020 | * out to those who haven't gotten their orders in yet.
1021 | */
1022 |
1023 | if ((dipent.process < dipent.deadline && temp < dipent.deadline) ||
1024 | temp < dipent.process)
1025 | dipent.process = max(dipent.start, temp);
1026 |
1027 | if (ret == 1) {
1028 | /* We have adjusted the deadline due to an absence */
1029 | /* Recursively call to re-adjust if necessary */
1030 | deadline_recursive(seq, new, rec_count);
1031 | /* set the mailing flag so we can advise an absence adjust */
1032 | broadcast_absence_adjust = 1;
1033 | }
1034 | return 0;
1035 |
1036 | }
1037 |
1038 |
1039 | /***** */
1040 | /* This following function will remove all leading
1041 | * spaces and convert double (or more) spaces to only one
1042 | */
1043 |
1044 | int despace(char *intext)
1045 | {
1046 | char temp[350];
1047 | int space_count = 0;
1048 | char *s = intext;
1049 | char *outtext = temp;
1050 |
1051 | /* First take away leading spaces */
1052 | while (isspace(*s)) s++;
1053 |
1054 | while (*s) {
1055 | if (isspace(*s)) {
1056 | space_count++;
1057 | if (space_count == 1) *outtext++ = ' ';
1058 | }
1059 | else {
1060 | *outtext++ = *s;
1061 | space_count = 0;
1062 | }
1063 | s++;
1064 | }
1065 | *outtext = '\0'; /* terminate the string */
1066 | strcpy(intext,temp);
1067 | return strlen(temp);
1068 | }
1069 |
1070 |
1071 | /***** */
1072 | /* This function will display the absences for
1073 | * a particular power (passsed as a parameter)
1074 | * Normal powers will see their absences only
1075 | * Master will see all absences
1076 | * Observer will see none
1077 | */
1078 |
1079 | void display_one_absences(int pindex, FILE *fptr)
1080 | {
1081 | int i = pindex;
1082 | int k;
1083 |
1084 | if (dipent.players[i].absence_count > 0) {
1085 | for (k = 0; k < MAX_ABSENCES;k++) {
1086 | if (dipent.players[i].absence_start[k] != 0) {
1087 | fprintf(fptr, "%s: Absence requested from \n%s to ",
1088 | powers[dipent.players[i].power],
1089 | ptime(&dipent.players[i].absence_start[k]));
1090 | fprintf(fptr,"%s\n", ptime(&dipent.players[i].absence_end[k]));
1091 | }
1092 | }
1093 | fprintf(rfp,"\n");
1094 | }
1095 | return;
1096 | }
1097 | void display_absence(int pnum, FILE *fptr)
1098 | {
1099 |
1100 | int i;
1101 |
1102 | switch (pnum) {
1103 |
1104 | case OBSERVER:
1105 | case WILD_PLAYER:
1106 | /* Do nothing */
1107 | break;
1108 |
1109 | case MASTER:
1110 | for (i = 0; i < MAXPLAYERS; i++ ) {
1111 | display_one_absences(i, fptr);
1112 | }
1113 | break;
1114 |
1115 | default:
1116 | display_one_absences(FindPower(pnum), fptr);
1117 | }
1118 | return;
1119 | }
1120 |
1121 | /***********************************************************************/
1122 |
1123 |
1124 | void AddOrderToOrder(char *text, int order)
1125 | {
1126 | if (text == NULL) return;
1127 |
1128 | switch (order)
1129 | {
1130 | case 'a':
1131 | strcat(text,"AIRLIFT ");
1132 | break;
1133 | case 'c':
1134 | strcat(text,"CONVOY ");
1135 | break;
1136 | case 'm':
1137 | strcat(text, "-> ");
1138 | break;
1139 | case 't':
1140 | strcat(text,"TRANSFORM ");
1141 | break;
1142 | case 'p':
1143 | strcat(text, "PROXY TO ");
1144 | break;
1145 | case 'd':
1146 | strcat(text, "DISBANDS ");
1147 | break;
1148 | case 'h':
1149 | strcat(text, "HOLD ");
1150 | break;
1151 | case 's':
1152 | strcat(text, "SUPPORT ");
1153 | break;
1154 | case 'r':
1155 | strcat(text, "REMOVE ");
1156 | break;
1157 | case 'b':
1158 | strcat(text,"BUILD ");
1159 | break;
1160 | case 'w':
1161 | strcat(text, "WAIVE ");
1162 | break;
1163 | case 'x':
1164 | break;
1165 | default:
1166 | strcat(text,"*unknown order*");
1167 | }
1168 | }
1169 |
1170 | void AddCoastToOrder(char *text, int c1)
1171 | {
1172 | char temp[50];
1173 | if (text == NULL) return;
1174 | sprintf(temp, "(%s) ", mtype[c1]);
1175 | strcat(text, temp);
1176 | }
1177 |
1178 | void AddUnitToOrder(char *text, int unit)
1179 | {
1180 | if (text == NULL) return;
1181 | strcat(text, Utype(unit));
1182 | strcat(text," ");
1183 | }
1184 |
1185 | void AddPlaceToOrder(char *text, int place)
1186 | {
1187 | if (text == NULL) return;
1188 | if (place <= 0) return;
1189 | strcat(text, pr[place].name);
1190 | strcat(text," ");
1191 | }
1192 | void AddProvinceToOrder(char *text, int place)
1193 | {
1194 | return AddPlaceToOrder(text,place);
1195 | }
1196 | void AddUnitProvinceToOrder(char *text, int unit, int place)
1197 | {
1198 | AddUnitToOrder(text, unit);
1199 | AddPlaceToOrder(text,place);
1200 | }
1201 | void AddPowerToOrder(char *text, int power)
1202 | {
1203 | if (text == NULL) return;
1204 | strcat(text,powers[power]);
1205 | }
1206 | void InformCustodians( char *game_name, char *text, int variant, int is_gunboat)
1207 | {
1208 | char *variant_guardian;
1209 | char guardian_string[200];
1210 | char *out_addr;
1211 |
1212 | if (variant != V_STANDARD || is_gunboat) {
1213 | sprintf(line, text, "dip.temp", "MNC", game_name);
1214 | out_addr = MN_CUSTODIAN;
1215 | } else {
1216 | sprintf(line, text, "dip.temp", "BNC", game_name);
1217 | out_addr = BN_CUSTODIAN;
1218 | }
1219 | MailOut(line, out_addr);
1220 | sprintf(guardian_string, "CUSTODIAN_%s", variants[variant]);
1221 | variant_guardian = config(guardian_string);
1222 |
1223 | if (variant_guardian) {
1224 | sprintf(line, text, "dip.temp", variants[variant], game_name);
1225 | MailOut(line, variant_guardian);
1226 | }
1227 |
1228 | }
1229 | int ValidGatewayProvince(int igw, int p)
1230 | {
1231 | int i;
1232 |
1233 | if (!(dipent.x2flags & X2F_GATEWAYS)) return 0;
1234 |
1235 | for (i = 0; i < gw[igw].np; i++)
1236 | if (gw[igw].pr[i] == p)
1237 | return 1;
1238 |
1239 | return 0; /* not found in valid province list */
1240 | }
1241 | int ValidRailwayProvince( int irw, int p, int *pr_index)
1242 | {
1243 | int i;
1244 | *pr_index = -1;
1245 |
1246 | if (!(dipent.x2flags & X2F_RAILWAYS)) return 0;
1247 |
1248 | for (i=0; i < rw[irw].np; i++)
1249 | if (rw[irw].pr[i] == p) {
1250 | *pr_index = i;
1251 | return 1;
1252 | }
1253 |
1254 | return 0; /* not found in valid provinces list */
1255 | }
1256 |
1257 | int PermittedRailwayPower(int irw, char power_letter)
1258 | {
1259 | int i;
1260 |
1261 | if (!(dipent.x2flags & X2F_RAILWAYS)) return 0;
1262 |
1263 | for (i=0; i < MAX_POWERS+1; i++) {
1264 | if (rw[irw].power_letter[i] == power_letter)
1265 | return 1;
1266 | }
1267 | return 0;
1268 | }
1269 | ;
1270 | /* This function determines if the source-dest order uses a gateway */
1271 | int IsGatewayOrder(int u, int *igw)
1272 | {
1273 | int source = unit[u].loc;
1274 | int dest = unit[u].dest;
1275 | int i,j,k;
1276 |
1277 | if (!(dipent.x2flags & X2F_GATEWAYS)) return 0;
1278 |
1279 | /* If not moving or supporting, not using gateway! */
1280 | if (unit[u].order != 'm' && unit[u].order != 's') return 0;
1281 | for (i=0; i < ngw; i++) {
1282 | for (j=0; j < MAX_GW_PROVINCES; j++) {
1283 | if (gw[i].pr[j] == source) {
1284 | for (k = 0; k < MAX_GW_PROVINCES; k++) {
1285 | if (gw[i].pr[k] == dest) {
1286 | *igw = i;
1287 | return 1;
1288 | }
1289 | }
1290 | } else if (gw[i].pr[j] == dest) {
1291 | for (k = 0; k < MAX_GW_PROVINCES; k++) {
1292 | if (gw[i].pr[k] == source) {
1293 | *igw = i;
1294 | return 1;
1295 | }
1296 | }
1297 | }
1298 | }
1299 | }
1300 | return 0;
1301 | };
1302 |
1303 | /* Check that the passed gateway follows the unit's order */
1304 | int IsGatewayOrdered(int igw,int u, int result[])
1305 | {
1306 | int u1;
1307 |
1308 | if (!(dipent.x2flags & X2F_GATEWAYS)) return 0;
1309 |
1310 | /* Sanity check */
1311 | if (igw < 0 || igw >= ngw) return 0;
1312 |
1313 | u1 = pr[gw[igw].prov_index].unit;
1314 |
1315 | if (!u1) return 0; /* No unit there */
1316 |
1317 | if (unit[u1].order != 's') return 0; /* Not supporting anyone */
1318 |
1319 | if (unit[u].order == 'm' &&
1320 | unit[u1].dest == unit[u].dest &&
1321 | unit[u1].unit == u && !result[u1])
1322 | return 1;
1323 |
1324 | if (unit[u].order == 's' &&
1325 | unit[u1].dest == unit[u].loc &&
1326 | unit[u1].unit == u && !result[u1])
1327 | return 1;
1328 |
1329 | return 0;
1330 | };
1331 |
1332 | char HasUnit(int province_index)
1333 | {
1334 | if (pr[province_index].unit >= 0)
1335 | return unit[pr[province_index].unit].type;
1336 | else
1337 | return '\0';
1338 | };
1339 |
1340 | /* See if a gateway province, then only can retreat if owns gateway */
1341 | int AllowedGatewayRetreat( int u, int p)
1342 | {
1343 | int i, j, k, u1;
1344 |
1345 | if (!(dipent.x2flags & X2F_GATEWAYS))
1346 | return 1; /* No gateways, so always allowed */
1347 |
1348 | for (i=0; i < ngw; i++) {
1349 | for (j=0; j < MAX_GW_PROVINCES; j++) {
1350 | if (gw[i].pr[j] == unit[u].loc) {
1351 | for (k = 0; k < MAX_GW_PROVINCES; k++) {
1352 | if (gw[i].pr[k] == p) {
1353 | for (u1 = 1; u1 <= nunit; u1++) {
1354 | if (unit[u1].loc == gw[i].prov_index) {
1355 | if (unit[u1].status == ':' &&
1356 | unit[u1].owner == unit[u].owner) {
1357 | return 1;
1358 | /* Found gatway unit of same owner, not dislodged */
1359 | }
1360 | }
1361 | }
1362 | return 0; /* using gateway, but not allowed */
1363 | }
1364 | }
1365 | }
1366 | }
1367 | }
1368 | return 1; /* not on a gateway, so ok to retreat */
1369 | }
1370 |
1371 | /* See if natives in gaem, return their index if so, else 0 */
1372 | int GetNativeIndex()
1373 | {
1374 | int i;
1375 |
1376 | for (i=1; i < AUTONOMOUS; i++)
1377 | if (pletter[dipent.variant][i] == NATIVE_POWER)
1378 | return i;
1379 |
1380 | return 0; /* No native power found */
1381 |
1382 | }
1383 |
1384 | /* See if game has multi-unit provinces */
1385 | int CheckForMultiUnitProvinces()
1386 | {
1387 | int i;
1388 |
1389 | for (i = 1; i <= npr; i++)
1390 | if (highsea(i))
1391 | return !0;
1392 |
1393 | return 0;
1394 | }
1395 |
1396 | /* See if province concerned is a multi-unit province */
1397 | int IsMultiProvince(int p)
1398 | {
1399 | if (!dipent.has_multi_unit_provs) return 0;
1400 |
1401 | if (highsea(p))
1402 | return !0;
1403 |
1404 | return 0;
1405 | }
1406 |
1407 | /* return the 'n'th unit in a multi-unit province */
1408 | int GetUnitIndex(int p, int power)
1409 | {
1410 | int u;
1411 | int index = 0; /* 'n'th unit found in this province */
1412 | int *nordinal = &pr[p].order_index;
1413 | int first_unit = pr[p].unit;
1414 |
1415 | if (!IsMultiProvince(p))
1416 | return pr[p].unit; /* Normal province, return first unit */
1417 |
1418 | for (u=1; u <= nunit; u++) {
1419 | if (unit[u].loc == p) {
1420 | if (unit[u].owner == power || power == MASTER) {
1421 | first_unit = u; /* remember first unit for this person */
1422 | if (index == *nordinal) {
1423 | (*nordinal)++; /* Found requested unit, next time is n+1 */
1424 | return u;
1425 | }
1426 | index++; /* now looking for next unit */
1427 | }
1428 | }
1429 | }
1430 |
1431 | *nordinal = 0; /* not found requested unit, so reset ordinal */
1432 | return first_unit; /* wrapped round to first unit in province */
1433 | }
1434 |
1435 | /* return the nth address in a string
1436 | using ';' and ',' as separators
1437 | returns a string with only the address if found
1438 | return NULL if not found */
1439 |
1440 | static char *GetAddressPart(int index, char *address)
1441 | {
1442 |
1443 | static char part_address[256]; /* Address part to find */
1444 | int i, count = 0;
1445 | char *cur_ptr, *ptr;
1446 |
1447 | strcpy(part_address, address);
1448 |
1449 | /* Convert all ';' to ',', so as to have only one separator character */
1450 |
1451 | for (i = 0; i < strlen(part_address); i++)
1452 | if (part_address[i] == ';')
1453 | part_address[i] = ',';
1454 |
1455 |
1456 | cur_ptr = part_address;
1457 | do {
1458 | ptr = strchr(cur_ptr, ',');
1459 | if (ptr) {
1460 | *ptr = '\0';
1461 | }
1462 | if (count != index ) {
1463 | if (ptr) cur_ptr = ptr + 1;
1464 | count++;
1465 | }
1466 | } while (count < index && ptr);
1467 |
1468 | if (count != index || (count > 0 && !ptr))
1469 | return NULL; /* No next part found */
1470 | else
1471 | return cur_ptr; /* Return found part */
1472 |
1473 | }
1474 |
1475 | /* Send mail out, using individual calls to SMAIL_CMD per email address */
1476 |
1477 | void MailOut(char *out_line, char *address)
1478 | {
1479 |
1480 | int count = 0;
1481 | char *ptr;
1482 | static char lline[256];
1483 |
1484 | while ( ptr = GetAddressPart(count++, address)) {
1485 | if (*ptr != '*' ) {
1486 | sprintf(lline, "%s %s '%s'", SMAIL_CMD, out_line, ptr);
1487 | execute(lline);
1488 | }
1489 | }
1490 |
1491 | }