Add shadows and light effects
[mercenary-reloaded.git] / src / mercenary / mercenary2.c
1 /* game specials
2  *
3  * (C) 2018 by Andreas Eversberg <jolly@eversberg.eu>
4  * All Rights Reserved
5  *
6  * This program 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 #include <stdio.h>
21 #include <string.h>
22 #include <stdint.h>
23 #include <stdlib.h>
24 #include <math.h>
25 #include "../libsdl/print.h"
26 #include "../libcpu/m68k.h"
27 #include "../libcpu/m68kcpu.h"
28 #include "../libcpu/execute.h"
29 #include "mercenary.h"
30
31 #define INITIAL_STACK   0x7fffa
32 #define RESET_VECTOR    0x59484
33
34 static int info_walking = 0;
35
36 /* VR heading to alter direction we walk to */
37 static struct vr_move vr_move;
38
39 /* interrupt CPU execution at special break points and tell emulation what to do */
40 const struct cpu_stop mercenary_stop_at[] = {
41         { 0x59a4e,      STOP_AT_WAIT_VBL },                     /* done with rendering, waiting for VBL */
42         { 0x54c26,      STOP_AT_WAIT_INPUT },                   /* after pressing 'HELP' key before showing menu line on benson */
43         { 0x55438,      STOP_AT_WAIT_INPUT },                   /* waiting for menu command */
44         { 0x54c2e,      STOP_AT_WAIT_INPUT },                   /* after pressing 'HELP' key while showing menu line on benson */
45         { 0x55446,      STOP_AT_WAIT_INPUT },                   /* after pressing 'RETURN' while game waits for other key to resume */
46         { 0x51620,      STOP_AT_WAIT_VBL },                     /* after dying, waiting for VBL to fade out palette */
47         { 0x596BC,      STOP_AT_CLEAR_SCREEN1 },                /* clear the screen inside building, no windows (here we know the color 8, this is wy we cannot do it earlier) */
48         { 0x596F4,      STOP_AT_CLEAR_SCREEN1 },                /* walking above ground */
49         { 0x59720,      STOP_AT_CLEAR_SCREEN1 },                /* unknown */
50         { 0x598A8,      STOP_AT_CLEAR_SCREEN1 },                /* space craft on ground */
51         { 0x598E6,      STOP_AT_CLEAR_SCREEN2 },                /* special case where we use color index 15 when we are flying (no raster split for ground color) */
52         { 0x59982,      STOP_AT_CLEAR_SCREEN3 },                /* special case where we are in universe and do not have any ground */
53         { 0x55F2E,      STOP_AT_DRAW_GROUND },                  /* the ground is rendered. the color index is captured at CLEAR_SCREEN */
54         { 0x596CE,      STOP_AT_DRAW_GROUND },                  /* at this point there is ground (inside building) rendered, because game uses raster split for ground color, but we do render opengl */
55         { 0x59710,      STOP_AT_DRAW_GROUND },                  /* at this point there is ground (outside) rendered, because game uses raster split for ground color, but we do render opengl */
56         { 0x531EA,      STOP_AT_INFO_OBJECT_MOVING },           /* get ID and position of next object */
57         /* note: there a no fix objects in this game (no taxi/bus/intercity) */
58         { 0x534EA,      STOP_AT_TAG_IS_OBJECT_1 },              /* indicates that the next tag is an object */
59         { 0x534EE,      STOP_AT_TAG_IS_OBJECT_0 },              /* indicates that the tag of object was rendered */
60         { 0x5346E,      STOP_AT_COORD_OBJECT },                 /* object coordinates are ready */
61         { 0x534F6,      STOP_AT_POLY_OBJECT_M2 },               /* object polygon is rendered */
62         { 0x534F0,      STOP_AT_LINE_OBJECT },                  /* object line is rendered */
63         { 0x5324A,      STOP_AT_COORD_BEACON },                 /* beacon's point coordinates are ready */
64         { 0x53284,      STOP_AT_POINT_BEACON },                 /* becon point is rendered */
65         { 0x53A00,      STOP_AT_COORD_BUILDING_EXTERIOR },      /* building (house) coordinates are ready */
66         { 0x53A5C,      STOP_AT_POLY_BUILDING_EXTERIOR },       /* building polygons are rendered */
67         { 0x53A54,      STOP_AT_LINE_BUILDING_EXTERIOR },       /* lines of building, like radio tower on icarus */
68         { 0x5AA10,      STOP_AT_COORD_BUILDING_INTERIOR },      /* building coordinates for interrior */
69         { 0x5B218,      STOP_AT_POLY_BUILDING_INTERIOR1 },      /* floor of building will be rendered */
70         { 0x5B1BE,      STOP_AT_POLY_BUILDING_INTERIOR2 },      /* ceiling of building will be rendered */
71         { 0x5B154,      STOP_AT_POLY_BUILDING_INTERIOR3 },      /* ceiling of window/door will be rendered */
72         { 0x5B0E0,      STOP_AT_POLY_BUILDING_INTERIOR4 },      /* floor of window will be rendered */
73         { 0x5B2DE,      STOP_AT_POLY_BUILDING_INTERIOR1to4 },   /* ceiling/floor of building is rendered */
74         { 0x5AFF4,      STOP_AT_POLY_BUILDING_INTERIOR5 },      /* part above window/door will be rendered */
75         { 0x5AF8A,      STOP_AT_POLY_BUILDING_INTERIOR6 },      /* part below window will be rendered */
76         { 0x5B0BE,      STOP_AT_POLY_BUILDING_INTERIOR5to6 },   /* part below/above window/door of building is rendered */
77         { 0x5B332,      STOP_AT_WALL_BUILDING },                /* a wall (between floor and ceiling/window/door) is rendered) */
78         { 0x4F462,      STOP_AT_COORD_COMET },                  /* comet's coordinates are ready */
79         { 0x4F496,      STOP_AT_MATRIX_COMET },                 /* what rotation matrix to use */
80         { 0x4F4B8,      STOP_AT_POLY_COMET },                   /* comet's horizontal polygon (without culling no need to render 0x4F4BC) */
81         { 0x4F4C0,      STOP_AT_POLY_COMET },                   /* comet's vertival polygon (without culling no need to render 0x4F4C4) */
82         { 0x54634,      STOP_AT_COORD_LINE_ROADS },             /* road's line coordinates are ready */
83         { 0x54676,      STOP_AT_LINE_ROADS },                   /* road's and ground surface's polygon */
84         { 0x5469C,      STOP_AT_LINE_ROADS },
85         { 0x546B2,      STOP_AT_LINE_ROADS },
86         { 0x56496,      STOP_AT_LINE_ROADS_CENTER },            /* center line of roads */
87         { 0x56442,      STOP_AT_COORD_POLY_ROADS },             /* road's and ground surface's coordinates are ready */
88         { 0x5649C,      STOP_AT_POLY_ROADS },                   /* road's and ground surface's polygon */
89         { 0x53CB6,      STOP_AT_COORD_TAGS },                   /* coordinates for tags, like key's marking are ready */
90         /* note: there are no coordinates for large tags in this game (no faces) */
91         /* note: there are no STOP_AT_LINE_TAGS1 and STOP_AT_POLY_TAGS1 in this game(given color) */
92         { 0x53CFA,      STOP_AT_LINE_TAGS2 },                   /* tag's line is rendered (use last color) */
93         { 0x53CF0,      STOP_AT_POLY_TAGS2 },                   /* tag's polygon is rendered (use last color) */
94         { 0x52BF4,      STOP_AT_COORD_PLANET },                 /* planet's coordinates are ready (viewed from ground) */
95         { 0x52C1A,      STOP_AT_MATRIX_PLANET },                /* what rotation matrix to use */
96         { 0x52756,      STOP_AT_COORD_PLANET },                 /* planet's coordinates are ready (viewed from universe) */
97         { 0x5277C,      STOP_AT_MATRIX_PLANET },                /* what rotation matrix to use */
98         { 0x52C70,      STOP_AT_DRAW_PLANET },                  /* planet's sphere is rendered, D0 is color (viewed from ground) */
99         { 0x52B36,      STOP_AT_DRAW_PLANET },                  /* planet's sphere is rendered, D0 is color (viewed from universe) */
100         { 0x4F4E6,      STOP_AT_DRAW_COMET },                   /* comet's sphere is rendered */
101         { 0x50006,      STOP_AT_DRAW_STARS_SPACE },             /* stars are rendered (viewed from universe) */
102         { 0x4FF4C + 4,  STOP_AT_DRAW_STARS_GROUND },            /* stars are rendered (viewed from ground) */ // + 4 because the actual adress is set by patching, which does not allow to break
103         { 0x4FE24,      STOP_AT_DRAW_STARS_FLYING },            /* stars are rendered (viewed from planet when flying) */
104         { 0x4FCAC,      STOP_AT_DRAW_STARS_FLYING2 },           /* same as above, but stars when upside down (above zenit) */
105         { 0x50FC2,      STOP_AT_DRAW_STARS_INTERSTELLAR },      /* interstellar star flight */
106         { 0x50BF4,      STOP_AT_DRAW_SUN_INTERSTELLAR },        /* draw sun dot while flying interstellar */
107         { 0x50BB8,      STOP_AT_CLEAR_SCREEN3 },                /* clear while flying interstellar */
108         { 0x563B8,      STOP_AT_COORD_ISLANDS },                /* island's coordinates are ready */
109         { 0x56416,      STOP_AT_POLY_ISLANDS },                 /* island's polygon is rendered */
110         { 0x56410,      STOP_AT_LINE_ISLANDS },
111         { 0x511FE,      STOP_AT_DRAW_SIGHTS },                  /* when sights are rendered */
112         { 0x5351A,      STOP_AT_COORD_EXPLOSION },              /* explosion debris coordinate */
113         { 0x5356C,      STOP_AT_DRAW_EXPLOSION },               /* explosion debris render */
114         { 0x4C514,      STOP_AT_COORD_EXPLOSION },
115         { 0x4C6F0,      STOP_AT_DRAW_EXPLOSION },
116         { 0x52C42,      STOP_AT_PATCH_RENDER },                 /* patch away planet check (behind observer) */
117         { 0x528C4,      STOP_AT_PATCH_RENDER },                 /* patch away planet check (behind observer) */
118         { 0x45806,      STOP_AT_PATCH_RENDER },                 /* patch away planet rendering (would crash without check above) */
119         { 0x53276,      STOP_AT_PATCH_RENDER },                 /* patch away beacon check (not visible on screen) */
120         { 0x4FD8E,      STOP_AT_PATCH_RENDER },                 /* always render stars, also at day */
121         { 0x4FF4A,      STOP_AT_PATCH_RENDER },
122         { 0x59622,      STOP_AT_PATCH_VR },                     /* we are walking/taxi */
123         { 0x597CA,      STOP_AT_PATCH_VR },                     /* we are on a craft */
124         { 0x597F2,      STOP_AT_PATCH_VR },                     /* we are ??? */
125         { 0x59944,      STOP_AT_PATCH_VR },                     /* we are in space */
126         { 0x58EA8,      STOP_AT_PATCH_VR },                     /* step that moves player on surface */
127         { 0x58060,      STOP_AT_PATCH_VR },                     /* soft pitch over planet */
128         { 0x58918,      STOP_AT_PATCH_VR },                     /* soft pitch in space */
129
130         { 0x53872,      STOP_AT_SHADOW_BUILDING },              /* paint shadow from building ot of game data */
131
132         { 0x0,          STOP_AT_END },                          /* end */
133 };
134
135 extern const uint32_t mercenary2_hex[];
136 extern int mercenary2_hex_size;
137
138 void mercenary_load(void)
139 {
140         int i;
141
142         memset(&vr_move, 0, sizeof(vr_move));
143         vr_move.yaw = 256;
144         vr_move.pitch = 256;
145
146         /* load game binary from constant to volatile memory */
147         for (i = 0; i < mercenary2_hex_size; i += 4) {
148                 m68k_write_memory_32(i, mercenary2_hex[i / 4]);
149         }
150 }
151
152 void mercenary_patch(void)
153 {
154         uint32_t address;
155
156         /* initial stack */
157         m68k_write_memory_32(0x00000, INITIAL_STACK);
158
159         /* reset vector */
160         m68k_write_memory_32(0x00004, RESET_VECTOR);
161
162         /* remove function that checks what stars are rendered when flying above plante
163          * instead of just rendering the necessary parts, both parts are always rendered:
164          * 1. stars from horizont up to zenith
165          * 2. stars from zenith up to horizon (upside down)
166          * we need that, so opengl rendering can use wider FOV without missing stars.
167          * the game will actually have no problem with it, except that it requires more cpu cycles
168          */
169         for (address = 0x4FD90; address < 0x4FDB6; address += 2)
170                 m68k_write_memory_16(address, 0x4e71); /* nop */
171
172         /* remove wait for VBL */
173         m68k_write_memory_16(0x59a54, 0x4e71); /* nop */
174
175         /* reduce loop that waits for disk stepper to move */
176         if (m68k_read_memory_32(0x55398) != 0x0000091b) {
177                 print_error("expecting loop counter of 0x0000091b here, please fix!\n");
178                 exit(0);
179         }
180         m68k_write_memory_32(0x55398, 1);
181         /* reduce loop that waits for disk side change */
182         if (m68k_read_memory_32(0x54ffc) != 0x00000d02) {
183                 print_error("expecting loop counter of 0x00000d02 here, please fix!\n");
184                 exit(0);
185         }
186         m68k_write_memory_32(0x54ffc, 1);
187 }
188
189 /* skip certain parts when rendering improved graphics */
190 void mercenary_patch_render(int shadows)
191 {
192         switch (REG_PC) {
193         case 0x52C42: /* take that branch, so planets get rendered behind obersver */
194                 REG_PC += 10;
195                 break;
196         case 0x528C4: /* take that branch, so planets get rendered behind obersver */
197                 REG_PC += 10;
198                 break;
199         case 0x45806: /* just RTS to avoid crashing of rendering functions when planets are behind obersver */
200                 REG_PC -= 2;
201                 break;
202         case 0x53276: /* skip that branch, so beacons get rendered outside screen */
203                 REG_PC += 2;
204                 break;
205         case 0x53284: /* skip point render, because projected coordinates may be invalid outside screen */
206                 REG_PC += 4;
207                 break;
208         case 0x4FD8E: /* render stars at day, required for shadow calculation */
209         case 0x4FF4A:
210                 if (!shadows)
211                         break;
212                 REG_PC += 2;
213                 break;
214         }
215 }
216
217 /* patch execution for VR improvement */
218 void mercenary_patch_vr(void)
219 {
220         switch (REG_PC) {
221         case 0x59622: /* we are walking/taxi */
222                 info_walking = 1;
223                 break;
224         case 0x597CA: /* we are on a craft */
225                 info_walking = 0;
226                 break;
227         case 0x597F2: /* we are ??? */
228                 info_walking = 0;
229                 break;
230         case 0x59944: /* we are in space */
231                 info_walking = 0;
232                 break;
233         case 0x58EA8: /* at this point we process one step of the player walking on the ground */
234                 if (vr_move.override && vr_move.index < 4) {
235                         REG_D[2] = vr_move.east[vr_move.index];
236                         REG_D[3] = vr_move.north[vr_move.index];
237                         vr_move.index++;
238                 }
239                 break;
240         case 0x58060: /* soft pitch over surface */
241                 REG_D[1] = ((int16_t)REG_D[1] * vr_move.pitch / 256) & 0xfff;
242                 break;
243         }
244 }
245
246 uint32_t mercenary_palette_view(void)
247 {
248         return m68k_read_memory_32(0x007c14);
249 }
250
251 uint32_t mercenary_palette_render(void)
252 {
253         return m68k_read_memory_32(0x007c18);
254 }
255
256 uint32_t mercenary_palette_predefined(void)
257 {
258         return m68k_read_memory_32(0x007a0e);
259 }
260
261 uint32_t mercenary_palette_stars(void)
262 {
263         return 0x500C4+66;
264 }
265
266 void mercenary_get_sky_colors(uint16_t *day, uint16_t *night)
267 {
268         *day = m68k_read_memory_16(0x7946);
269         *night = m68k_read_memory_16(0x7948);
270 }
271
272 void mercenary_get_orientation(double *roll, double *pitch, double *yaw)
273 {
274         int16_t r;
275
276         /* get observer's tilt, pitch, yaw */
277         r = (int16_t)(m68k_read_memory_16(0x007A9E) & 0x3ff);
278         *roll = (double)r / 1024.0 * 2 * M_PI;
279         r = (int16_t)((m68k_read_memory_16(0x007AA0) + 0x201) & 0x3ff); /* add one extra to make view leveled to ground */
280         *pitch = -(double)r / 1024.0 * 2 * M_PI;
281         r = (int16_t)((m68k_read_memory_16(0x007AA2) + 0x200) & 0x3ff);
282         *yaw = -(double)r / 1024.0 * 2 * M_PI;
283 }
284
285 void mercenary_set_orientation(double yaw)
286 {
287         int16_t r;
288
289         /* set the oberver */
290         r = (int16_t)fmod(-yaw * 1024.0 / 2.0 / M_PI, 1024.0);
291         m68k_write_memory_16(0x007AC2, (r - 0x200) & 0x3ff);
292 }
293
294 void mercenary_get_orientation_raw(int16_t *pitch, uint16_t *yaw)
295 {
296         *pitch = m68k_read_memory_16(0x007AA0);
297         *yaw = m68k_read_memory_16(0x007AA2);
298 }
299
300 void mercenary_get_orientation_planet(double *inclination, double *azimuth, int improved)
301 {
302         uint32_t t;
303
304         if (!improved) {
305                 /* get plant's inclination and azimuth */
306                 t = m68k_read_memory_16(0x42C70) & 0x3ff;
307                 *inclination = (double)t / 0x400 * 2 * M_PI;
308                 t = m68k_read_memory_16(0x42C6c) & 0x3ff;
309                 *azimuth = -(double)t / 0x400 * 2 * M_PI;
310         } else {
311                 /* inclination depends on north/south position */
312                 t = m68k_read_memory_32(0x007ABA) >> 2; /* get position */
313                 t += 0x01000000;
314                 t &= 0x03ffffff;
315                 *inclination = (double)t / 0x04000000 * 2 * M_PI;
316                 /* azimuth depends on east/west position and planet's rotation */
317                 t = m68k_read_memory_32(0x007AA6); /* get planet index */
318                 t = m68k_read_memory_32(25322 + t); /* get rotation of planet */
319                 t += m68k_read_memory_32(0x007AB2) >> 2; /* add position */
320                 t &= 0x03ffffff;
321                 *azimuth = -(double)t / 0x04000000 * 2 * M_PI;
322
323         }
324 }
325
326 void mercenary_get_location(int32_t *east, int32_t *height, int32_t *north)
327 {
328         *east = (int32_t)m68k_read_memory_32(0x7a92);
329         *height = (int32_t)m68k_read_memory_32(0x7a30);
330         *north = (int32_t)m68k_read_memory_32(0x7a9a);
331 }
332
333 void mercenary_get_object_info(int *id, int32_t *east, int32_t *height, int32_t *north)
334 {
335         *id = REG_A[0];
336         *east = (int32_t)m68k_read_memory_32(REG_A[0] + 25512);
337         *height = (int32_t)m68k_read_memory_32(REG_A[0] + 26536);
338         *north = (int32_t)m68k_read_memory_32(REG_A[0] + 27560);
339 }
340
341 void mercenary_get_building_exterior_info(int32_t *loc_x, int32_t *loc_z, int32_t *scale_x, int32_t *scale_y, int32_t *scale_z, uint16_t *anim_flag, uint16_t *anim_x, uint16_t *anim_y, uint16_t *anim_z, uint16_t *anim_phase, uint32_t *a4, uint32_t *a0, uint32_t *a5)
342 {
343         /* location of building relative to the observer */
344         *loc_x = (int32_t)m68k_read_memory_32(0x63A8);
345         *loc_z = (int32_t)m68k_read_memory_32(0x6BA8);
346         /* scale of building */
347         *scale_x = (int16_t)m68k_read_memory_16(0x5357E + 0);
348         *scale_y = (int16_t)m68k_read_memory_16(0x5357E + 2);
349         *scale_z = (int16_t)m68k_read_memory_16(0x5357E + 4);
350         /* animation flag (axis to rotate about) */
351         *anim_flag = m68k_read_memory_16(0x53584);
352         /* animation offset */
353         *anim_x = m68k_read_memory_16(0x53582 + 0);
354         *anim_y = m68k_read_memory_16(0x53582 + 2);
355         *anim_z = m68k_read_memory_16(0x53582 + 4);
356         /* animation angle */
357         *anim_phase = m68k_read_memory_16(0x7888);
358         /* vertex list of building's exterior */
359         *a4 = m68k_read_memory_32(0x53638);
360         /* pointer to all datasets */
361         *a0 = 0x0000C13C;
362         /* primitive pointer array */
363         *a5 = 0x0000C03C;
364 }
365
366 void mercenary_coord_building_interior(int16_t *east, int32_t *height1, int32_t *height2, int32_t *height3, int32_t *height4, int16_t *north)
367 {
368         *east = (int16_t)m68k_read_memory_16(5698+REG_A[0]) - (int16_t)REG_A[2];
369         *north = (int16_t)m68k_read_memory_16(6722+REG_A[0]) - (int16_t)REG_A[3];
370         *height1 = -(int16_t)m68k_read_memory_16(0x79AC);
371         *height2 = (int16_t)m68k_read_memory_16(0x7B26) - (int16_t)m68k_read_memory_16(0x79AC);
372         *height3 = (int16_t)m68k_read_memory_16(0x7B28) - (int16_t)m68k_read_memory_16(0x79AC);
373         *height4 = (int16_t)m68k_read_memory_16(0x7B2A) - (int16_t)m68k_read_memory_16(0x79AC);
374 }
375
376 int mercenary_street_color_index(void)
377 {
378         return (m68k_read_memory_16(0x7BE2) >> 5) & 0xf;
379 }
380
381 int mercenary_line_tags_index(void)
382 {
383         return (m68k_read_memory_16(0x7BE2) >> 5) & 0xf;
384 }
385
386 uint16_t mercenary_poly_tags_color(void)
387 {
388         return m68k_read_memory_16(0x7B06);
389 }
390
391 int mercenary_background_index(void)
392 {
393         return m68k_read_memory_16(0x7AEA) >> 2;
394 }
395
396 uint32_t mercenary_planet_scale_index(void)
397 {
398         return 24640;
399 }
400
401 uint32_t mercenary_star_table(void)
402 {
403         return 0x005D8C0;
404 }
405
406 void mercenary_vr_move(int override, int32_t *east, int32_t *north, int yaw, int pitch)
407 {
408         vr_move.override = override;
409         if (east)
410                 memcpy(vr_move.east, east, sizeof(vr_move.east));
411         if (north)
412                 memcpy(vr_move.north, north, sizeof(vr_move.north));
413         vr_move.yaw = yaw;
414         vr_move.pitch = pitch;
415         vr_move.index = 0;
416 }
417
418 int mercenary_get_info_walking(void)
419 {
420         return info_walking;
421 }
422
423 const char *mercenary_name = "Mercenary II - Damocles";
424 const char *mercenary_gamesavesuffix = ".m2save";
425
426 extern const uint8_t mercenary2_mission_disk_1[5][0x1820 << 2];
427 extern const uint8_t mercenary2_mission_disk_2[9][0x1820 << 2];
428
429 const uint8_t *get_mission_disk(int disk, int mission)
430 {
431         switch (disk) {
432         case 1:
433                 if (mission < 1 || mission > 5)
434                         return NULL;
435                 return mercenary2_mission_disk_1[mission - 1];
436         case 2:
437                 if (mission < 1 || mission > 9)
438                         return NULL;
439                 return mercenary2_mission_disk_2[mission - 1];
440         default:
441                 return NULL;
442         }
443 }
444