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);
348 CloseDoor(DOOR_CLOSE_1);
354 void InitGameEngine_MM_AfterFadingIn()
360 /* copy default game door content to main double buffer */
361 BlitBitmap(pix[PIX_DOOR], drawto,
362 DOOR_GFX_PAGEX5, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY);
366 DrawText(DX_LEVEL, DY_LEVEL,
367 int2str(level_nr, 2), FONT_TEXT_2);
368 DrawText(DX_KETTLES, DY_KETTLES,
369 int2str(game_mm.kettles_still_needed, 3), FONT_TEXT_2);
370 DrawText(DX_SCORE, DY_SCORE,
371 int2str(game_mm.score, 4), FONT_TEXT_2);
380 /* copy actual game door content to door double buffer for OpenDoor() */
381 BlitBitmap(drawto, pix[PIX_DB_DOOR],
382 DX, DY, DXSIZE, DYSIZE, DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1);
386 OpenDoor(DOOR_OPEN_ALL);
389 if (setup.sound_loops)
390 PlaySoundExt(SND_FUEL, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT, SND_CTRL_PLAY_LOOP);
392 #if 0 // !!! TEMPORARILY DISABLED !!!
393 for(i=0; i<=game_mm.energy_left; i+=2)
395 if (!setup.sound_loops)
396 PlaySoundStereo(SND_FUEL, SOUND_MAX_RIGHT);
399 BlitBitmap(pix[PIX_DOOR], drawto,
400 DOOR_GFX_PAGEX4 + XX_ENERGY,
401 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
403 DX_ENERGY, DY_ENERGY + ENERGY_YSIZE - i);
406 redraw_mask |= REDRAW_DOOR_1;
412 if (setup.quick_doors)
419 if (setup.sound_loops)
424 if (setup.sound_music && num_bg_loops)
425 PlayMusic(level_nr % num_bg_loops);
431 void AddLaserEdge(int lx, int ly)
433 if (lx < -2 || ly < -2 || lx >= SXSIZE + 2 || ly >= SYSIZE + 2)
435 Error(ERR_WARN, "AddLaserEdge: out of bounds: %d, %d", lx, ly);
439 laser.edge[laser.num_edges].x = SX + 2 + lx;
440 laser.edge[laser.num_edges].y = SY + 2 + ly;
446 void AddDamagedField(int ex, int ey)
448 laser.damage[laser.num_damages].is_mirror = FALSE;
449 laser.damage[laser.num_damages].angle = laser.current_angle;
450 laser.damage[laser.num_damages].edge = laser.num_edges;
451 laser.damage[laser.num_damages].x = ex;
452 laser.damage[laser.num_damages].y = ey;
462 int last_x = laser.edge[laser.num_edges - 1].x - SX - 2;
463 int last_y = laser.edge[laser.num_edges - 1].y - SY - 2;
465 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
471 static int getMaskFromElement(int element)
473 if (IS_GRID(element))
474 return IMG_MM_MASK_GRID_1 + get_element_phase(element);
475 else if (IS_MCDUFFIN(element))
476 return IMG_MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
477 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
478 return IMG_MM_MASK_RECTANGLE;
480 return IMG_MM_MASK_CIRCLE;
488 printf("ScanPixel: start scanning at (%d, %d) [%d, %d] [%d, %d]\n",
489 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
492 /* follow laser beam until it hits something (at least the screen border) */
493 while (hit_mask == HIT_MASK_NO_HIT)
499 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
500 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
502 printf("ScanPixel: touched screen border!\n");
510 int px = LX + (i % 2) * 2;
511 int py = LY + (i / 2) * 2;
514 int lx = (px + TILEX) / TILEX - 1; /* ...+TILEX...-1 to get correct */
515 int ly = (py + TILEY) / TILEY - 1; /* negative values! */
518 if (IN_LEV_FIELD(lx, ly))
520 int element = Feld[lx][ly];
522 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
526 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
528 int pos = dy / MINI_TILEY * 2 + dx / MINI_TILEX;
530 pixel = ((element & (1 << pos)) ? 1 : 0);
534 int graphic_mask = getMaskFromElement(element);
539 getGraphicSource(graphic_mask, 0, &bitmap, &src_x, &src_y);
544 pixel = (ReadPixel(bitmap, mask_x, mask_y) ? 1 : 0);
549 pixel = (SX + px < REAL_SX || SX + px >= REAL_SX + FULL_SXSIZE ||
550 SY + py < REAL_SY || SY + py >= REAL_SY + FULL_SYSIZE);
553 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
554 hit_mask |= (1 << i);
557 if (hit_mask == HIT_MASK_NO_HIT)
559 /* hit nothing -- go on with another step */
571 int end = 0, rf = laser.num_edges;
573 laser.overloaded = FALSE;
574 laser.stops_inside_element = FALSE;
576 DrawLaser(0, DL_LASER_ENABLED);
579 printf("Start scanning with LX == %d, LY == %d, XS == %d, YS == %d\n",
587 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
590 laser.overloaded = TRUE;
594 hit_mask = ScanPixel();
597 printf("Hit something at LX == %d, LY == %d, XS == %d, YS == %d\n",
601 /* hit something -- check out what it was */
602 ELX = (LX + XS) / TILEX;
603 ELY = (LY + YS) / TILEY;
606 printf("hit_mask (1) == '%x' (%d, %d) (%d, %d)\n",
607 hit_mask, LX, LY, ELX, ELY);
610 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
613 laser.dest_element = element;
618 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
620 /* we have hit the top-right and bottom-left element --
621 choose the bottom-left one */
622 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
623 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
624 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
625 ELX = (LX - 2) / TILEX;
626 ELY = (LY + 2) / TILEY;
629 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
631 /* we have hit the top-left and bottom-right element --
632 choose the top-left one */
633 /* !!! SEE ABOVE !!! */
634 ELX = (LX - 2) / TILEX;
635 ELY = (LY - 2) / TILEY;
639 printf("hit_mask (2) == '%x' (%d, %d) (%d, %d)\n",
640 hit_mask, LX, LY, ELX, ELY);
643 element = Feld[ELX][ELY];
644 laser.dest_element = element;
647 printf("Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]\n",
650 LX % TILEX, LY % TILEY,
655 if (!IN_LEV_FIELD(ELX, ELY))
656 printf("WARNING! (1) %d, %d (%d)\n", ELX, ELY, element);
659 if (element == EL_EMPTY)
661 if (!HitOnlyAnEdge(element, hit_mask))
664 else if (element == EL_FUSE_ON)
666 if (HitPolarizer(element, hit_mask))
669 else if (IS_GRID(element) || IS_DF_GRID(element))
671 if (HitPolarizer(element, hit_mask))
674 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
675 element == EL_GATE_STONE || element == EL_GATE_WOOD)
677 if (HitBlock(element, hit_mask))
683 else if (IS_MCDUFFIN(element))
685 if (HitLaserSource(element, hit_mask))
688 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
689 IS_RECEIVER(element))
691 if (HitLaserDestination(element, hit_mask))
694 else if (IS_WALL(element))
696 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
698 if (HitReflectingWalls(element, hit_mask))
703 if (HitAbsorbingWalls(element, hit_mask))
709 if (HitElement(element, hit_mask))
714 DrawLaser(rf - 1, DL_LASER_ENABLED);
715 rf = laser.num_edges;
719 if (laser.dest_element != Feld[ELX][ELY])
721 printf("ALARM: laser.dest_element == %d, Feld[ELX][ELY] == %d\n",
722 laser.dest_element, Feld[ELX][ELY]);
726 if (!end && !laser.stops_inside_element && !StepBehind())
729 printf("ScanLaser: Go one step back\n");
734 AddLaserEdge(LX, LY);
738 DrawLaser(rf - 1, DL_LASER_ENABLED);
743 if (!IN_LEV_FIELD(ELX, ELY))
744 printf("WARNING! (2) %d, %d\n", ELX, ELY);
748 void DrawLaserExt(int start_edge, int num_edges, int mode)
754 printf("DrawLaserExt: start_edge, num_edges, mode == %d, %d, %d\n",
755 start_edge, num_edges, mode);
760 Error(ERR_WARN, "DrawLaserExt: start_edge < 0");
766 Error(ERR_WARN, "DrawLaserExt: num_edges < 0");
771 if (mode == DL_LASER_DISABLED)
773 printf("DrawLaser: Delete laser from edge %d\n", start_edge);
777 /* now draw the laser to the backbuffer and (if enabled) to the screen */
778 DrawLines(drawto, &laser.edge[start_edge], num_edges,
779 (mode == DL_LASER_ENABLED ? pen_ray : pen_bg));
781 redraw_mask |= REDRAW_FIELD;
783 if (mode == DL_LASER_ENABLED)
786 /* after the laser was deleted, the "damaged" graphics must be restored */
787 if (laser.num_damages)
789 int damage_start = 0;
792 /* determine the starting edge, from which graphics need to be restored */
795 for(i=0; i<laser.num_damages; i++)
797 if (laser.damage[i].edge == start_edge + 1)
805 /* restore graphics from this starting edge to the end of damage list */
806 for(i=damage_start; i<laser.num_damages; i++)
808 int lx = laser.damage[i].x;
809 int ly = laser.damage[i].y;
810 int element = Feld[lx][ly];
812 if (Hit[lx][ly] == laser.damage[i].edge)
813 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
816 if (Box[lx][ly] == laser.damage[i].edge)
819 if (IS_DRAWABLE(element))
820 DrawField_MM(lx, ly);
823 elx = laser.damage[damage_start].x;
824 ely = laser.damage[damage_start].y;
825 element = Feld[elx][ely];
828 if (IS_BEAMER(element))
832 for (i=0; i<laser.num_beamers; i++)
833 printf("-> %d\n", laser.beamer_edge[i]);
834 printf("DrawLaserExt: IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]\n",
835 mode, elx, ely, Hit[elx][ely], start_edge);
836 printf("DrawLaserExt: IS_BEAMER: %d / %d\n",
837 get_element_angle(element), laser.damage[damage_start].angle);
841 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
842 laser.num_beamers > 0 &&
843 start_edge == laser.beamer_edge[laser.num_beamers - 1])
845 /* element is outgoing beamer */
846 laser.num_damages = damage_start + 1;
847 if (IS_BEAMER(element))
848 laser.current_angle = get_element_angle(element);
852 /* element is incoming beamer or other element */
853 laser.num_damages = damage_start;
854 laser.current_angle = laser.damage[laser.num_damages].angle;
859 /* no damages but McDuffin himself (who needs to be redrawn anyway) */
861 elx = laser.start_edge.x;
862 ely = laser.start_edge.y;
863 element = Feld[elx][ely];
866 laser.num_edges = start_edge + 1;
868 laser.current_angle = laser.start_angle;
869 LX = laser.edge[start_edge].x - (SX + 2);
870 LY = laser.edge[start_edge].y - (SY + 2);
871 XS = 2 * Step[laser.current_angle].x;
872 YS = 2 * Step[laser.current_angle].y;
875 printf("DrawLaser: Set (LX, LY) to (%d, %d) [%d]\n",
881 if (IS_BEAMER(element) ||
882 IS_FIBRE_OPTIC(element) ||
883 IS_PACMAN(element) ||
885 IS_POLAR_CROSS(element) ||
886 element == EL_FUSE_ON)
891 printf("element == %d\n", element);
894 if (IS_22_5_ANGLE(laser.current_angle)) /* neither 90° nor 45° angle */
895 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
899 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
900 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
901 (laser.num_beamers == 0 ||
902 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
904 /* element is incoming beamer or other element */
905 step_size = -step_size;
910 if (IS_BEAMER(element))
912 printf("start_edge == %d, laser.beamer_edge == %d\n",
913 start_edge, laser.beamer_edge);
917 LX += step_size * XS;
918 LY += step_size * YS;
920 else if (element != EL_EMPTY)
929 printf("DrawLaser: Finally: (LX, LY) to (%d, %d) [%d]\n",
934 void DrawLaser(int start_edge, int mode)
936 if (laser.num_edges - start_edge < 0)
938 Error(ERR_WARN, "DrawLaser: laser.num_edges - start_edge < 0");
942 /* check if laser is interrupted by beamer element */
943 if (laser.num_beamers > 0 &&
944 start_edge < laser.beamer_edge[laser.num_beamers - 1])
946 if (mode == DL_LASER_ENABLED)
949 int tmp_start_edge = start_edge;
951 /* draw laser segments forward from the start to the last beamer */
952 for (i=0; i<laser.num_beamers; i++)
954 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
956 if (tmp_num_edges <= 0)
960 printf("DrawLaser: DL_LASER_ENABLED: i==%d: %d, %d\n",
961 i, laser.beamer_edge[i], tmp_start_edge);
964 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
965 tmp_start_edge = laser.beamer_edge[i];
968 /* draw last segment from last beamer to the end */
969 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
975 int last_num_edges = laser.num_edges;
976 int num_beamers = laser.num_beamers;
978 /* delete laser segments backward from the end to the first beamer */
979 for (i=num_beamers-1; i>=0; i--)
981 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
983 if (laser.beamer_edge[i] - start_edge <= 0)
986 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
987 last_num_edges = laser.beamer_edge[i];
992 if (last_num_edges - start_edge <= 0)
993 printf("DrawLaser: DL_LASER_DISABLED: %d, %d\n",
994 last_num_edges, start_edge);
997 /* delete first segment from start to the first beamer */
998 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1002 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1005 boolean HitElement(int element, int hit_mask)
1007 if (HitOnlyAnEdge(element, hit_mask))
1010 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1011 element = MovingOrBlocked2Element_MM(ELX, ELY);
1014 printf("HitElement (1): element == %d\n", element);
1018 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1019 printf("HitElement (%d): EXACT MATCH @ (%d, %d)\n", element, ELX, ELY);
1021 printf("HitElement (%d): FUZZY MATCH @ (%d, %d)\n", element, ELX, ELY);
1024 AddDamagedField(ELX, ELY);
1026 /* this is more precise: check if laser would go through the center */
1027 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1029 /* skip the whole element before continuing the scan */
1035 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1037 if (LX/TILEX > ELX || LY/TILEY > ELY)
1039 /* skipping scan positions to the right and down skips one scan
1040 position too much, because this is only the top left scan position
1041 of totally four scan positions (plus one to the right, one to the
1042 bottom and one to the bottom right) */
1052 printf("HitElement (2): element == %d\n", element);
1055 if (LX + 5 * XS < 0 ||
1065 printf("HitElement (3): element == %d\n", element);
1068 if (IS_POLAR(element) &&
1069 ((element - EL_POLAR_START) % 2 ||
1070 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1072 PlaySoundStereo(SND_KINK, ST(ELX));
1073 laser.num_damages--;
1078 if (IS_POLAR_CROSS(element) &&
1079 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1081 PlaySoundStereo(SND_KINK, ST(ELX));
1082 laser.num_damages--;
1087 if (!IS_BEAMER(element) &&
1088 !IS_FIBRE_OPTIC(element) &&
1089 !IS_GRID_WOOD(element) &&
1090 element != EL_FUEL_EMPTY)
1093 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1094 printf("EXACT MATCH @ (%d, %d)\n", ELX, ELY);
1096 printf("FUZZY MATCH @ (%d, %d)\n", ELX, ELY);
1099 LX = ELX * TILEX + 14;
1100 LY = ELY * TILEY + 14;
1101 AddLaserEdge(LX, LY);
1104 if (IS_MIRROR(element) ||
1105 IS_MIRROR_FIXED(element) ||
1106 IS_POLAR(element) ||
1107 IS_POLAR_CROSS(element) ||
1108 IS_DF_MIRROR(element) ||
1109 IS_DF_MIRROR_AUTO(element) ||
1110 element == EL_PRISM ||
1111 element == EL_REFRACTOR)
1113 int current_angle = laser.current_angle;
1116 laser.num_damages--;
1117 AddDamagedField(ELX, ELY);
1118 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1121 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1123 if (IS_MIRROR(element) ||
1124 IS_MIRROR_FIXED(element) ||
1125 IS_DF_MIRROR(element) ||
1126 IS_DF_MIRROR_AUTO(element))
1127 laser.current_angle = get_mirrored_angle(laser.current_angle,
1128 get_element_angle(element));
1130 if (element == EL_PRISM || element == EL_REFRACTOR)
1131 laser.current_angle = RND(16);
1133 XS = 2 * Step[laser.current_angle].x;
1134 YS = 2 * Step[laser.current_angle].y;
1136 if (!IS_22_5_ANGLE(laser.current_angle)) /* 90° or 45° angle */
1141 LX += step_size * XS;
1142 LY += step_size * YS;
1145 /* draw sparkles on mirror */
1146 if ((IS_MIRROR(element) || IS_MIRROR_FIXED(element)) &&
1147 current_angle != laser.current_angle)
1149 MoveSprite(vp, &Pfeil[2], 4 + 16 * ELX, 5 + 16 * ELY + 1);
1153 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1154 current_angle != laser.current_angle)
1155 PlaySoundStereo(SND_LASER, ST(ELX));
1158 (get_opposite_angle(laser.current_angle) ==
1159 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1161 return (laser.overloaded ? TRUE : FALSE);
1164 if (element == EL_FUEL_FULL)
1166 laser.stops_inside_element = TRUE;
1171 if (element == EL_BOMB || element == EL_MINE)
1173 PlaySoundStereo(SND_KINK, ST(ELX));
1175 if (element == EL_MINE)
1176 laser.overloaded = TRUE;
1179 if (element == EL_KETTLE ||
1180 element == EL_CELL ||
1181 element == EL_KEY ||
1182 element == EL_LIGHTBALL ||
1183 element == EL_PACMAN ||
1186 if (!IS_PACMAN(element))
1189 if (element == EL_PACMAN)
1192 if (element == EL_KETTLE || element == EL_CELL)
1196 if (game_mm.kettles_still_needed == 0)
1199 static int xy[4][2] =
1207 PlaySoundStereo(SND_KLING, ST(ELX));
1209 for(y=0; y<lev_fieldy; y++)
1211 for(x=0; x<lev_fieldx; x++)
1213 /* initiate opening animation of exit door */
1214 if (Feld[x][y] == EL_EXIT_CLOSED)
1215 Feld[x][y] = EL_EXIT_OPENING;
1217 /* remove field that blocks receiver */
1218 if (IS_RECEIVER(Feld[x][y]))
1220 int phase = Feld[x][y] - EL_RECEIVER_START;
1221 int blocking_x, blocking_y;
1223 blocking_x = x + xy[phase][0];
1224 blocking_y = y + xy[phase][1];
1226 if (IN_LEV_FIELD(blocking_x, blocking_y))
1228 Feld[blocking_x][blocking_y] = EL_EMPTY;
1229 DrawField_MM(blocking_x, blocking_y);
1235 DrawLaser(0, DL_LASER_ENABLED);
1238 else if (element == EL_KEY)
1240 else if (element == EL_LIGHTBALL)
1242 else if (IS_PACMAN(element))
1244 DeletePacMan(ELX, ELY);
1251 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1253 PlaySoundStereo(SND_KINK, ST(ELX));
1255 DrawLaser(0, DL_LASER_ENABLED);
1257 if (Feld[ELX][ELY] == EL_LIGHTBULB_OFF)
1259 Feld[ELX][ELY] = EL_LIGHTBULB_ON;
1260 game_mm.lights_still_needed--;
1264 Feld[ELX][ELY] = EL_LIGHTBULB_OFF;
1265 game_mm.lights_still_needed++;
1268 DrawField_MM(ELX, ELY);
1269 DrawLaser(0, DL_LASER_ENABLED);
1274 laser.stops_inside_element = TRUE;
1280 printf("HitElement (4): element == %d\n", element);
1283 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1284 laser.num_beamers < MAX_NUM_BEAMERS &&
1285 laser.beamer[BEAMER_NR(element)][1].num)
1287 int beamer_angle = get_element_angle(element);
1288 int beamer_nr = BEAMER_NR(element);
1292 printf("HitElement (BEAMER): element == %d\n", element);
1295 laser.num_damages--;
1297 if (IS_FIBRE_OPTIC(element) ||
1298 laser.current_angle == get_opposite_angle(beamer_angle))
1302 LX = ELX * TILEX + 14;
1303 LY = ELY * TILEY + 14;
1304 AddLaserEdge(LX, LY);
1305 AddDamagedField(ELX, ELY);
1306 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1309 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1311 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1312 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1313 ELX = laser.beamer[beamer_nr][pos].x;
1314 ELY = laser.beamer[beamer_nr][pos].y;
1315 LX = ELX * TILEX + 14;
1316 LY = ELY * TILEY + 14;
1318 if (IS_BEAMER(element))
1320 laser.current_angle = get_element_angle(Feld[ELX][ELY]);
1321 XS = 2 * Step[laser.current_angle].x;
1322 YS = 2 * Step[laser.current_angle].y;
1325 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1326 AddLaserEdge(LX, LY);
1327 AddDamagedField(ELX, ELY);
1328 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1331 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1333 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1338 LX += step_size * XS;
1339 LY += step_size * YS;
1341 laser.num_beamers++;
1350 boolean HitOnlyAnEdge(int element, int hit_mask)
1352 /* check if the laser hit only the edge of an element and, if so, go on */
1355 printf("LX, LY, hit_mask == %d, %d, %d\n", LX, LY, hit_mask);
1358 if ((hit_mask == HIT_MASK_TOPLEFT ||
1359 hit_mask == HIT_MASK_TOPRIGHT ||
1360 hit_mask == HIT_MASK_BOTTOMLEFT ||
1361 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1362 laser.current_angle % 4) /* angle is not 90° */
1366 if (hit_mask == HIT_MASK_TOPLEFT)
1371 else if (hit_mask == HIT_MASK_TOPRIGHT)
1376 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1381 else /* (hit_mask == HIT_MASK_BOTTOMRIGHT) */
1387 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1392 printf("[HitOnlyAnEdge() == TRUE]\n");
1399 printf("[HitOnlyAnEdge() == FALSE]\n");
1405 boolean HitPolarizer(int element, int hit_mask)
1407 if (HitOnlyAnEdge(element, hit_mask))
1410 if (IS_DF_GRID(element))
1412 int grid_angle = get_element_angle(element);
1415 printf("HitPolarizer: angle: grid == %d, laser == %d\n",
1416 grid_angle, laser.current_angle);
1419 AddLaserEdge(LX, LY);
1420 AddDamagedField(ELX, ELY);
1423 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1425 if (laser.current_angle == grid_angle ||
1426 laser.current_angle == get_opposite_angle(grid_angle))
1428 /* skip the whole element before continuing the scan */
1434 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1436 if (LX/TILEX > ELX || LY/TILEY > ELY)
1438 /* skipping scan positions to the right and down skips one scan
1439 position too much, because this is only the top left scan position
1440 of totally four scan positions (plus one to the right, one to the
1441 bottom and one to the bottom right) */
1447 AddLaserEdge(LX, LY);
1453 printf("HitPolarizer: LX, LY == %d, %d [%d, %d] [%d, %d]\n",
1455 LX / TILEX, LY / TILEY,
1456 LX % TILEX, LY % TILEY);
1461 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1462 return HitReflectingWalls(element, hit_mask);
1464 return HitAbsorbingWalls(element, hit_mask);
1466 else if (IS_GRID_STEEL(element))
1467 return HitReflectingWalls(element, hit_mask);
1468 else /* IS_GRID_WOOD */
1469 return HitAbsorbingWalls(element, hit_mask);
1474 boolean HitBlock(int element, int hit_mask)
1476 boolean check = FALSE;
1478 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1479 game_mm.num_keys == 0)
1482 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1485 int ex = ELX * TILEX + 14;
1486 int ey = ELY * TILEY + 14;
1495 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1500 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1501 return HitAbsorbingWalls(element, hit_mask);
1505 AddLaserEdge(LX - XS, LY - YS);
1506 AddDamagedField(ELX, ELY);
1509 Box[ELX][ELY] = laser.num_edges;
1511 return HitReflectingWalls(element, hit_mask);
1514 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1516 int xs = XS / 2, ys = YS / 2;
1517 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1518 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1520 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1521 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1523 laser.overloaded = (element == EL_GATE_STONE);
1527 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1528 (hit_mask == HIT_MASK_TOP ||
1529 hit_mask == HIT_MASK_LEFT ||
1530 hit_mask == HIT_MASK_RIGHT ||
1531 hit_mask == HIT_MASK_BOTTOM))
1532 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1533 hit_mask == HIT_MASK_BOTTOM),
1534 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1535 hit_mask == HIT_MASK_RIGHT));
1536 AddLaserEdge(LX, LY);
1541 if (element == EL_GATE_STONE && Box[ELX][ELY])
1543 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1555 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1557 int xs = XS / 2, ys = YS / 2;
1558 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1559 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1561 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1562 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1564 laser.overloaded = (element == EL_BLOCK_STONE);
1569 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1570 (hit_mask == HIT_MASK_TOP ||
1571 hit_mask == HIT_MASK_LEFT ||
1572 hit_mask == HIT_MASK_RIGHT ||
1573 hit_mask == HIT_MASK_BOTTOM))
1574 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1575 hit_mask == HIT_MASK_BOTTOM),
1576 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1577 hit_mask == HIT_MASK_RIGHT));
1578 AddDamagedField(ELX, ELY);
1580 LX = ELX * TILEX + 14;
1581 LY = ELY * TILEY + 14;
1582 AddLaserEdge(LX, LY);
1584 laser.stops_inside_element = TRUE;
1592 boolean HitLaserSource(int element, int hit_mask)
1594 if (HitOnlyAnEdge(element, hit_mask))
1597 PlaySoundStereo(SND_AUTSCH, ST(ELX));
1598 laser.overloaded = TRUE;
1603 boolean HitLaserDestination(int element, int hit_mask)
1605 if (HitOnlyAnEdge(element, hit_mask))
1608 if (element != EL_EXIT_OPEN &&
1609 !(IS_RECEIVER(element) &&
1610 game_mm.kettles_still_needed == 0 &&
1611 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1613 PlaySoundStereo(SND_HOLZ, ST(ELX));
1617 if (IS_RECEIVER(element) ||
1618 (IS_22_5_ANGLE(laser.current_angle) &&
1619 (ELX != (LX + 6 * XS) / TILEX ||
1620 ELY != (LY + 6 * YS) / TILEY ||
1629 LX = ELX * TILEX + 14;
1630 LY = ELY * TILEY + 14;
1632 laser.stops_inside_element = TRUE;
1635 AddLaserEdge(LX, LY);
1636 AddDamagedField(ELX, ELY);
1638 if (game_mm.lights_still_needed == 0)
1639 game_mm.level_solved = TRUE;
1644 boolean HitReflectingWalls(int element, int hit_mask)
1646 /* check if laser hits side of a wall with an angle that is not 90° */
1647 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
1648 hit_mask == HIT_MASK_LEFT ||
1649 hit_mask == HIT_MASK_RIGHT ||
1650 hit_mask == HIT_MASK_BOTTOM))
1652 PlaySoundStereo(SND_HUI, ST(ELX));
1655 if (!IS_DF_GRID(element))
1656 AddLaserEdge(LX, LY);
1658 /* check if laser hits wall with an angle of 45° */
1659 if (!IS_22_5_ANGLE(laser.current_angle))
1661 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1664 laser.current_angle = get_mirrored_angle(laser.current_angle,
1667 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
1670 laser.current_angle = get_mirrored_angle(laser.current_angle,
1674 AddLaserEdge(LX, LY);
1675 XS = 2 * Step[laser.current_angle].x;
1676 YS = 2 * Step[laser.current_angle].y;
1680 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1682 laser.current_angle = get_mirrored_angle(laser.current_angle,
1687 if (!IS_DF_GRID(element))
1688 AddLaserEdge(LX, LY);
1693 if (!IS_DF_GRID(element))
1694 AddLaserEdge(LX, LY + YS / 2);
1697 if (!IS_DF_GRID(element))
1698 AddLaserEdge(LX, LY);
1701 YS = 2 * Step[laser.current_angle].y;
1705 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
1707 laser.current_angle = get_mirrored_angle(laser.current_angle,
1712 if (!IS_DF_GRID(element))
1713 AddLaserEdge(LX, LY);
1718 if (!IS_DF_GRID(element))
1719 AddLaserEdge(LX + XS / 2, LY);
1722 if (!IS_DF_GRID(element))
1723 AddLaserEdge(LX, LY);
1726 XS = 2 * Step[laser.current_angle].x;
1732 /* reflection at the edge of reflecting DF style wall */
1733 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
1735 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
1736 hit_mask == HIT_MASK_TOPRIGHT) ||
1737 ((laser.current_angle == 5 || laser.current_angle == 7) &&
1738 hit_mask == HIT_MASK_TOPLEFT) ||
1739 ((laser.current_angle == 9 || laser.current_angle == 11) &&
1740 hit_mask == HIT_MASK_BOTTOMLEFT) ||
1741 ((laser.current_angle == 13 || laser.current_angle == 15) &&
1742 hit_mask == HIT_MASK_BOTTOMRIGHT))
1745 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
1746 ANG_MIRROR_135 : ANG_MIRROR_45);
1748 PlaySoundStereo(SND_HUI, ST(ELX));
1749 AddDamagedField(ELX, ELY);
1750 AddLaserEdge(LX, LY);
1752 laser.current_angle = get_mirrored_angle(laser.current_angle,
1759 AddLaserEdge(LX, LY);
1765 /* reflection inside an edge of reflecting DF style wall */
1766 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
1768 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
1769 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
1770 ((laser.current_angle == 5 || laser.current_angle == 7) &&
1771 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
1772 ((laser.current_angle == 9 || laser.current_angle == 11) &&
1773 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
1774 ((laser.current_angle == 13 || laser.current_angle == 15) &&
1775 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
1778 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
1779 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
1780 ANG_MIRROR_135 : ANG_MIRROR_45);
1782 PlaySoundStereo(SND_HUI, ST(ELX));
1784 AddDamagedField(ELX, ELY);
1786 AddLaserEdge(LX - XS, LY - YS);
1787 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
1788 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
1790 laser.current_angle = get_mirrored_angle(laser.current_angle,
1797 AddLaserEdge(LX, LY);
1803 /* check if laser hits DF style wall with an angle of 90° */
1804 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
1806 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
1807 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
1808 (IS_VERT_ANGLE(laser.current_angle) &&
1809 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
1811 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
1813 /* laser at last step touched nothing or the same side of the wall */
1814 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
1816 AddDamagedField(ELX, ELY);
1822 last_hit_mask = hit_mask;
1829 if (!HitOnlyAnEdge(element, hit_mask))
1831 laser.overloaded = TRUE;
1838 boolean HitAbsorbingWalls(int element, int hit_mask)
1840 if (HitOnlyAnEdge(element, hit_mask))
1844 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
1846 AddLaserEdge(LX - XS, LY - YS);
1852 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
1854 AddLaserEdge(LX - XS, LY - YS);
1859 if (IS_WALL_WOOD(element) ||
1860 IS_DF_WALL_WOOD(element) ||
1861 IS_GRID_WOOD(element) ||
1862 IS_GRID_WOOD_FIXED(element) ||
1863 IS_GRID_WOOD_AUTO(element) ||
1864 element == EL_FUSE_ON ||
1865 element == EL_BLOCK_WOOD ||
1866 element == EL_GATE_WOOD)
1868 PlaySoundStereo(SND_HOLZ, ST(ELX));
1872 if (IS_WALL_ICE(element))
1876 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; /* Quadrant (horizontal) */
1877 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; /* || (vertical) */
1879 /* check if laser hits wall with an angle of 90° */
1880 if (IS_90_ANGLE(laser.current_angle))
1881 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
1883 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
1889 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
1890 mask = 15 - (8 >> i);
1891 else if (ABS(XS) == 4 &&
1893 (XS > 0) == (i % 2) &&
1894 (YS < 0) == (i / 2))
1895 mask = 3 + (i / 2) * 9;
1896 else if (ABS(YS) == 4 &&
1898 (XS < 0) == (i % 2) &&
1899 (YS > 0) == (i / 2))
1900 mask = 5 + (i % 2) * 5;
1904 laser.wall_mask = mask;
1906 else if (IS_WALL_AMOEBA(element))
1908 int elx = (LX - 2 * XS) / TILEX;
1909 int ely = (LY - 2 * YS) / TILEY;
1910 int element2 = Feld[elx][ely];
1913 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
1915 laser.dest_element = EL_EMPTY;
1922 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
1923 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
1925 if (IS_90_ANGLE(laser.current_angle))
1926 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
1928 laser.dest_element = element2 | EL_WALL_AMOEBA;
1930 laser.wall_mask = mask;
1936 void OpenExit(int x, int y)
1940 if (!MovDelay[x][y]) /* next animation frame */
1941 MovDelay[x][y] = 4 * delay;
1943 if (MovDelay[x][y]) /* wait some time before next frame */
1948 phase = MovDelay[x][y] / delay;
1949 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
1950 DrawGraphic_MM(x, y, EL_EXIT_OPEN - phase);
1952 if (!MovDelay[x][y])
1954 Feld[x][y] = EL_EXIT_OPEN;
1960 void OpenSurpriseBall(int x, int y)
1964 if (!MovDelay[x][y]) /* next animation frame */
1965 MovDelay[x][y] = 50 * delay;
1967 if (MovDelay[x][y]) /* wait some time before next frame */
1970 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
1973 int graphic = el2gfx(Store[x][y]);
1975 int dx = RND(26), dy = RND(26);
1977 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
1978 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
1979 SX + x * TILEX + dx, SY + y * TILEY + dy);
1980 MarkTileDirty(x, y);
1983 if (!MovDelay[x][y])
1985 Feld[x][y] = Store[x][y];
1994 void MeltIce(int x, int y)
1999 if (!MovDelay[x][y]) /* next animation frame */
2000 MovDelay[x][y] = frames * delay;
2002 if (MovDelay[x][y]) /* wait some time before next frame */
2005 int wall_mask = Store2[x][y];
2006 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2009 phase = frames - MovDelay[x][y] / delay - 1;
2011 if (!MovDelay[x][y])
2015 Feld[x][y] = real_element & (wall_mask ^ 0xFF);
2016 Store[x][y] = Store2[x][y] = 0;
2018 DrawWalls_MM(x, y, Feld[x][y]);
2020 if (Feld[x][y] == EL_WALL_ICE)
2021 Feld[x][y] = EL_EMPTY;
2023 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
2024 if (laser.damage[i].is_mirror)
2028 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2030 DrawLaser(0, DL_LASER_DISABLED);
2034 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2036 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2038 laser.redraw = TRUE;
2043 void GrowAmoeba(int x, int y)
2048 if (!MovDelay[x][y]) /* next animation frame */
2049 MovDelay[x][y] = frames * delay;
2051 if (MovDelay[x][y]) /* wait some time before next frame */
2054 int wall_mask = Store2[x][y];
2055 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2058 phase = MovDelay[x][y] / delay;
2060 if (!MovDelay[x][y])
2062 Feld[x][y] = real_element;
2063 Store[x][y] = Store2[x][y] = 0;
2065 DrawWalls_MM(x, y, Feld[x][y]);
2066 DrawLaser(0, DL_LASER_ENABLED);
2068 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2069 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2073 static void Explode_MM(int x, int y, int phase, int mode)
2075 int num_phase = 9, delay = 2;
2076 int last_phase = num_phase * delay;
2077 int half_phase = (num_phase / 2) * delay;
2079 laser.redraw = TRUE;
2081 if (phase == EX_PHASE_START) /* initialize 'Store[][]' field */
2083 int center_element = Feld[x][y];
2085 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2087 /* put moving element to center field (and let it explode there) */
2088 center_element = MovingOrBlocked2Element_MM(x, y);
2089 RemoveMovingField_MM(x, y);
2090 Feld[x][y] = center_element;
2093 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2094 Store[x][y] = center_element;
2096 Store[x][y] = EL_EMPTY;
2097 Store2[x][y] = mode;
2098 Feld[x][y] = EL_EXPLODING_OPAQUE;
2099 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2105 Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
2107 if (phase == half_phase)
2109 Feld[x][y] = EL_EXPLODING_TRANSP;
2111 if (x == ELX && y == ELY)
2115 if (phase == last_phase)
2117 if (Store[x][y] == EL_BOMB)
2119 laser.num_damages--;
2120 DrawLaser(0, DL_LASER_DISABLED);
2121 laser.num_edges = 0;
2123 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2124 Store[x][y] = EL_EMPTY;
2126 else if (IS_MCDUFFIN(Store[x][y]))
2128 game_mm.game_over = TRUE;
2129 game_mm.game_over_cause = GAME_OVER_BOMB;
2130 Store[x][y] = EL_EMPTY;
2133 Feld[x][y] = Store[x][y];
2134 Store[x][y] = Store2[x][y] = 0;
2135 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2136 InitField(x, y, FALSE);
2139 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2141 int graphic = IMG_MM_DEFAULT_EXPLODING;
2142 int graphic_phase = (phase / delay - 1);
2146 if (Store2[x][y] == EX_KETTLE)
2148 if (graphic_phase < 3)
2149 graphic = IMG_MM_KETTLE_EXPLODING;
2150 else if (graphic_phase < 5)
2156 graphic = IMG_EMPTY;
2160 else if (Store2[x][y] == EX_SHORT)
2162 if (graphic_phase < 4)
2166 graphic = GFX_EMPTY;
2171 getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2173 BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2174 FX + x * TILEX, FY + y * TILEY);
2175 MarkTileDirty(x, y);
2179 static void Bang_MM(int x, int y)
2181 int element = Feld[x][y];
2182 int mode = EX_NORMAL;
2185 DrawLaser(0, DL_LASER_ENABLED);
2204 if (IS_PACMAN(element))
2205 PlaySoundStereo(SND_QUIEK, ST(x));
2206 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2207 PlaySoundStereo(SND_ROAAAR, ST(x));
2208 else if (element == EL_KEY)
2209 PlaySoundStereo(SND_KLING, ST(x));
2211 PlaySoundStereo((mode == EX_SHORT ? SND_WHOOSH : SND_KABUMM), ST(x));
2213 Explode_MM(x, y, EX_PHASE_START, mode);
2216 void TurnRound(int x, int y)
2228 { 0, 0 }, { 0, 0 }, { 0, 0 },
2233 int left, right, back;
2237 { MV_DOWN, MV_UP, MV_RIGHT },
2238 { MV_UP, MV_DOWN, MV_LEFT },
2240 { MV_LEFT, MV_RIGHT, MV_DOWN },
2241 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2242 { MV_RIGHT, MV_LEFT, MV_UP }
2245 int element = Feld[x][y];
2246 int old_move_dir = MovDir[x][y];
2247 int right_dir = turn[old_move_dir].right;
2248 int back_dir = turn[old_move_dir].back;
2249 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2250 int right_x = x+right_dx, right_y = y+right_dy;
2252 if (element == EL_PACMAN)
2254 boolean can_turn_right = FALSE;
2256 if (IN_LEV_FIELD(right_x, right_y) &&
2257 IS_EATABLE4PACMAN(Feld[right_x][right_y]))
2258 can_turn_right = TRUE;
2261 MovDir[x][y] = right_dir;
2263 MovDir[x][y] = back_dir;
2269 static void StartMoving_MM(int x, int y)
2271 int element = Feld[x][y];
2276 if (CAN_MOVE(element))
2280 if (MovDelay[x][y]) /* wait some time before next movement */
2288 /* now make next step */
2290 Moving2Blocked_MM(x, y, &newx, &newy); /* get next screen position */
2292 if (element == EL_PACMAN &&
2293 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Feld[newx][newy]) &&
2294 !ObjHit(newx, newy, HIT_POS_CENTER))
2296 Store[newx][newy] = Feld[newx][newy];
2297 Feld[newx][newy] = EL_EMPTY;
2298 DrawField_MM(newx, newy);
2300 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2301 ObjHit(newx, newy, HIT_POS_CENTER))
2303 /* object was running against a wall */
2310 InitMovingField_MM(x, y, MovDir[x][y]);
2314 ContinueMoving_MM(x, y);
2317 static void ContinueMoving_MM(int x, int y)
2319 int element = Feld[x][y];
2320 int direction = MovDir[x][y];
2321 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2322 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2323 int horiz_move = (dx!=0);
2324 int newx = x + dx, newy = y + dy;
2325 int step = (horiz_move ? dx : dy) * TILEX / 8;
2327 MovPos[x][y] += step;
2329 if (ABS(MovPos[x][y]) >= TILEX) /* object reached its destination */
2331 Feld[x][y] = EL_EMPTY;
2332 Feld[newx][newy] = element;
2334 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2335 MovDelay[newx][newy] = 0;
2337 if (!CAN_MOVE(element))
2338 MovDir[newx][newy] = 0;
2341 DrawField_MM(newx, newy);
2343 Stop[newx][newy] = TRUE;
2345 if (element == EL_PACMAN)
2347 if (Store[newx][newy] == EL_BOMB)
2348 Bang_MM(newx, newy);
2350 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2351 (LX + 2 * XS) / TILEX == newx &&
2352 (LY + 2 * YS) / TILEY == newy)
2359 else /* still moving on */
2362 laser.redraw = TRUE;
2365 void ClickElement(int mx, int my, int button)
2367 static unsigned int click_delay = 0;
2368 static int click_delay_value = CLICK_DELAY_SHORT;
2369 static boolean new_button = TRUE;
2371 int x = (mx - SX) / TILEX, y = (my - SY) / TILEY;
2373 if (button == MB_RELEASED)
2376 click_delay_value = CLICK_DELAY_SHORT;
2378 /* release eventually hold auto-rotating mirror */
2379 RotateMirror(x, y, MB_RELEASED);
2384 if (!DelayReached(&click_delay, click_delay_value) && !new_button)
2387 if (button == MB_MIDDLEBUTTON) /* middle button has no function */
2390 if (!IN_PIX_FIELD(mx - SX, my - SY))
2393 if (Feld[x][y] == EL_EMPTY)
2396 element = Feld[x][y];
2398 if (IS_MIRROR(element) ||
2399 IS_BEAMER(element) ||
2400 IS_POLAR(element) ||
2401 IS_POLAR_CROSS(element) ||
2402 IS_DF_MIRROR(element) ||
2403 IS_DF_MIRROR_AUTO(element))
2405 RotateMirror(x, y, button);
2407 else if (IS_MCDUFFIN(element))
2409 if (!laser.fuse_off)
2411 DrawLaser(0, DL_LASER_DISABLED);
2417 element = get_rotated_element(element, BUTTON_ROTATION(button));
2418 laser.start_angle = get_element_angle(element);
2422 Feld[x][y] = element;
2427 if (!laser.fuse_off)
2430 else if (element == EL_FUSE_ON && laser.fuse_off)
2432 if (x != laser.fuse_x || y != laser.fuse_y)
2435 laser.fuse_off = FALSE;
2436 laser.fuse_x = laser.fuse_y = -1;
2438 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2441 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2443 laser.fuse_off = TRUE;
2446 laser.overloaded = FALSE;
2448 DrawLaser(0, DL_LASER_DISABLED);
2449 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2451 else if (element == EL_LIGHTBALL)
2455 DrawLaser(0, DL_LASER_ENABLED);
2458 click_delay_value = (new_button ? CLICK_DELAY_LONG : CLICK_DELAY_SHORT);
2462 void RotateMirror(int x, int y, int button)
2464 static int hold_x = -1, hold_y = -1;
2466 if (button == MB_RELEASED)
2468 /* release eventually hold auto-rotating mirror */
2475 if (IS_MIRROR(Feld[x][y]) ||
2476 IS_POLAR_CROSS(Feld[x][y]) ||
2477 IS_POLAR(Feld[x][y]) ||
2478 IS_BEAMER(Feld[x][y]) ||
2479 IS_DF_MIRROR(Feld[x][y]) ||
2480 IS_GRID_STEEL_AUTO(Feld[x][y]) ||
2481 IS_GRID_WOOD_AUTO(Feld[x][y]))
2483 Feld[x][y] = get_rotated_element(Feld[x][y], BUTTON_ROTATION(button));
2485 else if (IS_DF_MIRROR_AUTO(Feld[x][y]))
2487 if (button == MB_LEFTBUTTON)
2489 /* left mouse button only for manual adjustment, no auto-rotating;
2490 freeze mirror for until mouse button released */
2494 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2495 Feld[x][y] = get_rotated_element(Feld[x][y], ROTATE_RIGHT);
2498 if (IS_GRID_STEEL_AUTO(Feld[x][y]) || IS_GRID_WOOD_AUTO(Feld[x][y]))
2500 int edge = Hit[x][y];
2506 DrawLaser(edge - 1, DL_LASER_DISABLED);
2510 else if (ObjHit(x, y, HIT_POS_CENTER))
2512 int edge = Hit[x][y];
2516 Error(ERR_WARN, "RotateMirror: inconsistent field Hit[][]!\n");
2520 DrawLaser(edge - 1, DL_LASER_DISABLED);
2527 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2532 if ((IS_BEAMER(Feld[x][y]) ||
2533 IS_POLAR(Feld[x][y]) ||
2534 IS_POLAR_CROSS(Feld[x][y])) && x == ELX && y == ELY)
2538 if (IS_BEAMER(Feld[x][y]))
2541 printf("TEST (%d, %d) [%d] [%d]\n",
2543 laser.beamer_edge, laser.beamer[1].num);
2553 DrawLaser(0, DL_LASER_ENABLED);
2557 void AutoRotateMirrors()
2559 static unsigned int rotate_delay = 0;
2562 if (!DelayReached(&rotate_delay, AUTO_ROTATE_DELAY))
2565 for (x=0; x<lev_fieldx; x++)
2567 for (y=0; y<lev_fieldy; y++)
2569 int element = Feld[x][y];
2571 if (IS_DF_MIRROR_AUTO(element) ||
2572 IS_GRID_WOOD_AUTO(element) ||
2573 IS_GRID_STEEL_AUTO(element) ||
2574 element == EL_REFRACTOR)
2575 RotateMirror(x, y, MB_RIGHTBUTTON);
2580 boolean ObjHit(int obx, int oby, int bits)
2587 if (bits & HIT_POS_CENTER)
2589 if (ReadPixel(drawto, SX + obx + 15, SY + oby + 15) == pen_ray)
2593 if (bits & HIT_POS_EDGE)
2596 if (ReadPixel(drawto,
2597 SX + obx + 31 * (i % 2),
2598 SY + oby + 31 * (i / 2)) == pen_ray)
2602 if (bits & HIT_POS_BETWEEN)
2605 if (ReadPixel(drawto,
2606 SX + 4 + obx + 22 * (i % 2),
2607 SY + 4 + oby + 22 * (i / 2)) == pen_ray)
2614 void DeletePacMan(int px, int py)
2620 if (game_mm.num_pacman <= 1)
2622 game_mm.num_pacman = 0;
2626 for(i=0; i<game_mm.num_pacman; i++)
2627 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
2630 game_mm.num_pacman--;
2632 for(j=i; j<game_mm.num_pacman; j++)
2634 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
2635 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
2636 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
2640 void ColorCycling(void)
2642 static int CC, Cc = 0;
2644 static int color, old = 0xF00, new = 0x010, mult = 1;
2645 static unsigned short red, green, blue;
2647 if (color_status == STATIC_COLORS)
2652 if (CC < Cc || CC > Cc + 50)
2656 color = old + new * mult;
2662 if (ABS(mult) == 16)
2671 red = 0x0e00 * ((color & 0xF00) >> 8);
2672 green = 0x0e00 * ((color & 0x0F0) >> 4);
2673 blue = 0x0e00 * ((color & 0x00F));
2674 SetRGB(pen_magicolor[0], red, green, blue);
2676 red = 0x1111 * ((color & 0xF00) >> 8);
2677 green = 0x1111 * ((color & 0x0F0) >> 4);
2678 blue = 0x1111 * ((color & 0x00F));
2679 SetRGB(pen_magicolor[1], red, green, blue);
2683 static void GameActions_MM_Ext(byte action[MAX_PLAYERS], boolean warp_mode)
2685 static unsigned int action_delay = 0;
2686 static unsigned int pacman_delay = 0;
2687 static unsigned int energy_delay = 0;
2688 static unsigned int overload_delay = 0;
2694 WaitUntilDelayReached(&action_delay, GameFrameDelay);
2696 for (y=0; y<lev_fieldy; y++) for (x=0; x<lev_fieldx; x++)
2699 for (y=0; y<lev_fieldy; y++) for (x=0; x<lev_fieldx; x++)
2701 element = Feld[x][y];
2703 if (!IS_MOVING(x, y) && CAN_MOVE(element))
2704 StartMoving_MM(x, y);
2705 else if (IS_MOVING(x, y))
2706 ContinueMoving_MM(x, y);
2707 else if (IS_EXPLODING(element))
2708 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
2709 else if (element == EL_EXIT_OPENING)
2711 else if (element == EL_GRAY_BALL_OPENING)
2712 OpenSurpriseBall(x, y);
2713 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
2715 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
2719 AutoRotateMirrors();
2722 /* !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!! */
2724 /* redraw after Explode_MM() ... */
2726 DrawLaser(0, DL_LASER_ENABLED);
2727 laser.redraw = FALSE;
2732 if (game_mm.num_pacman && DelayReached(&pacman_delay, 250))
2736 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
2738 DrawLaser(0, DL_LASER_DISABLED);
2743 if (DelayReached(&energy_delay, 4000))
2745 game_mm.energy_left--;
2746 if (game_mm.energy_left >= 0)
2749 BlitBitmap(pix[PIX_DOOR], drawto,
2750 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
2751 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
2752 DX_ENERGY, DY_ENERGY);
2754 redraw_mask |= REDRAW_DOOR_1;
2756 else if (setup.time_limit)
2760 for(i=15; i>=0; i--)
2763 SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
2765 pen_ray = GetPixelFromRGB(window,
2766 native_mm_level.laser_red * 0x11 * i,
2767 native_mm_level.laser_green * 0x11 * i,
2768 native_mm_level.laser_blue * 0x11 * i);
2769 DrawLaser(0, DL_LASER_ENABLED);
2774 StopSound(SND_WARNTON);
2777 DrawLaser(0, DL_LASER_DISABLED);
2778 game_mm.game_over = TRUE;
2779 game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
2782 if (Request("Out of magic energy ! Play it again ?",
2783 REQ_ASK | REQ_STAY_CLOSED))
2789 game_status = MAINMENU;
2798 element = laser.dest_element;
2801 if (element != Feld[ELX][ELY])
2803 printf("element == %d, Feld[ELX][ELY] == %d\n",
2804 element, Feld[ELX][ELY]);
2808 if (!laser.overloaded && laser.overload_value == 0 &&
2809 element != EL_BOMB &&
2810 element != EL_MINE &&
2811 element != EL_BALL_GRAY &&
2812 element != EL_BLOCK_STONE &&
2813 element != EL_BLOCK_WOOD &&
2814 element != EL_FUSE_ON &&
2815 element != EL_FUEL_FULL &&
2816 !IS_WALL_ICE(element) &&
2817 !IS_WALL_AMOEBA(element))
2820 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
2821 (!laser.overloaded && laser.overload_value > 0)) &&
2822 DelayReached(&overload_delay, 60 + !laser.overloaded * 120))
2824 if (laser.overloaded)
2825 laser.overload_value++;
2827 laser.overload_value--;
2829 if (game_mm.cheat_no_overload)
2831 laser.overloaded = FALSE;
2832 laser.overload_value = 0;
2835 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
2837 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
2838 int color_down = 0xFF - color_up;
2841 SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
2842 (15 - (laser.overload_value / 6)) * color_scale);
2844 pen_ray = GetPixelFromRGB(window,
2845 (native_mm_level.laser_red ? 0xFF : color_up),
2846 (native_mm_level.laser_green ? color_down : 0x00),
2847 (native_mm_level.laser_blue ? color_down : 0x00));
2848 DrawLaser(0, DL_LASER_ENABLED);
2852 if (laser.overloaded)
2854 if (setup.sound_loops)
2855 PlaySoundExt(SND_WARNTON, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT, SND_CTRL_PLAY_LOOP);
2857 PlaySoundStereo(SND_WARNTON, SOUND_MAX_RIGHT);
2860 if (!laser.overloaded)
2861 StopSound(SND_WARNTON);
2863 if (laser.overloaded)
2866 BlitBitmap(pix[PIX_DOOR], drawto,
2867 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
2868 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
2869 - laser.overload_value,
2870 OVERLOAD_XSIZE, laser.overload_value,
2871 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
2872 - laser.overload_value);
2874 redraw_mask |= REDRAW_DOOR_1;
2879 BlitBitmap(pix[PIX_DOOR], drawto,
2880 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
2881 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
2882 DX_OVERLOAD, DY_OVERLOAD);
2884 redraw_mask |= REDRAW_DOOR_1;
2887 if (laser.overload_value == MAX_LASER_OVERLOAD)
2891 for(i=15; i>=0; i--)
2894 SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
2897 pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
2898 DrawLaser(0, DL_LASER_ENABLED);
2903 DrawLaser(0, DL_LASER_DISABLED);
2904 game_mm.game_over = TRUE;
2905 game_mm.game_over_cause = GAME_OVER_OVERLOADED;
2908 if (Request("Magic spell hit Mc Duffin ! Play it again ?",
2909 REQ_ASK | REQ_STAY_CLOSED))
2915 game_status = MAINMENU;
2929 if (element == EL_BOMB && CT > 1500)
2931 if (game_mm.cheat_no_explosion)
2935 laser.num_damages--;
2936 DrawLaser(0, DL_LASER_DISABLED);
2937 laser.num_edges = 0;
2942 laser.dest_element = EL_EXPLODING_OPAQUE;
2946 laser.num_damages--;
2947 DrawLaser(0, DL_LASER_DISABLED);
2949 laser.num_edges = 0;
2950 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2952 if (Request("Bomb killed Mc Duffin ! Play it again ?",
2953 REQ_ASK | REQ_STAY_CLOSED))
2959 game_status = MAINMENU;
2967 if (element == EL_FUSE_ON && CT > 500)
2969 laser.fuse_off = TRUE;
2972 DrawLaser(0, DL_LASER_DISABLED);
2973 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
2976 if (element == EL_BALL_GRAY && CT > 1500)
2978 static int new_elements[] =
2981 EL_MIRROR_FIXED_START,
2983 EL_POLAR_CROSS_START,
2989 int num_new_elements = sizeof(new_elements) / sizeof(int);
2990 int new_element = new_elements[RND(num_new_elements)];
2992 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
2993 Feld[ELX][ELY] = EL_GRAY_BALL_OPENING;
2995 /* !!! CHECK AGAIN: Laser on Polarizer !!! */
3006 element = EL_MIRROR_START + RND(16);
3012 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3019 element = (rnd == 0 ? EL_FUSE_ON :
3020 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3021 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3022 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3023 EL_MIRROR_FIXED_START + rnd - 25);
3028 graphic = el2gfx(element);
3036 BlitBitmap(pix[PIX_BACK], drawto,
3037 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3038 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3039 SX + ELX * TILEX + x,
3040 SY + ELY * TILEY + y);
3042 MarkTileDirty(ELX, ELY);
3045 DrawLaser(0, DL_LASER_ENABLED);
3050 Feld[ELX][ELY] = element;
3051 DrawField_MM(ELX, ELY);
3054 printf("NEW ELEMENT: (%d, %d)\n", ELX, ELY);
3057 /* above stuff: GRAY BALL -> PRISM !!! */
3059 LX = ELX * TILEX + 14;
3060 LY = ELY * TILEY + 14;
3061 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3068 laser.num_edges -= 2;
3069 laser.num_damages--;
3073 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3074 if (laser.damage[i].is_mirror)
3078 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3080 DrawLaser(0, DL_LASER_DISABLED);
3082 DrawLaser(0, DL_LASER_DISABLED);
3088 printf("TEST ELEMENT: %d\n", Feld[0][0]);
3095 if (IS_WALL_ICE(element) && CT > 1000)
3097 PlaySoundStereo(SND_SLURP, ST(ELX));
3102 Feld[ELX][ELY] = Feld[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3103 Store[ELX][ELY] = EL_WALL_ICE;
3104 Store2[ELX][ELY] = laser.wall_mask;
3106 laser.dest_element = Feld[ELX][ELY];
3120 Feld[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3124 DrawWallsAnimation_MM(ELX, ELY, Feld[ELX][ELY], phase, laser.wall_mask);
3129 if (Feld[ELX][ELY] == EL_WALL_ICE)
3130 Feld[ELX][ELY] = EL_EMPTY;
3134 LX = laser.edge[laser.num_edges].x - (SX + 2);
3135 LY = laser.edge[laser.num_edges].y - (SY + 2);
3138 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3139 if (laser.damage[i].is_mirror)
3143 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3145 DrawLaser(0, DL_LASER_DISABLED);
3152 if (IS_WALL_AMOEBA(element) && CT > 1200)
3154 int k1, k2, k3, dx, dy, de, dm;
3155 int element2 = Feld[ELX][ELY];
3157 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3160 for (i = laser.num_damages - 1; i>=0; i--)
3161 if (laser.damage[i].is_mirror)
3164 r = laser.num_edges;
3165 d = laser.num_damages;
3172 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3175 DrawLaser(0, DL_LASER_ENABLED);
3178 x = laser.damage[k1].x;
3179 y = laser.damage[k1].y;
3185 if (laser.wall_mask & (1 << i))
3187 if (ReadPixel(drawto,
3188 SX + ELX * TILEX + 14 + (i % 2) * 2,
3189 SY + ELY * TILEY + 31 * (i / 2)) == pen_ray)
3191 if (ReadPixel(drawto,
3192 SX + ELX * TILEX + 31 * (i % 2),
3193 SY + ELY * TILEY + 14 + (i / 2) * 2) == pen_ray)
3202 if (laser.wall_mask & (1 << i))
3204 if (ReadPixel(drawto,
3205 SX + ELX * TILEX + 31 * (i % 2),
3206 SY + ELY * TILEY + 31 * (i / 2)) == pen_ray)
3213 if (laser.num_beamers > 0 ||
3214 k1 < 1 || k2 < 4 || k3 < 4 ||
3215 ReadPixel(drawto, SX + ELX * TILEX + 14, SY + ELY * TILEY + 14)
3218 laser.num_edges = r;
3219 laser.num_damages = d;
3220 DrawLaser(0, DL_LASER_DISABLED);
3223 Feld[ELX][ELY] = element | laser.wall_mask;
3226 de = Feld[ELX][ELY];
3227 dm = laser.wall_mask;
3233 int x = ELX, y = ELY;
3234 int wall_mask = laser.wall_mask;
3238 DrawLaser(0, DL_LASER_ENABLED);
3240 PlaySoundStereo(SND_AMOEBE, ST(dx));
3244 Feld[x][y] = Feld[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3245 Store[x][y] = EL_WALL_AMOEBA;
3246 Store2[x][y] = wall_mask;
3254 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3256 DrawLaser(0, DL_LASER_ENABLED);
3258 PlaySoundStereo(SND_AMOEBE, ST(dx));
3262 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3267 DrawLaser(0, DL_LASER_ENABLED);
3272 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3273 laser.stops_inside_element && CT > 1500)
3278 if (ABS(XS) > ABS(YS))
3292 x = ELX + Step[k * 4].x;
3293 y = ELY + Step[k * 4].y;
3295 if (!IN_LEV_FIELD(x, y) || Feld[x][y] != EL_EMPTY)
3298 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3306 laser.overloaded = (element == EL_BLOCK_STONE);
3310 PlaySoundStereo(SND_BONG, ST(ELX));
3313 Feld[x][y] = element;
3315 DrawGraphic_MM(ELX, ELY, IMG_EMPTY);
3318 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3320 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3321 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3329 if (element == EL_FUEL_FULL && CT > 200)
3331 for(i=game_mm.energy_left; i<=MAX_LASER_ENERGY; i+=2)
3334 BlitBitmap(pix[PIX_DOOR], drawto,
3335 DOOR_GFX_PAGEX4 + XX_ENERGY,
3336 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3337 ENERGY_XSIZE, i, DX_ENERGY,
3338 DY_ENERGY + ENERGY_YSIZE - i);
3341 redraw_mask |= REDRAW_DOOR_1;
3347 game_mm.energy_left = MAX_LASER_ENERGY;
3348 Feld[ELX][ELY] = EL_FUEL_EMPTY;
3349 DrawField_MM(ELX, ELY);
3351 DrawLaser(0, DL_LASER_ENABLED);
3359 void GameActions_MM(byte action[MAX_PLAYERS], boolean warp_mode)
3362 ClickElement(0, 0, MB_NOT_PRESSED);
3364 GameActions_MM_Ext(action, warp_mode);
3370 int mx, my, ox, oy, nx, ny;
3374 if (++p >= game_mm.num_pacman)
3376 game_mm.pacman[p].dir--;
3380 game_mm.pacman[p].dir++;
3382 if (game_mm.pacman[p].dir > 4)
3383 game_mm.pacman[p].dir = 1;
3385 if (game_mm.pacman[p].dir % 2)
3388 my = game_mm.pacman[p].dir - 2;
3393 mx = 3 - game_mm.pacman[p].dir;
3396 ox = game_mm.pacman[p].x;
3397 oy = game_mm.pacman[p].y;
3400 element = Feld[nx][ny];
3401 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3404 if (!IS_EATABLE4PACMAN(element))
3407 if (ObjHit(nx, ny, HIT_POS_CENTER))
3410 Feld[ox][oy] = EL_EMPTY;
3412 EL_PACMAN_RIGHT - 1 +
3413 (game_mm.pacman[p].dir - 1 +
3414 (game_mm.pacman[p].dir % 2) * 2);
3416 game_mm.pacman[p].x = nx;
3417 game_mm.pacman[p].y = ny;
3419 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3421 if (element != EL_EMPTY)
3423 int graphic = el2gfx(Feld[nx][ny]);
3428 getGraphicSource(graphic, 0, &bitmap, &src_x, &src_y);
3431 ox = SX + ox * TILEX;
3432 oy = SY + oy * TILEY;
3434 for(i=1; i<33; i+=2)
3435 BlitBitmap(bitmap, window,
3436 src_x, src_y, TILEX, TILEY,
3437 ox + i * mx, oy + i * my);
3438 Ct = Ct + Counter() - CT;
3440 DrawField_MM(nx, ny);
3443 if (!laser.fuse_off)
3445 DrawLaser(0, DL_LASER_ENABLED);
3447 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3449 AddDamagedField(nx, ny);
3450 laser.damage[laser.num_damages - 1].edge = 0;
3454 if (element == EL_BOMB)
3456 DeletePacMan(nx, ny);
3459 if (IS_WALL_AMOEBA(element) &&
3460 (LX + 2 * XS) / TILEX == nx &&
3461 (LY + 2 * YS) / TILEY == ny)
3473 boolean raise_level = FALSE;
3476 if (local_player->MovPos)
3479 local_player->LevelSolved = FALSE;
3482 if (game_mm.energy_left)
3484 if (setup.sound_loops)
3485 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT, SND_CTRL_PLAY_LOOP);
3487 while(game_mm.energy_left > 0)
3489 if (!setup.sound_loops)
3490 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3493 if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10))
3494 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3499 game_mm.energy_left--;
3500 if (game_mm.energy_left >= 0)
3503 BlitBitmap(pix[PIX_DOOR], drawto,
3504 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3505 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3506 DX_ENERGY, DY_ENERGY);
3508 redraw_mask |= REDRAW_DOOR_1;
3515 if (setup.sound_loops)
3516 StopSound(SND_SIRR);
3518 else if (native_mm_level.time == 0) /* level without time limit */
3520 if (setup.sound_loops)
3521 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT, SND_CTRL_PLAY_LOOP);
3523 while(TimePlayed < 999)
3525 if (!setup.sound_loops)
3526 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3527 if (TimePlayed < 999 && !(TimePlayed % 10))
3528 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3529 if (TimePlayed < 900 && !(TimePlayed % 10))
3535 DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
3542 if (setup.sound_loops)
3543 StopSound(SND_SIRR);
3550 CloseDoor(DOOR_CLOSE_1);
3552 Request("Level solved !", REQ_CONFIRM);
3554 if (level_nr == leveldir_current->handicap_level)
3556 leveldir_current->handicap_level++;
3557 SaveLevelSetup_SeriesInfo();
3560 if (level_editor_test_game)
3561 game_mm.score = -1; /* no highscore when playing from editor */
3562 else if (level_nr < leveldir_current->last_level)
3563 raise_level = TRUE; /* advance to next level */
3565 if ((hi_pos = NewHiScore_MM()) >= 0)
3567 game_status = HALLOFFAME;
3568 // DrawHallOfFame(hi_pos);
3574 game_status = MAINMENU;
3588 // LoadScore(level_nr);
3590 if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
3591 game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score)
3594 for (k=0; k<MAX_SCORE_ENTRIES; k++)
3596 if (game_mm.score > highscore[k].Score)
3598 /* player has made it to the hall of fame */
3600 if (k < MAX_SCORE_ENTRIES - 1)
3602 int m = MAX_SCORE_ENTRIES - 1;
3605 for (l=k; l<MAX_SCORE_ENTRIES; l++)
3606 if (!strcmp(setup.player_name, highscore[l].Name))
3608 if (m == k) /* player's new highscore overwrites his old one */
3614 strcpy(highscore[l].Name, highscore[l - 1].Name);
3615 highscore[l].Score = highscore[l - 1].Score;
3622 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
3623 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
3624 highscore[k].Score = game_mm.score;
3630 else if (!strncmp(setup.player_name, highscore[k].Name,
3631 MAX_PLAYER_NAME_LEN))
3632 break; /* player already there with a higher score */
3637 // if (position >= 0)
3638 // SaveScore(level_nr);
3643 static void InitMovingField_MM(int x, int y, int direction)
3645 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3646 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3648 MovDir[x][y] = direction;
3649 MovDir[newx][newy] = direction;
3650 if (Feld[newx][newy] == EL_EMPTY)
3651 Feld[newx][newy] = EL_BLOCKED;
3654 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3656 int direction = MovDir[x][y];
3657 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3658 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3664 static void Blocked2Moving_MM(int x, int y,
3665 int *comes_from_x, int *comes_from_y)
3667 int oldx = x, oldy = y;
3668 int direction = MovDir[x][y];
3670 if (direction == MV_LEFT)
3672 else if (direction == MV_RIGHT)
3674 else if (direction == MV_UP)
3676 else if (direction == MV_DOWN)
3679 *comes_from_x = oldx;
3680 *comes_from_y = oldy;
3683 static int MovingOrBlocked2Element_MM(int x, int y)
3685 int element = Feld[x][y];
3687 if (element == EL_BLOCKED)
3691 Blocked2Moving_MM(x, y, &oldx, &oldy);
3692 return Feld[oldx][oldy];
3699 static void RemoveField(int x, int y)
3701 Feld[x][y] = EL_EMPTY;
3708 static void RemoveMovingField_MM(int x, int y)
3710 int oldx = x, oldy = y, newx = x, newy = y;
3712 if (Feld[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3715 if (IS_MOVING(x, y))
3717 Moving2Blocked_MM(x, y, &newx, &newy);
3718 if (Feld[newx][newy] != EL_BLOCKED)
3721 else if (Feld[x][y] == EL_BLOCKED)
3723 Blocked2Moving_MM(x, y, &oldx, &oldy);
3724 if (!IS_MOVING(oldx, oldy))
3728 Feld[oldx][oldy] = EL_EMPTY;
3729 Feld[newx][newy] = EL_EMPTY;
3730 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3731 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3733 DrawLevelField_MM(oldx, oldy);
3734 DrawLevelField_MM(newx, newy);
3737 void PlaySoundLevel(int x, int y, int sound_nr)
3739 int sx = SCREENX(x), sy = SCREENY(y);
3741 int silence_distance = 8;
3743 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3744 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3747 if (!IN_LEV_FIELD(x, y) ||
3748 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3749 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3752 volume = SOUND_MAX_VOLUME;
3755 stereo = (sx - SCR_FIELDX/2) * 12;
3757 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3758 if (stereo > SOUND_MAX_RIGHT)
3759 stereo = SOUND_MAX_RIGHT;
3760 if (stereo < SOUND_MAX_LEFT)
3761 stereo = SOUND_MAX_LEFT;
3764 if (!IN_SCR_FIELD(sx, sy))
3766 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3767 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3769 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3772 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3775 static void RaiseScore_MM(int value)
3777 game_mm.score += value;
3779 DrawText(DX_SCORE, DY_SCORE, int2str(game_mm.score, 4),
3784 void RaiseScoreElement_MM(int element)
3789 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3792 RaiseScore_MM(native_mm_level.score[SC_KEY]);