2 * Copyright (c) 2007, 2008, 2009, Czirkos Zoltan <cirix@fw.hu>
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #include <glib/gi18n.h>
23 /* arrays for movements */
24 /* also no1 and bd2 cave data import helpers; line direction coordinates */
27 0, 0, 1, 1, 1, 0, -1, -1, -1, 0, 2, 2, 2, 0, -2, -2, -2
31 0, -1, -1, 0, 1, 1, 1, 0, -1, -2, -2, 0, 2, 2, 2, 0, -2
35 None here means "no direction to move"; when there is no gravity while stirring the pot. */
36 static const char* direction_name[] =
49 static const char* direction_filename[] =
62 static const char* scheduling_name[] =
70 "Atari BD2/Construction Kit"
73 static const char* scheduling_filename[] =
84 static GHashTable *name_to_element;
85 GdElement gd_char_to_element[256];
87 /* color of flashing the screen, gate opening to exit */
88 const GdColor gd_flash_color = 0xFFFFC0;
90 /* selected object in editor */
91 const GdColor gd_select_color = 0x8080FF;
93 /* direction to string and vice versa */
94 const char *gd_direction_get_visible_name(GdDirection dir)
96 return direction_name[dir];
99 const char *gd_direction_get_filename(GdDirection dir)
101 return direction_filename[dir];
104 GdDirection gd_direction_from_string(const char *str)
108 for (i = 1; i<G_N_ELEMENTS(direction_filename); i++)
109 if (strcasecmp(str, direction_filename[i]) == 0)
110 return (GdDirection) i;
112 Warn("invalid direction name '%s', defaulting to down", str);
116 /* scheduling name to string and vice versa */
117 const char *gd_scheduling_get_filename(GdScheduling sched)
119 return scheduling_filename[sched];
122 const char *gd_scheduling_get_visible_name(GdScheduling sched)
124 return scheduling_name[sched];
127 GdScheduling gd_scheduling_from_string(const char *str)
131 for (i = 0; i < G_N_ELEMENTS(scheduling_filename); i++)
132 if (strcasecmp(str, scheduling_filename[i]) == 0)
133 return (GdScheduling) i;
135 Warn("invalid scheduling name '%s', defaulting to plck", str);
137 return GD_SCHEDULING_PLCK;
141 fill a given struct with default properties.
142 "str" is the struct (data),
143 "properties" describes the structure and its pointers,
144 "defaults" are the pieces of data which will be copied to str.
146 void gd_struct_set_defaults_from_array(gpointer str,
147 const GdStructDescriptor *properties,
148 GdPropertyDefault *defaults)
152 for (i = 0; defaults[i].offset != -1; i++)
154 gpointer pvalue = G_STRUCT_MEMBER_P(str, defaults[i].offset);
155 /* these point to the same, but to avoid the awkward cast syntax */
156 int *ivalue = pvalue;
157 GdElement *evalue = pvalue;
158 GdDirection *dvalue = pvalue;
159 GdScheduling *svalue = pvalue;
160 boolean *bvalue = pvalue;
161 GdColor *cvalue = pvalue;
164 /* check which property we are talking about: find it in gd_cave_properties. */
165 n = defaults[i].property_index;
168 while (properties[n].identifier != NULL &&
169 properties[n].offset != defaults[i].offset)
172 /* remember so we will be fast later*/
173 defaults[i].property_index = n;
176 /* some properties are arrays. this loop fills all with the same values */
177 for (j = 0; j < properties[n].count; j++)
179 switch (properties[n].type)
181 /* these are for the gui; do nothing */
184 /* no default value for strings */
186 case GD_TYPE_LONGSTRING:
190 /* this is also an integer, difference is only when saving to bdcff */
192 if (defaults[i].defval < properties[n].min ||
193 defaults[i].defval > properties[n].max)
194 Warn("integer property %s out of range", properties[n].identifier);
195 ivalue[j] = defaults[i].defval;
198 case GD_TYPE_PROBABILITY:
199 /* floats are stored as integer, /million; but are integers */
200 if (defaults[i].defval < 0 ||
201 defaults[i].defval > 1000000)
202 Warn("integer property %s out of range", properties[n].identifier);
203 ivalue[j] = defaults[i].defval;
206 case GD_TYPE_BOOLEAN:
207 bvalue[j] = defaults[i].defval != 0;
210 case GD_TYPE_ELEMENT:
212 evalue[j] = (GdElement) defaults[i].defval;
216 cvalue[j] = gd_c64_color(defaults[i].defval);
219 case GD_TYPE_DIRECTION:
220 dvalue[j] = (GdDirection) defaults[i].defval;
223 case GD_TYPE_SCHEDULING:
224 svalue[j] = (GdScheduling) defaults[i].defval;
231 /* creates the character->element conversion table; using
232 the fixed-in-the-bdcff characters. later, this table
233 may be filled with more elements.
235 void gd_create_char_to_element_table(void)
239 /* fill all with unknown */
240 for (i = 0; i < G_N_ELEMENTS(gd_char_to_element); i++)
241 gd_char_to_element[i] = O_UNKNOWN;
243 /* then set fixed characters */
244 for (i = 0; i < O_MAX; i++)
246 int c = gd_elements[i].character;
250 if (gd_char_to_element[c]!=O_UNKNOWN)
251 Warn("Character %c already used for element %x", c, gd_char_to_element[c]);
253 gd_char_to_element[c] = i;
258 /* search the element database for the specified character, and return the element. */
259 GdElement gd_get_element_from_character (guint8 character)
261 if (gd_char_to_element[character] != O_UNKNOWN)
262 return gd_char_to_element[character];
264 Warn ("Invalid character representing element: %c", character);
270 do some init; this function is to be called at the start of the application
272 void gd_cave_init(void)
276 /* put names to a hash table */
277 /* this is a helper for file read operations */
278 /* maps g_strdupped strings to elemenets (integers) */
279 name_to_element = g_hash_table_new_full(gd_str_case_hash, gd_str_case_equal,
282 for (i = 0; i < O_MAX; i++)
286 key = g_ascii_strup(gd_elements[i].filename, -1);
288 if (g_hash_table_lookup_extended(name_to_element, key, NULL, NULL))
289 Warn("Name %s already used for element %x", key, i);
291 g_hash_table_insert(name_to_element, key, GINT_TO_POINTER(i));
292 /* ^^^ do not free "key", as hash table needs it during the whole time! */
294 key = g_strdup_printf("SCANNED_%s", key); /* new string */
296 g_hash_table_insert(name_to_element, key, GINT_TO_POINTER(i));
297 /* once again, do not free "key" ^^^ */
300 /* for compatibility with tim stridmann's memorydump->bdcff converter... .... ... */
301 g_hash_table_insert(name_to_element, "HEXPANDING_WALL", GINT_TO_POINTER(O_H_EXPANDING_WALL));
302 g_hash_table_insert(name_to_element, "FALLING_DIAMOND", GINT_TO_POINTER(O_DIAMOND_F));
303 g_hash_table_insert(name_to_element, "FALLING_BOULDER", GINT_TO_POINTER(O_STONE_F));
304 g_hash_table_insert(name_to_element, "EXPLOSION1S", GINT_TO_POINTER(O_EXPLODE_1));
305 g_hash_table_insert(name_to_element, "EXPLOSION2S", GINT_TO_POINTER(O_EXPLODE_2));
306 g_hash_table_insert(name_to_element, "EXPLOSION3S", GINT_TO_POINTER(O_EXPLODE_3));
307 g_hash_table_insert(name_to_element, "EXPLOSION4S", GINT_TO_POINTER(O_EXPLODE_4));
308 g_hash_table_insert(name_to_element, "EXPLOSION5S", GINT_TO_POINTER(O_EXPLODE_5));
309 g_hash_table_insert(name_to_element, "EXPLOSION1D", GINT_TO_POINTER(O_PRE_DIA_1));
310 g_hash_table_insert(name_to_element, "EXPLOSION2D", GINT_TO_POINTER(O_PRE_DIA_2));
311 g_hash_table_insert(name_to_element, "EXPLOSION3D", GINT_TO_POINTER(O_PRE_DIA_3));
312 g_hash_table_insert(name_to_element, "EXPLOSION4D", GINT_TO_POINTER(O_PRE_DIA_4));
313 g_hash_table_insert(name_to_element, "EXPLOSION5D", GINT_TO_POINTER(O_PRE_DIA_5));
314 g_hash_table_insert(name_to_element, "WALL2", GINT_TO_POINTER(O_STEEL_EXPLODABLE));
316 /* compatibility with old bd-faq (pre disassembly of bladder) */
317 g_hash_table_insert(name_to_element, "BLADDERd9", GINT_TO_POINTER(O_BLADDER_8));
319 /* create table to show errors at the start of the application */
320 gd_create_char_to_element_table();
323 /* search the element database for the specified name, and return the element */
324 GdElement gd_get_element_from_string (const char *string)
326 char *upper = g_ascii_strup(string, -1);
332 Warn("Invalid string representing element: (null)");
336 found = g_hash_table_lookup_extended(name_to_element, upper, NULL, &value);
339 return (GdElement) (GPOINTER_TO_INT(value));
341 Warn("Invalid string representing element: %s", string);
345 void gd_cave_set_defaults_from_array(GdCave* cave, GdPropertyDefault *defaults)
347 gd_struct_set_defaults_from_array(cave, gd_cave_properties, defaults);
351 load default values from description array
352 these are default for gdash and bdcff.
354 void gd_cave_set_gdash_defaults(GdCave* cave)
358 gd_cave_set_defaults_from_array(cave, gd_cave_defaults_gdash);
360 /* these did not fit into the descriptor array */
361 for (i = 0; i < 5; i++)
363 cave->level_rand[i] = i;
364 cave->level_timevalue[i] = i + 1;
368 /* for quicksort. compares two highscores. */
369 int gd_highscore_compare(gconstpointer a, gconstpointer b)
371 const GdHighScore *ha = a;
372 const GdHighScore *hb = b;
373 return hb->score - ha->score;
376 void gd_clear_highscore(GdHighScore *hs)
380 for (i = 0; i < GD_HIGHSCORE_NUM; i++)
382 strcpy(hs[i].name, "");
387 boolean gd_has_highscore(GdHighScore *hs)
389 return hs[0].score > 0;
392 /* return true if score achieved is a highscore */
393 boolean gd_is_highscore(GdHighScore *scores, int score)
395 /* if score is above zero AND bigger than the last one */
396 if (score > 0 && score > scores[GD_HIGHSCORE_NUM-1].score)
402 int gd_add_highscore(GdHighScore *highscores, const char *name, int score)
406 if (!gd_is_highscore(highscores, score))
409 /* overwrite the last one */
410 gd_strcpy(highscores[GD_HIGHSCORE_NUM-1].name, name);
411 highscores[GD_HIGHSCORE_NUM-1].score = score;
414 qsort(highscores, GD_HIGHSCORE_NUM, sizeof(GdHighScore), gd_highscore_compare);
416 for (i = 0; i < GD_HIGHSCORE_NUM; i++)
417 if (g_str_equal(highscores[i].name, name) && highscores[i].score == score)
423 /* for the case-insensitive hash keys */
424 boolean gd_str_case_equal(gconstpointer s1, gconstpointer s2)
426 return strcasecmp(s1, s2) == 0;
429 guint gd_str_case_hash(gconstpointer v)
434 upper = g_ascii_strup(v, -1);
435 hash = g_str_hash(v);
441 create new cave with default values.
442 sets every value, also default size, diamond value etc.
444 GdCave *gd_cave_new(void)
449 cave = checked_calloc(sizeof(GdCave));
451 /* hash table which stores unknown tags as strings. */
452 cave->tags = g_hash_table_new_full(gd_str_case_hash, gd_str_case_equal, free, free);
455 for (i = 0; gd_cave_properties[i].identifier != NULL; i++)
456 if (gd_cave_properties[i].type == GD_TYPE_LONGSTRING)
457 G_STRUCT_MEMBER(GString *, cave, gd_cave_properties[i].offset) = g_string_new(NULL);
459 gd_cave_set_gdash_defaults(cave);
465 cave maps are continuous areas in memory. the allocated memory
466 is width * height * bytes_per_cell long.
467 the cave map[0] stores the pointer given by g_malloc().
468 the map itself is also an allocated array of pointers to the
471 rows = new (pointers to rows);
473 rows[1..h-1] = rows[0] + width * bytes
481 allocate a cave map-like array, and initialize to zero.
482 one cell is cell_size bytes long.
484 gpointer gd_cave_map_new_for_cave(const GdCave *cave, const int cell_size)
486 gpointer *rows; /* this is void**, pointer to array of ... */
489 rows = checked_malloc((cave->h) * sizeof(gpointer));
490 rows[0] = checked_calloc(cell_size * cave->w * cave->h);
492 for (y = 1; y < cave->h; y++)
493 /* base pointer + num_of_bytes_per_element * width * number_of_row; as sizeof(char) = 1 */
494 rows[y] = (char *)rows[0] + cell_size * cave->w * y;
502 if map is null, this also returns null.
504 gpointer gd_cave_map_dup_size(const GdCave *cave, const gpointer map, const int cell_size)
507 gpointer *maplines = (gpointer *)map;
513 rows = checked_malloc((cave->h) * sizeof(gpointer));
514 rows[0] = g_memdup (maplines[0], cell_size * cave->w * cave->h);
516 for (y = 1; y < cave->h; y++)
517 rows[y] = (char *)rows[0] + cell_size * cave->w * y;
522 void gd_cave_map_free(gpointer map)
524 gpointer *maplines = (gpointer *) map;
534 frees memory associated to cave
536 void gd_cave_free(GdCave *cave)
544 g_hash_table_destroy(cave->tags);
546 if (cave->random) /* random generator is a GRand * */
547 g_rand_free(cave->random);
550 for (i = 0; gd_cave_properties[i].identifier != NULL; i++)
551 if (gd_cave_properties[i].type==GD_TYPE_LONGSTRING)
552 g_string_free(G_STRUCT_MEMBER(GString *, cave, gd_cave_properties[i].offset), TRUE);
555 gd_cave_map_free(cave->map);
558 gd_cave_map_free(cave->objects_order);
560 /* hammered walls to reappear data */
561 gd_cave_map_free(cave->hammered_reappear);
564 g_list_foreach(cave->objects, (GFunc) free, NULL);
565 g_list_free (cave->objects);
568 g_list_foreach(cave->replays, (GFunc) gd_replay_free, NULL);
569 g_list_free(cave->replays);
571 /* freeing main pointer */
575 static void hash_copy_foreach(const char *key, const char *value, GHashTable *dest)
577 g_hash_table_insert(dest, g_strdup(key), g_strdup(value));
580 /* copy cave from src to destination, with duplicating dynamically allocated data */
581 void gd_cave_copy(GdCave *dest, const GdCave *src)
585 /* copy entire data */
586 g_memmove(dest, src, sizeof(GdCave));
588 /* but duplicate dynamic data */
589 dest->tags = g_hash_table_new_full(gd_str_case_hash, gd_str_case_equal,
592 g_hash_table_foreach(src->tags, (GHFunc) hash_copy_foreach, dest->tags);
594 dest->map = gd_cave_map_dup(src, map);
595 dest->hammered_reappear = gd_cave_map_dup(src, hammered_reappear);
597 /* for longstrings */
598 for (i = 0; gd_cave_properties[i].identifier != NULL; i++)
599 if (gd_cave_properties[i].type==GD_TYPE_LONGSTRING)
600 G_STRUCT_MEMBER(GString *, dest, gd_cave_properties[i].offset) =
601 g_string_new(G_STRUCT_MEMBER(GString *, src, gd_cave_properties[i].offset)->str);
603 /* no reason to copy this */
604 dest->objects_order = NULL;
606 /* copy objects list */
611 dest->objects = NULL; /* new empty list */
612 for (iter = src->objects; iter != NULL; iter = iter->next) /* do a deep copy */
613 dest->objects = g_list_append(dest->objects, g_memdup (iter->data, sizeof (GdObject)));
621 dest->replays = NULL;
622 for (iter = src->replays; iter != NULL; iter = iter->next) /* do a deep copy */
623 dest->replays = g_list_append(dest->replays, gd_replay_new_from_replay(iter->data));
626 /* copy random number generator */
628 dest->random = g_rand_copy(src->random);
631 /* create new cave, which is a copy of the cave given. */
632 GdCave *gd_cave_new_from_cave(const GdCave *orig)
636 cave = gd_cave_new();
637 gd_cave_copy(cave, orig);
643 Put an object to the specified position.
644 Performs range checking.
645 If wraparound objects are selected, wraps around x coordinates, with or without lineshift.
646 (The y coordinate is not wrapped, as it did not work like that on the c64)
647 order is a pointer to the GdObject describing this object. Thus the editor can identify which cell was created by which object.
649 void gd_cave_store_rc(GdCave *cave, int x, int y, const GdElement element, const void *order)
651 /* if we do not need to draw, exit now */
652 if (element == O_NONE)
656 if (cave->wraparound_objects)
660 /* fit x coordinate within range, with correcting y at the same time */
663 x += cave->w; /* out of bounds on the left... */
664 y--; /* previous row */
673 /* lineshifting does not fix the y coordinates.
674 if out of bounds, element will not be displayed. */
675 /* if such an object appeared in the c64 game, well, it was a buffer overrun. */
679 /* non lineshifting: changing x does not change y coordinate. */
686 /* after that, fix y coordinate */
695 /* if the above wraparound code fixed the coordinates, this will always be true. */
696 /* but see the above comment for lineshifting y coordinate */
697 if (x >= 0 && x < cave->w && y >= 0 && y < cave->h)
699 cave->map[y][x] = element;
700 cave->objects_order[y][x] = (void *)order;
704 GdElement gd_cave_get_rc(const GdCave *cave, int x, int y)
706 /* always fix coordinates as if cave was wraparound. */
708 /* fix x coordinate */
711 /* fit x coordinate within range, with correcting y at the same time */
714 x += cave->w; /* out of bounds on the left... */
715 y--; /* previous row */
725 /* non lineshifting: changing x does not change y coordinate. */
733 /* after that, fix y coordinate */
740 return cave->map[y][x];
743 unsigned int gd_c64_random(GdC64RandomGenerator *rand)
745 unsigned int temp_rand_1, temp_rand_2, carry, result;
747 temp_rand_1 = (rand->rand_seed_1 & 0x0001) << 7;
748 temp_rand_2 = (rand->rand_seed_2 >> 1) & 0x007F;
749 result = (rand->rand_seed_2) + ((rand->rand_seed_2 & 0x0001) << 7);
750 carry = (result >> 8);
751 result = result & 0x00FF;
752 result = result + carry + 0x13;
753 carry = (result >> 8);
754 rand->rand_seed_2 = result & 0x00FF;
755 result = rand->rand_seed_1 + carry + temp_rand_1;
756 carry = (result >> 8);
757 result = result & 0x00FF;
758 result = result + carry + temp_rand_2;
759 rand->rand_seed_1 = result & 0x00FF;
761 return rand->rand_seed_1;
765 C64 BD predictable random number generator.
766 Used to load the original caves imported from c64 files.
767 Also by the predictable slime.
769 unsigned int gd_cave_c64_random(GdCave *cave)
771 return gd_c64_random(&cave->c64_rand);
774 void gd_c64_random_set_seed(GdC64RandomGenerator *rand, int seed1, int seed2)
776 rand->rand_seed_1 = seed1;
777 rand->rand_seed_2 = seed2;
780 void gd_cave_c64_random_set_seed(GdCave *cave, int seed1, int seed2)
782 gd_c64_random_set_seed(&cave->c64_rand, seed1, seed2);
786 select random colors for a given cave.
787 this function will select colors so that they should look somewhat nice; for example
788 brick walls won't be the darkest color, for example.
790 static inline void swap(int *i1, int *i2)
799 if last line or last row is just steel wall (or (invisible) outbox).
800 used after loading a game for playing.
801 after this, ew and eh will contain the effective width and height.
803 void gd_cave_auto_shrink(GdCave *cave)
815 /* set to maximum size, then try to shrink */
818 cave->x2 = cave->w - 1;
819 cave->y2 = cave->h - 1;
821 /* search for empty, steel-wall-only last rows. */
822 /* clear all lines, which are only steel wall.
823 * and clear only one line, which is steel wall, but also has a player or an outbox. */
828 for (y = cave->y2 - 1; y <= cave->y2; y++)
830 for (x = cave->x1; x <= cave->x2; x++)
832 switch (gd_cave_get_rc (cave, x, y))
834 /* if steels only, this is to be deleted. */
839 case O_PRE_INVIS_OUTBOX:
841 if (empty == STEEL_OR_OTHER)
844 /* if this, delete only this one, and exit. */
845 if (empty == STEEL_ONLY)
846 empty = STEEL_OR_OTHER;
850 /* anything else, that should be left in the cave. */
857 /* shrink if full steel or steel and player/outbox. */
858 if (empty != NO_SHRINK)
859 cave->y2--; /* one row shorter */
861 while (empty == STEEL_ONLY); /* if found just steels, repeat. */
863 /* search for empty, steel-wall-only first rows. */
868 for (y = cave->y1; y <= cave->y1 + 1; y++)
870 for (x = cave->x1; x <= cave->x2; x++)
872 switch (gd_cave_get_rc (cave, x, y))
878 case O_PRE_INVIS_OUTBOX:
880 /* shrink only lines, which have only ONE player or outbox.
881 this is for bd4 intermission 2, for example. */
882 if (empty==STEEL_OR_OTHER)
884 if (empty==STEEL_ONLY)
885 empty = STEEL_OR_OTHER;
895 if (empty != NO_SHRINK)
898 while (empty == STEEL_ONLY); /* if found one, repeat. */
900 /* empty last columns. */
905 for (y = cave->y1; y <= cave->y2; y++)
907 for (x = cave->x2 - 1; x <= cave->x2; x++)
909 switch (gd_cave_get_rc (cave, x, y))
915 case O_PRE_INVIS_OUTBOX:
917 if (empty==STEEL_OR_OTHER)
919 if (empty==STEEL_ONLY)
920 empty = STEEL_OR_OTHER;
930 /* just remember that one column shorter.
931 free will know the size of memchunk, no need to realloc! */
932 if (empty != NO_SHRINK)
935 while (empty == STEEL_ONLY); /* if found one, repeat. */
937 /* empty first columns. */
942 for (y = cave->y1; y <= cave->y2; y++)
944 for (x = cave->x1; x <= cave->x1 + 1; x++)
946 switch (gd_cave_get_rc (cave, x, y))
952 case O_PRE_INVIS_OUTBOX:
954 if (empty==STEEL_OR_OTHER)
956 if (empty==STEEL_ONLY)
957 empty = STEEL_OR_OTHER;
967 if (empty != NO_SHRINK)
970 while (empty == STEEL_ONLY); /* if found one, repeat. */
973 /* check if cave visible part coordinates
974 are outside cave sizes, or not in the right order.
975 correct them if needed.
977 void gd_cave_correct_visible_size(GdCave *cave)
979 /* change visible coordinates if they do not point to upperleft and lowerright */
980 if (cave->x2 < cave->x1)
987 if (cave->y2 < cave->y1)
1000 if (cave->x2 > cave->w - 1)
1001 cave->x2 = cave->w - 1;
1003 if (cave->y2 > cave->h - 1)
1004 cave->y2 = cave->h - 1;
1008 bd1 and similar engines had animation bits in cave data, to set which elements to animate
1009 (firefly, butterfly, amoeba).
1010 animating an element also caused some delay each frame; according to my measurements,
1011 around 2.6 ms/element.
1013 static void cave_set_ckdelay_extra_for_animation(GdCave *cave)
1016 boolean has_amoeba = FALSE, has_firefly = FALSE, has_butterfly = FALSE;
1018 for (y = 0; y < cave->h; y++)
1020 for (x = 0; x < cave->w; x++)
1022 switch (cave->map[y][x] & ~SCANNED)
1035 has_butterfly = TRUE;
1045 cave->ckdelay_extra_for_animation = 0;
1047 cave->ckdelay_extra_for_animation += 2600;
1049 cave->ckdelay_extra_for_animation += 2600;
1051 cave->ckdelay_extra_for_animation += 2600;
1053 cave->ckdelay_extra_for_animation += 2600;
1056 /* do some init - setup some cave variables before the game. */
1057 void gd_cave_setup_for_game(GdCave *cave)
1061 cave_set_ckdelay_extra_for_animation(cave);
1063 /* find the player which will be the one to scroll to at the beginning of the game
1064 (before the player's birth) */
1065 if (cave->active_is_first_found)
1067 /* uppermost player is active */
1068 for (y = cave->h - 1; y >= 0; y--)
1070 for (x = cave->w - 1; x >= 0; x--)
1072 if (cave->map[y][x] == O_INBOX)
1082 /* lowermost player is active */
1083 for (y = 0; y < cave->h; y++)
1085 for (x = 0; x < cave->w; x++)
1087 if (cave->map[y][x] == O_INBOX)
1096 /* select number of milliseconds (for pal and ntsc) */
1097 cave->timing_factor = cave->pal_timing ? 1200 : 1000;
1099 cave->time *= cave->timing_factor;
1100 cave->magic_wall_time *= cave->timing_factor;
1101 cave->amoeba_time *= cave->timing_factor;
1102 cave->amoeba_2_time *= cave->timing_factor;
1103 cave->hatching_delay_time *= cave->timing_factor;
1105 if (cave->hammered_walls_reappear)
1106 cave->hammered_reappear = gd_cave_map_new(cave, int);
1109 /* cave diamonds needed can be set to n<=0. */
1110 /* if so, count the diamonds at the time of the hatching, and decrement that value from */
1111 /* the number of diamonds found. */
1112 /* of course, this function is to be called from the cave engine, at the exact time of hatching. */
1113 void gd_cave_count_diamonds(GdCave *cave)
1117 /* if automatically counting diamonds. if this was negative,
1118 * the sum will be this less than the number of all the diamonds in the cave */
1119 if (cave->diamonds_needed <= 0)
1121 for (y = 0; y < cave->h; y++)
1122 for (x = 0; x < cave->w; x++)
1123 if (cave->map[y][x] == O_DIAMOND)
1124 cave->diamonds_needed++;
1126 /* if still below zero, let this be 0, so gate will be open immediately */
1127 if (cave->diamonds_needed < 0)
1128 cave->diamonds_needed = 0;
1132 /* takes a cave and a gfx buffer, and fills the buffer with cell indexes.
1133 the indexes might change if bonus life flash is active (small lines in
1135 for the paused state (which is used in gdash but not in sdash) - yellowish
1137 also one can select the animation frame (0..7) to draw the cave on. so the
1141 if a cell is changed, it is flagged with GD_REDRAW; the flag can be cleared
1144 void gd_drawcave_game(const GdCave *cave, int **element_buffer, int **gfx_buffer,
1145 boolean bonus_life_flash, int animcycle, boolean hate_invisible_outbox)
1147 static int player_blinking = 0;
1148 static int player_tapping = 0;
1149 int elemmapping[O_MAX];
1150 int elemdrawing[O_MAX];
1151 int x, y, map, draw;
1153 if (cave->last_direction)
1155 /* he is moving, so stop blinking and tapping. */
1156 player_blinking = 0;
1161 /* he is idle, so animations can be done. */
1164 /* blinking and tapping is started at the beginning of animation sequences. */
1165 /* 1/4 chance of blinking, every sequence. */
1166 player_blinking = g_random_int_range(0, 4) == 0;
1168 /* 1/16 chance of starting or stopping tapping. */
1169 if (g_random_int_range(0, 16) == 0)
1170 player_tapping = !player_tapping;
1174 for (x = 0; x < O_MAX; x++)
1177 elemdrawing[x] = gd_elements[x].image_game;
1180 if (bonus_life_flash)
1182 elemmapping[O_SPACE] = O_FAKE_BONUS;
1183 elemdrawing[O_SPACE] = gd_elements[O_FAKE_BONUS].image_game;
1186 elemmapping[O_MAGIC_WALL] = (cave->magic_wall_state == GD_MW_ACTIVE ? O_MAGIC_WALL : O_BRICK);
1187 elemdrawing[O_MAGIC_WALL] = gd_elements[cave->magic_wall_state == GD_MW_ACTIVE ? O_MAGIC_WALL : O_BRICK].image_game;
1189 elemmapping[O_CREATURE_SWITCH] = (cave->creatures_backwards ? O_CREATURE_SWITCH_ON : O_CREATURE_SWITCH);
1190 elemdrawing[O_CREATURE_SWITCH] = gd_elements[cave->creatures_backwards ? O_CREATURE_SWITCH_ON : O_CREATURE_SWITCH].image_game;
1192 elemmapping[O_EXPANDING_WALL_SWITCH] = (cave->expanding_wall_changed ? O_EXPANDING_WALL_SWITCH_VERT : O_EXPANDING_WALL_SWITCH_HORIZ);
1193 elemdrawing[O_EXPANDING_WALL_SWITCH] = gd_elements[cave->expanding_wall_changed ? O_EXPANDING_WALL_SWITCH_VERT : O_EXPANDING_WALL_SWITCH_HORIZ].image_game;
1195 elemmapping[O_GRAVITY_SWITCH] = (cave->gravity_switch_active ? O_GRAVITY_SWITCH_ACTIVE : O_GRAVITY_SWITCH);
1196 elemdrawing[O_GRAVITY_SWITCH] = gd_elements[cave->gravity_switch_active ? O_GRAVITY_SWITCH_ACTIVE : O_GRAVITY_SWITCH].image_game;
1198 elemmapping[O_REPLICATOR_SWITCH] = (cave->replicators_active ? O_REPLICATOR_SWITCH_ON : O_REPLICATOR_SWITCH_OFF);
1199 elemdrawing[O_REPLICATOR_SWITCH] = gd_elements[cave->replicators_active ? O_REPLICATOR_SWITCH_ON : O_REPLICATOR_SWITCH_OFF].image_game;
1201 if (cave->replicators_active)
1202 /* if the replicators are active, animate them. */
1203 elemmapping[O_REPLICATOR] = O_REPLICATOR_ACTIVE;
1205 if (!cave->replicators_active)
1206 /* if the replicators are inactive, do not animate them. */
1207 elemdrawing[O_REPLICATOR] = ABS(elemdrawing[O_REPLICATOR]);
1209 elemmapping[O_CONVEYOR_SWITCH] = (cave->conveyor_belts_active ? O_CONVEYOR_SWITCH_ON : O_CONVEYOR_SWITCH_OFF);
1210 elemdrawing[O_CONVEYOR_SWITCH] = gd_elements[cave->conveyor_belts_active ? O_CONVEYOR_SWITCH_ON : O_CONVEYOR_SWITCH_OFF].image_game;
1212 if (cave->conveyor_belts_direction_changed)
1214 /* if direction is changed, animation is changed. */
1217 elemmapping[O_CONVEYOR_LEFT] = O_CONVEYOR_RIGHT;
1218 elemmapping[O_CONVEYOR_RIGHT] = O_CONVEYOR_LEFT;
1220 temp = elemdrawing[O_CONVEYOR_LEFT];
1221 elemdrawing[O_CONVEYOR_LEFT] = elemdrawing[O_CONVEYOR_RIGHT];
1222 elemdrawing[O_CONVEYOR_RIGHT] = temp;
1224 elemmapping[O_CONVEYOR_DIR_SWITCH] = O_CONVEYOR_DIR_CHANGED;
1225 elemdrawing[O_CONVEYOR_DIR_SWITCH] = gd_elements[O_CONVEYOR_DIR_CHANGED].image_game;
1229 elemmapping[O_CONVEYOR_DIR_SWITCH] = O_CONVEYOR_DIR_NORMAL;
1230 elemdrawing[O_CONVEYOR_DIR_SWITCH] = gd_elements[O_CONVEYOR_DIR_NORMAL].image_game;
1233 if (cave->conveyor_belts_active)
1235 /* keep potentially changed direction */
1236 int offset = (O_CONVEYOR_LEFT_ACTIVE - O_CONVEYOR_LEFT);
1238 /* if they are running, animate them. */
1239 elemmapping[O_CONVEYOR_LEFT] += offset;
1240 elemmapping[O_CONVEYOR_RIGHT] += offset;
1242 if (!cave->conveyor_belts_active)
1244 /* if they are not running, do not animate them. */
1245 elemdrawing[O_CONVEYOR_LEFT] = ABS(elemdrawing[O_CONVEYOR_LEFT]);
1246 elemdrawing[O_CONVEYOR_RIGHT] = ABS(elemdrawing[O_CONVEYOR_RIGHT]);
1251 /* also a hack, like biter_switch */
1252 elemdrawing[O_PNEUMATIC_ACTIVE_LEFT] += 2;
1253 elemdrawing[O_PNEUMATIC_ACTIVE_RIGHT] += 2;
1254 elemdrawing[O_PLAYER_PNEUMATIC_LEFT] += 2;
1255 elemdrawing[O_PLAYER_PNEUMATIC_RIGHT] += 2;
1258 if ((cave->last_direction) == GD_MV_STILL)
1260 /* player is idle. */
1261 if (player_blinking && player_tapping)
1263 map = O_PLAYER_TAP_BLINK;
1264 draw = gd_elements[O_PLAYER_TAP_BLINK].image_game;
1266 else if (player_blinking)
1268 map = O_PLAYER_BLINK;
1269 draw = gd_elements[O_PLAYER_BLINK].image_game;
1271 else if (player_tapping)
1274 draw = gd_elements[O_PLAYER_TAP].image_game;
1279 draw = gd_elements[O_PLAYER].image_game;
1282 else if (cave->last_horizontal_direction == GD_MV_LEFT)
1284 map = O_PLAYER_LEFT;
1285 draw = gd_elements[O_PLAYER_LEFT].image_game;
1289 /* of course this is GD_MV_RIGHT. */
1290 map = O_PLAYER_RIGHT;
1291 draw = gd_elements[O_PLAYER_RIGHT].image_game;
1294 elemmapping[O_PLAYER] = map;
1295 elemmapping[O_PLAYER_GLUED] = map;
1297 elemdrawing[O_PLAYER] = draw;
1298 elemdrawing[O_PLAYER_GLUED] = draw;
1300 /* player with bomb does not blink or tap - no graphics drawn for that.
1301 running is drawn using w/o bomb cells */
1302 if (cave->last_direction!=GD_MV_STILL)
1304 elemmapping[O_PLAYER_BOMB] = map;
1305 elemdrawing[O_PLAYER_BOMB] = draw;
1308 elemmapping[O_INBOX] = (cave->inbox_flash_toggle ? O_INBOX_OPEN : O_INBOX_CLOSED);
1309 elemdrawing[O_INBOX] = gd_elements[cave->inbox_flash_toggle ? O_OUTBOX_OPEN : O_OUTBOX_CLOSED].image_game;
1311 elemmapping[O_OUTBOX] = (cave->inbox_flash_toggle ? O_OUTBOX_OPEN : O_OUTBOX_CLOSED);
1312 elemdrawing[O_OUTBOX] = gd_elements[cave->inbox_flash_toggle ? O_OUTBOX_OPEN : O_OUTBOX_CLOSED].image_game;
1314 /* hack, not fit into gd_elements */
1315 elemmapping[O_BITER_SWITCH] = O_BITER_SWITCH_1 + cave->biter_delay_frame;
1316 /* hack, not fit into gd_elements */
1317 elemdrawing[O_BITER_SWITCH] = gd_elements[O_BITER_SWITCH].image_game + cave->biter_delay_frame;
1319 /* visual effects */
1320 elemmapping[O_DIRT] = cave->dirt_looks_like;
1321 elemmapping[O_EXPANDING_WALL] = cave->expanding_wall_looks_like;
1322 elemmapping[O_V_EXPANDING_WALL] = cave->expanding_wall_looks_like;
1323 elemmapping[O_H_EXPANDING_WALL] = cave->expanding_wall_looks_like;
1324 elemmapping[O_AMOEBA_2] = cave->amoeba_2_looks_like;
1326 /* visual effects */
1327 elemdrawing[O_DIRT] = elemdrawing[cave->dirt_looks_like];
1328 elemdrawing[O_EXPANDING_WALL] = elemdrawing[cave->expanding_wall_looks_like];
1329 elemdrawing[O_V_EXPANDING_WALL] = elemdrawing[cave->expanding_wall_looks_like];
1330 elemdrawing[O_H_EXPANDING_WALL] = elemdrawing[cave->expanding_wall_looks_like];
1331 elemdrawing[O_AMOEBA_2] = elemdrawing[cave->amoeba_2_looks_like];
1333 /* change only graphically */
1334 if (hate_invisible_outbox)
1336 elemmapping[O_PRE_INVIS_OUTBOX] = O_PRE_OUTBOX;
1337 elemmapping[O_INVIS_OUTBOX] = O_OUTBOX;
1340 if (hate_invisible_outbox)
1342 elemdrawing[O_PRE_INVIS_OUTBOX] = elemdrawing[O_PRE_OUTBOX];
1343 elemdrawing[O_INVIS_OUTBOX] = elemdrawing[O_OUTBOX];
1346 for (y = cave->y1; y <= cave->y2; y++)
1348 for (x = cave->x1; x <= cave->x2; x++)
1350 GdElement actual = cave->map[y][x];
1352 /* if covered, real element is not important */
1353 if (actual & COVERED)
1356 map = elemmapping[actual];
1358 /* if covered, real element is not important */
1359 if (actual & COVERED)
1360 draw = gd_elements[O_COVERED].image_game;
1362 draw = elemdrawing[actual];
1364 /* if negative, animated. */
1366 draw = -draw + animcycle;
1369 if (cave->gate_open_flash)
1370 draw += GD_NUM_OF_CELLS;
1372 /* set to buffer, with caching */
1373 if (element_buffer[y][x] != map)
1374 element_buffer[y][x] = map;
1376 if (gfx_buffer[y][x] != draw)
1377 gfx_buffer[y][x] = draw | GD_REDRAW;
1382 /* cave time is rounded _UP_ to seconds. so at the exact moment when it
1384 2sec remaining to 1sec remaining, the player has exactly one second.
1386 to zero, it is the exact moment of timeout. */
1387 /* internal time is milliseconds (or 1200 milliseconds for pal timing). */
1388 int gd_cave_time_show(const GdCave *cave, int internal_time)
1390 return (internal_time + cave->timing_factor - 1) / cave->timing_factor;
1393 GdReplay *gd_replay_new(void)
1397 rep = checked_calloc(sizeof(GdReplay));
1399 /* create dynamic objects */
1400 rep->comment = g_string_new(NULL);
1401 rep->movements = g_byte_array_new();
1406 GdReplay *gd_replay_new_from_replay(GdReplay *orig)
1410 rep = g_memdup(orig, sizeof(GdReplay));
1412 /* replicate dynamic data */
1413 rep->comment = g_string_new(orig->comment->str);
1414 rep->movements = g_byte_array_new();
1415 g_byte_array_append(rep->movements, orig->movements->data, orig->movements->len);
1420 void gd_replay_free(GdReplay *replay)
1422 g_byte_array_free(replay->movements, TRUE);
1423 g_string_free(replay->comment, TRUE);
1427 /* store movement in a replay */
1428 void gd_replay_store_movement(GdReplay *replay, GdDirection player_move,
1429 boolean player_fire, boolean suicide)
1433 data[0] = ((player_move) |
1434 (player_fire ? GD_REPLAY_FIRE_MASK : 0) |
1435 (suicide ? GD_REPLAY_SUICIDE_MASK : 0));
1437 g_byte_array_append(replay->movements, data, 1);
1440 /* get next available movement from a replay; store variables to player_move,
1441 player_fire, suicide */
1442 /* return true if successful */
1443 boolean gd_replay_get_next_movement(GdReplay *replay, GdDirection *player_move,
1444 boolean *player_fire, boolean *suicide)
1448 /* if no more available movements */
1449 if (replay->current_playing_pos >= replay->movements->len)
1452 data = replay->movements->data[replay->current_playing_pos++];
1453 *suicide = (data & GD_REPLAY_SUICIDE_MASK) != 0;
1454 *player_fire = (data & GD_REPLAY_FIRE_MASK) != 0;
1455 *player_move = (data & GD_REPLAY_MOVE_MASK);
1460 void gd_replay_rewind(GdReplay *replay)
1462 replay->current_playing_pos = 0;
1465 #define REPLAY_BDCFF_UP "u"
1466 #define REPLAY_BDCFF_UP_RIGHT "ur"
1467 #define REPLAY_BDCFF_RIGHT "r"
1468 #define REPLAY_BDCFF_DOWN_RIGHT "dr"
1469 #define REPLAY_BDCFF_DOWN "d"
1470 #define REPLAY_BDCFF_DOWN_LEFT "dl"
1471 #define REPLAY_BDCFF_LEFT "l"
1472 #define REPLAY_BDCFF_UP_LEFT "ul"
1473 /* when not moving */
1474 #define REPLAY_BDCFF_STILL "."
1475 /* when the fire is pressed */
1476 #define REPLAY_BDCFF_FIRE "F"
1477 #define REPLAY_BDCFF_SUICIDE "k"
1479 static char *direction_to_bdcff(GdDirection mov)
1484 case GD_MV_STILL: return REPLAY_BDCFF_STILL;
1487 case GD_MV_UP: return REPLAY_BDCFF_UP;
1488 case GD_MV_UP_RIGHT: return REPLAY_BDCFF_UP_RIGHT;
1489 case GD_MV_RIGHT: return REPLAY_BDCFF_RIGHT;
1490 case GD_MV_DOWN_RIGHT: return REPLAY_BDCFF_DOWN_RIGHT;
1491 case GD_MV_DOWN: return REPLAY_BDCFF_DOWN;
1492 case GD_MV_DOWN_LEFT: return REPLAY_BDCFF_DOWN_LEFT;
1493 case GD_MV_LEFT: return REPLAY_BDCFF_LEFT;
1494 case GD_MV_UP_LEFT: return REPLAY_BDCFF_UP_LEFT;
1497 return REPLAY_BDCFF_STILL;
1501 /* same as above; pressing fire will be a capital letter. */
1502 static char *direction_fire_to_bdcff(GdDirection dir, boolean fire)
1504 static char mov[10];
1506 strcpy(mov, direction_to_bdcff(dir));
1512 for (i = 0; mov[i] != 0; i++)
1513 mov[i] = g_ascii_toupper(mov[i]);
1519 char *gd_replay_movements_to_bdcff(GdReplay *replay)
1524 str = g_string_new(NULL);
1526 for (pos = 0; pos < replay->movements->len; pos++)
1531 /* if this is not the first movement, append a space. */
1533 g_string_append_c(str, ' ');
1535 /* if same byte appears, count number of occurrences - something like an rle compression. */
1536 /* be sure not to cross the array boundaries */
1537 while (pos < replay->movements->len - 1 &&
1538 replay->movements->data[pos] == replay->movements->data[pos + 1])
1544 data = replay->movements->data[pos];
1546 if (data & GD_REPLAY_SUICIDE_MASK)
1547 g_string_append(str, REPLAY_BDCFF_SUICIDE);
1549 g_string_append(str, direction_fire_to_bdcff(data & GD_REPLAY_MOVE_MASK,
1550 data & GD_REPLAY_FIRE_MASK));
1553 g_string_append_printf(str, "%d", num);
1556 return g_string_free(str, FALSE);
1559 /* calculate adler checksum for a rendered cave; this can be used for more caves. */
1560 void gd_cave_adler_checksum_more(GdCave *cave, guint32 *a, guint32 *b)
1564 for (y = 0; y < cave->h; y++)
1565 for (x = 0; x < cave->w; x++)
1567 *a += gd_elements[cave->map[y][x]].character;
1575 /* calculate adler checksum for a single rendered cave. */
1577 gd_cave_adler_checksum(GdCave *cave)
1582 gd_cave_adler_checksum_more(cave, &a, &b);
1583 return (b << 16) + a;
1586 /* return c64 color with index. */
1587 GdColor gd_c64_color(int index)
1589 return (GD_COLOR_TYPE_C64 << 24) + index;