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