More boolean conversions and cleanup.
[vms-empire.git] / object.c
1 /*
2  *    Copyright (C) 1987, 1988 Chuck Simmons
3  * 
4  * See the file COPYING, distributed with empire, for restriction
5  * and warranty information.
6  */
7
8 /*
9 object.c -- routines for manipulating objects.
10 */
11
12 #include <string.h>
13 #include <stdio.h>
14 #include <ctype.h>
15 #include "empire.h"
16 #include "extern.h"
17
18 extern int get_piece_name(void);
19
20 /*
21 Find the nearest city to a location.  Return the location
22 of the city and the estimated cost to reach the city.
23 Distances are computed as straight-line distances.
24 */
25
26 int
27 find_nearest_city(loc_t loc, int owner, loc_t *city_loc)
28 {
29         loc_t best_loc;
30         long best_dist;
31         long new_dist, i;
32         
33         best_dist = INFINITY;
34         best_loc = loc;
35         
36         for (i = 0; i < NUM_CITY; i++) 
37         if (city[i].owner == owner) {
38                new_dist = dist (loc, city[i].loc);
39                if (new_dist < best_dist) {
40                        best_dist = new_dist;
41                        best_loc = city[i].loc;
42                }
43         }
44         *city_loc = best_loc;
45         return best_dist;
46 }
47
48 /*
49 Given the location of a city, return the index of that city.
50 */
51
52 city_info_t *find_city(loc_t loc)
53 {
54         return (map[loc].cityp);
55 }
56
57 /*
58 Return the number of moves an object gets to make.  This amount
59 is based on the damage the object has suffered and the number of
60 moves it normally gets to make.  The object always gets to make
61 at least one move, assuming it is not dead.  Damaged objects move
62 at a fraction of their normal speed.  An object which has lost
63 half of its hits moves at half-speed, for example.
64 */
65
66 int obj_moves (piece_info_t *obj)
67 {
68         return (piece_attr[obj->type].speed * obj->hits
69                + piece_attr[obj->type].max_hits - 1) /* round up */
70                / piece_attr[obj->type].max_hits;
71 }
72
73 /*
74 Figure out the capacity for an object.
75 */
76
77 int obj_capacity(piece_info_t *obj)
78 {
79         return (piece_attr[obj->type].capacity * obj->hits
80                + piece_attr[obj->type].max_hits - 1) /* round up */
81                / piece_attr[obj->type].max_hits;
82 }
83
84 /*
85 Search for an object of a given type at a location.  We scan the
86 list of objects at the given location for one of the given type.
87 */
88
89 piece_info_t *find_obj(int type, loc_t loc)
90 {
91         piece_info_t *p;
92
93         for (p = map[loc].objp; p != NULL; p = p->loc_link.next)
94         if (p->type == type) return (p);
95
96         return (NULL);
97 }
98
99 /*
100 Find a non-full item of the appropriate type at the given location.
101 */
102
103 piece_info_t *find_nfull(int type, loc_t loc)
104 {
105         piece_info_t *p;
106
107         for (p = map[loc].objp; p != NULL; p = p->loc_link.next)
108         if (p->type == type) {
109                 if (obj_capacity (p) > p->count) return (p);
110         }
111         return (NULL);
112 }
113
114 /*
115 Look around a location for an unfull transport.  Return the location
116 of the transport if there is one.
117 */
118
119 loc_t
120 find_transport(int owner, loc_t loc)
121 {
122         int i;
123         loc_t new_loc;
124         piece_info_t *t;
125
126         for (i = 0; i < 8; i++) { /* look around */
127                 new_loc = loc + dir_offset[i];
128                 t = find_nfull (TRANSPORT, new_loc);
129                 if (t != NULL && t->owner == owner) return (new_loc);
130         }
131         return (loc); /* no tt found */
132 }
133
134 /*
135 Search a list of objects at a location for any kind of object.
136 We prefer transports and carriers to other objects.
137 */
138
139 piece_info_t *
140 find_obj_at_loc(loc_t loc)
141 {
142         piece_info_t *p, *best;
143         
144         best = map[loc].objp;
145         if (best == NULL) return (NULL); /* nothing here */
146
147         for (p = best->loc_link.next; p != NULL; p = p->loc_link.next)
148         if (p->type > best->type && p->type != SATELLITE)
149                 best = p;
150
151         return (best);
152 }
153
154 /*
155 If an object is on a ship, remove it from that ship.
156 */
157
158 void disembark(piece_info_t *obj)
159 {
160         if (obj->ship) {
161                 UNLINK (obj->ship->cargo, obj, cargo_link);
162                 obj->ship->count -= 1;
163                 obj->ship = NULL;
164         }
165 }
166
167 /*
168 Move an object onto a ship.
169 */
170
171 void embark(piece_info_t *ship, piece_info_t *obj)
172 {
173         obj->ship = ship;
174         LINK (ship->cargo, obj, cargo_link);
175         ship->count += 1;
176 }
177
178 /*
179 Kill an object.  We scan around the piece and free it.  If there is
180 anything in the object, it is killed as well.
181 */
182
183 void kill_obj(piece_info_t *obj, loc_t loc)
184 {
185         void kill_one();
186
187         piece_info_t **list;
188         view_map_t *vmap;
189         
190         vmap = MAP(obj->owner);
191         list = LIST(obj->owner);
192         
193         while (obj->cargo != NULL) /* kill contents */
194                 kill_one (list, obj->cargo);
195
196         kill_one (list, obj);
197         scan (vmap, loc); /* scan around new location */
198 }
199
200 /* kill an object without scanning */
201
202 void kill_one(piece_info_t **list, piece_info_t *obj)
203 {
204         UNLINK (list[obj->type], obj, piece_link); /* unlink obj from all lists */
205         UNLINK (map[obj->loc].objp, obj, loc_link);
206         disembark (obj);
207
208         LINK (free_list, obj, piece_link); /* return object to free list */
209         obj->hits = 0; /* let all know this object is dead */
210         obj->moved = piece_attr[obj->type].speed; /* object has moved */
211 }
212
213 /*
214 Kill a city.  We kill off all objects in the city and set its type
215 to unowned.  We scan around the city's location.
216 */
217
218 void kill_city(city_info_t *cityp)
219 {
220         view_map_t *vmap;
221         piece_info_t *p;
222         piece_info_t *next_p;
223         piece_info_t **list;
224         int i;
225         
226         /* change ownership of hardware at this location; but not satellites */
227         for (p = map[cityp->loc].objp; p; p = next_p) {
228                 next_p = p->loc_link.next;
229                 
230                 if (p->type == ARMY) kill_obj (p, cityp->loc);
231                 else if (p->type != SATELLITE) {
232                         if (p->type == TRANSPORT) {
233                                 list = LIST(p->owner);
234                                 
235                                 while (p->cargo != NULL) /* kill contents */
236                                         kill_one (list, p->cargo);
237                         }
238                         list = LIST (p->owner);
239                         UNLINK (list[p->type], p, piece_link);
240                         p->owner = (p->owner == USER ? COMP : USER);
241                         list = LIST (p->owner);
242                         LINK (list[p->type], p, piece_link);
243                         
244                         p->func = NOFUNC;
245                 }
246         }
247
248         if (cityp->owner != UNOWNED) {
249                 vmap = MAP(cityp->owner);
250                 cityp->owner = UNOWNED;
251                 cityp->work = 0;
252                 cityp->prod = NOPIECE;
253                 
254                 for (i = 0; i < NUM_OBJECTS; i++)
255                         cityp->func[i] = NOFUNC;
256                 
257                 scan (vmap, cityp->loc);
258         }
259 }
260
261 /*
262 Produce an item for a city.
263 */
264
265 static int sat_dir[4] = {MOVE_NW, MOVE_SW, MOVE_NE, MOVE_SE};
266
267 void
268 produce(city_info_t *cityp)
269 {
270         piece_info_t **list;
271         piece_info_t *new;
272         
273         list = LIST (cityp->owner);
274
275         cityp->work -= piece_attr[(int)cityp->prod].build_time;
276         
277         ASSERT (free_list); /* can we allocate? */
278         new = free_list;
279         UNLINK (free_list, new, piece_link);
280         LINK (list[(int)cityp->prod], new, piece_link);
281         LINK (map[cityp->loc].objp, new, loc_link);
282         new->cargo_link.next = NULL;
283         new->cargo_link.prev = NULL;
284         
285         new->loc = cityp->loc;
286         new->func = NOFUNC;
287         new->hits = piece_attr[(int)cityp->prod].max_hits;
288         new->owner = cityp->owner;
289         new->type = cityp->prod;
290         new->moved = 0;
291         new->cargo = NULL;
292         new->ship = NULL;
293         new->count = 0;
294         new->range = piece_attr[(int)cityp->prod].range;
295         
296         if (new->type == SATELLITE) { /* set random move direction */
297                 new->func = sat_dir[irand (4)];
298         }
299 }
300
301 /*
302 Move an object to a location.  We mark the object moved, we move
303 the object to the new square, and we scan around the object.
304 We also do lots of little maintenance like updating the range
305 of an object, keeping track of the number of pieces on a boat, 
306 etc.
307 */
308
309 void move_obj(piece_info_t *obj, loc_t new_loc)
310 {
311         view_map_t *vmap;
312         loc_t old_loc;
313         piece_info_t *p;
314
315         ASSERT (obj->hits);
316         vmap = MAP(obj->owner);
317
318         old_loc = obj->loc; /* save original location */
319         obj->moved += 1;
320         obj->loc = new_loc;
321         obj->range--;
322         
323         disembark (obj); /* remove object from any ship */
324         
325         UNLINK (map[old_loc].objp, obj, loc_link);
326         LINK (map[new_loc].objp, obj, loc_link);
327
328         /* move any objects contained in object */
329         for (p = obj->cargo; p != NULL; p = p->cargo_link.next) {
330                 p->loc = new_loc;
331                 UNLINK (map[old_loc].objp, p, loc_link);
332                 LINK (map[new_loc].objp, p, loc_link);
333         }
334         
335         switch (obj->type) { /* board new ship */
336         case FIGHTER:
337                 if (map[obj->loc].cityp == NULL) { /* not in a city? */
338                         p = find_nfull (CARRIER, obj->loc);
339                         if (p != NULL) embark (p, obj);
340                 }
341                 break;
342
343         case ARMY:
344                 p = find_nfull (TRANSPORT, obj->loc);
345                 if (p != NULL) embark (p, obj);
346                 break;
347         }
348
349         if (obj->type == SATELLITE)
350                 scan_sat (vmap, obj->loc);
351         scan (vmap, obj->loc);
352 }
353
354 /*
355 Move a satellite.  It moves according to the preset direction.
356 Satellites bounce off the edge of the board.
357
358 We start off with some preliminary routines.
359 */
360
361 /* Return next direction for a sattellite to travel. */
362
363 static loc_t
364 bounce(loc_t loc, loc_t dir1, loc_t dir2, loc_t dir3)
365 {
366         int new_loc;
367
368         new_loc = loc + dir_offset[MOVE_DIR (dir1)];
369         if (map[new_loc].on_board) return dir1;
370
371         new_loc = loc + dir_offset[MOVE_DIR (dir2)];
372         if (map[new_loc].on_board) return dir2;
373
374         return dir3;
375 }
376
377 /* Move a satellite one square. */
378
379 static void
380 move_sat1(piece_info_t *obj)
381 {
382         int dir;
383         loc_t new_loc;
384
385         dir = MOVE_DIR(obj->func);
386         new_loc = obj->loc + dir_offset[dir];
387
388         if (!map[new_loc].on_board) {
389                 switch (obj->func) {
390                 case MOVE_NE:
391                         obj->func = bounce (obj->loc, MOVE_NW, MOVE_SE, MOVE_SW);
392                         break;
393                 case MOVE_NW:
394                         obj->func = bounce (obj->loc, MOVE_NE, MOVE_SW, MOVE_SE);
395                         break;
396                 case MOVE_SE:
397                         obj->func = bounce (obj->loc, MOVE_SW, MOVE_NE, MOVE_NW);
398                         break;
399                 case MOVE_SW:
400                         obj->func = bounce (obj->loc, MOVE_SE, MOVE_NW, MOVE_NE);
401                         break;
402                 default: ABORT;
403                 }
404                 dir = MOVE_DIR(obj->func);
405                 new_loc = obj->loc + dir_offset[dir];
406         }
407         move_obj (obj, new_loc);
408 }
409
410 /*
411 Now move the satellite all of its squares.
412 Satellite burns iff it's range reaches zero.
413 */
414                 
415 void
416 move_sat(piece_info_t *obj)
417 {
418         obj->moved = 0;
419         
420         while (obj->moved < obj_moves (obj)) {
421                 move_sat1 (obj);
422                 if (obj->range == 0) {
423                         if (obj->owner == USER)
424                                 comment ("Satellite at %d crashed and burned.", loc_disp(obj->loc),0,0,0,0,0,0,0);
425                                 ksend ("Satellite at %d crashed and burned.", loc_disp(obj->loc),0,0,0,0,0,0,0);
426                         kill_obj (obj, obj->loc);
427                 }
428         }
429 }
430
431 /*
432 Return true if a piece can move to a specified location.
433 We are passed the object and the location.  The location
434 must be on the board, and the player's view map must have an appropriate
435 terrain type for the location.  Boats may move into port, armies may
436 move onto transports, and fighters may move onto cities or carriers.
437 */
438
439 bool good_loc(piece_info_t *obj, loc_t loc)
440 {
441         view_map_t *vmap;
442         piece_info_t *p;
443         
444         if (!map[loc].on_board) return (false);
445
446         vmap = MAP (obj->owner);
447
448         if (strchr (piece_attr[obj->type].terrain, vmap[loc].contents) != NULL)
449                 return (true);
450
451         /* armies can move into unfull transports */
452         if (obj->type == ARMY) {
453                 p = find_nfull (TRANSPORT, loc);
454                 return (p != NULL && p->owner == obj->owner);
455         }
456
457         /* ships and fighters can move into cities */
458         if (map[loc].cityp && map[loc].cityp->owner == obj->owner)
459                 return (true);
460
461         /* fighters can move onto unfull carriers */
462         if (obj->type == FIGHTER) {
463                 p = find_nfull (CARRIER, loc);
464                 return (p != NULL && p->owner == obj->owner);
465         }
466
467         return (false);
468 }
469
470 void describe_obj(piece_info_t *obj)
471 {
472         char func[STRSIZE];
473         char other[STRSIZE];
474
475         if (obj->func >= 0) (void) sprintf (func, "%d", loc_disp(obj->func));
476         else (void) sprintf (func, func_name[FUNCI(obj->func)]);
477         
478         other[0] = 0;
479
480         switch (obj->type) { /* set other information */
481         case FIGHTER:
482                 (void) sprintf (other,"; range = %d",obj->range);
483                 break;
484
485         case TRANSPORT:
486                 (void) sprintf (other,"; armies = %d",obj->count);
487                 break;
488
489         case CARRIER:
490                 (void) sprintf (other,"; fighters = %d",obj->count);
491                 break;
492         }
493
494         prompt2 ("%s at %d:  moves = %d; hits = %d; func = %s%s",
495                 piece_attr[obj->type].name,
496                 loc_disp(obj->loc),
497                 obj_moves (obj) - obj->moved,
498                 obj->hits,
499                 func,
500                 other,0,0);
501 }
502
503 /*
504 Scan around a location to update a player's view of the world.  For each
505 surrounding cell, we remember the date the cell was examined, and the
506 contents of the cell.  Notice how we carefully update the cell to first
507 reflect land, water, or city, then army or fighter, then boat, and finally
508 city owner.  This guarantees that the object we want to display will appear
509 on top.
510 */
511
512 void
513 scan(view_map_t vmap[], loc_t loc)
514 {
515         void update(), check(void);
516
517         int i;
518         loc_t xloc;
519
520 #ifdef DEBUG
521         check (); /* perform a consistency check */
522 #endif
523         ASSERT (map[loc].on_board); /* passed loc must be on board */
524
525         for (i = 0; i < 8; i++) { /* for each surrounding cell */
526                 xloc = loc + dir_offset[i];
527                 update (vmap, xloc);
528         }
529         update (vmap, loc); /* update current location as well */
530 }
531
532 /*
533 Scan a portion of the board for a satellite.
534 */
535
536 void
537 scan_sat(view_map_t vmap[], loc_t loc)
538 {
539         int i;
540         loc_t xloc;
541         
542         ASSERT (map[loc].on_board);
543
544         for (i = 0; i < 8; i++) { /* for each surrounding cell */
545                 xloc = loc + 2 * dir_offset[i];
546                 if (xloc >= 0 && xloc < MAP_SIZE && map[xloc].on_board)
547                         scan (vmap, xloc);
548         }
549         scan (vmap, loc);
550 }
551
552 /*
553 Update a location.  We set the date seen, the land type, object
554 contents starting with armies, then fighters, then boats, and the
555 city type.
556 */
557
558 char city_char[] = {'*', 'O', 'X'};
559
560 void
561 update(view_map_t vmap[], loc_t loc)
562 {
563         piece_info_t *p;
564
565         vmap[loc].seen = date;
566         
567         if (map[loc].cityp) /* is there a city here? */
568                 vmap[loc].contents = city_char[map[loc].cityp->owner];
569         
570         else {
571                 p = find_obj_at_loc (loc);
572                 
573                 if (p == NULL) /* nothing here? */
574                         vmap[loc].contents = map[loc].contents;
575                 else if (p->owner == USER)
576                         vmap[loc].contents = piece_attr[p->type].sname;
577                 else vmap[loc].contents = tolower (piece_attr[p->type].sname);
578         }
579         if (vmap == comp_map)
580                 display_locx (COMP, comp_map, loc);
581         else if (vmap == user_map)
582                 display_locx (USER, user_map, loc);
583 }
584
585 /*
586 Set the production for a city.  We make sure the city is displayed
587 on the screen, and we ask the user for the new production.  We keep
588 asking until we get a valid answer.
589 */
590
591 void
592 set_prod(city_info_t *cityp)
593 {
594         int i;
595
596         scan (user_map, cityp->loc);
597         display_loc_u (cityp->loc);
598
599         for (;;) {
600                 prompt ("What do you want the city at %d to produce? ",loc_disp(cityp->loc),0,0,0,0,0,0,0);
601
602                 i = get_piece_name ();
603                 
604                 if (i == NOPIECE)
605                         error ("I don't know how to build those.",0,0,0,0,0,0,0,0);
606                         
607                 else {
608                         cityp->prod = i;
609                         city->work = -(piece_attr[i].build_time / 5);
610                         return;
611                 }
612         }
613 }
614
615 /* Get the name of a type of object. */
616
617 int
618 get_piece_name(void)
619 {
620         char c;
621         int i;
622         
623         c = get_chx (); /* get the answer */
624
625         for (i = 0; i < NUM_OBJECTS; i++)
626         if (piece_attr[i].sname == c) {
627                 return i;
628         }
629         return NOPIECE;
630 }