1 // ============================================================================
2 // Mirror Magic -- McDuffin's Revenge
3 // ----------------------------------------------------------------------------
4 // (c) 1994-2017 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
18 /* graphic position values for game controls */
19 #define ENERGY_XSIZE 32
20 #define ENERGY_YSIZE MAX_LASER_ENERGY
21 #define OVERLOAD_XSIZE ENERGY_XSIZE
22 #define OVERLOAD_YSIZE MAX_LASER_OVERLOAD
24 /* values for Explode_MM() */
25 #define EX_PHASE_START 0
30 /* special positions in the game control window (relative to control window) */
39 #define XX_OVERLOAD 60
40 #define YY_OVERLOAD YY_ENERGY
42 /* special positions in the game control window (relative to main window) */
43 #define DX_LEVEL (DX + XX_LEVEL)
44 #define DY_LEVEL (DY + YY_LEVEL)
45 #define DX_KETTLES (DX + XX_KETTLES)
46 #define DY_KETTLES (DY + YY_KETTLES)
47 #define DX_SCORE (DX + XX_SCORE)
48 #define DY_SCORE (DY + YY_SCORE)
49 #define DX_ENERGY (DX + XX_ENERGY)
50 #define DY_ENERGY (DY + YY_ENERGY)
51 #define DX_OVERLOAD (DX + XX_OVERLOAD)
52 #define DY_OVERLOAD (DY + YY_OVERLOAD)
54 #define IS_LOOP_SOUND(s) ((s) == SND_FUEL)
55 #define IS_MUSIC_SOUND(s) ((s) == SND_TYGER || (s) == SND_VOYAGER)
57 /* game button identifiers */
58 #define GAME_CTRL_ID_LEFT 0
59 #define GAME_CTRL_ID_MIDDLE 1
60 #define GAME_CTRL_ID_RIGHT 2
62 #define NUM_GAME_BUTTONS 3
64 /* values for DrawLaser() */
65 #define DL_LASER_DISABLED 0
66 #define DL_LASER_ENABLED 1
68 /* values for 'click_delay_value' in ClickElement() */
69 #define CLICK_DELAY_SHORT 125
70 #define CLICK_DELAY_LONG 250
71 #define AUTO_ROTATE_DELAY CLICK_DELAY_SHORT
73 /* forward declaration for internal use */
74 static int MovingOrBlocked2Element_MM(int, int);
75 static void Bang_MM(int, int);
76 static void RaiseScore_MM(int);
77 static void RemoveMovingField_MM(int, int);
78 static void InitMovingField_MM(int, int, int);
79 static void ContinueMoving_MM(int, int);
80 static void Moving2Blocked_MM(int, int, int *, int *);
83 static int get_element_angle(int element)
85 int element_phase = get_element_phase(element);
87 if (IS_MIRROR_FIXED(element) ||
88 IS_MCDUFFIN(element) ||
91 return 4 * element_phase;
96 static int get_opposite_angle(int angle)
98 int opposite_angle = angle + ANG_RAY_180;
100 /* make sure "opposite_angle" is in valid interval [0, 15] */
101 return (opposite_angle + 16) % 16;
104 static int get_mirrored_angle(int laser_angle, int mirror_angle)
106 int reflected_angle = 16 - laser_angle + mirror_angle;
108 /* make sure "reflected_angle" is in valid interval [0, 15] */
109 return (reflected_angle + 16) % 16;
112 static void InitMovDir_MM(int x, int y)
114 int element = Feld[x][y];
115 static int direction[3][4] =
117 { MV_RIGHT, MV_UP, MV_LEFT, MV_DOWN },
118 { MV_LEFT, MV_DOWN, MV_RIGHT, MV_UP },
119 { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN }
124 case EL_PACMAN_RIGHT:
128 Feld[x][y] = EL_PACMAN;
129 MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT];
137 static void InitField(int x, int y, boolean init_game)
139 int element = Feld[x][y];
144 Feld[x][y] = EL_EMPTY;
149 if (native_mm_level.auto_count_kettles)
150 game_mm.kettles_still_needed++;
153 case EL_LIGHTBULB_OFF:
154 game_mm.lights_still_needed++;
158 if (IS_MIRROR(element) ||
159 IS_BEAMER_OLD(element) ||
160 IS_BEAMER(element) ||
162 IS_POLAR_CROSS(element) ||
163 IS_DF_MIRROR(element) ||
164 IS_DF_MIRROR_AUTO(element) ||
165 IS_GRID_STEEL_AUTO(element) ||
166 IS_GRID_WOOD_AUTO(element) ||
167 IS_FIBRE_OPTIC(element))
169 if (IS_BEAMER_OLD(element))
171 Feld[x][y] = EL_BEAMER_BLUE_START + (element - EL_BEAMER_START);
172 element = Feld[x][y];
175 if (!IS_FIBRE_OPTIC(element))
177 static int steps_grid_auto = 0;
179 if (game_mm.num_cycle == 0) /* initialize cycle steps for grids */
180 steps_grid_auto = RND(16) * (RND(2) ? -1 : +1);
182 if (IS_GRID_STEEL_AUTO(element) ||
183 IS_GRID_WOOD_AUTO(element))
184 game_mm.cycle[game_mm.num_cycle].steps = steps_grid_auto;
186 game_mm.cycle[game_mm.num_cycle].steps = RND(16) * (RND(2) ? -1 : +1);
188 game_mm.cycle[game_mm.num_cycle].x = x;
189 game_mm.cycle[game_mm.num_cycle].y = y;
193 if (IS_BEAMER(element) || IS_FIBRE_OPTIC(element))
195 int beamer_nr = BEAMER_NR(element);
196 int nr = laser.beamer[beamer_nr][0].num;
198 laser.beamer[beamer_nr][nr].x = x;
199 laser.beamer[beamer_nr][nr].y = y;
200 laser.beamer[beamer_nr][nr].num = 1;
203 else if (IS_PACMAN(element))
207 else if (IS_MCDUFFIN(element) || IS_LASER(element))
209 laser.start_edge.x = x;
210 laser.start_edge.y = y;
211 laser.start_angle = get_element_angle(element);
218 static void InitCycleElements()
222 if (game_mm.num_cycle == 0) /* no elements to cycle */
227 for(j=0; j<game_mm.num_cycle; j++)
229 int x = game_mm.cycle[j].x;
230 int y = game_mm.cycle[j].y;
231 int step = SIGN(game_mm.cycle[j].steps);
232 int last_element = Feld[x][y];
233 int next_element = get_rotated_element(last_element, step);
235 if (!game_mm.cycle[j].steps)
238 Feld[x][y] = next_element;
241 game_mm.cycle[j].steps -= step;
248 if (setup.quick_doors)
252 Delay(AUTO_ROTATE_DELAY);
256 static void InitLaser()
258 int start_element = Feld[laser.start_edge.x][laser.start_edge.y];
259 int step = (IS_LASER(start_element) ? 4 : 0);
261 LX = laser.start_edge.x * TILEX;
262 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
265 LX += (laser.start_angle == ANG_RAY_RIGHT ? 28 + step : 0 - step);
267 LY = laser.start_edge.y * TILEY;
268 if (laser.start_angle == ANG_RAY_UP || laser.start_angle == ANG_RAY_DOWN)
269 LY += (laser.start_angle == ANG_RAY_DOWN ? 28 + step : 0 - step);
273 XS = 2 * Step[laser.start_angle].x;
274 YS = 2 * Step[laser.start_angle].y;
276 laser.current_angle = laser.start_angle;
278 laser.num_damages = 0;
280 laser.num_beamers = 0;
281 laser.beamer_edge[0] = 0;
283 AddLaserEdge(LX, LY); /* set laser starting edge */
285 pen_ray = GetPixelFromRGB(window,
286 native_mm_level.laser_red * 0xFF,
287 native_mm_level.laser_green * 0xFF,
288 native_mm_level.laser_blue * 0xFF);
291 void InitGameEngine_MM()
295 /* set global editor control values */
296 editor.draw_walls_masked = FALSE;
298 /* set global game control values */
299 game_mm.num_cycle = 0;
300 game_mm.num_pacman = 0;
303 game_mm.energy_left = native_mm_level.time;
304 game_mm.kettles_still_needed =
305 (native_mm_level.auto_count_kettles ? 0 : native_mm_level.kettles_needed);
306 game_mm.lights_still_needed = 0;
307 game_mm.num_keys = 0;
309 game_mm.level_solved = FALSE;
310 game_mm.game_over = FALSE;
311 game_mm.game_over_cause = 0;
313 /* set global laser control values (must be set before "InitLaser()") */
314 laser.start_edge.x = 0;
315 laser.start_edge.y = 0;
316 laser.start_angle = 0;
318 for (i=0; i<MAX_NUM_BEAMERS; i++)
319 laser.beamer[i][0].num = laser.beamer[i][1].num = 0;
321 laser.overloaded = FALSE;
322 laser.overload_value = 0;
323 laser.fuse_off = FALSE;
324 laser.fuse_x = laser.fuse_y = -1;
326 laser.dest_element = EL_EMPTY;
331 for (x=0; x<lev_fieldx; x++)
333 for (y=0; y<lev_fieldy; y++)
335 Feld[x][y] = Ur[x][y];
336 Hit[x][y] = Box[x][y] = 0;
338 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
339 Store[x][y] = Store2[x][y] = 0;
343 InitField(x, y, TRUE);
347 CloseDoor(DOOR_CLOSE_1);
354 /* copy default game door content to main double buffer */
355 BlitBitmap(pix[PIX_DOOR], drawto,
356 DOOR_GFX_PAGEX5, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY);
360 DrawText(DX_LEVEL, DY_LEVEL,
361 int2str(level_nr, 2), FONT_TEXT_2);
362 DrawText(DX_KETTLES, DY_KETTLES,
363 int2str(game_mm.kettles_still_needed, 3), FONT_TEXT_2);
364 DrawText(DX_SCORE, DY_SCORE,
365 int2str(game_mm.score, 4), FONT_TEXT_2);
372 /* copy actual game door content to door double buffer for OpenDoor() */
373 BlitBitmap(drawto, pix[PIX_DB_DOOR],
374 DX, DY, DXSIZE, DYSIZE, DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1);
377 OpenDoor(DOOR_OPEN_ALL);
379 if (setup.sound_loops)
380 PlaySoundExt(SND_FUEL, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT, SND_CTRL_PLAY_LOOP);
382 #if 0 // !!! TEMPORARILY DISABLED !!!
383 for(i=0; i<=game_mm.energy_left; i+=2)
385 if (!setup.sound_loops)
386 PlaySoundStereo(SND_FUEL, SOUND_MAX_RIGHT);
389 BlitBitmap(pix[PIX_DOOR], drawto,
390 DOOR_GFX_PAGEX4 + XX_ENERGY,
391 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
393 DX_ENERGY, DY_ENERGY + ENERGY_YSIZE - i);
396 redraw_mask |= REDRAW_DOOR_1;
402 if (setup.quick_doors)
409 if (setup.sound_loops)
414 if (setup.sound_music && num_bg_loops)
415 PlayMusic(level_nr % num_bg_loops);
421 void AddLaserEdge(int lx, int ly)
423 if (lx < -2 || ly < -2 || lx >= SXSIZE + 2 || ly >= SYSIZE + 2)
425 Error(ERR_WARN, "AddLaserEdge: out of bounds: %d, %d", lx, ly);
429 laser.edge[laser.num_edges].x = SX + 2 + lx;
430 laser.edge[laser.num_edges].y = SY + 2 + ly;
436 void AddDamagedField(int ex, int ey)
438 laser.damage[laser.num_damages].is_mirror = FALSE;
439 laser.damage[laser.num_damages].angle = laser.current_angle;
440 laser.damage[laser.num_damages].edge = laser.num_edges;
441 laser.damage[laser.num_damages].x = ex;
442 laser.damage[laser.num_damages].y = ey;
452 int last_x = laser.edge[laser.num_edges - 1].x - SX - 2;
453 int last_y = laser.edge[laser.num_edges - 1].y - SY - 2;
455 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
461 static int getMaskFromElement(int element)
463 if (IS_GRID(element))
464 return GFX_MASK_GRID_00 + get_element_phase(element);
465 else if (IS_MCDUFFIN(element))
466 return GFX_MASK_MCDUFFIN_00 + get_element_phase(element);
467 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
468 return GFX_MASK_RECTANGLE;
470 return GFX_MASK_CIRCLE;
478 printf("ScanPixel: start scanning at (%d, %d) [%d, %d] [%d, %d]\n",
479 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
482 /* follow laser beam until it hits something (at least the screen border) */
483 while (hit_mask == HIT_MASK_NO_HIT)
489 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
490 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
492 printf("ScanPixel: touched screen border!\n");
503 px = SX + LX + (i % 2) * 2;
504 py = SY + LY + (i / 2) * 2;
505 lx = (px - SX + TILEX) / TILEX - 1; /* ...+TILEX...-1 to get correct */
506 ly = (py - SY + TILEY) / TILEY - 1; /* negative values! */
508 if (IN_LEV_FIELD(lx, ly))
510 int element = Feld[lx][ly];
512 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
514 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
517 ((py - SY - ly * TILEY) / MINI_TILEX) * 2 +
518 (px - SX - lx * TILEX) / MINI_TILEY;
520 pixel = ((element & (1 << pos)) ? 1 : 0);
524 int graphic_mask = getMaskFromElement(element);
526 int dx = px - lx * TILEX;
527 int dy = py - ly * TILEY;
529 mask_x = (graphic_mask % GFX_PER_LINE) * TILEX + dx;
530 mask_y = (graphic_mask / GFX_PER_LINE) * TILEY + dy;
533 // !!! temporary fix to compile -- change to game graphics !!!
534 pixel = (ReadPixel(drawto, mask_x, mask_y) ? 1 : 0);
536 pixel = (ReadPixel(pix[PIX_BACK], mask_x, mask_y) ? 1 : 0);
542 if (px < REAL_SX || px >= REAL_SX + FULL_SXSIZE ||
543 py < REAL_SY || py >= REAL_SY + FULL_SYSIZE)
549 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
550 hit_mask |= (1 << i);
553 if (hit_mask == HIT_MASK_NO_HIT)
555 /* hit nothing -- go on with another step */
567 int end = 0, rf = laser.num_edges;
569 laser.overloaded = FALSE;
570 laser.stops_inside_element = FALSE;
572 DrawLaser(0, DL_LASER_ENABLED);
575 printf("Start scanning with LX == %d, LY == %d, XS == %d, YS == %d\n",
583 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
586 laser.overloaded = TRUE;
590 hit_mask = ScanPixel();
593 printf("Hit something at LX == %d, LY == %d, XS == %d, YS == %d\n",
597 /* hit something -- check out what it was */
598 ELX = (LX + XS) / TILEX;
599 ELY = (LY + YS) / TILEY;
602 printf("hit_mask (1) == '%x' (%d, %d) (%d, %d)\n",
603 hit_mask, LX, LY, ELX, ELY);
606 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
609 laser.dest_element = element;
614 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
616 /* we have hit the top-right and bottom-left element --
617 choose the bottom-left one */
618 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
619 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
620 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
621 ELX = (LX - 2) / TILEX;
622 ELY = (LY + 2) / TILEY;
625 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
627 /* we have hit the top-left and bottom-right element --
628 choose the top-left one */
629 /* !!! SEE ABOVE !!! */
630 ELX = (LX - 2) / TILEX;
631 ELY = (LY - 2) / TILEY;
635 printf("hit_mask (2) == '%x' (%d, %d) (%d, %d)\n",
636 hit_mask, LX, LY, ELX, ELY);
639 element = Feld[ELX][ELY];
640 laser.dest_element = element;
643 printf("Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]\n",
646 LX % TILEX, LY % TILEY,
651 if (!IN_LEV_FIELD(ELX, ELY))
652 printf("WARNING! (1) %d, %d (%d)\n", ELX, ELY, element);
655 if (element == EL_EMPTY)
657 if (!HitOnlyAnEdge(element, hit_mask))
660 else if (element == EL_FUSE_ON)
662 if (HitPolarizer(element, hit_mask))
665 else if (IS_GRID(element) || IS_DF_GRID(element))
667 if (HitPolarizer(element, hit_mask))
670 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
671 element == EL_GATE_STONE || element == EL_GATE_WOOD)
673 if (HitBlock(element, hit_mask))
679 else if (IS_MCDUFFIN(element))
681 if (HitLaserSource(element, hit_mask))
684 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
685 IS_RECEIVER(element))
687 if (HitLaserDestination(element, hit_mask))
690 else if (IS_WALL(element))
692 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
694 if (HitReflectingWalls(element, hit_mask))
699 if (HitAbsorbingWalls(element, hit_mask))
705 if (HitElement(element, hit_mask))
710 DrawLaser(rf - 1, DL_LASER_ENABLED);
711 rf = laser.num_edges;
715 if (laser.dest_element != Feld[ELX][ELY])
717 printf("ALARM: laser.dest_element == %d, Feld[ELX][ELY] == %d\n",
718 laser.dest_element, Feld[ELX][ELY]);
722 if (!end && !laser.stops_inside_element && !StepBehind())
725 printf("ScanLaser: Go one step back\n");
730 AddLaserEdge(LX, LY);
734 DrawLaser(rf - 1, DL_LASER_ENABLED);
739 if (!IN_LEV_FIELD(ELX, ELY))
740 printf("WARNING! (2) %d, %d\n", ELX, ELY);
744 void DrawLaserExt(int start_edge, int num_edges, int mode)
750 printf("DrawLaserExt: start_edge, num_edges, mode == %d, %d, %d\n",
751 start_edge, num_edges, mode);
756 Error(ERR_WARN, "DrawLaserExt: start_edge < 0");
762 Error(ERR_WARN, "DrawLaserExt: num_edges < 0");
767 if (mode == DL_LASER_DISABLED)
769 printf("DrawLaser: Delete laser from edge %d\n", start_edge);
773 /* now draw the laser to the backbuffer and (if enabled) to the screen */
774 DrawLines(drawto, &laser.edge[start_edge], num_edges,
775 (mode == DL_LASER_ENABLED ? pen_ray : pen_bg));
777 redraw_mask |= REDRAW_FIELD;
779 if (mode == DL_LASER_ENABLED)
782 /* after the laser was deleted, the "damaged" graphics must be restored */
783 if (laser.num_damages)
785 int damage_start = 0;
788 /* determine the starting edge, from which graphics need to be restored */
791 for(i=0; i<laser.num_damages; i++)
793 if (laser.damage[i].edge == start_edge + 1)
801 /* restore graphics from this starting edge to the end of damage list */
802 for(i=damage_start; i<laser.num_damages; i++)
804 int lx = laser.damage[i].x;
805 int ly = laser.damage[i].y;
806 int element = Feld[lx][ly];
808 if (Hit[lx][ly] == laser.damage[i].edge)
809 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
812 if (Box[lx][ly] == laser.damage[i].edge)
815 if (IS_DRAWABLE(element))
816 DrawField_MM(lx, ly);
819 elx = laser.damage[damage_start].x;
820 ely = laser.damage[damage_start].y;
821 element = Feld[elx][ely];
824 if (IS_BEAMER(element))
828 for (i=0; i<laser.num_beamers; i++)
829 printf("-> %d\n", laser.beamer_edge[i]);
830 printf("DrawLaserExt: IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]\n",
831 mode, elx, ely, Hit[elx][ely], start_edge);
832 printf("DrawLaserExt: IS_BEAMER: %d / %d\n",
833 get_element_angle(element), laser.damage[damage_start].angle);
837 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
838 laser.num_beamers > 0 &&
839 start_edge == laser.beamer_edge[laser.num_beamers - 1])
841 /* element is outgoing beamer */
842 laser.num_damages = damage_start + 1;
843 if (IS_BEAMER(element))
844 laser.current_angle = get_element_angle(element);
848 /* element is incoming beamer or other element */
849 laser.num_damages = damage_start;
850 laser.current_angle = laser.damage[laser.num_damages].angle;
855 /* no damages but McDuffin himself (who needs to be redrawn anyway) */
857 elx = laser.start_edge.x;
858 ely = laser.start_edge.y;
859 element = Feld[elx][ely];
862 laser.num_edges = start_edge + 1;
864 laser.current_angle = laser.start_angle;
865 LX = laser.edge[start_edge].x - (SX + 2);
866 LY = laser.edge[start_edge].y - (SY + 2);
867 XS = 2 * Step[laser.current_angle].x;
868 YS = 2 * Step[laser.current_angle].y;
871 printf("DrawLaser: Set (LX, LY) to (%d, %d) [%d]\n",
877 if (IS_BEAMER(element) ||
878 IS_FIBRE_OPTIC(element) ||
879 IS_PACMAN(element) ||
881 IS_POLAR_CROSS(element) ||
882 element == EL_FUSE_ON)
887 printf("element == %d\n", element);
890 if (IS_22_5_ANGLE(laser.current_angle)) /* neither 90° nor 45° angle */
891 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
895 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
896 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
897 (laser.num_beamers == 0 ||
898 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
900 /* element is incoming beamer or other element */
901 step_size = -step_size;
906 if (IS_BEAMER(element))
908 printf("start_edge == %d, laser.beamer_edge == %d\n",
909 start_edge, laser.beamer_edge);
913 LX += step_size * XS;
914 LY += step_size * YS;
916 else if (element != EL_EMPTY)
925 printf("DrawLaser: Finally: (LX, LY) to (%d, %d) [%d]\n",
930 void DrawLaser(int start_edge, int mode)
932 if (laser.num_edges - start_edge < 0)
934 Error(ERR_WARN, "DrawLaser: laser.num_edges - start_edge < 0");
938 /* check if laser is interrupted by beamer element */
939 if (laser.num_beamers > 0 &&
940 start_edge < laser.beamer_edge[laser.num_beamers - 1])
942 if (mode == DL_LASER_ENABLED)
945 int tmp_start_edge = start_edge;
947 /* draw laser segments forward from the start to the last beamer */
948 for (i=0; i<laser.num_beamers; i++)
950 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
952 if (tmp_num_edges <= 0)
956 printf("DrawLaser: DL_LASER_ENABLED: i==%d: %d, %d\n",
957 i, laser.beamer_edge[i], tmp_start_edge);
960 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
961 tmp_start_edge = laser.beamer_edge[i];
964 /* draw last segment from last beamer to the end */
965 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
971 int last_num_edges = laser.num_edges;
972 int num_beamers = laser.num_beamers;
974 /* delete laser segments backward from the end to the first beamer */
975 for (i=num_beamers-1; i>=0; i--)
977 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
979 if (laser.beamer_edge[i] - start_edge <= 0)
982 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
983 last_num_edges = laser.beamer_edge[i];
988 if (last_num_edges - start_edge <= 0)
989 printf("DrawLaser: DL_LASER_DISABLED: %d, %d\n",
990 last_num_edges, start_edge);
993 /* delete first segment from start to the first beamer */
994 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
998 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1001 boolean HitElement(int element, int hit_mask)
1003 if (HitOnlyAnEdge(element, hit_mask))
1006 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1007 element = MovingOrBlocked2Element_MM(ELX, ELY);
1010 printf("HitElement (1): element == %d\n", element);
1014 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1015 printf("HitElement (%d): EXACT MATCH @ (%d, %d)\n", element, ELX, ELY);
1017 printf("HitElement (%d): FUZZY MATCH @ (%d, %d)\n", element, ELX, ELY);
1020 AddDamagedField(ELX, ELY);
1022 /* this is more precise: check if laser would go through the center */
1023 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1025 /* skip the whole element before continuing the scan */
1031 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1033 if (LX/TILEX > ELX || LY/TILEY > ELY)
1035 /* skipping scan positions to the right and down skips one scan
1036 position too much, because this is only the top left scan position
1037 of totally four scan positions (plus one to the right, one to the
1038 bottom and one to the bottom right) */
1048 printf("HitElement (2): element == %d\n", element);
1051 if (LX + 5 * XS < 0 ||
1061 printf("HitElement (3): element == %d\n", element);
1064 if (IS_POLAR(element) &&
1065 ((element - EL_POLAR_START) % 2 ||
1066 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1068 PlaySoundStereo(SND_KINK, ST(ELX));
1069 laser.num_damages--;
1074 if (IS_POLAR_CROSS(element) &&
1075 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1077 PlaySoundStereo(SND_KINK, ST(ELX));
1078 laser.num_damages--;
1083 if (!IS_BEAMER(element) &&
1084 !IS_FIBRE_OPTIC(element) &&
1085 !IS_GRID_WOOD(element) &&
1086 element != EL_FUEL_EMPTY)
1089 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1090 printf("EXACT MATCH @ (%d, %d)\n", ELX, ELY);
1092 printf("FUZZY MATCH @ (%d, %d)\n", ELX, ELY);
1095 LX = ELX * TILEX + 14;
1096 LY = ELY * TILEY + 14;
1097 AddLaserEdge(LX, LY);
1100 if (IS_MIRROR(element) ||
1101 IS_MIRROR_FIXED(element) ||
1102 IS_POLAR(element) ||
1103 IS_POLAR_CROSS(element) ||
1104 IS_DF_MIRROR(element) ||
1105 IS_DF_MIRROR_AUTO(element) ||
1106 element == EL_PRISM ||
1107 element == EL_REFRACTOR)
1109 int current_angle = laser.current_angle;
1112 laser.num_damages--;
1113 AddDamagedField(ELX, ELY);
1114 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1117 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1119 if (IS_MIRROR(element) ||
1120 IS_MIRROR_FIXED(element) ||
1121 IS_DF_MIRROR(element) ||
1122 IS_DF_MIRROR_AUTO(element))
1123 laser.current_angle = get_mirrored_angle(laser.current_angle,
1124 get_element_angle(element));
1126 if (element == EL_PRISM || element == EL_REFRACTOR)
1127 laser.current_angle = RND(16);
1129 XS = 2 * Step[laser.current_angle].x;
1130 YS = 2 * Step[laser.current_angle].y;
1132 if (!IS_22_5_ANGLE(laser.current_angle)) /* 90° or 45° angle */
1137 LX += step_size * XS;
1138 LY += step_size * YS;
1141 /* draw sparkles on mirror */
1142 if ((IS_MIRROR(element) || IS_MIRROR_FIXED(element)) &&
1143 current_angle != laser.current_angle)
1145 MoveSprite(vp, &Pfeil[2], 4 + 16 * ELX, 5 + 16 * ELY + 1);
1149 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1150 current_angle != laser.current_angle)
1151 PlaySoundStereo(SND_LASER, ST(ELX));
1154 (get_opposite_angle(laser.current_angle) ==
1155 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1157 return (laser.overloaded ? TRUE : FALSE);
1160 if (element == EL_FUEL_FULL)
1162 laser.stops_inside_element = TRUE;
1167 if (element == EL_BOMB || element == EL_MINE)
1169 PlaySoundStereo(SND_KINK, ST(ELX));
1171 if (element == EL_MINE)
1172 laser.overloaded = TRUE;
1175 if (element == EL_KETTLE ||
1176 element == EL_CELL ||
1177 element == EL_KEY ||
1178 element == EL_LIGHTBALL ||
1179 element == EL_PACMAN ||
1182 if (!IS_PACMAN(element))
1185 if (element == EL_PACMAN)
1188 if (element == EL_KETTLE || element == EL_CELL)
1192 if (game_mm.kettles_still_needed == 0)
1195 static int xy[4][2] =
1203 PlaySoundStereo(SND_KLING, ST(ELX));
1205 for(y=0; y<lev_fieldy; y++)
1207 for(x=0; x<lev_fieldx; x++)
1209 /* initiate opening animation of exit door */
1210 if (Feld[x][y] == EL_EXIT_CLOSED)
1211 Feld[x][y] = EL_EXIT_OPENING;
1213 /* remove field that blocks receiver */
1214 if (IS_RECEIVER(Feld[x][y]))
1216 int phase = Feld[x][y] - EL_RECEIVER_START;
1217 int blocking_x, blocking_y;
1219 blocking_x = x + xy[phase][0];
1220 blocking_y = y + xy[phase][1];
1222 if (IN_LEV_FIELD(blocking_x, blocking_y))
1224 Feld[blocking_x][blocking_y] = EL_EMPTY;
1225 DrawField_MM(blocking_x, blocking_y);
1231 DrawLaser(0, DL_LASER_ENABLED);
1234 else if (element == EL_KEY)
1236 else if (element == EL_LIGHTBALL)
1238 else if (IS_PACMAN(element))
1240 DeletePacMan(ELX, ELY);
1247 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1249 PlaySoundStereo(SND_KINK, ST(ELX));
1251 DrawLaser(0, DL_LASER_ENABLED);
1253 if (Feld[ELX][ELY] == EL_LIGHTBULB_OFF)
1255 Feld[ELX][ELY] = EL_LIGHTBULB_ON;
1256 game_mm.lights_still_needed--;
1260 Feld[ELX][ELY] = EL_LIGHTBULB_OFF;
1261 game_mm.lights_still_needed++;
1264 DrawField_MM(ELX, ELY);
1265 DrawLaser(0, DL_LASER_ENABLED);
1270 laser.stops_inside_element = TRUE;
1276 printf("HitElement (4): element == %d\n", element);
1279 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1280 laser.num_beamers < MAX_NUM_BEAMERS &&
1281 laser.beamer[BEAMER_NR(element)][1].num)
1283 int beamer_angle = get_element_angle(element);
1284 int beamer_nr = BEAMER_NR(element);
1288 printf("HitElement (BEAMER): element == %d\n", element);
1291 laser.num_damages--;
1293 if (IS_FIBRE_OPTIC(element) ||
1294 laser.current_angle == get_opposite_angle(beamer_angle))
1298 LX = ELX * TILEX + 14;
1299 LY = ELY * TILEY + 14;
1300 AddLaserEdge(LX, LY);
1301 AddDamagedField(ELX, ELY);
1302 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1305 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1307 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1308 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1309 ELX = laser.beamer[beamer_nr][pos].x;
1310 ELY = laser.beamer[beamer_nr][pos].y;
1311 LX = ELX * TILEX + 14;
1312 LY = ELY * TILEY + 14;
1314 if (IS_BEAMER(element))
1316 laser.current_angle = get_element_angle(Feld[ELX][ELY]);
1317 XS = 2 * Step[laser.current_angle].x;
1318 YS = 2 * Step[laser.current_angle].y;
1321 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1322 AddLaserEdge(LX, LY);
1323 AddDamagedField(ELX, ELY);
1324 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1327 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1329 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1334 LX += step_size * XS;
1335 LY += step_size * YS;
1337 laser.num_beamers++;
1346 boolean HitOnlyAnEdge(int element, int hit_mask)
1348 /* check if the laser hit only the edge of an element and, if so, go on */
1351 printf("LX, LY, hit_mask == %d, %d, %d\n", LX, LY, hit_mask);
1354 if ((hit_mask == HIT_MASK_TOPLEFT ||
1355 hit_mask == HIT_MASK_TOPRIGHT ||
1356 hit_mask == HIT_MASK_BOTTOMLEFT ||
1357 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1358 laser.current_angle % 4) /* angle is not 90° */
1362 if (hit_mask == HIT_MASK_TOPLEFT)
1367 else if (hit_mask == HIT_MASK_TOPRIGHT)
1372 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1377 else /* (hit_mask == HIT_MASK_BOTTOMRIGHT) */
1383 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1388 printf("[HitOnlyAnEdge() == TRUE]\n");
1395 printf("[HitOnlyAnEdge() == FALSE]\n");
1401 boolean HitPolarizer(int element, int hit_mask)
1403 if (HitOnlyAnEdge(element, hit_mask))
1406 if (IS_DF_GRID(element))
1408 int grid_angle = get_element_angle(element);
1411 printf("HitPolarizer: angle: grid == %d, laser == %d\n",
1412 grid_angle, laser.current_angle);
1415 AddLaserEdge(LX, LY);
1416 AddDamagedField(ELX, ELY);
1419 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1421 if (laser.current_angle == grid_angle ||
1422 laser.current_angle == get_opposite_angle(grid_angle))
1424 /* skip the whole element before continuing the scan */
1430 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1432 if (LX/TILEX > ELX || LY/TILEY > ELY)
1434 /* skipping scan positions to the right and down skips one scan
1435 position too much, because this is only the top left scan position
1436 of totally four scan positions (plus one to the right, one to the
1437 bottom and one to the bottom right) */
1443 AddLaserEdge(LX, LY);
1449 printf("HitPolarizer: LX, LY == %d, %d [%d, %d] [%d, %d]\n",
1451 LX / TILEX, LY / TILEY,
1452 LX % TILEX, LY % TILEY);
1457 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1458 return HitReflectingWalls(element, hit_mask);
1460 return HitAbsorbingWalls(element, hit_mask);
1462 else if (IS_GRID_STEEL(element))
1463 return HitReflectingWalls(element, hit_mask);
1464 else /* IS_GRID_WOOD */
1465 return HitAbsorbingWalls(element, hit_mask);
1470 boolean HitBlock(int element, int hit_mask)
1472 boolean check = FALSE;
1474 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1475 game_mm.num_keys == 0)
1478 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1481 int ex = ELX * TILEX + 14;
1482 int ey = ELY * TILEY + 14;
1491 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1496 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1497 return HitAbsorbingWalls(element, hit_mask);
1501 AddLaserEdge(LX - XS, LY - YS);
1502 AddDamagedField(ELX, ELY);
1505 Box[ELX][ELY] = laser.num_edges;
1507 return HitReflectingWalls(element, hit_mask);
1510 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1512 int xs = XS / 2, ys = YS / 2;
1513 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1514 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1516 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1517 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1519 laser.overloaded = (element == EL_GATE_STONE);
1523 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1524 (hit_mask == HIT_MASK_TOP ||
1525 hit_mask == HIT_MASK_LEFT ||
1526 hit_mask == HIT_MASK_RIGHT ||
1527 hit_mask == HIT_MASK_BOTTOM))
1528 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1529 hit_mask == HIT_MASK_BOTTOM),
1530 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1531 hit_mask == HIT_MASK_RIGHT));
1532 AddLaserEdge(LX, LY);
1537 if (element == EL_GATE_STONE && Box[ELX][ELY])
1539 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1551 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1553 int xs = XS / 2, ys = YS / 2;
1554 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1555 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1557 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1558 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1560 laser.overloaded = (element == EL_BLOCK_STONE);
1565 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1566 (hit_mask == HIT_MASK_TOP ||
1567 hit_mask == HIT_MASK_LEFT ||
1568 hit_mask == HIT_MASK_RIGHT ||
1569 hit_mask == HIT_MASK_BOTTOM))
1570 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1571 hit_mask == HIT_MASK_BOTTOM),
1572 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1573 hit_mask == HIT_MASK_RIGHT));
1574 AddDamagedField(ELX, ELY);
1576 LX = ELX * TILEX + 14;
1577 LY = ELY * TILEY + 14;
1578 AddLaserEdge(LX, LY);
1580 laser.stops_inside_element = TRUE;
1588 boolean HitLaserSource(int element, int hit_mask)
1590 if (HitOnlyAnEdge(element, hit_mask))
1593 PlaySoundStereo(SND_AUTSCH, ST(ELX));
1594 laser.overloaded = TRUE;
1599 boolean HitLaserDestination(int element, int hit_mask)
1601 if (HitOnlyAnEdge(element, hit_mask))
1604 if (element != EL_EXIT_OPEN &&
1605 !(IS_RECEIVER(element) &&
1606 game_mm.kettles_still_needed == 0 &&
1607 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1609 PlaySoundStereo(SND_HOLZ, ST(ELX));
1613 if (IS_RECEIVER(element) ||
1614 (IS_22_5_ANGLE(laser.current_angle) &&
1615 (ELX != (LX + 6 * XS) / TILEX ||
1616 ELY != (LY + 6 * YS) / TILEY ||
1625 LX = ELX * TILEX + 14;
1626 LY = ELY * TILEY + 14;
1628 laser.stops_inside_element = TRUE;
1631 AddLaserEdge(LX, LY);
1632 AddDamagedField(ELX, ELY);
1634 if (game_mm.lights_still_needed == 0)
1635 game_mm.level_solved = TRUE;
1640 boolean HitReflectingWalls(int element, int hit_mask)
1642 /* check if laser hits side of a wall with an angle that is not 90° */
1643 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
1644 hit_mask == HIT_MASK_LEFT ||
1645 hit_mask == HIT_MASK_RIGHT ||
1646 hit_mask == HIT_MASK_BOTTOM))
1648 PlaySoundStereo(SND_HUI, ST(ELX));
1651 if (!IS_DF_GRID(element))
1652 AddLaserEdge(LX, LY);
1654 /* check if laser hits wall with an angle of 45° */
1655 if (!IS_22_5_ANGLE(laser.current_angle))
1657 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1660 laser.current_angle = get_mirrored_angle(laser.current_angle,
1663 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
1666 laser.current_angle = get_mirrored_angle(laser.current_angle,
1670 AddLaserEdge(LX, LY);
1671 XS = 2 * Step[laser.current_angle].x;
1672 YS = 2 * Step[laser.current_angle].y;
1676 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1678 laser.current_angle = get_mirrored_angle(laser.current_angle,
1683 if (!IS_DF_GRID(element))
1684 AddLaserEdge(LX, LY);
1689 if (!IS_DF_GRID(element))
1690 AddLaserEdge(LX, LY + YS / 2);
1693 if (!IS_DF_GRID(element))
1694 AddLaserEdge(LX, LY);
1697 YS = 2 * Step[laser.current_angle].y;
1701 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
1703 laser.current_angle = get_mirrored_angle(laser.current_angle,
1708 if (!IS_DF_GRID(element))
1709 AddLaserEdge(LX, LY);
1714 if (!IS_DF_GRID(element))
1715 AddLaserEdge(LX + XS / 2, LY);
1718 if (!IS_DF_GRID(element))
1719 AddLaserEdge(LX, LY);
1722 XS = 2 * Step[laser.current_angle].x;
1728 /* reflection at the edge of reflecting DF style wall */
1729 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
1731 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
1732 hit_mask == HIT_MASK_TOPRIGHT) ||
1733 ((laser.current_angle == 5 || laser.current_angle == 7) &&
1734 hit_mask == HIT_MASK_TOPLEFT) ||
1735 ((laser.current_angle == 9 || laser.current_angle == 11) &&
1736 hit_mask == HIT_MASK_BOTTOMLEFT) ||
1737 ((laser.current_angle == 13 || laser.current_angle == 15) &&
1738 hit_mask == HIT_MASK_BOTTOMRIGHT))
1741 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
1742 ANG_MIRROR_135 : ANG_MIRROR_45);
1744 PlaySoundStereo(SND_HUI, ST(ELX));
1745 AddDamagedField(ELX, ELY);
1746 AddLaserEdge(LX, LY);
1748 laser.current_angle = get_mirrored_angle(laser.current_angle,
1755 AddLaserEdge(LX, LY);
1761 /* reflection inside an edge of reflecting DF style wall */
1762 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
1764 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
1765 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
1766 ((laser.current_angle == 5 || laser.current_angle == 7) &&
1767 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
1768 ((laser.current_angle == 9 || laser.current_angle == 11) &&
1769 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
1770 ((laser.current_angle == 13 || laser.current_angle == 15) &&
1771 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
1774 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
1775 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
1776 ANG_MIRROR_135 : ANG_MIRROR_45);
1778 PlaySoundStereo(SND_HUI, ST(ELX));
1780 AddDamagedField(ELX, ELY);
1782 AddLaserEdge(LX - XS, LY - YS);
1783 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
1784 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
1786 laser.current_angle = get_mirrored_angle(laser.current_angle,
1793 AddLaserEdge(LX, LY);
1799 /* check if laser hits DF style wall with an angle of 90° */
1800 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
1802 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
1803 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
1804 (IS_VERT_ANGLE(laser.current_angle) &&
1805 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
1807 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
1809 /* laser at last step touched nothing or the same side of the wall */
1810 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
1812 AddDamagedField(ELX, ELY);
1818 last_hit_mask = hit_mask;
1825 if (!HitOnlyAnEdge(element, hit_mask))
1827 laser.overloaded = TRUE;
1834 boolean HitAbsorbingWalls(int element, int hit_mask)
1836 if (HitOnlyAnEdge(element, hit_mask))
1840 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
1842 AddLaserEdge(LX - XS, LY - YS);
1848 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
1850 AddLaserEdge(LX - XS, LY - YS);
1855 if (IS_WALL_WOOD(element) ||
1856 IS_DF_WALL_WOOD(element) ||
1857 IS_GRID_WOOD(element) ||
1858 IS_GRID_WOOD_FIXED(element) ||
1859 IS_GRID_WOOD_AUTO(element) ||
1860 element == EL_FUSE_ON ||
1861 element == EL_BLOCK_WOOD ||
1862 element == EL_GATE_WOOD)
1864 PlaySoundStereo(SND_HOLZ, ST(ELX));
1868 if (IS_WALL_ICE(element))
1872 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; /* Quadrant (horizontal) */
1873 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; /* || (vertical) */
1875 /* check if laser hits wall with an angle of 90° */
1876 if (IS_90_ANGLE(laser.current_angle))
1877 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
1879 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
1885 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
1886 mask = 15 - (8 >> i);
1887 else if (ABS(XS) == 4 &&
1889 (XS > 0) == (i % 2) &&
1890 (YS < 0) == (i / 2))
1891 mask = 3 + (i / 2) * 9;
1892 else if (ABS(YS) == 4 &&
1894 (XS < 0) == (i % 2) &&
1895 (YS > 0) == (i / 2))
1896 mask = 5 + (i % 2) * 5;
1900 laser.wall_mask = mask;
1902 else if (IS_WALL_AMOEBA(element))
1904 int elx = (LX - 2 * XS) / TILEX;
1905 int ely = (LY - 2 * YS) / TILEY;
1906 int element2 = Feld[elx][ely];
1909 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
1911 laser.dest_element = EL_EMPTY;
1918 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
1919 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
1921 if (IS_90_ANGLE(laser.current_angle))
1922 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
1924 laser.dest_element = element2 | EL_WALL_AMOEBA;
1926 laser.wall_mask = mask;
1932 void OpenExit(int x, int y)
1936 if (!MovDelay[x][y]) /* next animation frame */
1937 MovDelay[x][y] = 4 * delay;
1939 if (MovDelay[x][y]) /* wait some time before next frame */
1944 phase = MovDelay[x][y] / delay;
1945 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
1946 DrawGraphic_MM(x, y, EL_EXIT_OPEN - phase);
1948 if (!MovDelay[x][y])
1950 Feld[x][y] = EL_EXIT_OPEN;
1956 void OpenSurpriseBall(int x, int y)
1960 if (!MovDelay[x][y]) /* next animation frame */
1961 MovDelay[x][y] = 50 * delay;
1963 if (MovDelay[x][y]) /* wait some time before next frame */
1966 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
1969 int graphic = el2gfx(Store[x][y]);
1971 int dx = RND(26), dy = RND(26);
1973 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
1974 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
1975 SX + x * TILEX + dx, SY + y * TILEY + dy);
1976 MarkTileDirty(x, y);
1979 if (!MovDelay[x][y])
1981 Feld[x][y] = Store[x][y];
1990 void MeltIce(int x, int y)
1995 if (!MovDelay[x][y]) /* next animation frame */
1996 MovDelay[x][y] = frames * delay;
1998 if (MovDelay[x][y]) /* wait some time before next frame */
2001 int wall_mask = Store2[x][y];
2002 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2005 phase = frames - MovDelay[x][y] / delay - 1;
2007 if (!MovDelay[x][y])
2011 Feld[x][y] = real_element & (wall_mask ^ 0xFF);
2012 Store[x][y] = Store2[x][y] = 0;
2014 DrawWalls_MM(x, y, Feld[x][y]);
2016 if (Feld[x][y] == EL_WALL_ICE)
2017 Feld[x][y] = EL_EMPTY;
2019 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
2020 if (laser.damage[i].is_mirror)
2024 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2026 DrawLaser(0, DL_LASER_DISABLED);
2030 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2032 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2034 laser.redraw = TRUE;
2039 void GrowAmoeba(int x, int y)
2044 if (!MovDelay[x][y]) /* next animation frame */
2045 MovDelay[x][y] = frames * delay;
2047 if (MovDelay[x][y]) /* wait some time before next frame */
2050 int wall_mask = Store2[x][y];
2051 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2054 phase = MovDelay[x][y] / delay;
2056 if (!MovDelay[x][y])
2058 Feld[x][y] = real_element;
2059 Store[x][y] = Store2[x][y] = 0;
2061 DrawWalls_MM(x, y, Feld[x][y]);
2062 DrawLaser(0, DL_LASER_ENABLED);
2064 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2065 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2069 static void Explode_MM(int x, int y, int phase, int mode)
2071 int num_phase = 9, delay = 2;
2072 int last_phase = num_phase * delay;
2073 int half_phase = (num_phase / 2) * delay;
2075 laser.redraw = TRUE;
2077 if (phase == EX_PHASE_START) /* initialize 'Store[][]' field */
2079 int center_element = Feld[x][y];
2081 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2083 /* put moving element to center field (and let it explode there) */
2084 center_element = MovingOrBlocked2Element_MM(x, y);
2085 RemoveMovingField_MM(x, y);
2086 Feld[x][y] = center_element;
2089 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2090 Store[x][y] = center_element;
2092 Store[x][y] = EL_EMPTY;
2093 Store2[x][y] = mode;
2094 Feld[x][y] = EL_EXPLODING_OPAQUE;
2095 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2101 Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
2103 if (phase == half_phase)
2105 Feld[x][y] = EL_EXPLODING_TRANSP;
2107 if (x == ELX && y == ELY)
2111 if (phase == last_phase)
2113 if (Store[x][y] == EL_BOMB)
2115 laser.num_damages--;
2116 DrawLaser(0, DL_LASER_DISABLED);
2117 laser.num_edges = 0;
2119 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2120 Store[x][y] = EL_EMPTY;
2122 else if (IS_MCDUFFIN(Store[x][y]))
2124 game_mm.game_over = TRUE;
2125 game_mm.game_over_cause = GAME_OVER_BOMB;
2126 Store[x][y] = EL_EMPTY;
2129 Store[x][y] = Store2[x][y] = 0;
2130 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2131 InitField(x, y, FALSE);
2134 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2136 int graphic = GFX_EXPLOSION_START;
2137 int graphic_phase = (phase / delay - 1);
2139 if (Store2[x][y] == EX_KETTLE)
2141 if (graphic_phase < 3)
2142 graphic = GFX_EXPLOSION_KETTLE;
2143 else if (graphic_phase < 5)
2145 graphic = GFX_EXPLOSION_LAST;
2146 graphic_phase -= graphic_phase;
2150 graphic = GFX_EMPTY;
2154 else if (Store2[x][y] == EX_SHORT)
2156 if (graphic_phase < 4)
2157 graphic = GFX_EXPLOSION_SHORT;
2160 graphic = GFX_EMPTY;
2165 DrawGraphic_MM(x, y, graphic + graphic_phase);
2169 static void Bang_MM(int x, int y)
2171 int element = Feld[x][y];
2172 int mode = EX_NORMAL;
2175 DrawLaser(0, DL_LASER_ENABLED);
2194 if (IS_PACMAN(element))
2195 PlaySoundStereo(SND_QUIEK, ST(x));
2196 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2197 PlaySoundStereo(SND_ROAAAR, ST(x));
2198 else if (element == EL_KEY)
2199 PlaySoundStereo(SND_KLING, ST(x));
2201 PlaySoundStereo((mode == EX_SHORT ? SND_WHOOSH : SND_KABUMM), ST(x));
2203 Explode_MM(x, y, EX_PHASE_START, mode);
2206 void TurnRound(int x, int y)
2218 { 0, 0 }, { 0, 0 }, { 0, 0 },
2223 int left, right, back;
2227 { MV_DOWN, MV_UP, MV_RIGHT },
2228 { MV_UP, MV_DOWN, MV_LEFT },
2230 { MV_LEFT, MV_RIGHT, MV_DOWN },
2231 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2232 { MV_RIGHT, MV_LEFT, MV_UP }
2235 int element = Feld[x][y];
2236 int old_move_dir = MovDir[x][y];
2237 int right_dir = turn[old_move_dir].right;
2238 int back_dir = turn[old_move_dir].back;
2239 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2240 int right_x = x+right_dx, right_y = y+right_dy;
2242 if (element == EL_PACMAN)
2244 boolean can_turn_right = FALSE;
2246 if (IN_LEV_FIELD(right_x, right_y) &&
2247 IS_EATABLE4PACMAN(Feld[right_x][right_y]))
2248 can_turn_right = TRUE;
2251 MovDir[x][y] = right_dir;
2253 MovDir[x][y] = back_dir;
2259 static void StartMoving_MM(int x, int y)
2261 int element = Feld[x][y];
2266 if (CAN_MOVE(element))
2270 if (MovDelay[x][y]) /* wait some time before next movement */
2278 /* now make next step */
2280 Moving2Blocked_MM(x, y, &newx, &newy); /* get next screen position */
2282 if (element == EL_PACMAN &&
2283 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Feld[newx][newy]) &&
2284 !ObjHit(newx, newy, HIT_POS_CENTER))
2286 Store[newx][newy] = Feld[newx][newy];
2287 Feld[newx][newy] = EL_EMPTY;
2288 DrawField_MM(newx, newy);
2290 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2291 ObjHit(newx, newy, HIT_POS_CENTER))
2293 /* object was running against a wall */
2300 InitMovingField_MM(x, y, MovDir[x][y]);
2304 ContinueMoving_MM(x, y);
2307 static void ContinueMoving_MM(int x, int y)
2309 int element = Feld[x][y];
2310 int direction = MovDir[x][y];
2311 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2312 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2313 int horiz_move = (dx!=0);
2314 int newx = x + dx, newy = y + dy;
2315 int step = (horiz_move ? dx : dy) * TILEX / 8;
2317 MovPos[x][y] += step;
2319 if (ABS(MovPos[x][y]) >= TILEX) /* object reached its destination */
2321 Feld[x][y] = EL_EMPTY;
2322 Feld[newx][newy] = element;
2324 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2325 MovDelay[newx][newy] = 0;
2327 if (!CAN_MOVE(element))
2328 MovDir[newx][newy] = 0;
2331 DrawField_MM(newx, newy);
2333 Stop[newx][newy] = TRUE;
2335 if (element == EL_PACMAN)
2337 if (Store[newx][newy] == EL_BOMB)
2338 Bang_MM(newx, newy);
2340 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2341 (LX + 2 * XS) / TILEX == newx &&
2342 (LY + 2 * YS) / TILEY == newy)
2349 else /* still moving on */
2352 laser.redraw = TRUE;
2355 void ClickElement(int mx, int my, int button)
2357 static unsigned int click_delay = 0;
2358 static int click_delay_value = CLICK_DELAY_SHORT;
2359 static boolean new_button = TRUE;
2361 int x = (mx - SX) / TILEX, y = (my - SY) / TILEY;
2363 if (button == MB_RELEASED)
2366 click_delay_value = CLICK_DELAY_SHORT;
2368 /* release eventually hold auto-rotating mirror */
2369 RotateMirror(x, y, MB_RELEASED);
2374 if (!DelayReached(&click_delay, click_delay_value) && !new_button)
2377 if (button == MB_MIDDLEBUTTON) /* middle button has no function */
2380 if (!IN_PIX_FIELD(mx - SX, my - SY))
2383 if (Feld[x][y] == EL_EMPTY)
2386 element = Feld[x][y];
2388 if (IS_MIRROR(element) ||
2389 IS_BEAMER(element) ||
2390 IS_POLAR(element) ||
2391 IS_POLAR_CROSS(element) ||
2392 IS_DF_MIRROR(element) ||
2393 IS_DF_MIRROR_AUTO(element))
2395 RotateMirror(x, y, button);
2397 else if (IS_MCDUFFIN(element))
2399 if (!laser.fuse_off)
2401 DrawLaser(0, DL_LASER_DISABLED);
2407 element = get_rotated_element(element, BUTTON_ROTATION(button));
2408 laser.start_angle = get_element_angle(element);
2412 Feld[x][y] = element;
2417 if (!laser.fuse_off)
2420 else if (element == EL_FUSE_ON && laser.fuse_off)
2422 if (x != laser.fuse_x || y != laser.fuse_y)
2425 laser.fuse_off = FALSE;
2426 laser.fuse_x = laser.fuse_y = -1;
2428 DrawGraphic_MM(x, y, GFX_FUSE_ON);
2431 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2433 laser.fuse_off = TRUE;
2436 laser.overloaded = FALSE;
2438 DrawLaser(0, DL_LASER_DISABLED);
2439 DrawGraphic_MM(x, y, GFX_FUSE_OFF);
2441 else if (element == EL_LIGHTBALL)
2445 DrawLaser(0, DL_LASER_ENABLED);
2448 click_delay_value = (new_button ? CLICK_DELAY_LONG : CLICK_DELAY_SHORT);
2452 void RotateMirror(int x, int y, int button)
2454 static int hold_x = -1, hold_y = -1;
2456 if (button == MB_RELEASED)
2458 /* release eventually hold auto-rotating mirror */
2465 if (IS_MIRROR(Feld[x][y]) ||
2466 IS_POLAR_CROSS(Feld[x][y]) ||
2467 IS_POLAR(Feld[x][y]) ||
2468 IS_BEAMER(Feld[x][y]) ||
2469 IS_DF_MIRROR(Feld[x][y]) ||
2470 IS_GRID_STEEL_AUTO(Feld[x][y]) ||
2471 IS_GRID_WOOD_AUTO(Feld[x][y]))
2473 Feld[x][y] = get_rotated_element(Feld[x][y], BUTTON_ROTATION(button));
2475 else if (IS_DF_MIRROR_AUTO(Feld[x][y]))
2477 if (button == MB_LEFTBUTTON)
2479 /* left mouse button only for manual adjustment, no auto-rotating;
2480 freeze mirror for until mouse button released */
2484 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2485 Feld[x][y] = get_rotated_element(Feld[x][y], ROTATE_RIGHT);
2488 if (IS_GRID_STEEL_AUTO(Feld[x][y]) || IS_GRID_WOOD_AUTO(Feld[x][y]))
2490 int edge = Hit[x][y];
2496 DrawLaser(edge - 1, DL_LASER_DISABLED);
2500 else if (ObjHit(x, y, HIT_POS_CENTER))
2502 int edge = Hit[x][y];
2506 Error(ERR_WARN, "RotateMirror: inconsistent field Hit[][]!\n");
2510 DrawLaser(edge - 1, DL_LASER_DISABLED);
2517 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2522 if ((IS_BEAMER(Feld[x][y]) ||
2523 IS_POLAR(Feld[x][y]) ||
2524 IS_POLAR_CROSS(Feld[x][y])) && x == ELX && y == ELY)
2528 if (IS_BEAMER(Feld[x][y]))
2531 printf("TEST (%d, %d) [%d] [%d]\n",
2533 laser.beamer_edge, laser.beamer[1].num);
2543 DrawLaser(0, DL_LASER_ENABLED);
2547 void AutoRotateMirrors()
2549 static unsigned int rotate_delay = 0;
2552 if (!DelayReached(&rotate_delay, AUTO_ROTATE_DELAY))
2555 for (x=0; x<lev_fieldx; x++)
2557 for (y=0; y<lev_fieldy; y++)
2559 int element = Feld[x][y];
2561 if (IS_DF_MIRROR_AUTO(element) ||
2562 IS_GRID_WOOD_AUTO(element) ||
2563 IS_GRID_STEEL_AUTO(element) ||
2564 element == EL_REFRACTOR)
2565 RotateMirror(x, y, MB_RIGHTBUTTON);
2570 boolean ObjHit(int obx, int oby, int bits)
2577 if (bits & HIT_POS_CENTER)
2579 if (ReadPixel(drawto, SX + obx + 15, SY + oby + 15) == pen_ray)
2583 if (bits & HIT_POS_EDGE)
2586 if (ReadPixel(drawto,
2587 SX + obx + 31 * (i % 2),
2588 SY + oby + 31 * (i / 2)) == pen_ray)
2592 if (bits & HIT_POS_BETWEEN)
2595 if (ReadPixel(drawto,
2596 SX + 4 + obx + 22 * (i % 2),
2597 SY + 4 + oby + 22 * (i / 2)) == pen_ray)
2604 void DeletePacMan(int px, int py)
2610 if (game_mm.num_pacman <= 1)
2612 game_mm.num_pacman = 0;
2616 for(i=0; i<game_mm.num_pacman; i++)
2617 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
2620 game_mm.num_pacman--;
2622 for(j=i; j<game_mm.num_pacman; j++)
2624 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
2625 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
2626 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
2630 void ColorCycling(void)
2632 static int CC, Cc = 0;
2634 static int color, old = 0xF00, new = 0x010, mult = 1;
2635 static unsigned short red, green, blue;
2637 if (color_status == STATIC_COLORS)
2642 if (CC < Cc || CC > Cc + 50)
2646 color = old + new * mult;
2652 if (ABS(mult) == 16)
2661 red = 0x0e00 * ((color & 0xF00) >> 8);
2662 green = 0x0e00 * ((color & 0x0F0) >> 4);
2663 blue = 0x0e00 * ((color & 0x00F));
2664 SetRGB(pen_magicolor[0], red, green, blue);
2666 red = 0x1111 * ((color & 0xF00) >> 8);
2667 green = 0x1111 * ((color & 0x0F0) >> 4);
2668 blue = 0x1111 * ((color & 0x00F));
2669 SetRGB(pen_magicolor[1], red, green, blue);
2673 void GameActions_MM(byte action[MAX_PLAYERS], boolean warp_mode)
2675 static unsigned int action_delay = 0;
2676 static unsigned int pacman_delay = 0;
2677 static unsigned int energy_delay = 0;
2678 static unsigned int overload_delay = 0;
2684 WaitUntilDelayReached(&action_delay, GameFrameDelay);
2686 for (y=0; y<lev_fieldy; y++) for (x=0; x<lev_fieldx; x++)
2689 for (y=0; y<lev_fieldy; y++) for (x=0; x<lev_fieldx; x++)
2691 element = Feld[x][y];
2693 if (!IS_MOVING(x, y) && CAN_MOVE(element))
2694 StartMoving_MM(x, y);
2695 else if (IS_MOVING(x, y))
2696 ContinueMoving_MM(x, y);
2697 else if (IS_EXPLODING(element))
2698 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
2699 else if (element == EL_EXIT_OPENING)
2701 else if (element == EL_GRAY_BALL_OPENING)
2702 OpenSurpriseBall(x, y);
2703 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
2705 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
2709 AutoRotateMirrors();
2712 /* !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!! */
2714 /* redraw after Explode_MM() ... */
2716 DrawLaser(0, DL_LASER_ENABLED);
2717 laser.redraw = FALSE;
2722 if (game_mm.num_pacman && DelayReached(&pacman_delay, 250))
2726 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
2728 DrawLaser(0, DL_LASER_DISABLED);
2733 if (DelayReached(&energy_delay, 4000))
2735 game_mm.energy_left--;
2736 if (game_mm.energy_left >= 0)
2739 BlitBitmap(pix[PIX_DOOR], drawto,
2740 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
2741 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
2742 DX_ENERGY, DY_ENERGY);
2744 redraw_mask |= REDRAW_DOOR_1;
2746 else if (setup.time_limit)
2750 for(i=15; i>=0; i--)
2753 SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
2755 pen_ray = GetPixelFromRGB(window,
2756 native_mm_level.laser_red * 0x11 * i,
2757 native_mm_level.laser_green * 0x11 * i,
2758 native_mm_level.laser_blue * 0x11 * i);
2759 DrawLaser(0, DL_LASER_ENABLED);
2764 StopSound(SND_WARNTON);
2767 DrawLaser(0, DL_LASER_DISABLED);
2768 game_mm.game_over = TRUE;
2769 game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
2772 if (Request("Out of magic energy ! Play it again ?",
2773 REQ_ASK | REQ_STAY_CLOSED))
2779 game_status = MAINMENU;
2788 element = laser.dest_element;
2791 if (element != Feld[ELX][ELY])
2793 printf("element == %d, Feld[ELX][ELY] == %d\n",
2794 element, Feld[ELX][ELY]);
2798 if (!laser.overloaded && laser.overload_value == 0 &&
2799 element != EL_BOMB &&
2800 element != EL_MINE &&
2801 element != EL_BALL_GRAY &&
2802 element != EL_BLOCK_STONE &&
2803 element != EL_BLOCK_WOOD &&
2804 element != EL_FUSE_ON &&
2805 element != EL_FUEL_FULL &&
2806 !IS_WALL_ICE(element) &&
2807 !IS_WALL_AMOEBA(element))
2810 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
2811 (!laser.overloaded && laser.overload_value > 0)) &&
2812 DelayReached(&overload_delay, 60 + !laser.overloaded * 120))
2814 if (laser.overloaded)
2815 laser.overload_value++;
2817 laser.overload_value--;
2819 if (game_mm.cheat_no_overload)
2821 laser.overloaded = FALSE;
2822 laser.overload_value = 0;
2825 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
2827 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
2828 int color_down = 0xFF - color_up;
2831 SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
2832 (15 - (laser.overload_value / 6)) * color_scale);
2834 pen_ray = GetPixelFromRGB(window,
2835 (native_mm_level.laser_red ? 0xFF : color_up),
2836 (native_mm_level.laser_green ? color_down : 0x00),
2837 (native_mm_level.laser_blue ? color_down : 0x00));
2838 DrawLaser(0, DL_LASER_ENABLED);
2842 if (laser.overloaded)
2844 if (setup.sound_loops)
2845 PlaySoundExt(SND_WARNTON, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT, SND_CTRL_PLAY_LOOP);
2847 PlaySoundStereo(SND_WARNTON, SOUND_MAX_RIGHT);
2850 if (!laser.overloaded)
2851 StopSound(SND_WARNTON);
2853 if (laser.overloaded)
2856 BlitBitmap(pix[PIX_DOOR], drawto,
2857 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
2858 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
2859 - laser.overload_value,
2860 OVERLOAD_XSIZE, laser.overload_value,
2861 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
2862 - laser.overload_value);
2864 redraw_mask |= REDRAW_DOOR_1;
2869 BlitBitmap(pix[PIX_DOOR], drawto,
2870 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
2871 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
2872 DX_OVERLOAD, DY_OVERLOAD);
2874 redraw_mask |= REDRAW_DOOR_1;
2877 if (laser.overload_value == MAX_LASER_OVERLOAD)
2881 for(i=15; i>=0; i--)
2884 SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
2887 pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
2888 DrawLaser(0, DL_LASER_ENABLED);
2893 DrawLaser(0, DL_LASER_DISABLED);
2894 game_mm.game_over = TRUE;
2895 game_mm.game_over_cause = GAME_OVER_OVERLOADED;
2898 if (Request("Magic spell hit Mc Duffin ! Play it again ?",
2899 REQ_ASK | REQ_STAY_CLOSED))
2905 game_status = MAINMENU;
2919 if (element == EL_BOMB && CT > 1500)
2921 if (game_mm.cheat_no_explosion)
2925 laser.num_damages--;
2926 DrawLaser(0, DL_LASER_DISABLED);
2927 laser.num_edges = 0;
2932 laser.dest_element = EL_EXPLODING_OPAQUE;
2936 laser.num_damages--;
2937 DrawLaser(0, DL_LASER_DISABLED);
2939 laser.num_edges = 0;
2940 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2942 if (Request("Bomb killed Mc Duffin ! Play it again ?",
2943 REQ_ASK | REQ_STAY_CLOSED))
2949 game_status = MAINMENU;
2957 if (element == EL_FUSE_ON && CT > 500)
2959 laser.fuse_off = TRUE;
2962 DrawLaser(0, DL_LASER_DISABLED);
2963 DrawGraphic_MM(ELX, ELY, GFX_FUSE_OFF);
2966 if (element == EL_BALL_GRAY && CT > 1500)
2968 static int new_elements[] =
2971 EL_MIRROR_FIXED_START,
2973 EL_POLAR_CROSS_START,
2979 int num_new_elements = sizeof(new_elements) / sizeof(int);
2980 int new_element = new_elements[RND(num_new_elements)];
2982 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
2983 Feld[ELX][ELY] = EL_GRAY_BALL_OPENING;
2985 /* !!! CHECK AGAIN: Laser on Polarizer !!! */
2996 element = EL_MIRROR_START + RND(16);
3002 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3009 element = (rnd == 0 ? EL_FUSE_ON :
3010 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3011 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3012 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3013 EL_MIRROR_FIXED_START + rnd - 25);
3018 graphic = el2gfx(element);
3026 BlitBitmap(pix[PIX_BACK], drawto,
3027 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3028 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3029 SX + ELX * TILEX + x,
3030 SY + ELY * TILEY + y);
3032 MarkTileDirty(ELX, ELY);
3035 DrawLaser(0, DL_LASER_ENABLED);
3040 Feld[ELX][ELY] = element;
3041 DrawField_MM(ELX, ELY);
3044 printf("NEW ELEMENT: (%d, %d)\n", ELX, ELY);
3047 /* above stuff: GRAY BALL -> PRISM !!! */
3049 LX = ELX * TILEX + 14;
3050 LY = ELY * TILEY + 14;
3051 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3058 laser.num_edges -= 2;
3059 laser.num_damages--;
3063 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3064 if (laser.damage[i].is_mirror)
3068 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3070 DrawLaser(0, DL_LASER_DISABLED);
3072 DrawLaser(0, DL_LASER_DISABLED);
3078 printf("TEST ELEMENT: %d\n", Feld[0][0]);
3085 if (IS_WALL_ICE(element) && CT > 1000)
3087 PlaySoundStereo(SND_SLURP, ST(ELX));
3092 Feld[ELX][ELY] = Feld[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3093 Store[ELX][ELY] = EL_WALL_ICE;
3094 Store2[ELX][ELY] = laser.wall_mask;
3096 laser.dest_element = Feld[ELX][ELY];
3110 Feld[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3114 DrawWallsAnimation_MM(ELX, ELY, Feld[ELX][ELY], phase, laser.wall_mask);
3119 if (Feld[ELX][ELY] == EL_WALL_ICE)
3120 Feld[ELX][ELY] = EL_EMPTY;
3124 LX = laser.edge[laser.num_edges].x - (SX + 2);
3125 LY = laser.edge[laser.num_edges].y - (SY + 2);
3128 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3129 if (laser.damage[i].is_mirror)
3133 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3135 DrawLaser(0, DL_LASER_DISABLED);
3142 if (IS_WALL_AMOEBA(element) && CT > 1200)
3144 int k1, k2, k3, dx, dy, de, dm;
3145 int element2 = Feld[ELX][ELY];
3147 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3150 for (i = laser.num_damages - 1; i>=0; i--)
3151 if (laser.damage[i].is_mirror)
3154 r = laser.num_edges;
3155 d = laser.num_damages;
3162 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3165 DrawLaser(0, DL_LASER_ENABLED);
3168 x = laser.damage[k1].x;
3169 y = laser.damage[k1].y;
3175 if (laser.wall_mask & (1 << i))
3177 if (ReadPixel(drawto,
3178 SX + ELX * TILEX + 14 + (i % 2) * 2,
3179 SY + ELY * TILEY + 31 * (i / 2)) == pen_ray)
3181 if (ReadPixel(drawto,
3182 SX + ELX * TILEX + 31 * (i % 2),
3183 SY + ELY * TILEY + 14 + (i / 2) * 2) == pen_ray)
3192 if (laser.wall_mask & (1 << i))
3194 if (ReadPixel(drawto,
3195 SX + ELX * TILEX + 31 * (i % 2),
3196 SY + ELY * TILEY + 31 * (i / 2)) == pen_ray)
3203 if (laser.num_beamers > 0 ||
3204 k1 < 1 || k2 < 4 || k3 < 4 ||
3205 ReadPixel(drawto, SX + ELX * TILEX + 14, SY + ELY * TILEY + 14)
3208 laser.num_edges = r;
3209 laser.num_damages = d;
3210 DrawLaser(0, DL_LASER_DISABLED);
3213 Feld[ELX][ELY] = element | laser.wall_mask;
3216 de = Feld[ELX][ELY];
3217 dm = laser.wall_mask;
3223 int x = ELX, y = ELY;
3224 int wall_mask = laser.wall_mask;
3228 DrawLaser(0, DL_LASER_ENABLED);
3230 PlaySoundStereo(SND_AMOEBE, ST(dx));
3234 Feld[x][y] = Feld[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3235 Store[x][y] = EL_WALL_AMOEBA;
3236 Store2[x][y] = wall_mask;
3244 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3246 DrawLaser(0, DL_LASER_ENABLED);
3248 PlaySoundStereo(SND_AMOEBE, ST(dx));
3252 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3257 DrawLaser(0, DL_LASER_ENABLED);
3262 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3263 laser.stops_inside_element && CT > 1500)
3268 if (ABS(XS) > ABS(YS))
3282 x = ELX + Step[k * 4].x;
3283 y = ELY + Step[k * 4].y;
3285 if (!IN_LEV_FIELD(x, y) || Feld[x][y] != EL_EMPTY)
3288 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3296 laser.overloaded = (element == EL_BLOCK_STONE);
3300 PlaySoundStereo(SND_BONG, ST(ELX));
3303 Feld[x][y] = element;
3305 DrawGraphic_MM(ELX, ELY, -1);
3308 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3310 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3311 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3319 if (element == EL_FUEL_FULL && CT > 200)
3321 for(i=game_mm.energy_left; i<=MAX_LASER_ENERGY; i+=2)
3324 BlitBitmap(pix[PIX_DOOR], drawto,
3325 DOOR_GFX_PAGEX4 + XX_ENERGY,
3326 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3327 ENERGY_XSIZE, i, DX_ENERGY,
3328 DY_ENERGY + ENERGY_YSIZE - i);
3331 redraw_mask |= REDRAW_DOOR_1;
3337 game_mm.energy_left = MAX_LASER_ENERGY;
3338 Feld[ELX][ELY] = EL_FUEL_EMPTY;
3339 DrawField_MM(ELX, ELY);
3341 DrawLaser(0, DL_LASER_ENABLED);
3352 int mx, my, ox, oy, nx, ny;
3356 if (++p >= game_mm.num_pacman)
3358 game_mm.pacman[p].dir--;
3362 game_mm.pacman[p].dir++;
3364 if (game_mm.pacman[p].dir > 4)
3365 game_mm.pacman[p].dir = 1;
3367 if (game_mm.pacman[p].dir % 2)
3370 my = game_mm.pacman[p].dir - 2;
3375 mx = 3 - game_mm.pacman[p].dir;
3378 ox = game_mm.pacman[p].x;
3379 oy = game_mm.pacman[p].y;
3382 element = Feld[nx][ny];
3383 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3386 if (!IS_EATABLE4PACMAN(element))
3389 if (ObjHit(nx, ny, HIT_POS_CENTER))
3392 Feld[ox][oy] = EL_EMPTY;
3394 EL_PACMAN_RIGHT - 1 +
3395 (game_mm.pacman[p].dir - 1 +
3396 (game_mm.pacman[p].dir % 2) * 2);
3398 game_mm.pacman[p].x = nx;
3399 game_mm.pacman[p].y = ny;
3400 g = Feld[nx][ny] - EL_PACMAN_RIGHT;
3401 DrawGraphic_MM(ox, oy, GFX_EMPTY);
3403 if (element != EL_EMPTY)
3408 ox = SX + ox * TILEX;
3409 oy = SY + oy * TILEY;
3411 for(i=1; i<33; i+=2)
3414 // !!! temporary fix to compile -- change to game graphics !!!
3415 BlitBitmap(drawto, window,
3416 SX + g * TILEX, SY + 4 * TILEY, TILEX, TILEY,
3417 ox + i * mx, oy + i * my);
3419 BlitBitmap(pix[PIX_BACK], window,
3420 SX + g * TILEX, SY + 4 * TILEY, TILEX, TILEY,
3421 ox + i * mx, oy + i * my);
3424 Ct = Ct + Counter() - CT;
3426 DrawField_MM(nx, ny);
3429 if (!laser.fuse_off)
3431 DrawLaser(0, DL_LASER_ENABLED);
3433 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3435 AddDamagedField(nx, ny);
3436 laser.damage[laser.num_damages - 1].edge = 0;
3440 if (element == EL_BOMB)
3442 DeletePacMan(nx, ny);
3445 if (IS_WALL_AMOEBA(element) &&
3446 (LX + 2 * XS) / TILEX == nx &&
3447 (LY + 2 * YS) / TILEY == ny)
3459 boolean raise_level = FALSE;
3462 if (local_player->MovPos)
3465 local_player->LevelSolved = FALSE;
3468 if (game_mm.energy_left)
3470 if (setup.sound_loops)
3471 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT, SND_CTRL_PLAY_LOOP);
3473 while(game_mm.energy_left > 0)
3475 if (!setup.sound_loops)
3476 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3479 if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10))
3480 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3485 game_mm.energy_left--;
3486 if (game_mm.energy_left >= 0)
3489 BlitBitmap(pix[PIX_DOOR], drawto,
3490 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3491 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3492 DX_ENERGY, DY_ENERGY);
3494 redraw_mask |= REDRAW_DOOR_1;
3501 if (setup.sound_loops)
3502 StopSound(SND_SIRR);
3504 else if (native_mm_level.time == 0) /* level without time limit */
3506 if (setup.sound_loops)
3507 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT, SND_CTRL_PLAY_LOOP);
3509 while(TimePlayed < 999)
3511 if (!setup.sound_loops)
3512 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3513 if (TimePlayed < 999 && !(TimePlayed % 10))
3514 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3515 if (TimePlayed < 900 && !(TimePlayed % 10))
3521 DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
3528 if (setup.sound_loops)
3529 StopSound(SND_SIRR);
3536 CloseDoor(DOOR_CLOSE_1);
3538 Request("Level solved !", REQ_CONFIRM);
3540 if (level_nr == leveldir_current->handicap_level)
3542 leveldir_current->handicap_level++;
3543 SaveLevelSetup_SeriesInfo();
3546 if (level_editor_test_game)
3547 game_mm.score = -1; /* no highscore when playing from editor */
3548 else if (level_nr < leveldir_current->last_level)
3549 raise_level = TRUE; /* advance to next level */
3551 if ((hi_pos = NewHiScore_MM()) >= 0)
3553 game_status = HALLOFFAME;
3554 // DrawHallOfFame(hi_pos);
3560 game_status = MAINMENU;
3574 // LoadScore(level_nr);
3576 if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
3577 game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score)
3580 for (k=0; k<MAX_SCORE_ENTRIES; k++)
3582 if (game_mm.score > highscore[k].Score)
3584 /* player has made it to the hall of fame */
3586 if (k < MAX_SCORE_ENTRIES - 1)
3588 int m = MAX_SCORE_ENTRIES - 1;
3591 for (l=k; l<MAX_SCORE_ENTRIES; l++)
3592 if (!strcmp(setup.player_name, highscore[l].Name))
3594 if (m == k) /* player's new highscore overwrites his old one */
3600 strcpy(highscore[l].Name, highscore[l - 1].Name);
3601 highscore[l].Score = highscore[l - 1].Score;
3608 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
3609 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
3610 highscore[k].Score = game_mm.score;
3616 else if (!strncmp(setup.player_name, highscore[k].Name,
3617 MAX_PLAYER_NAME_LEN))
3618 break; /* player already there with a higher score */
3623 // if (position >= 0)
3624 // SaveScore(level_nr);
3629 static void InitMovingField_MM(int x, int y, int direction)
3631 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3632 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3634 MovDir[x][y] = direction;
3635 MovDir[newx][newy] = direction;
3636 if (Feld[newx][newy] == EL_EMPTY)
3637 Feld[newx][newy] = EL_BLOCKED;
3640 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3642 int direction = MovDir[x][y];
3643 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3644 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3650 static void Blocked2Moving_MM(int x, int y,
3651 int *comes_from_x, int *comes_from_y)
3653 int oldx = x, oldy = y;
3654 int direction = MovDir[x][y];
3656 if (direction == MV_LEFT)
3658 else if (direction == MV_RIGHT)
3660 else if (direction == MV_UP)
3662 else if (direction == MV_DOWN)
3665 *comes_from_x = oldx;
3666 *comes_from_y = oldy;
3669 static int MovingOrBlocked2Element_MM(int x, int y)
3671 int element = Feld[x][y];
3673 if (element == EL_BLOCKED)
3677 Blocked2Moving_MM(x, y, &oldx, &oldy);
3678 return Feld[oldx][oldy];
3685 static void RemoveField(int x, int y)
3687 Feld[x][y] = EL_EMPTY;
3694 static void RemoveMovingField_MM(int x, int y)
3696 int oldx = x, oldy = y, newx = x, newy = y;
3698 if (Feld[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3701 if (IS_MOVING(x, y))
3703 Moving2Blocked_MM(x, y, &newx, &newy);
3704 if (Feld[newx][newy] != EL_BLOCKED)
3707 else if (Feld[x][y] == EL_BLOCKED)
3709 Blocked2Moving_MM(x, y, &oldx, &oldy);
3710 if (!IS_MOVING(oldx, oldy))
3714 Feld[oldx][oldy] = EL_EMPTY;
3715 Feld[newx][newy] = EL_EMPTY;
3716 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3717 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3719 DrawLevelField_MM(oldx, oldy);
3720 DrawLevelField_MM(newx, newy);
3723 void PlaySoundLevel(int x, int y, int sound_nr)
3725 int sx = SCREENX(x), sy = SCREENY(y);
3727 int silence_distance = 8;
3729 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3730 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3733 if (!IN_LEV_FIELD(x, y) ||
3734 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3735 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3738 volume = SOUND_MAX_VOLUME;
3741 stereo = (sx - SCR_FIELDX/2) * 12;
3743 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3744 if (stereo > SOUND_MAX_RIGHT)
3745 stereo = SOUND_MAX_RIGHT;
3746 if (stereo < SOUND_MAX_LEFT)
3747 stereo = SOUND_MAX_LEFT;
3750 if (!IN_SCR_FIELD(sx, sy))
3752 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3753 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3755 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3758 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3761 static void RaiseScore_MM(int value)
3763 game_mm.score += value;
3765 DrawText(DX_SCORE, DY_SCORE, int2str(game_mm.score, 4),
3770 void RaiseScoreElement_MM(int element)
3775 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3778 RaiseScore_MM(native_mm_level.score[SC_KEY]);