retreat: Rewrite automatic retreat code to fix its many bugs
[empserver] / src / lib / subs / retreat.c
1 /*
2  *  Empire - A multi-player, client/server Internet based war game.
3  *  Copyright (C) 1986-2015, Dave Pare, Jeff Bailey, Thomas Ruschak,
4  *                Ken Stevens, Steve McClure, Markus Armbruster
5  *
6  *  Empire is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  *  ---
20  *
21  *  See files README, COPYING and CREDITS in the root of the source
22  *  tree for related information and legal notices.  It is expected
23  *  that future projects/authors will amend these files as needed.
24  *
25  *  ---
26  *
27  *  retreat.c: Retreat subroutines
28  *
29  *  Known contributors to this file:
30  *     Steve McClure, 2000
31  *     Ron Koenderink, 2005-2006
32  *     Markus Armbruster, 2006-2015
33  */
34
35 #include <config.h>
36
37 #include "file.h"
38 #include "nsc.h"
39 #include "path.h"
40 #include "player.h"
41 #include "prototypes.h"
42 #include "retreat.h"
43 #include "unit.h"
44
45 static void retreat_ship_sel(struct shpstr *, struct emp_qelem *, int);
46 static int retreat_ships_step(struct emp_qelem *, char, natid);
47 static void retreat_land_sel(struct lndstr *, struct emp_qelem *, int);
48 static int retreat_lands_step(struct emp_qelem *, char, natid);
49
50 static int
51 retreat_steps(char *rpath)
52 {
53     int i;
54
55     for (i = 0; i < MAX_RETREAT && rpath[i]; i++) {
56         if (rpath[i] == 'h')
57             return i + 1;
58     }
59     return i;
60 }
61
62 static void
63 consume_step(char *rpath, int *rflags)
64 {
65     memmove(rpath, rpath + 1, RET_LEN - 1);
66     if (!rpath[0])
67         *rflags = 0;
68 }
69
70 void
71 retreat_ship(struct shpstr *sp, char code)
72 {
73     int n, i;
74     natid own;
75     struct emp_qelem list;
76     struct nstr_item ni;
77     struct shpstr ship;
78
79     if (CANT_HAPPEN(!sp->shp_own))
80         return;
81     if (sp->shp_own == player->cnum || !sp->shp_rpath[0])
82         return;
83
84     n = retreat_steps(sp->shp_rpath);
85     if (!n)
86         return;
87
88     /*
89      * We're going to put a copy of *sp into list.  The movement loop
90      * will use that copy, which may render *sp stale.  To avoid
91      * leaving the caller with a stale *sp, we'll re-get it at the
92      * end.  To make that work, we need to put it now.  However, that
93      * resets sp->shp_own when the ship sinks, so save it first.
94      */
95     own = sp->shp_own;
96     putship(sp->shp_uid, sp);
97
98     emp_initque(&list);
99     if (sp->shp_own)
100         retreat_ship_sel(sp, &list, n);
101
102     if (sp->shp_rflags & RET_GROUP) {
103         snxtitem_xy(&ni, EF_SHIP, sp->shp_x, sp->shp_y);
104         while (nxtitem(&ni, &ship)) {
105             if (ship.shp_own != own
106                 || !(ship.shp_rflags & RET_GROUP)
107                 || ship.shp_fleet != sp->shp_fleet
108                 || ship.shp_uid == sp->shp_uid)
109                 continue;
110             if (strncmp(ship.shp_rpath, sp->shp_rpath, MAX_RETREAT + 1))
111                 continue;
112             retreat_ship_sel(&ship, &list, n);
113         }
114     }
115
116     /* Loop similar to the one in unit_move().  Keep it that way!  */
117     for (i = 0; i < n && !QEMPTY(&list); i++) {
118         /*
119          * Invariant: shp_may_nav() true for all ships
120          * Implies all are in the same sector
121          */
122         if (!retreat_ships_step(&list, sp->shp_rpath[i], own))
123             n = i;
124         shp_nav_stay_behind(&list, own);
125         unit_rad_map_set(&list);
126     }
127
128     if (!QEMPTY(&list))
129         shp_nav_put(&list, own);
130     getship(sp->shp_uid, sp);
131 }
132
133 static void
134 retreat_ship_sel(struct shpstr *sp, struct emp_qelem *list, int n)
135 {
136     struct shpstr *flg = QEMPTY(list) ? NULL
137         : &((struct ulist *)(list->q_back))->unit.ship;
138
139     if (!shp_may_nav(sp, flg, ", and can't retreat!"))
140         return;
141     if (sp->shp_mobil <= 0) {
142         mpr(sp->shp_own, "%s has no mobility, and can't retreat!\n",
143             prship(sp));
144         return;
145     }
146
147     if (flg)
148         mpr(sp->shp_own, "%s retreats with her\n", prship(sp));
149     else
150         mpr(sp->shp_own, "%s retreats along path %.*s\n",
151             prship(sp), n, sp->shp_rpath);
152     shp_insque(sp, list);
153 }
154
155 static int
156 retreat_ships_step(struct emp_qelem *list, char step, natid actor)
157 {
158     int dir = chkdir(step, DIR_STOP, DIR_LAST);
159     struct emp_qelem *qp;
160     struct ulist *mlp;
161     struct shpstr *sp;
162
163     if (dir != DIR_STOP && shp_nav_dir(list, dir, actor))
164         return 0;               /* can't go there */
165
166     for (qp = list->q_back; qp != list; qp = qp->q_back) {
167         mlp = (struct ulist *)qp;
168         sp = &mlp->unit.ship;
169         consume_step(sp->shp_rpath, &sp->shp_rflags);
170         if (dir != DIR_STOP)
171             sp->shp_mission = 0;
172         putship(sp->shp_uid, sp);
173     }
174
175     return dir != DIR_STOP && !shp_nav_gauntlet(list, 0, actor);
176 }
177
178 void
179 retreat_land(struct lndstr *lp, char code)
180 {
181     int n, i;
182     natid own;
183     struct emp_qelem list;
184     struct nstr_item ni;
185     struct lndstr land;
186
187     if (CANT_HAPPEN(!lp->lnd_own))
188         return;
189     if (lp->lnd_own == player->cnum || !lp->lnd_rpath[0])
190         return;
191
192     n = retreat_steps(lp->lnd_rpath);
193     if (!n)
194         return;
195
196     /* See explanation in retreat_ship() */
197     own = lp->lnd_own;
198     putland(lp->lnd_uid, lp);
199
200     emp_initque(&list);
201     if (lp->lnd_own)
202         retreat_land_sel(lp, &list, n);
203
204     if (lp->lnd_rflags & RET_GROUP) {
205         snxtitem_xy(&ni, EF_LAND, lp->lnd_x, lp->lnd_y);
206         while (nxtitem(&ni, &land)) {
207             if (land.lnd_own != own
208                 || !(land.lnd_rflags & RET_GROUP)
209                 || land.lnd_army != lp->lnd_army
210                 || land.lnd_uid == lp->lnd_uid)
211                 continue;
212             if (strncmp(land.lnd_rpath, lp->lnd_rpath, MAX_RETREAT + 1))
213                 continue;
214             retreat_land_sel(&land, &list, n);
215         }
216     }
217
218     /* Loop similar to the one in unit_move().  Keep it that way!  */
219     for (i = 0; i < n && !QEMPTY(&list); i++) {
220         /*
221          * Invariant: lnd_may_nav() true for all land units
222          * Implies all are in the same sector
223          */
224         if (!retreat_lands_step(&list, lp->lnd_rpath[i], own))
225             n = i;
226         lnd_mar_stay_behind(&list, own);
227         unit_rad_map_set(&list);
228     }
229
230     if (!QEMPTY(&list))
231         lnd_mar_put(&list, own);
232     getland(lp->lnd_uid, lp);
233 }
234
235 static void
236 retreat_land_sel(struct lndstr *lp, struct emp_qelem *list, int n)
237 {
238     struct lndstr *ldr = QEMPTY(list)
239         ? NULL : &((struct ulist *)(list->q_back))->unit.land;
240
241     if (!lnd_may_mar(lp, ldr, ", and can't retreat!"))
242         return;
243     if (lp->lnd_mobil <= 0) {
244         mpr(lp->lnd_own, "%s has no mobility, and can't retreat!\n",
245             prland(lp));
246         return;
247     }
248
249     if (ldr)
250         mpr(lp->lnd_own, "%s retreats with them\n", prland(lp));
251     else
252         mpr(lp->lnd_own, "%s retreats along path %.*s\n",
253             prland(lp), n, lp->lnd_rpath);
254     lnd_insque(lp, list);
255 }
256
257 static int
258 retreat_lands_step(struct emp_qelem *list, char step, natid actor)
259 {
260     int dir = chkdir(step, DIR_STOP, DIR_LAST);
261     struct emp_qelem *qp;
262     struct ulist *llp;
263     struct lndstr *lp;
264
265     if (dir != DIR_STOP && lnd_mar_dir(list, dir, actor))
266         return 0;               /* can't go there */
267
268     for (qp = list->q_back; qp != list; qp = qp->q_back) {
269         llp = (struct ulist *)qp;
270         lp = &llp->unit.land;
271         consume_step(lp->lnd_rpath, &lp->lnd_rflags);
272         if (dir != DIR_STOP) {
273             lp->lnd_mission = 0;
274             lp->lnd_harden = 0;
275         }
276         putland(lp->lnd_uid, lp);
277     }
278
279     return dir != DIR_STOP && !lnd_mar_gauntlet(list, 0, actor);
280 }