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);
356 /* copy default game door content to main double buffer */
357 BlitBitmap(pix[PIX_DOOR], drawto,
358 DOOR_GFX_PAGEX5, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY);
362 DrawText(DX_LEVEL, DY_LEVEL,
363 int2str(level_nr, 2), FONT_TEXT_2);
364 DrawText(DX_KETTLES, DY_KETTLES,
365 int2str(game_mm.kettles_still_needed, 3), FONT_TEXT_2);
366 DrawText(DX_SCORE, DY_SCORE,
367 int2str(game_mm.score, 4), FONT_TEXT_2);
376 /* copy actual game door content to door double buffer for OpenDoor() */
377 BlitBitmap(drawto, pix[PIX_DB_DOOR],
378 DX, DY, DXSIZE, DYSIZE, DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1);
382 OpenDoor(DOOR_OPEN_ALL);
385 if (setup.sound_loops)
386 PlaySoundExt(SND_FUEL, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT, SND_CTRL_PLAY_LOOP);
388 #if 0 // !!! TEMPORARILY DISABLED !!!
389 for(i=0; i<=game_mm.energy_left; i+=2)
391 if (!setup.sound_loops)
392 PlaySoundStereo(SND_FUEL, SOUND_MAX_RIGHT);
395 BlitBitmap(pix[PIX_DOOR], drawto,
396 DOOR_GFX_PAGEX4 + XX_ENERGY,
397 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
399 DX_ENERGY, DY_ENERGY + ENERGY_YSIZE - i);
402 redraw_mask |= REDRAW_DOOR_1;
408 if (setup.quick_doors)
415 if (setup.sound_loops)
420 if (setup.sound_music && num_bg_loops)
421 PlayMusic(level_nr % num_bg_loops);
427 void AddLaserEdge(int lx, int ly)
429 if (lx < -2 || ly < -2 || lx >= SXSIZE + 2 || ly >= SYSIZE + 2)
431 Error(ERR_WARN, "AddLaserEdge: out of bounds: %d, %d", lx, ly);
435 laser.edge[laser.num_edges].x = SX + 2 + lx;
436 laser.edge[laser.num_edges].y = SY + 2 + ly;
442 void AddDamagedField(int ex, int ey)
444 laser.damage[laser.num_damages].is_mirror = FALSE;
445 laser.damage[laser.num_damages].angle = laser.current_angle;
446 laser.damage[laser.num_damages].edge = laser.num_edges;
447 laser.damage[laser.num_damages].x = ex;
448 laser.damage[laser.num_damages].y = ey;
458 int last_x = laser.edge[laser.num_edges - 1].x - SX - 2;
459 int last_y = laser.edge[laser.num_edges - 1].y - SY - 2;
461 return ((x - last_x) * XS < 0 || (y - last_y) * YS < 0);
467 static int getMaskFromElement(int element)
469 if (IS_GRID(element))
470 return IMG_MM_MASK_GRID_1 + get_element_phase(element);
471 else if (IS_MCDUFFIN(element))
472 return IMG_MM_MASK_MCDUFFIN_RIGHT + get_element_phase(element);
473 else if (IS_RECTANGLE(element) || IS_DF_GRID(element))
474 return IMG_MM_MASK_RECTANGLE;
476 return IMG_MM_MASK_CIRCLE;
484 printf("ScanPixel: start scanning at (%d, %d) [%d, %d] [%d, %d]\n",
485 LX, LY, LX / TILEX, LY / TILEY, LX % TILEX, LY % TILEY);
488 /* follow laser beam until it hits something (at least the screen border) */
489 while (hit_mask == HIT_MASK_NO_HIT)
495 if (SX + LX < REAL_SX || SX + LX >= REAL_SX + FULL_SXSIZE ||
496 SY + LY < REAL_SY || SY + LY >= REAL_SY + FULL_SYSIZE)
498 printf("ScanPixel: touched screen border!\n");
509 px = SX + LX + (i % 2) * 2;
510 py = SY + LY + (i / 2) * 2;
511 lx = (px - SX + TILEX) / TILEX - 1; /* ...+TILEX...-1 to get correct */
512 ly = (py - SY + TILEY) / TILEY - 1; /* negative values! */
514 if (IN_LEV_FIELD(lx, ly))
516 int element = Feld[lx][ly];
518 if (element == EL_EMPTY || element == EL_EXPLODING_TRANSP)
520 else if (IS_WALL(element) || IS_WALL_CHANGING(element))
523 ((py - SY - ly * TILEY) / MINI_TILEX) * 2 +
524 (px - SX - lx * TILEX) / MINI_TILEY;
526 pixel = ((element & (1 << pos)) ? 1 : 0);
530 int graphic_mask = getMaskFromElement(element);
532 int dx = px - lx * TILEX;
533 int dy = py - ly * TILEY;
537 getGraphicSource(graphic_mask, 0, &bitmap, &src_x, &src_y);
542 pixel = (ReadPixel(bitmap, mask_x, mask_y) ? 1 : 0);
547 if (px < REAL_SX || px >= REAL_SX + FULL_SXSIZE ||
548 py < REAL_SY || py >= REAL_SY + FULL_SYSIZE)
554 if ((Sign[laser.current_angle] & (1 << i)) && pixel)
555 hit_mask |= (1 << i);
558 if (hit_mask == HIT_MASK_NO_HIT)
560 /* hit nothing -- go on with another step */
572 int end = 0, rf = laser.num_edges;
574 laser.overloaded = FALSE;
575 laser.stops_inside_element = FALSE;
577 DrawLaser(0, DL_LASER_ENABLED);
580 printf("Start scanning with LX == %d, LY == %d, XS == %d, YS == %d\n",
588 if (laser.num_edges > MAX_LASER_LEN || laser.num_damages > MAX_LASER_LEN)
591 laser.overloaded = TRUE;
595 hit_mask = ScanPixel();
598 printf("Hit something at LX == %d, LY == %d, XS == %d, YS == %d\n",
602 /* hit something -- check out what it was */
603 ELX = (LX + XS) / TILEX;
604 ELY = (LY + YS) / TILEY;
607 printf("hit_mask (1) == '%x' (%d, %d) (%d, %d)\n",
608 hit_mask, LX, LY, ELX, ELY);
611 if (!IN_LEV_FIELD(ELX, ELY) || !IN_PIX_FIELD(LX, LY))
614 laser.dest_element = element;
619 if (hit_mask == (HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT))
621 /* we have hit the top-right and bottom-left element --
622 choose the bottom-left one */
623 /* !!! THIS CAN BE DONE MORE INTELLIGENTLY, FOR EXAMPLE, IF ONE
624 ELEMENT WAS STEEL AND THE OTHER ONE WAS ICE => ALWAYS CHOOSE
625 THE ICE AND MELT IT AWAY INSTEAD OF OVERLOADING LASER !!! */
626 ELX = (LX - 2) / TILEX;
627 ELY = (LY + 2) / TILEY;
630 if (hit_mask == (HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT))
632 /* we have hit the top-left and bottom-right element --
633 choose the top-left one */
634 /* !!! SEE ABOVE !!! */
635 ELX = (LX - 2) / TILEX;
636 ELY = (LY - 2) / TILEY;
640 printf("hit_mask (2) == '%x' (%d, %d) (%d, %d)\n",
641 hit_mask, LX, LY, ELX, ELY);
644 element = Feld[ELX][ELY];
645 laser.dest_element = element;
648 printf("Hit element %d at (%d, %d) [%d, %d] [%d, %d] [%d]\n",
651 LX % TILEX, LY % TILEY,
656 if (!IN_LEV_FIELD(ELX, ELY))
657 printf("WARNING! (1) %d, %d (%d)\n", ELX, ELY, element);
660 if (element == EL_EMPTY)
662 if (!HitOnlyAnEdge(element, hit_mask))
665 else if (element == EL_FUSE_ON)
667 if (HitPolarizer(element, hit_mask))
670 else if (IS_GRID(element) || IS_DF_GRID(element))
672 if (HitPolarizer(element, hit_mask))
675 else if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD ||
676 element == EL_GATE_STONE || element == EL_GATE_WOOD)
678 if (HitBlock(element, hit_mask))
684 else if (IS_MCDUFFIN(element))
686 if (HitLaserSource(element, hit_mask))
689 else if ((element >= EL_EXIT_CLOSED && element <= EL_EXIT_OPEN) ||
690 IS_RECEIVER(element))
692 if (HitLaserDestination(element, hit_mask))
695 else if (IS_WALL(element))
697 if (IS_WALL_STEEL(element) || IS_DF_WALL_STEEL(element))
699 if (HitReflectingWalls(element, hit_mask))
704 if (HitAbsorbingWalls(element, hit_mask))
710 if (HitElement(element, hit_mask))
715 DrawLaser(rf - 1, DL_LASER_ENABLED);
716 rf = laser.num_edges;
720 if (laser.dest_element != Feld[ELX][ELY])
722 printf("ALARM: laser.dest_element == %d, Feld[ELX][ELY] == %d\n",
723 laser.dest_element, Feld[ELX][ELY]);
727 if (!end && !laser.stops_inside_element && !StepBehind())
730 printf("ScanLaser: Go one step back\n");
735 AddLaserEdge(LX, LY);
739 DrawLaser(rf - 1, DL_LASER_ENABLED);
744 if (!IN_LEV_FIELD(ELX, ELY))
745 printf("WARNING! (2) %d, %d\n", ELX, ELY);
749 void DrawLaserExt(int start_edge, int num_edges, int mode)
755 printf("DrawLaserExt: start_edge, num_edges, mode == %d, %d, %d\n",
756 start_edge, num_edges, mode);
761 Error(ERR_WARN, "DrawLaserExt: start_edge < 0");
767 Error(ERR_WARN, "DrawLaserExt: num_edges < 0");
772 if (mode == DL_LASER_DISABLED)
774 printf("DrawLaser: Delete laser from edge %d\n", start_edge);
778 /* now draw the laser to the backbuffer and (if enabled) to the screen */
779 DrawLines(drawto, &laser.edge[start_edge], num_edges,
780 (mode == DL_LASER_ENABLED ? pen_ray : pen_bg));
782 redraw_mask |= REDRAW_FIELD;
784 if (mode == DL_LASER_ENABLED)
787 /* after the laser was deleted, the "damaged" graphics must be restored */
788 if (laser.num_damages)
790 int damage_start = 0;
793 /* determine the starting edge, from which graphics need to be restored */
796 for(i=0; i<laser.num_damages; i++)
798 if (laser.damage[i].edge == start_edge + 1)
806 /* restore graphics from this starting edge to the end of damage list */
807 for(i=damage_start; i<laser.num_damages; i++)
809 int lx = laser.damage[i].x;
810 int ly = laser.damage[i].y;
811 int element = Feld[lx][ly];
813 if (Hit[lx][ly] == laser.damage[i].edge)
814 if (!((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
817 if (Box[lx][ly] == laser.damage[i].edge)
820 if (IS_DRAWABLE(element))
821 DrawField_MM(lx, ly);
824 elx = laser.damage[damage_start].x;
825 ely = laser.damage[damage_start].y;
826 element = Feld[elx][ely];
829 if (IS_BEAMER(element))
833 for (i=0; i<laser.num_beamers; i++)
834 printf("-> %d\n", laser.beamer_edge[i]);
835 printf("DrawLaserExt: IS_BEAMER: [%d]: Hit[%d][%d] == %d [%d]\n",
836 mode, elx, ely, Hit[elx][ely], start_edge);
837 printf("DrawLaserExt: IS_BEAMER: %d / %d\n",
838 get_element_angle(element), laser.damage[damage_start].angle);
842 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
843 laser.num_beamers > 0 &&
844 start_edge == laser.beamer_edge[laser.num_beamers - 1])
846 /* element is outgoing beamer */
847 laser.num_damages = damage_start + 1;
848 if (IS_BEAMER(element))
849 laser.current_angle = get_element_angle(element);
853 /* element is incoming beamer or other element */
854 laser.num_damages = damage_start;
855 laser.current_angle = laser.damage[laser.num_damages].angle;
860 /* no damages but McDuffin himself (who needs to be redrawn anyway) */
862 elx = laser.start_edge.x;
863 ely = laser.start_edge.y;
864 element = Feld[elx][ely];
867 laser.num_edges = start_edge + 1;
869 laser.current_angle = laser.start_angle;
870 LX = laser.edge[start_edge].x - (SX + 2);
871 LY = laser.edge[start_edge].y - (SY + 2);
872 XS = 2 * Step[laser.current_angle].x;
873 YS = 2 * Step[laser.current_angle].y;
876 printf("DrawLaser: Set (LX, LY) to (%d, %d) [%d]\n",
882 if (IS_BEAMER(element) ||
883 IS_FIBRE_OPTIC(element) ||
884 IS_PACMAN(element) ||
886 IS_POLAR_CROSS(element) ||
887 element == EL_FUSE_ON)
892 printf("element == %d\n", element);
895 if (IS_22_5_ANGLE(laser.current_angle)) /* neither 90° nor 45° angle */
896 step_size = ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) ? 4 : 3);
900 if (IS_POLAR(element) || IS_POLAR_CROSS(element) ||
901 ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
902 (laser.num_beamers == 0 ||
903 start_edge != laser.beamer_edge[laser.num_beamers - 1])))
905 /* element is incoming beamer or other element */
906 step_size = -step_size;
911 if (IS_BEAMER(element))
913 printf("start_edge == %d, laser.beamer_edge == %d\n",
914 start_edge, laser.beamer_edge);
918 LX += step_size * XS;
919 LY += step_size * YS;
921 else if (element != EL_EMPTY)
930 printf("DrawLaser: Finally: (LX, LY) to (%d, %d) [%d]\n",
935 void DrawLaser(int start_edge, int mode)
937 if (laser.num_edges - start_edge < 0)
939 Error(ERR_WARN, "DrawLaser: laser.num_edges - start_edge < 0");
943 /* check if laser is interrupted by beamer element */
944 if (laser.num_beamers > 0 &&
945 start_edge < laser.beamer_edge[laser.num_beamers - 1])
947 if (mode == DL_LASER_ENABLED)
950 int tmp_start_edge = start_edge;
952 /* draw laser segments forward from the start to the last beamer */
953 for (i=0; i<laser.num_beamers; i++)
955 int tmp_num_edges = laser.beamer_edge[i] - tmp_start_edge;
957 if (tmp_num_edges <= 0)
961 printf("DrawLaser: DL_LASER_ENABLED: i==%d: %d, %d\n",
962 i, laser.beamer_edge[i], tmp_start_edge);
965 DrawLaserExt(tmp_start_edge, tmp_num_edges, DL_LASER_ENABLED);
966 tmp_start_edge = laser.beamer_edge[i];
969 /* draw last segment from last beamer to the end */
970 DrawLaserExt(tmp_start_edge, laser.num_edges - tmp_start_edge,
976 int last_num_edges = laser.num_edges;
977 int num_beamers = laser.num_beamers;
979 /* delete laser segments backward from the end to the first beamer */
980 for (i=num_beamers-1; i>=0; i--)
982 int tmp_num_edges = last_num_edges - laser.beamer_edge[i];
984 if (laser.beamer_edge[i] - start_edge <= 0)
987 DrawLaserExt(laser.beamer_edge[i], tmp_num_edges, DL_LASER_DISABLED);
988 last_num_edges = laser.beamer_edge[i];
993 if (last_num_edges - start_edge <= 0)
994 printf("DrawLaser: DL_LASER_DISABLED: %d, %d\n",
995 last_num_edges, start_edge);
998 /* delete first segment from start to the first beamer */
999 DrawLaserExt(start_edge, last_num_edges - start_edge, DL_LASER_DISABLED);
1003 DrawLaserExt(start_edge, laser.num_edges - start_edge, mode);
1006 boolean HitElement(int element, int hit_mask)
1008 if (HitOnlyAnEdge(element, hit_mask))
1011 if (IS_MOVING(ELX, ELY) || IS_BLOCKED(ELX, ELY))
1012 element = MovingOrBlocked2Element_MM(ELX, ELY);
1015 printf("HitElement (1): element == %d\n", element);
1019 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1020 printf("HitElement (%d): EXACT MATCH @ (%d, %d)\n", element, ELX, ELY);
1022 printf("HitElement (%d): FUZZY MATCH @ (%d, %d)\n", element, ELX, ELY);
1025 AddDamagedField(ELX, ELY);
1027 /* this is more precise: check if laser would go through the center */
1028 if ((ELX * TILEX + 14 - LX) * YS != (ELY * TILEY + 14 - LY) * XS)
1030 /* skip the whole element before continuing the scan */
1036 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1038 if (LX/TILEX > ELX || LY/TILEY > ELY)
1040 /* skipping scan positions to the right and down skips one scan
1041 position too much, because this is only the top left scan position
1042 of totally four scan positions (plus one to the right, one to the
1043 bottom and one to the bottom right) */
1053 printf("HitElement (2): element == %d\n", element);
1056 if (LX + 5 * XS < 0 ||
1066 printf("HitElement (3): element == %d\n", element);
1069 if (IS_POLAR(element) &&
1070 ((element - EL_POLAR_START) % 2 ||
1071 (element - EL_POLAR_START) / 2 != laser.current_angle % 8))
1073 PlaySoundStereo(SND_KINK, ST(ELX));
1074 laser.num_damages--;
1079 if (IS_POLAR_CROSS(element) &&
1080 (element - EL_POLAR_CROSS_START) != laser.current_angle % 4)
1082 PlaySoundStereo(SND_KINK, ST(ELX));
1083 laser.num_damages--;
1088 if (!IS_BEAMER(element) &&
1089 !IS_FIBRE_OPTIC(element) &&
1090 !IS_GRID_WOOD(element) &&
1091 element != EL_FUEL_EMPTY)
1094 if ((ELX * TILEX + 14 - LX) * YS == (ELY * TILEY + 14 - LY) * XS)
1095 printf("EXACT MATCH @ (%d, %d)\n", ELX, ELY);
1097 printf("FUZZY MATCH @ (%d, %d)\n", ELX, ELY);
1100 LX = ELX * TILEX + 14;
1101 LY = ELY * TILEY + 14;
1102 AddLaserEdge(LX, LY);
1105 if (IS_MIRROR(element) ||
1106 IS_MIRROR_FIXED(element) ||
1107 IS_POLAR(element) ||
1108 IS_POLAR_CROSS(element) ||
1109 IS_DF_MIRROR(element) ||
1110 IS_DF_MIRROR_AUTO(element) ||
1111 element == EL_PRISM ||
1112 element == EL_REFRACTOR)
1114 int current_angle = laser.current_angle;
1117 laser.num_damages--;
1118 AddDamagedField(ELX, ELY);
1119 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1122 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1124 if (IS_MIRROR(element) ||
1125 IS_MIRROR_FIXED(element) ||
1126 IS_DF_MIRROR(element) ||
1127 IS_DF_MIRROR_AUTO(element))
1128 laser.current_angle = get_mirrored_angle(laser.current_angle,
1129 get_element_angle(element));
1131 if (element == EL_PRISM || element == EL_REFRACTOR)
1132 laser.current_angle = RND(16);
1134 XS = 2 * Step[laser.current_angle].x;
1135 YS = 2 * Step[laser.current_angle].y;
1137 if (!IS_22_5_ANGLE(laser.current_angle)) /* 90° or 45° angle */
1142 LX += step_size * XS;
1143 LY += step_size * YS;
1146 /* draw sparkles on mirror */
1147 if ((IS_MIRROR(element) || IS_MIRROR_FIXED(element)) &&
1148 current_angle != laser.current_angle)
1150 MoveSprite(vp, &Pfeil[2], 4 + 16 * ELX, 5 + 16 * ELY + 1);
1154 if ((!IS_POLAR(element) && !IS_POLAR_CROSS(element)) &&
1155 current_angle != laser.current_angle)
1156 PlaySoundStereo(SND_LASER, ST(ELX));
1159 (get_opposite_angle(laser.current_angle) ==
1160 laser.damage[laser.num_damages - 1].angle ? TRUE : FALSE);
1162 return (laser.overloaded ? TRUE : FALSE);
1165 if (element == EL_FUEL_FULL)
1167 laser.stops_inside_element = TRUE;
1172 if (element == EL_BOMB || element == EL_MINE)
1174 PlaySoundStereo(SND_KINK, ST(ELX));
1176 if (element == EL_MINE)
1177 laser.overloaded = TRUE;
1180 if (element == EL_KETTLE ||
1181 element == EL_CELL ||
1182 element == EL_KEY ||
1183 element == EL_LIGHTBALL ||
1184 element == EL_PACMAN ||
1187 if (!IS_PACMAN(element))
1190 if (element == EL_PACMAN)
1193 if (element == EL_KETTLE || element == EL_CELL)
1197 if (game_mm.kettles_still_needed == 0)
1200 static int xy[4][2] =
1208 PlaySoundStereo(SND_KLING, ST(ELX));
1210 for(y=0; y<lev_fieldy; y++)
1212 for(x=0; x<lev_fieldx; x++)
1214 /* initiate opening animation of exit door */
1215 if (Feld[x][y] == EL_EXIT_CLOSED)
1216 Feld[x][y] = EL_EXIT_OPENING;
1218 /* remove field that blocks receiver */
1219 if (IS_RECEIVER(Feld[x][y]))
1221 int phase = Feld[x][y] - EL_RECEIVER_START;
1222 int blocking_x, blocking_y;
1224 blocking_x = x + xy[phase][0];
1225 blocking_y = y + xy[phase][1];
1227 if (IN_LEV_FIELD(blocking_x, blocking_y))
1229 Feld[blocking_x][blocking_y] = EL_EMPTY;
1230 DrawField_MM(blocking_x, blocking_y);
1236 DrawLaser(0, DL_LASER_ENABLED);
1239 else if (element == EL_KEY)
1241 else if (element == EL_LIGHTBALL)
1243 else if (IS_PACMAN(element))
1245 DeletePacMan(ELX, ELY);
1252 if (element == EL_LIGHTBULB_OFF || element == EL_LIGHTBULB_ON)
1254 PlaySoundStereo(SND_KINK, ST(ELX));
1256 DrawLaser(0, DL_LASER_ENABLED);
1258 if (Feld[ELX][ELY] == EL_LIGHTBULB_OFF)
1260 Feld[ELX][ELY] = EL_LIGHTBULB_ON;
1261 game_mm.lights_still_needed--;
1265 Feld[ELX][ELY] = EL_LIGHTBULB_OFF;
1266 game_mm.lights_still_needed++;
1269 DrawField_MM(ELX, ELY);
1270 DrawLaser(0, DL_LASER_ENABLED);
1275 laser.stops_inside_element = TRUE;
1281 printf("HitElement (4): element == %d\n", element);
1284 if ((IS_BEAMER(element) || IS_FIBRE_OPTIC(element)) &&
1285 laser.num_beamers < MAX_NUM_BEAMERS &&
1286 laser.beamer[BEAMER_NR(element)][1].num)
1288 int beamer_angle = get_element_angle(element);
1289 int beamer_nr = BEAMER_NR(element);
1293 printf("HitElement (BEAMER): element == %d\n", element);
1296 laser.num_damages--;
1298 if (IS_FIBRE_OPTIC(element) ||
1299 laser.current_angle == get_opposite_angle(beamer_angle))
1303 LX = ELX * TILEX + 14;
1304 LY = ELY * TILEY + 14;
1305 AddLaserEdge(LX, LY);
1306 AddDamagedField(ELX, ELY);
1307 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1310 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1312 pos = (ELX == laser.beamer[beamer_nr][0].x &&
1313 ELY == laser.beamer[beamer_nr][0].y ? 1 : 0);
1314 ELX = laser.beamer[beamer_nr][pos].x;
1315 ELY = laser.beamer[beamer_nr][pos].y;
1316 LX = ELX * TILEX + 14;
1317 LY = ELY * TILEY + 14;
1319 if (IS_BEAMER(element))
1321 laser.current_angle = get_element_angle(Feld[ELX][ELY]);
1322 XS = 2 * Step[laser.current_angle].x;
1323 YS = 2 * Step[laser.current_angle].y;
1326 laser.beamer_edge[laser.num_beamers] = laser.num_edges;
1327 AddLaserEdge(LX, LY);
1328 AddDamagedField(ELX, ELY);
1329 laser.damage[laser.num_damages - 1].is_mirror = TRUE;
1332 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1334 if (laser.current_angle == (laser.current_angle >> 1) << 1)
1339 LX += step_size * XS;
1340 LY += step_size * YS;
1342 laser.num_beamers++;
1351 boolean HitOnlyAnEdge(int element, int hit_mask)
1353 /* check if the laser hit only the edge of an element and, if so, go on */
1356 printf("LX, LY, hit_mask == %d, %d, %d\n", LX, LY, hit_mask);
1359 if ((hit_mask == HIT_MASK_TOPLEFT ||
1360 hit_mask == HIT_MASK_TOPRIGHT ||
1361 hit_mask == HIT_MASK_BOTTOMLEFT ||
1362 hit_mask == HIT_MASK_BOTTOMRIGHT) &&
1363 laser.current_angle % 4) /* angle is not 90° */
1367 if (hit_mask == HIT_MASK_TOPLEFT)
1372 else if (hit_mask == HIT_MASK_TOPRIGHT)
1377 else if (hit_mask == HIT_MASK_BOTTOMLEFT)
1382 else /* (hit_mask == HIT_MASK_BOTTOMRIGHT) */
1388 AddDamagedField((LX + 2 * dx) / TILEX, (LY + 2 * dy) / TILEY);
1393 printf("[HitOnlyAnEdge() == TRUE]\n");
1400 printf("[HitOnlyAnEdge() == FALSE]\n");
1406 boolean HitPolarizer(int element, int hit_mask)
1408 if (HitOnlyAnEdge(element, hit_mask))
1411 if (IS_DF_GRID(element))
1413 int grid_angle = get_element_angle(element);
1416 printf("HitPolarizer: angle: grid == %d, laser == %d\n",
1417 grid_angle, laser.current_angle);
1420 AddLaserEdge(LX, LY);
1421 AddDamagedField(ELX, ELY);
1424 Hit[ELX][ELY] = laser.damage[laser.num_damages - 1].edge;
1426 if (laser.current_angle == grid_angle ||
1427 laser.current_angle == get_opposite_angle(grid_angle))
1429 /* skip the whole element before continuing the scan */
1435 while (ELX == LX/TILEX && ELY == LY/TILEY && LX > 0 && LY > 0);
1437 if (LX/TILEX > ELX || LY/TILEY > ELY)
1439 /* skipping scan positions to the right and down skips one scan
1440 position too much, because this is only the top left scan position
1441 of totally four scan positions (plus one to the right, one to the
1442 bottom and one to the bottom right) */
1448 AddLaserEdge(LX, LY);
1454 printf("HitPolarizer: LX, LY == %d, %d [%d, %d] [%d, %d]\n",
1456 LX / TILEX, LY / TILEY,
1457 LX % TILEX, LY % TILEY);
1462 else if (IS_GRID_STEEL_FIXED(element) || IS_GRID_STEEL_AUTO(element))
1463 return HitReflectingWalls(element, hit_mask);
1465 return HitAbsorbingWalls(element, hit_mask);
1467 else if (IS_GRID_STEEL(element))
1468 return HitReflectingWalls(element, hit_mask);
1469 else /* IS_GRID_WOOD */
1470 return HitAbsorbingWalls(element, hit_mask);
1475 boolean HitBlock(int element, int hit_mask)
1477 boolean check = FALSE;
1479 if ((element == EL_GATE_STONE || element == EL_GATE_WOOD) &&
1480 game_mm.num_keys == 0)
1483 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1486 int ex = ELX * TILEX + 14;
1487 int ey = ELY * TILEY + 14;
1496 if ((x == ex || x == ex + 1) && (y == ey || y == ey + 1))
1501 if (check && (element == EL_BLOCK_WOOD || element == EL_GATE_WOOD))
1502 return HitAbsorbingWalls(element, hit_mask);
1506 AddLaserEdge(LX - XS, LY - YS);
1507 AddDamagedField(ELX, ELY);
1510 Box[ELX][ELY] = laser.num_edges;
1512 return HitReflectingWalls(element, hit_mask);
1515 if (element == EL_GATE_STONE || element == EL_GATE_WOOD)
1517 int xs = XS / 2, ys = YS / 2;
1518 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1519 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1521 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1522 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1524 laser.overloaded = (element == EL_GATE_STONE);
1528 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1529 (hit_mask == HIT_MASK_TOP ||
1530 hit_mask == HIT_MASK_LEFT ||
1531 hit_mask == HIT_MASK_RIGHT ||
1532 hit_mask == HIT_MASK_BOTTOM))
1533 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1534 hit_mask == HIT_MASK_BOTTOM),
1535 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1536 hit_mask == HIT_MASK_RIGHT));
1537 AddLaserEdge(LX, LY);
1542 if (element == EL_GATE_STONE && Box[ELX][ELY])
1544 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
1556 if (element == EL_BLOCK_STONE || element == EL_BLOCK_WOOD)
1558 int xs = XS / 2, ys = YS / 2;
1559 int hit_mask_diagonal1 = HIT_MASK_TOPRIGHT | HIT_MASK_BOTTOMLEFT;
1560 int hit_mask_diagonal2 = HIT_MASK_TOPLEFT | HIT_MASK_BOTTOMRIGHT;
1562 if ((hit_mask & hit_mask_diagonal1) == hit_mask_diagonal1 ||
1563 (hit_mask & hit_mask_diagonal2) == hit_mask_diagonal2)
1565 laser.overloaded = (element == EL_BLOCK_STONE);
1570 if (ABS(xs) == 1 && ABS(ys) == 1 &&
1571 (hit_mask == HIT_MASK_TOP ||
1572 hit_mask == HIT_MASK_LEFT ||
1573 hit_mask == HIT_MASK_RIGHT ||
1574 hit_mask == HIT_MASK_BOTTOM))
1575 AddDamagedField(ELX - xs * (hit_mask == HIT_MASK_TOP ||
1576 hit_mask == HIT_MASK_BOTTOM),
1577 ELY - ys * (hit_mask == HIT_MASK_LEFT ||
1578 hit_mask == HIT_MASK_RIGHT));
1579 AddDamagedField(ELX, ELY);
1581 LX = ELX * TILEX + 14;
1582 LY = ELY * TILEY + 14;
1583 AddLaserEdge(LX, LY);
1585 laser.stops_inside_element = TRUE;
1593 boolean HitLaserSource(int element, int hit_mask)
1595 if (HitOnlyAnEdge(element, hit_mask))
1598 PlaySoundStereo(SND_AUTSCH, ST(ELX));
1599 laser.overloaded = TRUE;
1604 boolean HitLaserDestination(int element, int hit_mask)
1606 if (HitOnlyAnEdge(element, hit_mask))
1609 if (element != EL_EXIT_OPEN &&
1610 !(IS_RECEIVER(element) &&
1611 game_mm.kettles_still_needed == 0 &&
1612 laser.current_angle == get_opposite_angle(get_element_angle(element))))
1614 PlaySoundStereo(SND_HOLZ, ST(ELX));
1618 if (IS_RECEIVER(element) ||
1619 (IS_22_5_ANGLE(laser.current_angle) &&
1620 (ELX != (LX + 6 * XS) / TILEX ||
1621 ELY != (LY + 6 * YS) / TILEY ||
1630 LX = ELX * TILEX + 14;
1631 LY = ELY * TILEY + 14;
1633 laser.stops_inside_element = TRUE;
1636 AddLaserEdge(LX, LY);
1637 AddDamagedField(ELX, ELY);
1639 if (game_mm.lights_still_needed == 0)
1640 game_mm.level_solved = TRUE;
1645 boolean HitReflectingWalls(int element, int hit_mask)
1647 /* check if laser hits side of a wall with an angle that is not 90° */
1648 if (!IS_90_ANGLE(laser.current_angle) && (hit_mask == HIT_MASK_TOP ||
1649 hit_mask == HIT_MASK_LEFT ||
1650 hit_mask == HIT_MASK_RIGHT ||
1651 hit_mask == HIT_MASK_BOTTOM))
1653 PlaySoundStereo(SND_HUI, ST(ELX));
1656 if (!IS_DF_GRID(element))
1657 AddLaserEdge(LX, LY);
1659 /* check if laser hits wall with an angle of 45° */
1660 if (!IS_22_5_ANGLE(laser.current_angle))
1662 if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1665 laser.current_angle = get_mirrored_angle(laser.current_angle,
1668 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
1671 laser.current_angle = get_mirrored_angle(laser.current_angle,
1675 AddLaserEdge(LX, LY);
1676 XS = 2 * Step[laser.current_angle].x;
1677 YS = 2 * Step[laser.current_angle].y;
1681 else if (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM)
1683 laser.current_angle = get_mirrored_angle(laser.current_angle,
1688 if (!IS_DF_GRID(element))
1689 AddLaserEdge(LX, LY);
1694 if (!IS_DF_GRID(element))
1695 AddLaserEdge(LX, LY + YS / 2);
1698 if (!IS_DF_GRID(element))
1699 AddLaserEdge(LX, LY);
1702 YS = 2 * Step[laser.current_angle].y;
1706 else /* hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT */
1708 laser.current_angle = get_mirrored_angle(laser.current_angle,
1713 if (!IS_DF_GRID(element))
1714 AddLaserEdge(LX, LY);
1719 if (!IS_DF_GRID(element))
1720 AddLaserEdge(LX + XS / 2, LY);
1723 if (!IS_DF_GRID(element))
1724 AddLaserEdge(LX, LY);
1727 XS = 2 * Step[laser.current_angle].x;
1733 /* reflection at the edge of reflecting DF style wall */
1734 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
1736 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
1737 hit_mask == HIT_MASK_TOPRIGHT) ||
1738 ((laser.current_angle == 5 || laser.current_angle == 7) &&
1739 hit_mask == HIT_MASK_TOPLEFT) ||
1740 ((laser.current_angle == 9 || laser.current_angle == 11) &&
1741 hit_mask == HIT_MASK_BOTTOMLEFT) ||
1742 ((laser.current_angle == 13 || laser.current_angle == 15) &&
1743 hit_mask == HIT_MASK_BOTTOMRIGHT))
1746 (hit_mask == HIT_MASK_TOPRIGHT || hit_mask == HIT_MASK_BOTTOMLEFT ?
1747 ANG_MIRROR_135 : ANG_MIRROR_45);
1749 PlaySoundStereo(SND_HUI, ST(ELX));
1750 AddDamagedField(ELX, ELY);
1751 AddLaserEdge(LX, LY);
1753 laser.current_angle = get_mirrored_angle(laser.current_angle,
1760 AddLaserEdge(LX, LY);
1766 /* reflection inside an edge of reflecting DF style wall */
1767 if (IS_DF_WALL_STEEL(element) && IS_22_5_ANGLE(laser.current_angle))
1769 if (((laser.current_angle == 1 || laser.current_angle == 3) &&
1770 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT)) ||
1771 ((laser.current_angle == 5 || laser.current_angle == 7) &&
1772 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMRIGHT)) ||
1773 ((laser.current_angle == 9 || laser.current_angle == 11) &&
1774 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT)) ||
1775 ((laser.current_angle == 13 || laser.current_angle == 15) &&
1776 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPLEFT)))
1779 (hit_mask == (HIT_MASK_ALL ^ HIT_MASK_BOTTOMLEFT) ||
1780 hit_mask == (HIT_MASK_ALL ^ HIT_MASK_TOPRIGHT) ?
1781 ANG_MIRROR_135 : ANG_MIRROR_45);
1783 PlaySoundStereo(SND_HUI, ST(ELX));
1785 AddDamagedField(ELX, ELY);
1787 AddLaserEdge(LX - XS, LY - YS);
1788 AddLaserEdge(LX - XS + (ABS(XS) == 4 ? XS/2 : 0),
1789 LY - YS + (ABS(YS) == 4 ? YS/2 : 0));
1791 laser.current_angle = get_mirrored_angle(laser.current_angle,
1798 AddLaserEdge(LX, LY);
1804 /* check if laser hits DF style wall with an angle of 90° */
1805 if (IS_DF_WALL(element) && IS_90_ANGLE(laser.current_angle))
1807 if ((IS_HORIZ_ANGLE(laser.current_angle) &&
1808 (!(hit_mask & HIT_MASK_TOP) || !(hit_mask & HIT_MASK_BOTTOM))) ||
1809 (IS_VERT_ANGLE(laser.current_angle) &&
1810 (!(hit_mask & HIT_MASK_LEFT) || !(hit_mask & HIT_MASK_RIGHT))))
1812 static int last_LX = 0, last_LY = 0, last_hit_mask = 0;
1814 /* laser at last step touched nothing or the same side of the wall */
1815 if (LX != last_LX || LY != last_LY || hit_mask == last_hit_mask)
1817 AddDamagedField(ELX, ELY);
1823 last_hit_mask = hit_mask;
1830 if (!HitOnlyAnEdge(element, hit_mask))
1832 laser.overloaded = TRUE;
1839 boolean HitAbsorbingWalls(int element, int hit_mask)
1841 if (HitOnlyAnEdge(element, hit_mask))
1845 (hit_mask == HIT_MASK_LEFT || hit_mask == HIT_MASK_RIGHT))
1847 AddLaserEdge(LX - XS, LY - YS);
1853 (hit_mask == HIT_MASK_TOP || hit_mask == HIT_MASK_BOTTOM))
1855 AddLaserEdge(LX - XS, LY - YS);
1860 if (IS_WALL_WOOD(element) ||
1861 IS_DF_WALL_WOOD(element) ||
1862 IS_GRID_WOOD(element) ||
1863 IS_GRID_WOOD_FIXED(element) ||
1864 IS_GRID_WOOD_AUTO(element) ||
1865 element == EL_FUSE_ON ||
1866 element == EL_BLOCK_WOOD ||
1867 element == EL_GATE_WOOD)
1869 PlaySoundStereo(SND_HOLZ, ST(ELX));
1873 if (IS_WALL_ICE(element))
1877 mask = (LX + XS) / MINI_TILEX - ELX * 2 + 1; /* Quadrant (horizontal) */
1878 mask <<= (((LY + YS) / MINI_TILEY - ELY * 2) > 0) * 2; /* || (vertical) */
1880 /* check if laser hits wall with an angle of 90° */
1881 if (IS_90_ANGLE(laser.current_angle))
1882 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
1884 if (mask == 1 || mask == 2 || mask == 4 || mask == 8)
1890 if (mask == (1 << i) && (XS > 0) == (i % 2) && (YS > 0) == (i / 2))
1891 mask = 15 - (8 >> i);
1892 else if (ABS(XS) == 4 &&
1894 (XS > 0) == (i % 2) &&
1895 (YS < 0) == (i / 2))
1896 mask = 3 + (i / 2) * 9;
1897 else if (ABS(YS) == 4 &&
1899 (XS < 0) == (i % 2) &&
1900 (YS > 0) == (i / 2))
1901 mask = 5 + (i % 2) * 5;
1905 laser.wall_mask = mask;
1907 else if (IS_WALL_AMOEBA(element))
1909 int elx = (LX - 2 * XS) / TILEX;
1910 int ely = (LY - 2 * YS) / TILEY;
1911 int element2 = Feld[elx][ely];
1914 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element2))
1916 laser.dest_element = EL_EMPTY;
1923 mask = (LX - 2 * XS) / 16 - ELX * 2 + 1;
1924 mask <<= ((LY - 2 * YS) / 16 - ELY * 2) * 2;
1926 if (IS_90_ANGLE(laser.current_angle))
1927 mask += mask * (2 + IS_HORIZ_ANGLE(laser.current_angle) * 2);
1929 laser.dest_element = element2 | EL_WALL_AMOEBA;
1931 laser.wall_mask = mask;
1937 void OpenExit(int x, int y)
1941 if (!MovDelay[x][y]) /* next animation frame */
1942 MovDelay[x][y] = 4 * delay;
1944 if (MovDelay[x][y]) /* wait some time before next frame */
1949 phase = MovDelay[x][y] / delay;
1950 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
1951 DrawGraphic_MM(x, y, EL_EXIT_OPEN - phase);
1953 if (!MovDelay[x][y])
1955 Feld[x][y] = EL_EXIT_OPEN;
1961 void OpenSurpriseBall(int x, int y)
1965 if (!MovDelay[x][y]) /* next animation frame */
1966 MovDelay[x][y] = 50 * delay;
1968 if (MovDelay[x][y]) /* wait some time before next frame */
1971 if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
1974 int graphic = el2gfx(Store[x][y]);
1976 int dx = RND(26), dy = RND(26);
1978 getGraphicSource(graphic, 0, &bitmap, &gx, &gy);
1979 BlitBitmap(bitmap, drawto, gx + dx, gy + dy, 6, 6,
1980 SX + x * TILEX + dx, SY + y * TILEY + dy);
1981 MarkTileDirty(x, y);
1984 if (!MovDelay[x][y])
1986 Feld[x][y] = Store[x][y];
1995 void MeltIce(int x, int y)
2000 if (!MovDelay[x][y]) /* next animation frame */
2001 MovDelay[x][y] = frames * delay;
2003 if (MovDelay[x][y]) /* wait some time before next frame */
2006 int wall_mask = Store2[x][y];
2007 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_ICE;
2010 phase = frames - MovDelay[x][y] / delay - 1;
2012 if (!MovDelay[x][y])
2016 Feld[x][y] = real_element & (wall_mask ^ 0xFF);
2017 Store[x][y] = Store2[x][y] = 0;
2019 DrawWalls_MM(x, y, Feld[x][y]);
2021 if (Feld[x][y] == EL_WALL_ICE)
2022 Feld[x][y] = EL_EMPTY;
2024 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
2025 if (laser.damage[i].is_mirror)
2029 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
2031 DrawLaser(0, DL_LASER_DISABLED);
2035 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2037 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2039 laser.redraw = TRUE;
2044 void GrowAmoeba(int x, int y)
2049 if (!MovDelay[x][y]) /* next animation frame */
2050 MovDelay[x][y] = frames * delay;
2052 if (MovDelay[x][y]) /* wait some time before next frame */
2055 int wall_mask = Store2[x][y];
2056 int real_element = Feld[x][y] - EL_WALL_CHANGING + EL_WALL_AMOEBA;
2059 phase = MovDelay[x][y] / delay;
2061 if (!MovDelay[x][y])
2063 Feld[x][y] = real_element;
2064 Store[x][y] = Store2[x][y] = 0;
2066 DrawWalls_MM(x, y, Feld[x][y]);
2067 DrawLaser(0, DL_LASER_ENABLED);
2069 else if (!(MovDelay[x][y] % delay) && IN_SCR_FIELD(x, y))
2070 DrawWallsAnimation_MM(x, y, real_element, phase, wall_mask);
2074 static void Explode_MM(int x, int y, int phase, int mode)
2076 int num_phase = 9, delay = 2;
2077 int last_phase = num_phase * delay;
2078 int half_phase = (num_phase / 2) * delay;
2080 laser.redraw = TRUE;
2082 if (phase == EX_PHASE_START) /* initialize 'Store[][]' field */
2084 int center_element = Feld[x][y];
2086 if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
2088 /* put moving element to center field (and let it explode there) */
2089 center_element = MovingOrBlocked2Element_MM(x, y);
2090 RemoveMovingField_MM(x, y);
2091 Feld[x][y] = center_element;
2094 if (center_element == EL_BOMB || IS_MCDUFFIN(center_element))
2095 Store[x][y] = center_element;
2097 Store[x][y] = EL_EMPTY;
2098 Store2[x][y] = mode;
2099 Feld[x][y] = EL_EXPLODING_OPAQUE;
2100 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2106 Frame[x][y] = (phase < last_phase ? phase + 1 : 0);
2108 if (phase == half_phase)
2110 Feld[x][y] = EL_EXPLODING_TRANSP;
2112 if (x == ELX && y == ELY)
2116 if (phase == last_phase)
2118 if (Store[x][y] == EL_BOMB)
2120 laser.num_damages--;
2121 DrawLaser(0, DL_LASER_DISABLED);
2122 laser.num_edges = 0;
2124 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2125 Store[x][y] = EL_EMPTY;
2127 else if (IS_MCDUFFIN(Store[x][y]))
2129 game_mm.game_over = TRUE;
2130 game_mm.game_over_cause = GAME_OVER_BOMB;
2131 Store[x][y] = EL_EMPTY;
2134 Feld[x][y] = Store[x][y];
2135 Store[x][y] = Store2[x][y] = 0;
2136 MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0;
2137 InitField(x, y, FALSE);
2140 else if (!(phase % delay) && IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
2142 int graphic = IMG_MM_DEFAULT_EXPLODING;
2143 int graphic_phase = (phase / delay - 1);
2147 if (Store2[x][y] == EX_KETTLE)
2149 if (graphic_phase < 3)
2150 graphic = IMG_MM_KETTLE_EXPLODING;
2151 else if (graphic_phase < 5)
2157 graphic = IMG_EMPTY;
2161 else if (Store2[x][y] == EX_SHORT)
2163 if (graphic_phase < 4)
2167 graphic = GFX_EMPTY;
2172 getGraphicSource(graphic, graphic_phase, &bitmap, &src_x, &src_y);
2174 BlitBitmap(bitmap, drawto_field, src_x, src_y, TILEX, TILEY,
2175 FX + x * TILEX, FY + y * TILEY);
2176 MarkTileDirty(x, y);
2180 static void Bang_MM(int x, int y)
2182 int element = Feld[x][y];
2183 int mode = EX_NORMAL;
2186 DrawLaser(0, DL_LASER_ENABLED);
2205 if (IS_PACMAN(element))
2206 PlaySoundStereo(SND_QUIEK, ST(x));
2207 else if (element == EL_BOMB || IS_MCDUFFIN(element))
2208 PlaySoundStereo(SND_ROAAAR, ST(x));
2209 else if (element == EL_KEY)
2210 PlaySoundStereo(SND_KLING, ST(x));
2212 PlaySoundStereo((mode == EX_SHORT ? SND_WHOOSH : SND_KABUMM), ST(x));
2214 Explode_MM(x, y, EX_PHASE_START, mode);
2217 void TurnRound(int x, int y)
2229 { 0, 0 }, { 0, 0 }, { 0, 0 },
2234 int left, right, back;
2238 { MV_DOWN, MV_UP, MV_RIGHT },
2239 { MV_UP, MV_DOWN, MV_LEFT },
2241 { MV_LEFT, MV_RIGHT, MV_DOWN },
2242 { 0,0,0 }, { 0,0,0 }, { 0,0,0 },
2243 { MV_RIGHT, MV_LEFT, MV_UP }
2246 int element = Feld[x][y];
2247 int old_move_dir = MovDir[x][y];
2248 int right_dir = turn[old_move_dir].right;
2249 int back_dir = turn[old_move_dir].back;
2250 int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y;
2251 int right_x = x+right_dx, right_y = y+right_dy;
2253 if (element == EL_PACMAN)
2255 boolean can_turn_right = FALSE;
2257 if (IN_LEV_FIELD(right_x, right_y) &&
2258 IS_EATABLE4PACMAN(Feld[right_x][right_y]))
2259 can_turn_right = TRUE;
2262 MovDir[x][y] = right_dir;
2264 MovDir[x][y] = back_dir;
2270 static void StartMoving_MM(int x, int y)
2272 int element = Feld[x][y];
2277 if (CAN_MOVE(element))
2281 if (MovDelay[x][y]) /* wait some time before next movement */
2289 /* now make next step */
2291 Moving2Blocked_MM(x, y, &newx, &newy); /* get next screen position */
2293 if (element == EL_PACMAN &&
2294 IN_LEV_FIELD(newx, newy) && IS_EATABLE4PACMAN(Feld[newx][newy]) &&
2295 !ObjHit(newx, newy, HIT_POS_CENTER))
2297 Store[newx][newy] = Feld[newx][newy];
2298 Feld[newx][newy] = EL_EMPTY;
2299 DrawField_MM(newx, newy);
2301 else if (!IN_LEV_FIELD(newx, newy) || !IS_FREE(newx, newy) ||
2302 ObjHit(newx, newy, HIT_POS_CENTER))
2304 /* object was running against a wall */
2311 InitMovingField_MM(x, y, MovDir[x][y]);
2315 ContinueMoving_MM(x, y);
2318 static void ContinueMoving_MM(int x, int y)
2320 int element = Feld[x][y];
2321 int direction = MovDir[x][y];
2322 int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
2323 int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
2324 int horiz_move = (dx!=0);
2325 int newx = x + dx, newy = y + dy;
2326 int step = (horiz_move ? dx : dy) * TILEX / 8;
2328 MovPos[x][y] += step;
2330 if (ABS(MovPos[x][y]) >= TILEX) /* object reached its destination */
2332 Feld[x][y] = EL_EMPTY;
2333 Feld[newx][newy] = element;
2335 MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0;
2336 MovDelay[newx][newy] = 0;
2338 if (!CAN_MOVE(element))
2339 MovDir[newx][newy] = 0;
2342 DrawField_MM(newx, newy);
2344 Stop[newx][newy] = TRUE;
2346 if (element == EL_PACMAN)
2348 if (Store[newx][newy] == EL_BOMB)
2349 Bang_MM(newx, newy);
2351 if (IS_WALL_AMOEBA(Store[newx][newy]) &&
2352 (LX + 2 * XS) / TILEX == newx &&
2353 (LY + 2 * YS) / TILEY == newy)
2360 else /* still moving on */
2363 laser.redraw = TRUE;
2366 void ClickElement(int mx, int my, int button)
2368 static unsigned int click_delay = 0;
2369 static int click_delay_value = CLICK_DELAY_SHORT;
2370 static boolean new_button = TRUE;
2372 int x = (mx - SX) / TILEX, y = (my - SY) / TILEY;
2374 if (button == MB_RELEASED)
2377 click_delay_value = CLICK_DELAY_SHORT;
2379 /* release eventually hold auto-rotating mirror */
2380 RotateMirror(x, y, MB_RELEASED);
2385 if (!DelayReached(&click_delay, click_delay_value) && !new_button)
2388 if (button == MB_MIDDLEBUTTON) /* middle button has no function */
2391 if (!IN_PIX_FIELD(mx - SX, my - SY))
2394 if (Feld[x][y] == EL_EMPTY)
2397 element = Feld[x][y];
2399 if (IS_MIRROR(element) ||
2400 IS_BEAMER(element) ||
2401 IS_POLAR(element) ||
2402 IS_POLAR_CROSS(element) ||
2403 IS_DF_MIRROR(element) ||
2404 IS_DF_MIRROR_AUTO(element))
2406 RotateMirror(x, y, button);
2408 else if (IS_MCDUFFIN(element))
2410 if (!laser.fuse_off)
2412 DrawLaser(0, DL_LASER_DISABLED);
2418 element = get_rotated_element(element, BUTTON_ROTATION(button));
2419 laser.start_angle = get_element_angle(element);
2423 Feld[x][y] = element;
2428 if (!laser.fuse_off)
2431 else if (element == EL_FUSE_ON && laser.fuse_off)
2433 if (x != laser.fuse_x || y != laser.fuse_y)
2436 laser.fuse_off = FALSE;
2437 laser.fuse_x = laser.fuse_y = -1;
2439 DrawGraphic_MM(x, y, IMG_MM_FUSE_ACTIVE);
2442 else if (element == EL_FUSE_ON && !laser.fuse_off && new_button)
2444 laser.fuse_off = TRUE;
2447 laser.overloaded = FALSE;
2449 DrawLaser(0, DL_LASER_DISABLED);
2450 DrawGraphic_MM(x, y, IMG_MM_FUSE);
2452 else if (element == EL_LIGHTBALL)
2456 DrawLaser(0, DL_LASER_ENABLED);
2459 click_delay_value = (new_button ? CLICK_DELAY_LONG : CLICK_DELAY_SHORT);
2463 void RotateMirror(int x, int y, int button)
2465 static int hold_x = -1, hold_y = -1;
2467 if (button == MB_RELEASED)
2469 /* release eventually hold auto-rotating mirror */
2476 if (IS_MIRROR(Feld[x][y]) ||
2477 IS_POLAR_CROSS(Feld[x][y]) ||
2478 IS_POLAR(Feld[x][y]) ||
2479 IS_BEAMER(Feld[x][y]) ||
2480 IS_DF_MIRROR(Feld[x][y]) ||
2481 IS_GRID_STEEL_AUTO(Feld[x][y]) ||
2482 IS_GRID_WOOD_AUTO(Feld[x][y]))
2484 Feld[x][y] = get_rotated_element(Feld[x][y], BUTTON_ROTATION(button));
2486 else if (IS_DF_MIRROR_AUTO(Feld[x][y]))
2488 if (button == MB_LEFTBUTTON)
2490 /* left mouse button only for manual adjustment, no auto-rotating;
2491 freeze mirror for until mouse button released */
2495 else if (button == MB_RIGHTBUTTON && (hold_x != x || hold_y != y))
2496 Feld[x][y] = get_rotated_element(Feld[x][y], ROTATE_RIGHT);
2499 if (IS_GRID_STEEL_AUTO(Feld[x][y]) || IS_GRID_WOOD_AUTO(Feld[x][y]))
2501 int edge = Hit[x][y];
2507 DrawLaser(edge - 1, DL_LASER_DISABLED);
2511 else if (ObjHit(x, y, HIT_POS_CENTER))
2513 int edge = Hit[x][y];
2517 Error(ERR_WARN, "RotateMirror: inconsistent field Hit[][]!\n");
2521 DrawLaser(edge - 1, DL_LASER_DISABLED);
2528 if (ObjHit(x, y, HIT_POS_EDGE | HIT_POS_BETWEEN))
2533 if ((IS_BEAMER(Feld[x][y]) ||
2534 IS_POLAR(Feld[x][y]) ||
2535 IS_POLAR_CROSS(Feld[x][y])) && x == ELX && y == ELY)
2539 if (IS_BEAMER(Feld[x][y]))
2542 printf("TEST (%d, %d) [%d] [%d]\n",
2544 laser.beamer_edge, laser.beamer[1].num);
2554 DrawLaser(0, DL_LASER_ENABLED);
2558 void AutoRotateMirrors()
2560 static unsigned int rotate_delay = 0;
2563 if (!DelayReached(&rotate_delay, AUTO_ROTATE_DELAY))
2566 for (x=0; x<lev_fieldx; x++)
2568 for (y=0; y<lev_fieldy; y++)
2570 int element = Feld[x][y];
2572 if (IS_DF_MIRROR_AUTO(element) ||
2573 IS_GRID_WOOD_AUTO(element) ||
2574 IS_GRID_STEEL_AUTO(element) ||
2575 element == EL_REFRACTOR)
2576 RotateMirror(x, y, MB_RIGHTBUTTON);
2581 boolean ObjHit(int obx, int oby, int bits)
2588 if (bits & HIT_POS_CENTER)
2590 if (ReadPixel(drawto, SX + obx + 15, SY + oby + 15) == pen_ray)
2594 if (bits & HIT_POS_EDGE)
2597 if (ReadPixel(drawto,
2598 SX + obx + 31 * (i % 2),
2599 SY + oby + 31 * (i / 2)) == pen_ray)
2603 if (bits & HIT_POS_BETWEEN)
2606 if (ReadPixel(drawto,
2607 SX + 4 + obx + 22 * (i % 2),
2608 SY + 4 + oby + 22 * (i / 2)) == pen_ray)
2615 void DeletePacMan(int px, int py)
2621 if (game_mm.num_pacman <= 1)
2623 game_mm.num_pacman = 0;
2627 for(i=0; i<game_mm.num_pacman; i++)
2628 if (game_mm.pacman[i].x == px && game_mm.pacman[i].y == py)
2631 game_mm.num_pacman--;
2633 for(j=i; j<game_mm.num_pacman; j++)
2635 game_mm.pacman[j].x = game_mm.pacman[j + 1].x;
2636 game_mm.pacman[j].y = game_mm.pacman[j + 1].y;
2637 game_mm.pacman[j].dir = game_mm.pacman[j + 1].dir;
2641 void ColorCycling(void)
2643 static int CC, Cc = 0;
2645 static int color, old = 0xF00, new = 0x010, mult = 1;
2646 static unsigned short red, green, blue;
2648 if (color_status == STATIC_COLORS)
2653 if (CC < Cc || CC > Cc + 50)
2657 color = old + new * mult;
2663 if (ABS(mult) == 16)
2672 red = 0x0e00 * ((color & 0xF00) >> 8);
2673 green = 0x0e00 * ((color & 0x0F0) >> 4);
2674 blue = 0x0e00 * ((color & 0x00F));
2675 SetRGB(pen_magicolor[0], red, green, blue);
2677 red = 0x1111 * ((color & 0xF00) >> 8);
2678 green = 0x1111 * ((color & 0x0F0) >> 4);
2679 blue = 0x1111 * ((color & 0x00F));
2680 SetRGB(pen_magicolor[1], red, green, blue);
2684 static void GameActions_MM_Ext(byte action[MAX_PLAYERS], boolean warp_mode)
2686 static unsigned int action_delay = 0;
2687 static unsigned int pacman_delay = 0;
2688 static unsigned int energy_delay = 0;
2689 static unsigned int overload_delay = 0;
2695 WaitUntilDelayReached(&action_delay, GameFrameDelay);
2697 for (y=0; y<lev_fieldy; y++) for (x=0; x<lev_fieldx; x++)
2700 for (y=0; y<lev_fieldy; y++) for (x=0; x<lev_fieldx; x++)
2702 element = Feld[x][y];
2704 if (!IS_MOVING(x, y) && CAN_MOVE(element))
2705 StartMoving_MM(x, y);
2706 else if (IS_MOVING(x, y))
2707 ContinueMoving_MM(x, y);
2708 else if (IS_EXPLODING(element))
2709 Explode_MM(x, y, Frame[x][y], EX_NORMAL);
2710 else if (element == EL_EXIT_OPENING)
2712 else if (element == EL_GRAY_BALL_OPENING)
2713 OpenSurpriseBall(x, y);
2714 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_ICE)
2716 else if (IS_WALL_CHANGING(element) && Store[x][y] == EL_WALL_AMOEBA)
2720 AutoRotateMirrors();
2723 /* !!! CHANGE THIS: REDRAW ONLY WHEN NEEDED !!! */
2725 /* redraw after Explode_MM() ... */
2727 DrawLaser(0, DL_LASER_ENABLED);
2728 laser.redraw = FALSE;
2733 if (game_mm.num_pacman && DelayReached(&pacman_delay, 250))
2737 if (laser.num_damages > MAX_LASER_LEN && !laser.fuse_off)
2739 DrawLaser(0, DL_LASER_DISABLED);
2744 if (DelayReached(&energy_delay, 4000))
2746 game_mm.energy_left--;
2747 if (game_mm.energy_left >= 0)
2750 BlitBitmap(pix[PIX_DOOR], drawto,
2751 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
2752 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
2753 DX_ENERGY, DY_ENERGY);
2755 redraw_mask |= REDRAW_DOOR_1;
2757 else if (setup.time_limit)
2761 for(i=15; i>=0; i--)
2764 SetRGB(pen_ray, 0x0000, 0x0000, i * color_scale);
2766 pen_ray = GetPixelFromRGB(window,
2767 native_mm_level.laser_red * 0x11 * i,
2768 native_mm_level.laser_green * 0x11 * i,
2769 native_mm_level.laser_blue * 0x11 * i);
2770 DrawLaser(0, DL_LASER_ENABLED);
2775 StopSound(SND_WARNTON);
2778 DrawLaser(0, DL_LASER_DISABLED);
2779 game_mm.game_over = TRUE;
2780 game_mm.game_over_cause = GAME_OVER_NO_ENERGY;
2783 if (Request("Out of magic energy ! Play it again ?",
2784 REQ_ASK | REQ_STAY_CLOSED))
2790 game_status = MAINMENU;
2799 element = laser.dest_element;
2802 if (element != Feld[ELX][ELY])
2804 printf("element == %d, Feld[ELX][ELY] == %d\n",
2805 element, Feld[ELX][ELY]);
2809 if (!laser.overloaded && laser.overload_value == 0 &&
2810 element != EL_BOMB &&
2811 element != EL_MINE &&
2812 element != EL_BALL_GRAY &&
2813 element != EL_BLOCK_STONE &&
2814 element != EL_BLOCK_WOOD &&
2815 element != EL_FUSE_ON &&
2816 element != EL_FUEL_FULL &&
2817 !IS_WALL_ICE(element) &&
2818 !IS_WALL_AMOEBA(element))
2821 if (((laser.overloaded && laser.overload_value < MAX_LASER_OVERLOAD) ||
2822 (!laser.overloaded && laser.overload_value > 0)) &&
2823 DelayReached(&overload_delay, 60 + !laser.overloaded * 120))
2825 if (laser.overloaded)
2826 laser.overload_value++;
2828 laser.overload_value--;
2830 if (game_mm.cheat_no_overload)
2832 laser.overloaded = FALSE;
2833 laser.overload_value = 0;
2836 if (laser.overload_value < MAX_LASER_OVERLOAD - 8)
2838 int color_up = 0xFF * laser.overload_value / MAX_LASER_OVERLOAD;
2839 int color_down = 0xFF - color_up;
2842 SetRGB(pen_ray, (laser.overload_value / 6) * color_scale, 0x0000,
2843 (15 - (laser.overload_value / 6)) * color_scale);
2845 pen_ray = GetPixelFromRGB(window,
2846 (native_mm_level.laser_red ? 0xFF : color_up),
2847 (native_mm_level.laser_green ? color_down : 0x00),
2848 (native_mm_level.laser_blue ? color_down : 0x00));
2849 DrawLaser(0, DL_LASER_ENABLED);
2853 if (laser.overloaded)
2855 if (setup.sound_loops)
2856 PlaySoundExt(SND_WARNTON, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT, SND_CTRL_PLAY_LOOP);
2858 PlaySoundStereo(SND_WARNTON, SOUND_MAX_RIGHT);
2861 if (!laser.overloaded)
2862 StopSound(SND_WARNTON);
2864 if (laser.overloaded)
2867 BlitBitmap(pix[PIX_DOOR], drawto,
2868 DOOR_GFX_PAGEX4 + XX_OVERLOAD,
2869 DOOR_GFX_PAGEY1 + YY_OVERLOAD + OVERLOAD_YSIZE
2870 - laser.overload_value,
2871 OVERLOAD_XSIZE, laser.overload_value,
2872 DX_OVERLOAD, DY_OVERLOAD + OVERLOAD_YSIZE
2873 - laser.overload_value);
2875 redraw_mask |= REDRAW_DOOR_1;
2880 BlitBitmap(pix[PIX_DOOR], drawto,
2881 DOOR_GFX_PAGEX5 + XX_OVERLOAD, DOOR_GFX_PAGEY1 + YY_OVERLOAD,
2882 OVERLOAD_XSIZE, OVERLOAD_YSIZE - laser.overload_value,
2883 DX_OVERLOAD, DY_OVERLOAD);
2885 redraw_mask |= REDRAW_DOOR_1;
2888 if (laser.overload_value == MAX_LASER_OVERLOAD)
2892 for(i=15; i>=0; i--)
2895 SetRGB(pen_ray, i * color_scale, 0x0000, 0x0000);
2898 pen_ray = GetPixelFromRGB(window, 0x11 * i, 0x00, 0x00);
2899 DrawLaser(0, DL_LASER_ENABLED);
2904 DrawLaser(0, DL_LASER_DISABLED);
2905 game_mm.game_over = TRUE;
2906 game_mm.game_over_cause = GAME_OVER_OVERLOADED;
2909 if (Request("Magic spell hit Mc Duffin ! Play it again ?",
2910 REQ_ASK | REQ_STAY_CLOSED))
2916 game_status = MAINMENU;
2930 if (element == EL_BOMB && CT > 1500)
2932 if (game_mm.cheat_no_explosion)
2936 laser.num_damages--;
2937 DrawLaser(0, DL_LASER_DISABLED);
2938 laser.num_edges = 0;
2943 laser.dest_element = EL_EXPLODING_OPAQUE;
2947 laser.num_damages--;
2948 DrawLaser(0, DL_LASER_DISABLED);
2950 laser.num_edges = 0;
2951 Bang_MM(laser.start_edge.x, laser.start_edge.y);
2953 if (Request("Bomb killed Mc Duffin ! Play it again ?",
2954 REQ_ASK | REQ_STAY_CLOSED))
2960 game_status = MAINMENU;
2968 if (element == EL_FUSE_ON && CT > 500)
2970 laser.fuse_off = TRUE;
2973 DrawLaser(0, DL_LASER_DISABLED);
2974 DrawGraphic_MM(ELX, ELY, IMG_MM_FUSE);
2977 if (element == EL_BALL_GRAY && CT > 1500)
2979 static int new_elements[] =
2982 EL_MIRROR_FIXED_START,
2984 EL_POLAR_CROSS_START,
2990 int num_new_elements = sizeof(new_elements) / sizeof(int);
2991 int new_element = new_elements[RND(num_new_elements)];
2993 Store[ELX][ELY] = new_element + RND(get_num_elements(new_element));
2994 Feld[ELX][ELY] = EL_GRAY_BALL_OPENING;
2996 /* !!! CHECK AGAIN: Laser on Polarizer !!! */
3007 element = EL_MIRROR_START + RND(16);
3013 element = (rnd == 0 ? EL_KETTLE : rnd == 1 ? EL_BOMB : EL_PRISM);
3020 element = (rnd == 0 ? EL_FUSE_ON :
3021 rnd >= 1 && rnd <= 4 ? EL_PACMAN_RIGHT + rnd - 1 :
3022 rnd >= 5 && rnd <= 20 ? EL_POLAR_START + rnd - 5 :
3023 rnd >= 21 && rnd <= 24 ? EL_POLAR_CROSS_START + rnd - 21 :
3024 EL_MIRROR_FIXED_START + rnd - 25);
3029 graphic = el2gfx(element);
3037 BlitBitmap(pix[PIX_BACK], drawto,
3038 SX + (graphic % GFX_PER_LINE) * TILEX + x,
3039 SY + (graphic / GFX_PER_LINE) * TILEY + y, 6, 6,
3040 SX + ELX * TILEX + x,
3041 SY + ELY * TILEY + y);
3043 MarkTileDirty(ELX, ELY);
3046 DrawLaser(0, DL_LASER_ENABLED);
3051 Feld[ELX][ELY] = element;
3052 DrawField_MM(ELX, ELY);
3055 printf("NEW ELEMENT: (%d, %d)\n", ELX, ELY);
3058 /* above stuff: GRAY BALL -> PRISM !!! */
3060 LX = ELX * TILEX + 14;
3061 LY = ELY * TILEY + 14;
3062 if (laser.current_angle == (laser.current_angle >> 1) << 1)
3069 laser.num_edges -= 2;
3070 laser.num_damages--;
3074 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3075 if (laser.damage[i].is_mirror)
3079 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3081 DrawLaser(0, DL_LASER_DISABLED);
3083 DrawLaser(0, DL_LASER_DISABLED);
3089 printf("TEST ELEMENT: %d\n", Feld[0][0]);
3096 if (IS_WALL_ICE(element) && CT > 1000)
3098 PlaySoundStereo(SND_SLURP, ST(ELX));
3103 Feld[ELX][ELY] = Feld[ELX][ELY] - EL_WALL_ICE + EL_WALL_CHANGING;
3104 Store[ELX][ELY] = EL_WALL_ICE;
3105 Store2[ELX][ELY] = laser.wall_mask;
3107 laser.dest_element = Feld[ELX][ELY];
3121 Feld[ELX][ELY] &= (laser.wall_mask ^ 0xFF);
3125 DrawWallsAnimation_MM(ELX, ELY, Feld[ELX][ELY], phase, laser.wall_mask);
3130 if (Feld[ELX][ELY] == EL_WALL_ICE)
3131 Feld[ELX][ELY] = EL_EMPTY;
3135 LX = laser.edge[laser.num_edges].x - (SX + 2);
3136 LY = laser.edge[laser.num_edges].y - (SY + 2);
3139 for (i = (laser.num_damages > 0 ? laser.num_damages - 1 : 0); i>=0; i--)
3140 if (laser.damage[i].is_mirror)
3144 DrawLaser(laser.damage[i].edge - 1, DL_LASER_DISABLED);
3146 DrawLaser(0, DL_LASER_DISABLED);
3153 if (IS_WALL_AMOEBA(element) && CT > 1200)
3155 int k1, k2, k3, dx, dy, de, dm;
3156 int element2 = Feld[ELX][ELY];
3158 if (element2 != EL_EMPTY && !IS_WALL_AMOEBA(element))
3161 for (i = laser.num_damages - 1; i>=0; i--)
3162 if (laser.damage[i].is_mirror)
3165 r = laser.num_edges;
3166 d = laser.num_damages;
3173 DrawLaser(laser.damage[k1].edge - 1, DL_LASER_DISABLED);
3176 DrawLaser(0, DL_LASER_ENABLED);
3179 x = laser.damage[k1].x;
3180 y = laser.damage[k1].y;
3186 if (laser.wall_mask & (1 << i))
3188 if (ReadPixel(drawto,
3189 SX + ELX * TILEX + 14 + (i % 2) * 2,
3190 SY + ELY * TILEY + 31 * (i / 2)) == pen_ray)
3192 if (ReadPixel(drawto,
3193 SX + ELX * TILEX + 31 * (i % 2),
3194 SY + ELY * TILEY + 14 + (i / 2) * 2) == pen_ray)
3203 if (laser.wall_mask & (1 << i))
3205 if (ReadPixel(drawto,
3206 SX + ELX * TILEX + 31 * (i % 2),
3207 SY + ELY * TILEY + 31 * (i / 2)) == pen_ray)
3214 if (laser.num_beamers > 0 ||
3215 k1 < 1 || k2 < 4 || k3 < 4 ||
3216 ReadPixel(drawto, SX + ELX * TILEX + 14, SY + ELY * TILEY + 14)
3219 laser.num_edges = r;
3220 laser.num_damages = d;
3221 DrawLaser(0, DL_LASER_DISABLED);
3224 Feld[ELX][ELY] = element | laser.wall_mask;
3227 de = Feld[ELX][ELY];
3228 dm = laser.wall_mask;
3234 int x = ELX, y = ELY;
3235 int wall_mask = laser.wall_mask;
3239 DrawLaser(0, DL_LASER_ENABLED);
3241 PlaySoundStereo(SND_AMOEBE, ST(dx));
3245 Feld[x][y] = Feld[x][y] - EL_WALL_AMOEBA + EL_WALL_CHANGING;
3246 Store[x][y] = EL_WALL_AMOEBA;
3247 Store2[x][y] = wall_mask;
3255 DrawWallsAnimation_MM(dx, dy, de, 4, dm);
3257 DrawLaser(0, DL_LASER_ENABLED);
3259 PlaySoundStereo(SND_AMOEBE, ST(dx));
3263 DrawWallsAnimation_MM(dx, dy, de, i, dm);
3268 DrawLaser(0, DL_LASER_ENABLED);
3273 if ((element == EL_BLOCK_WOOD || element == EL_BLOCK_STONE) &&
3274 laser.stops_inside_element && CT > 1500)
3279 if (ABS(XS) > ABS(YS))
3293 x = ELX + Step[k * 4].x;
3294 y = ELY + Step[k * 4].y;
3296 if (!IN_LEV_FIELD(x, y) || Feld[x][y] != EL_EMPTY)
3299 if (ObjHit(x, y, HIT_POS_CENTER | HIT_POS_EDGE | HIT_POS_BETWEEN))
3307 laser.overloaded = (element == EL_BLOCK_STONE);
3311 PlaySoundStereo(SND_BONG, ST(ELX));
3314 Feld[x][y] = element;
3316 DrawGraphic_MM(ELX, ELY, -1);
3319 if (element == EL_BLOCK_STONE && Box[ELX][ELY])
3321 DrawLaser(Box[ELX][ELY] - 1, DL_LASER_DISABLED);
3322 DrawLaser(laser.num_edges - 1, DL_LASER_ENABLED);
3330 if (element == EL_FUEL_FULL && CT > 200)
3332 for(i=game_mm.energy_left; i<=MAX_LASER_ENERGY; i+=2)
3335 BlitBitmap(pix[PIX_DOOR], drawto,
3336 DOOR_GFX_PAGEX4 + XX_ENERGY,
3337 DOOR_GFX_PAGEY1 + YY_ENERGY + ENERGY_YSIZE - i,
3338 ENERGY_XSIZE, i, DX_ENERGY,
3339 DY_ENERGY + ENERGY_YSIZE - i);
3342 redraw_mask |= REDRAW_DOOR_1;
3348 game_mm.energy_left = MAX_LASER_ENERGY;
3349 Feld[ELX][ELY] = EL_FUEL_EMPTY;
3350 DrawField_MM(ELX, ELY);
3352 DrawLaser(0, DL_LASER_ENABLED);
3360 void GameActions_MM(byte action[MAX_PLAYERS], boolean warp_mode)
3363 ClickElement(0, 0, MB_NOT_PRESSED);
3365 GameActions_MM_Ext(action, warp_mode);
3371 int mx, my, ox, oy, nx, ny;
3375 if (++p >= game_mm.num_pacman)
3377 game_mm.pacman[p].dir--;
3381 game_mm.pacman[p].dir++;
3383 if (game_mm.pacman[p].dir > 4)
3384 game_mm.pacman[p].dir = 1;
3386 if (game_mm.pacman[p].dir % 2)
3389 my = game_mm.pacman[p].dir - 2;
3394 mx = 3 - game_mm.pacman[p].dir;
3397 ox = game_mm.pacman[p].x;
3398 oy = game_mm.pacman[p].y;
3401 element = Feld[nx][ny];
3402 if (nx < 0 || nx > 15 || ny < 0 || ny > 11)
3405 if (!IS_EATABLE4PACMAN(element))
3408 if (ObjHit(nx, ny, HIT_POS_CENTER))
3411 Feld[ox][oy] = EL_EMPTY;
3413 EL_PACMAN_RIGHT - 1 +
3414 (game_mm.pacman[p].dir - 1 +
3415 (game_mm.pacman[p].dir % 2) * 2);
3417 game_mm.pacman[p].x = nx;
3418 game_mm.pacman[p].y = ny;
3419 g = Feld[nx][ny] - EL_PACMAN_RIGHT;
3420 DrawGraphic_MM(ox, oy, IMG_EMPTY);
3422 if (element != EL_EMPTY)
3427 ox = SX + ox * TILEX;
3428 oy = SY + oy * TILEY;
3430 for(i=1; i<33; i+=2)
3433 // !!! temporary fix to compile -- change to game graphics !!!
3434 BlitBitmap(drawto, window,
3435 SX + g * TILEX, SY + 4 * TILEY, TILEX, TILEY,
3436 ox + i * mx, oy + i * my);
3438 BlitBitmap(pix[PIX_BACK], window,
3439 SX + g * TILEX, SY + 4 * TILEY, TILEX, TILEY,
3440 ox + i * mx, oy + i * my);
3443 Ct = Ct + Counter() - CT;
3445 DrawField_MM(nx, ny);
3448 if (!laser.fuse_off)
3450 DrawLaser(0, DL_LASER_ENABLED);
3452 if (ObjHit(nx, ny, HIT_POS_BETWEEN))
3454 AddDamagedField(nx, ny);
3455 laser.damage[laser.num_damages - 1].edge = 0;
3459 if (element == EL_BOMB)
3461 DeletePacMan(nx, ny);
3464 if (IS_WALL_AMOEBA(element) &&
3465 (LX + 2 * XS) / TILEX == nx &&
3466 (LY + 2 * YS) / TILEY == ny)
3478 boolean raise_level = FALSE;
3481 if (local_player->MovPos)
3484 local_player->LevelSolved = FALSE;
3487 if (game_mm.energy_left)
3489 if (setup.sound_loops)
3490 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT, SND_CTRL_PLAY_LOOP);
3492 while(game_mm.energy_left > 0)
3494 if (!setup.sound_loops)
3495 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3498 if (game_mm.energy_left > 0 && !(game_mm.energy_left % 10))
3499 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3504 game_mm.energy_left--;
3505 if (game_mm.energy_left >= 0)
3508 BlitBitmap(pix[PIX_DOOR], drawto,
3509 DOOR_GFX_PAGEX5 + XX_ENERGY, DOOR_GFX_PAGEY1 + YY_ENERGY,
3510 ENERGY_XSIZE, ENERGY_YSIZE - game_mm.energy_left,
3511 DX_ENERGY, DY_ENERGY);
3513 redraw_mask |= REDRAW_DOOR_1;
3520 if (setup.sound_loops)
3521 StopSound(SND_SIRR);
3523 else if (native_mm_level.time == 0) /* level without time limit */
3525 if (setup.sound_loops)
3526 PlaySoundExt(SND_SIRR, SOUND_MAX_VOLUME, SOUND_MAX_RIGHT, SND_CTRL_PLAY_LOOP);
3528 while(TimePlayed < 999)
3530 if (!setup.sound_loops)
3531 PlaySoundStereo(SND_SIRR, SOUND_MAX_RIGHT);
3532 if (TimePlayed < 999 && !(TimePlayed % 10))
3533 RaiseScore_MM(native_mm_level.score[SC_ZEITBONUS]);
3534 if (TimePlayed < 900 && !(TimePlayed % 10))
3540 DrawText(DX_TIME, DY_TIME, int2str(TimePlayed, 3), FONT_TEXT_2);
3547 if (setup.sound_loops)
3548 StopSound(SND_SIRR);
3555 CloseDoor(DOOR_CLOSE_1);
3557 Request("Level solved !", REQ_CONFIRM);
3559 if (level_nr == leveldir_current->handicap_level)
3561 leveldir_current->handicap_level++;
3562 SaveLevelSetup_SeriesInfo();
3565 if (level_editor_test_game)
3566 game_mm.score = -1; /* no highscore when playing from editor */
3567 else if (level_nr < leveldir_current->last_level)
3568 raise_level = TRUE; /* advance to next level */
3570 if ((hi_pos = NewHiScore_MM()) >= 0)
3572 game_status = HALLOFFAME;
3573 // DrawHallOfFame(hi_pos);
3579 game_status = MAINMENU;
3593 // LoadScore(level_nr);
3595 if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 ||
3596 game_mm.score < highscore[MAX_SCORE_ENTRIES - 1].Score)
3599 for (k=0; k<MAX_SCORE_ENTRIES; k++)
3601 if (game_mm.score > highscore[k].Score)
3603 /* player has made it to the hall of fame */
3605 if (k < MAX_SCORE_ENTRIES - 1)
3607 int m = MAX_SCORE_ENTRIES - 1;
3610 for (l=k; l<MAX_SCORE_ENTRIES; l++)
3611 if (!strcmp(setup.player_name, highscore[l].Name))
3613 if (m == k) /* player's new highscore overwrites his old one */
3619 strcpy(highscore[l].Name, highscore[l - 1].Name);
3620 highscore[l].Score = highscore[l - 1].Score;
3627 strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN);
3628 highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0';
3629 highscore[k].Score = game_mm.score;
3635 else if (!strncmp(setup.player_name, highscore[k].Name,
3636 MAX_PLAYER_NAME_LEN))
3637 break; /* player already there with a higher score */
3642 // if (position >= 0)
3643 // SaveScore(level_nr);
3648 static void InitMovingField_MM(int x, int y, int direction)
3650 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3651 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3653 MovDir[x][y] = direction;
3654 MovDir[newx][newy] = direction;
3655 if (Feld[newx][newy] == EL_EMPTY)
3656 Feld[newx][newy] = EL_BLOCKED;
3659 static void Moving2Blocked_MM(int x, int y, int *goes_to_x, int *goes_to_y)
3661 int direction = MovDir[x][y];
3662 int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0);
3663 int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0);
3669 static void Blocked2Moving_MM(int x, int y,
3670 int *comes_from_x, int *comes_from_y)
3672 int oldx = x, oldy = y;
3673 int direction = MovDir[x][y];
3675 if (direction == MV_LEFT)
3677 else if (direction == MV_RIGHT)
3679 else if (direction == MV_UP)
3681 else if (direction == MV_DOWN)
3684 *comes_from_x = oldx;
3685 *comes_from_y = oldy;
3688 static int MovingOrBlocked2Element_MM(int x, int y)
3690 int element = Feld[x][y];
3692 if (element == EL_BLOCKED)
3696 Blocked2Moving_MM(x, y, &oldx, &oldy);
3697 return Feld[oldx][oldy];
3704 static void RemoveField(int x, int y)
3706 Feld[x][y] = EL_EMPTY;
3713 static void RemoveMovingField_MM(int x, int y)
3715 int oldx = x, oldy = y, newx = x, newy = y;
3717 if (Feld[x][y] != EL_BLOCKED && !IS_MOVING(x, y))
3720 if (IS_MOVING(x, y))
3722 Moving2Blocked_MM(x, y, &newx, &newy);
3723 if (Feld[newx][newy] != EL_BLOCKED)
3726 else if (Feld[x][y] == EL_BLOCKED)
3728 Blocked2Moving_MM(x, y, &oldx, &oldy);
3729 if (!IS_MOVING(oldx, oldy))
3733 Feld[oldx][oldy] = EL_EMPTY;
3734 Feld[newx][newy] = EL_EMPTY;
3735 MovPos[oldx][oldy] = MovDir[oldx][oldy] = MovDelay[oldx][oldy] = 0;
3736 MovPos[newx][newy] = MovDir[newx][newy] = MovDelay[newx][newy] = 0;
3738 DrawLevelField_MM(oldx, oldy);
3739 DrawLevelField_MM(newx, newy);
3742 void PlaySoundLevel(int x, int y, int sound_nr)
3744 int sx = SCREENX(x), sy = SCREENY(y);
3746 int silence_distance = 8;
3748 if ((!setup.sound_simple && !IS_LOOP_SOUND(sound_nr)) ||
3749 (!setup.sound_loops && IS_LOOP_SOUND(sound_nr)))
3752 if (!IN_LEV_FIELD(x, y) ||
3753 sx < -silence_distance || sx >= SCR_FIELDX+silence_distance ||
3754 sy < -silence_distance || sy >= SCR_FIELDY+silence_distance)
3757 volume = SOUND_MAX_VOLUME;
3760 stereo = (sx - SCR_FIELDX/2) * 12;
3762 stereo = SOUND_MIDDLE + (2 * sx - (SCR_FIELDX - 1)) * 5;
3763 if (stereo > SOUND_MAX_RIGHT)
3764 stereo = SOUND_MAX_RIGHT;
3765 if (stereo < SOUND_MAX_LEFT)
3766 stereo = SOUND_MAX_LEFT;
3769 if (!IN_SCR_FIELD(sx, sy))
3771 int dx = ABS(sx - SCR_FIELDX/2) - SCR_FIELDX/2;
3772 int dy = ABS(sy - SCR_FIELDY/2) - SCR_FIELDY/2;
3774 volume -= volume * (dx > dy ? dx : dy) / silence_distance;
3777 PlaySoundExt(sound_nr, volume, stereo, SND_CTRL_PLAY_SOUND);
3780 static void RaiseScore_MM(int value)
3782 game_mm.score += value;
3784 DrawText(DX_SCORE, DY_SCORE, int2str(game_mm.score, 4),
3789 void RaiseScoreElement_MM(int element)
3794 RaiseScore_MM(native_mm_level.score[SC_PACMAN]);
3797 RaiseScore_MM(native_mm_level.score[SC_KEY]);