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