]> git.pond.sub.org Git - empserver/blob - src/lib/subs/retreat.c
retreat: Reject invalid retreat paths
[empserver] / src / lib / subs / retreat.c
1 /*
2  *  Empire - A multi-player, client/server Internet based war game.
3  *  Copyright (C) 1986-2014, 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-2014
33  */
34
35 #include <config.h>
36
37 #include "chance.h"
38 #include "damage.h"
39 #include "file.h"
40 #include "land.h"
41 #include "map.h"
42 #include "misc.h"
43 #include "nat.h"
44 #include "news.h"
45 #include "nsc.h"
46 #include "optlist.h"
47 #include "path.h"
48 #include "player.h"
49 #include "prototypes.h"
50 #include "retreat.h"
51 #include "sect.h"
52 #include "ship.h"
53 #include "xy.h"
54
55 static int findcondition(char);
56 static int retreat_land1(struct lndstr *, char, int);
57 static int retreat_ship1(struct shpstr *, char, int);
58
59 struct ccode {
60     char code;
61     char *desc[2];
62 };
63
64 static struct ccode conditions[] = {
65     { 'i', { "retreated with a damaged friend",
66              "was damaged" } },
67     { 't', { "retreated with a torpedoed ship",
68              "was hit by a torpedo" } },
69     { 's', { "retreated with a ship scared by sonar",
70              "detected a sonar ping" } },
71     { 'h', { "retreated with a helpless ship",
72              "was fired upon with no one able to defend it" } },
73     { 'b', { "retreated with a bombed friend",
74              "was bombed" } },
75     { 'd', { "retreated with a depth-charged ship",
76              "was depth-charged" } },
77     { 'u', { "retreated with a boarded ship", "was boarded" } },
78     { 0,   { "panicked", "panicked"} }
79 };
80
81 int
82 check_retreat_and_do_shipdamage(struct shpstr *sp, int dam)
83 {
84     if (dam <= 0)
85         return 0;
86
87     shipdamage(sp, dam);
88     if (sp->shp_rflags & RET_INJURED)
89         retreat_ship(sp, 'i');
90
91     return 1;
92 }
93
94 void
95 retreat_ship(struct shpstr *sp, char code)
96 {
97     struct nstr_item ni;
98     struct shpstr ship;
99
100     if (sp->shp_effic < SHIP_MINEFF || CANT_HAPPEN(!sp->shp_own))
101         return;
102     if (sp->shp_own == player->cnum)
103         return;
104
105     retreat_ship1(sp, code, 1);
106     if (sp->shp_rpath[0] == 0)
107         sp->shp_rflags = 0;
108
109     if (sp->shp_rflags & RET_GROUP) {
110         snxtitem_group(&ni, EF_SHIP, sp->shp_fleet);
111         while (nxtitem(&ni, &ship))
112             if (ship.shp_own == sp->shp_own) {
113                 if (ship.shp_uid != sp->shp_uid) {
114                     retreat_ship1(&ship, code, 0);
115                     getship(ship.shp_uid, &ship);
116                     if (ship.shp_rpath[0] == 0) {
117                         ship.shp_rflags = 0;
118                         putship(ship.shp_uid, &ship);
119                     }
120                 }
121             }
122     }
123 }
124
125 static int
126 retreat_ship1(struct shpstr *sp, char code, int orig)
127
128
129                         /* Is this the originally scared ship, or a follower */
130 {
131     struct sctstr sect;
132     int i;
133     int m;
134     int max;
135     int dir;
136     coord newx;
137     coord newy;
138     coord dx;
139     coord dy;
140     int mines;
141     int shells;
142     double mobcost;
143     struct mchrstr *mcp;
144     int changed;
145
146     if (opt_SAIL) {
147         /* can't retreat a ship that's sailin, bad things happend */
148         if (*sp->shp_path) {
149             wu(0, sp->shp_own,
150                "%s %s,\nbut had sailing orders, and couldn't retreat!\n",
151                prship(sp), conditions[findcondition(code)].desc[orig]);
152             return 0;
153         }
154     }
155     /* check crew - uws don't count */
156     if (sp->shp_item[I_MILIT] == 0 && sp->shp_item[I_CIVIL] == 0) {
157         wu(0, sp->shp_own,
158            "%s %s,\nbut had no crew, and couldn't retreat!\n", prship(sp),
159            conditions[findcondition(code)].desc[orig]);
160         return 0;
161     }
162
163     getsect(sp->shp_x, sp->shp_y, &sect);
164     switch (shp_check_nav(sp, &sect)) {
165     case NAV_02:
166     case NAV_60:
167         wu(0, sp->shp_own,
168            "%s %s,\nbut was caught in a construction zone, and couldn't retreat!\n",
169            prship(sp), conditions[findcondition(code)].desc[orig]);
170         return 0;
171     case NAV_NONE:
172     case NAV_CANAL:
173         wu(0, sp->shp_own,
174            "%s %s,\nbut was landlocked, and couldn't retreat!\n",
175            prship(sp), conditions[findcondition(code)].desc[orig]);
176         return 0;
177     case NAVOK:
178         break;
179     default:
180         CANT_REACH();
181         wu(0, sp->shp_own,
182            "%s %s,\nbut was subject to an empire error, and couldn't retreat!\n",
183            prship(sp), conditions[findcondition(code)].desc[orig]);
184         return 0;
185     }
186
187     if (sp->shp_mobil <= 0.0) {
188         wu(0, sp->shp_own,
189            "%s %s,\nbut had no mobility, and couldn't retreat!\n",
190            prship(sp), conditions[findcondition(code)].desc[orig]);
191         return 0;
192     }
193
194     for (i = 0; i < MAX_RETREAT && sp->shp_rpath[0]; i++) {
195         if (sp->shp_mobil <= 0.0) {
196             wu(0, sp->shp_own,
197                "%s %s,\nbut ran out of mobility, and couldn't retreat fully!\n",
198                prship(sp), conditions[findcondition(code)].desc[orig]);
199             if (!orig)
200                 putship(sp->shp_uid, sp);
201             return 0;
202         }
203         dir = chkdir(sp->shp_rpath[0], DIR_STOP, DIR_LAST);
204         memmove(sp->shp_rpath, sp->shp_rpath+1, sizeof(sp->shp_rpath) - 1);
205         if (dir == DIR_STOP || CANT_HAPPEN(dir < 0))
206             break;
207         dx = diroff[dir][0];
208         dy = diroff[dir][1];
209
210         mcp = &mchr[(int)sp->shp_type];
211         newx = xnorm(sp->shp_x + dx);
212         newy = ynorm(sp->shp_y + dy);
213         mobcost = shp_mobcost(sp);
214
215         getsect(newx, newy, &sect);
216         if (shp_check_nav(sp, &sect) != NAVOK ||
217             (sect.sct_own
218              && relations_with(sect.sct_own, sp->shp_own) < FRIENDLY)) {
219             wu(0, sp->shp_own, "%s %s,\nbut could not retreat to %s!\n",
220                prship(sp), conditions[findcondition(code)].desc[orig],
221                xyas(newx, newy, sp->shp_own));
222             if (!orig)
223                 putship(sp->shp_uid, sp);
224             return 0;
225         }
226         sp->shp_x = newx;
227         sp->shp_y = newy;
228         sp->shp_mobil -= mobcost;
229         sp->shp_mission = 0;
230
231         mines = sect.sct_mines;
232         changed = 0;
233         if (sect.sct_type != SCT_WATER || mines <= 0)
234             continue;
235         if (mcp->m_flags & M_SWEEP) {
236             max = mcp->m_item[I_SHELL];
237             shells = sp->shp_item[I_SHELL];
238             for (m = 0; mines > 0 && m < 5; m++) {
239                 if (chance(0.66)) {
240                     mines--;
241                     shells = MIN(max, shells + 1);
242                     changed |= map_set(sp->shp_own, sp->shp_x, sp->shp_y,
243                                        'X', 0);
244                 }
245             }
246             if (sect.sct_mines != mines) {
247                 wu(0, sp->shp_own,
248                    "%s cleared %d mine%s in %s while retreating\n",
249                    prship(sp), sect.sct_mines - mines,
250                    splur(sect.sct_mines - mines),
251                    xyas(newx, newy, sp->shp_own));
252                 sect.sct_mines = mines;
253                 sp->shp_item[I_SHELL] = shells;
254                 putsect(&sect);
255             }
256             if (changed)
257                 writemap(sp->shp_own);
258         }
259         if (chance(DMINE_HITCHANCE(mines))) {
260             wu(0, sp->shp_own,
261                "%s %s,\nand hit a mine in %s while retreating!\n",
262                prship(sp), conditions[findcondition(code)].desc[orig],
263                xyas(newx, newy, sp->shp_own));
264             nreport(sp->shp_own, N_HIT_MINE, 0, 1);
265             m = MINE_DAMAGE();
266             shipdamage(sp, m);
267             mines--;
268             if (map_set(sp->shp_own, sp->shp_x, sp->shp_y, 'X', 0))
269                 writemap(sp->shp_own);
270             sect.sct_mines = mines;
271             putsect(&sect);
272             if (!orig)
273                 putship(sp->shp_uid, sp);
274             return 0;
275         }
276     }
277
278     if (orig) {
279         wu(0, sp->shp_own, "%s %s, and retreated to %s\n",
280            prship(sp), conditions[findcondition(code)].desc[orig],
281            xyas(sp->shp_x, sp->shp_y, sp->shp_own));
282     } else {
283         wu(0, sp->shp_own, "%s %s, and ended up at %s\n",
284            prship(sp),
285            conditions[findcondition(code)].desc[orig],
286            xyas(sp->shp_x, sp->shp_y, sp->shp_own));
287     }
288     if (!orig)
289         putship(sp->shp_uid, sp);
290     return 1;
291 }
292
293 static int
294 findcondition(char code)
295 {
296     int i;
297
298     for (i = 0; conditions[i].code && conditions[i].code != code; i++) ;
299     CANT_HAPPEN(!conditions[i].code);
300     return i;
301 }
302
303 int
304 check_retreat_and_do_landdamage(struct lndstr *lp, int dam)
305 {
306     if (dam <= 0)
307         return 0;
308
309     landdamage(lp, dam);
310     if (lp->lnd_rflags & RET_INJURED)
311         retreat_land(lp, 'i');
312
313     return 1;
314 }
315
316 void
317 retreat_land(struct lndstr *lp, char code)
318 {
319     struct nstr_item ni;
320     struct lndstr land;
321
322     if (lp->lnd_effic < LAND_MINEFF || CANT_HAPPEN(!lp->lnd_own))
323         return;
324     if (lp->lnd_own == player->cnum)
325         return;
326
327     retreat_land1(lp, code, 1);
328     if (lp->lnd_rpath[0] == 0)
329         lp->lnd_rflags = 0;
330
331     if (lp->lnd_rflags & RET_GROUP) {
332         snxtitem_group(&ni, EF_LAND, lp->lnd_army);
333         while (nxtitem(&ni, &land))
334             if (land.lnd_own == lp->lnd_own) {
335                 if (land.lnd_uid != lp->lnd_uid) {
336                     retreat_land1(&land, code, 0);
337                     getland(land.lnd_uid, &land);
338                     if (land.lnd_rpath[0] == 0) {
339                         land.lnd_rflags = 0;
340                         putland(land.lnd_uid, &land);
341                     }
342                 }
343             }
344     }
345 }
346
347 static int
348 retreat_land1(struct lndstr *lp, char code, int orig)
349
350
351                         /* Is this the originally scared unit, or a follower */
352 {
353     struct sctstr sect;
354     int i;
355     int m;
356     int max;
357     int dir;
358     coord newx;
359     coord newy;
360     coord dx;
361     coord dy;
362     int mines;
363     int shells;
364     double mobcost;
365     struct lchrstr *lcp;
366
367     getsect(lp->lnd_x, lp->lnd_y, &sect);
368
369     if (lp->lnd_mobil <= 0.0) {
370         wu(0, lp->lnd_own,
371            "%s %s,\nbut had no mobility, and couldn't retreat!\n",
372            prland(lp), conditions[findcondition(code)].desc[orig]);
373         return 0;
374     }
375
376     for (i = 0; i < MAX_RETREAT && lp->lnd_rpath[0]; i++) {
377         if (lp->lnd_mobil <= 0.0) {
378             wu(0, lp->lnd_own,
379                "%s %s,\nbut ran out of mobility, and couldn't retreat fully!\n",
380                prland(lp), conditions[findcondition(code)].desc[orig]);
381             if (!orig)
382                 putland(lp->lnd_uid, lp);
383             return 0;
384         }
385         dir = chkdir(lp->lnd_rpath[0], DIR_STOP, DIR_LAST);
386         memmove(lp->lnd_rpath, lp->lnd_rpath+1, sizeof(lp->lnd_rpath) - 1);
387         if (dir == DIR_STOP || CANT_HAPPEN(dir < 0))
388             break;
389         dx = diroff[dir][0];
390         dy = diroff[dir][1];
391
392         lcp = &lchr[(int)lp->lnd_type];
393         newx = xnorm(lp->lnd_x + dx);
394         newy = ynorm(lp->lnd_y + dy);
395
396         getsect(newx, newy, &sect);
397         mobcost = lnd_mobcost(lp, &sect);
398         if (mobcost < 0
399             || sect.sct_type == SCT_MOUNT
400             || sect.sct_own != lp->lnd_own) {
401             wu(0, lp->lnd_own, "%s %s,\nbut could not retreat to %s!\n",
402                prland(lp),
403                conditions[findcondition(code)].desc[orig],
404                xyas(newx, newy, lp->lnd_own));
405             if (!orig)
406                 putland(lp->lnd_uid, lp);
407             return 0;
408         }
409         lp->lnd_x = newx;
410         lp->lnd_y = newy;
411         lp->lnd_mobil -= mobcost;
412         lp->lnd_mission = 0;
413
414         mines = SCT_LANDMINES(&sect);
415         if (mines <= 0 || sect.sct_oldown == lp->lnd_own)
416             continue;
417         if (lcp->l_flags & L_ENGINEER) {
418             max = lcp->l_item[I_SHELL];
419             shells = lp->lnd_item[I_SHELL];
420             for (m = 0; mines > 0 && m < 5; m++) {
421                 if (chance(0.66)) {
422                     mines--;
423                     shells = MIN(max, shells + 1);
424                 }
425             }
426             sect.sct_mines = mines;
427             lp->lnd_item[I_SHELL] = shells;
428             putsect(&sect);
429         }
430         if (chance(DMINE_LHITCHANCE(mines))) {
431             wu(0, lp->lnd_own,
432                "%s %s,\nand hit a mine in %s while retreating!\n",
433                prland(lp),
434                conditions[findcondition(code)].desc[orig],
435                xyas(newx, newy, lp->lnd_own));
436             nreport(lp->lnd_own, N_LHIT_MINE, 0, 1);
437             m = MINE_LDAMAGE();
438             if (lcp->l_flags & L_ENGINEER)
439                 m /= 2;
440             landdamage(lp, m);
441             mines--;
442             sect.sct_mines = mines;
443             putsect(&sect);
444             if (!orig)
445                 putland(lp->lnd_uid, lp);
446             return 0;
447         }
448     }
449
450     if (orig) {
451         wu(0, lp->lnd_own, "%s %s, and retreated to %s\n",
452            prland(lp),
453            conditions[findcondition(code)].desc[orig],
454            xyas(lp->lnd_x, lp->lnd_y, lp->lnd_own));
455     } else {
456         wu(0, lp->lnd_own, "%s %s, and ended up at %s\n",
457            prland(lp),
458            conditions[findcondition(code)].desc[orig],
459            xyas(lp->lnd_x, lp->lnd_y, lp->lnd_own));
460     }
461     if (!orig)
462         putland(lp->lnd_uid, lp);
463     return 1;
464 }