X-Git-Url: https://git.artsoft.org/?a=blobdiff_plain;f=src%2Ffiles.c;h=36751f4630437fb12df9e828268a5e960e02760a;hb=a2c9a458aaa4ac568f07ecd25b19fbca3f766e6b;hp=d214bf99cb5c53cdef8302312b033f01a8d2ac80;hpb=bf7f1c667ad861d6cadacd0b9d6fe517be977a6e;p=rocksndiamonds.git diff --git a/src/files.c b/src/files.c index d214bf99..36751f46 100644 --- a/src/files.c +++ b/src/files.c @@ -1,7 +1,7 @@ /*********************************************************** * Rocks'n'Diamonds -- McDuffin Strikes Back! * *----------------------------------------------------------* -* (c) 1995-2001 Artsoft Entertainment * +* (c) 1995-2002 Artsoft Entertainment * * Holger Schemel * * Detmolder Strasse 189 * * 33604 Bielefeld * @@ -12,430 +12,244 @@ ***********************************************************/ #include -#include #include #include "libgame/libgame.h" #include "files.h" +#include "init.h" #include "tools.h" #include "tape.h" -#include "joystick.h" + #define CHUNK_ID_LEN 4 /* IFF style chunk id length */ #define CHUNK_SIZE_UNDEFINED 0 /* undefined chunk size == 0 */ #define CHUNK_SIZE_NONE -1 /* do not write chunk size */ #define FILE_VERS_CHUNK_SIZE 8 /* size of file version chunk */ #define LEVEL_HEADER_SIZE 80 /* size of level file header */ -#define LEVEL_HEADER_UNUSED 14 /* unused level header bytes */ +#define LEVEL_HEADER_UNUSED 13 /* unused level header bytes */ #define LEVEL_CHUNK_CNT2_SIZE 160 /* size of level CNT2 chunk */ #define LEVEL_CHUNK_CNT2_UNUSED 11 /* unused CNT2 chunk bytes */ +#define LEVEL_CHUNK_CNT3_HEADER 16 /* size of level CNT3 header */ +#define LEVEL_CHUNK_CNT3_UNUSED 10 /* unused CNT3 chunk bytes */ +#define LEVEL_CPART_CUS3_SIZE 134 /* size of CUS3 chunk part */ +#define LEVEL_CPART_CUS3_UNUSED 15 /* unused CUS3 bytes / part */ #define TAPE_HEADER_SIZE 20 /* size of tape file header */ -#define TAPE_HEADER_UNUSED 7 /* unused tape header bytes */ +#define TAPE_HEADER_UNUSED 3 /* unused tape header bytes */ + +#define LEVEL_CHUNK_CUS3_SIZE(x) (2 + (x) * LEVEL_CPART_CUS3_SIZE) +#define LEVEL_CHUNK_CUS4_SIZE(x) (48 + 48 + (x) * 48) /* file identifier strings */ #define LEVEL_COOKIE_TMPL "ROCKSNDIAMONDS_LEVEL_FILE_VERSION_x.x" #define TAPE_COOKIE_TMPL "ROCKSNDIAMONDS_TAPE_FILE_VERSION_x.x" #define SCORE_COOKIE "ROCKSNDIAMONDS_SCORE_FILE_VERSION_1.2" -#define SETUP_COOKIE "ROCKSNDIAMONDS_SETUP_FILE_VERSION_1.2" -#define LEVELSETUP_COOKIE "ROCKSNDIAMONDS_LEVELSETUP_FILE_VERSION_1.2" -#define LEVELINFO_COOKIE "ROCKSNDIAMONDS_LEVELINFO_FILE_VERSION_1.2" - -/* file names and filename extensions */ -#if !defined(PLATFORM_MSDOS) -#define LEVELSETUP_DIRECTORY "levelsetup" -#define SETUP_FILENAME "setup.conf" -#define LEVELSETUP_FILENAME "levelsetup.conf" -#define LEVELINFO_FILENAME "levelinfo.conf" -#define LEVELFILE_EXTENSION "level" -#define TAPEFILE_EXTENSION "tape" -#define SCOREFILE_EXTENSION "score" -#else -#define LEVELSETUP_DIRECTORY "lvlsetup" -#define SETUP_FILENAME "setup.cnf" -#define LEVELSETUP_FILENAME "lvlsetup.cnf" -#define LEVELINFO_FILENAME "lvlinfo.cnf" -#define LEVELFILE_EXTENSION "lvl" -#define TAPEFILE_EXTENSION "tap" -#define SCOREFILE_EXTENSION "sco" -#endif -/* sort priorities of level series (also used as level series classes) */ -#define LEVELCLASS_TUTORIAL_START 10 -#define LEVELCLASS_TUTORIAL_END 99 -#define LEVELCLASS_CLASSICS_START 100 -#define LEVELCLASS_CLASSICS_END 199 -#define LEVELCLASS_CONTRIBUTION_START 200 -#define LEVELCLASS_CONTRIBUTION_END 299 -#define LEVELCLASS_USER_START 300 -#define LEVELCLASS_USER_END 399 -#define LEVELCLASS_BD_START 400 -#define LEVELCLASS_BD_END 499 -#define LEVELCLASS_EM_START 500 -#define LEVELCLASS_EM_END 599 -#define LEVELCLASS_SP_START 600 -#define LEVELCLASS_SP_END 699 -#define LEVELCLASS_DX_START 700 -#define LEVELCLASS_DX_END 799 - -#define LEVELCLASS_TUTORIAL LEVELCLASS_TUTORIAL_START -#define LEVELCLASS_CLASSICS LEVELCLASS_CLASSICS_START -#define LEVELCLASS_CONTRIBUTION LEVELCLASS_CONTRIBUTION_START -#define LEVELCLASS_USER LEVELCLASS_USER_START -#define LEVELCLASS_BD LEVELCLASS_BD_START -#define LEVELCLASS_EM LEVELCLASS_EM_START -#define LEVELCLASS_SP LEVELCLASS_SP_START -#define LEVELCLASS_DX LEVELCLASS_DX_START - -#define LEVELCLASS_UNDEFINED 999 - -#define NUM_LEVELCLASS_DESC 8 -char *levelclass_desc[NUM_LEVELCLASS_DESC] = -{ - "Tutorial Levels", - "Classic Originals", - "Contributions", - "Private Levels", - "Boulderdash", - "Emerald Mine", - "Supaplex", - "DX Boulderdash" -}; - -#define IS_LEVELCLASS_TUTORIAL(p) \ - ((p)->sort_priority >= LEVELCLASS_TUTORIAL_START && \ - (p)->sort_priority <= LEVELCLASS_TUTORIAL_END) -#define IS_LEVELCLASS_CLASSICS(p) \ - ((p)->sort_priority >= LEVELCLASS_CLASSICS_START && \ - (p)->sort_priority <= LEVELCLASS_CLASSICS_END) -#define IS_LEVELCLASS_CONTRIBUTION(p) \ - ((p)->sort_priority >= LEVELCLASS_CONTRIBUTION_START && \ - (p)->sort_priority <= LEVELCLASS_CONTRIBUTION_END) -#define IS_LEVELCLASS_USER(p) \ - ((p)->sort_priority >= LEVELCLASS_USER_START && \ - (p)->sort_priority <= LEVELCLASS_USER_END) -#define IS_LEVELCLASS_BD(p) \ - ((p)->sort_priority >= LEVELCLASS_BD_START && \ - (p)->sort_priority <= LEVELCLASS_BD_END) -#define IS_LEVELCLASS_EM(p) \ - ((p)->sort_priority >= LEVELCLASS_EM_START && \ - (p)->sort_priority <= LEVELCLASS_EM_END) -#define IS_LEVELCLASS_SP(p) \ - ((p)->sort_priority >= LEVELCLASS_SP_START && \ - (p)->sort_priority <= LEVELCLASS_SP_END) -#define IS_LEVELCLASS_DX(p) \ - ((p)->sort_priority >= LEVELCLASS_DX_START && \ - (p)->sort_priority <= LEVELCLASS_DX_END) - -#define LEVELCLASS(n) (IS_LEVELCLASS_TUTORIAL(n) ? LEVELCLASS_TUTORIAL : \ - IS_LEVELCLASS_CLASSICS(n) ? LEVELCLASS_CLASSICS : \ - IS_LEVELCLASS_CONTRIBUTION(n) ? LEVELCLASS_CONTRIBUTION : \ - IS_LEVELCLASS_USER(n) ? LEVELCLASS_USER : \ - IS_LEVELCLASS_BD(n) ? LEVELCLASS_BD : \ - IS_LEVELCLASS_EM(n) ? LEVELCLASS_EM : \ - IS_LEVELCLASS_SP(n) ? LEVELCLASS_SP : \ - IS_LEVELCLASS_DX(n) ? LEVELCLASS_DX : \ - LEVELCLASS_UNDEFINED) - -#define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \ - IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \ - IS_LEVELCLASS_BD(n) ? FC_GREEN : \ - IS_LEVELCLASS_EM(n) ? FC_YELLOW : \ - IS_LEVELCLASS_SP(n) ? FC_GREEN : \ - IS_LEVELCLASS_DX(n) ? FC_YELLOW : \ - IS_LEVELCLASS_CONTRIBUTION(n) ? FC_GREEN : \ - IS_LEVELCLASS_USER(n) ? FC_RED : \ - FC_BLUE) - -#define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \ - IS_LEVELCLASS_CLASSICS(n) ? 1 : \ - IS_LEVELCLASS_BD(n) ? 2 : \ - IS_LEVELCLASS_EM(n) ? 3 : \ - IS_LEVELCLASS_SP(n) ? 4 : \ - IS_LEVELCLASS_DX(n) ? 5 : \ - IS_LEVELCLASS_CONTRIBUTION(n) ? 6 : \ - IS_LEVELCLASS_USER(n) ? 7 : \ - 9) - -char *getLevelClassDescription(struct LevelDirInfo *ldi) -{ - int position = ldi->sort_priority / 100; - - if (position >= 0 && position < NUM_LEVELCLASS_DESC) - return levelclass_desc[position]; - else - return "Unknown Level Class"; -} -static void SaveUserLevelInfo(); /* for 'InitUserLevelDir()' */ -static char *getSetupLine(char *, int); /* for 'SaveUserLevelInfo()' */ +/* ========================================================================= */ +/* level file functions */ +/* ========================================================================= */ -static char *getUserLevelDir(char *level_subdir) +void setElementChangePages(struct ElementInfo *ei, int change_pages) { - static char *userlevel_dir = NULL; - char *data_dir = getUserDataDir(); - char *userlevel_subdir = LEVELS_DIRECTORY; + int change_page_size = sizeof(struct ElementChangeInfo); - if (userlevel_dir) - free(userlevel_dir); + ei->num_change_pages = MAX(1, change_pages); - if (strlen(level_subdir) > 0) - userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir); - else - userlevel_dir = getPath2(data_dir, userlevel_subdir); - - return userlevel_dir; -} - -static char *getTapeDir(char *level_subdir) -{ - static char *tape_dir = NULL; - char *data_dir = getUserDataDir(); - char *tape_subdir = TAPES_DIRECTORY; - - if (tape_dir) - free(tape_dir); + ei->change_page = + checked_realloc(ei->change_page, ei->num_change_pages * change_page_size); - if (strlen(level_subdir) > 0) - tape_dir = getPath3(data_dir, tape_subdir, level_subdir); - else - tape_dir = getPath2(data_dir, tape_subdir); + if (ei->current_change_page >= ei->num_change_pages) + ei->current_change_page = ei->num_change_pages - 1; - return tape_dir; + ei->change = &ei->change_page[ei->current_change_page]; } -static char *getScoreDir(char *level_subdir) +void setElementChangeInfoToDefaults(struct ElementChangeInfo *change) { - static char *score_dir = NULL; - char *data_dir = options.rw_base_directory; - char *score_subdir = SCORES_DIRECTORY; - - if (score_dir) - free(score_dir); - - if (strlen(level_subdir) > 0) - score_dir = getPath3(data_dir, score_subdir, level_subdir); - else - score_dir = getPath2(data_dir, score_subdir); + int x, y; - return score_dir; -} + change->can_change = FALSE; -static char *getLevelSetupDir(char *level_subdir) -{ - static char *levelsetup_dir = NULL; - char *data_dir = getUserDataDir(); - char *levelsetup_subdir = LEVELSETUP_DIRECTORY; + change->events = CE_BITMASK_DEFAULT; + change->sides = CH_SIDE_ANY; - if (levelsetup_dir) - free(levelsetup_dir); + change->target_element = EL_EMPTY_SPACE; - if (strlen(level_subdir) > 0) - levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir); - else - levelsetup_dir = getPath2(data_dir, levelsetup_subdir); + change->delay_fixed = 0; + change->delay_random = 0; + change->delay_frames = 1; - return levelsetup_dir; -} + change->trigger_element = EL_EMPTY_SPACE; -static char *getLevelFilename(int nr) -{ - static char *filename = NULL; - char basename[MAX_FILENAME_LEN]; + change->explode = FALSE; + change->use_content = FALSE; + change->only_complete = FALSE; + change->use_random_change = FALSE; + change->random = 100; + change->power = CP_NON_DESTRUCTIVE; - if (filename != NULL) - free(filename); + for(x=0; x<3; x++) + for(y=0; y<3; y++) + change->content[x][y] = EL_EMPTY_SPACE; - sprintf(basename, "%03d.%s", nr, LEVELFILE_EXTENSION); - filename = getPath3((leveldir_current->user_defined ? - getUserLevelDir("") : - options.level_directory), - leveldir_current->fullpath, - basename); + change->direct_action = 0; + change->other_action = 0; - return filename; + change->pre_change_function = NULL; + change->change_function = NULL; + change->post_change_function = NULL; } -static char *getTapeFilename(int nr) +static void setLevelInfoToDefaults(struct LevelInfo *level) { - static char *filename = NULL; - char basename[MAX_FILENAME_LEN]; - - if (filename != NULL) - free(filename); - - sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION); - filename = getPath2(getTapeDir(leveldir_current->filename), basename); - - return filename; -} + int i, j, x, y; -static char *getScoreFilename(int nr) -{ - static char *filename = NULL; - char basename[MAX_FILENAME_LEN]; + level->file_version = FILE_VERSION_ACTUAL; + level->game_version = GAME_VERSION_ACTUAL; - if (filename != NULL) - free(filename); + level->encoding_16bit_field = FALSE; /* default: only 8-bit elements */ + level->encoding_16bit_yamyam = FALSE; /* default: only 8-bit elements */ + level->encoding_16bit_amoeba = FALSE; /* default: only 8-bit elements */ - sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION); - filename = getPath2(getScoreDir(leveldir_current->filename), basename); + level->fieldx = STD_LEV_FIELDX; + level->fieldy = STD_LEV_FIELDY; - return filename; -} + for(x=0; xfield[x][y] = EL_SAND; + + level->time = 100; + level->gems_needed = 0; + level->amoeba_speed = 10; + level->time_magic_wall = 10; + level->time_wheel = 10; + level->time_light = 10; + level->time_timegate = 10; + level->amoeba_content = EL_DIAMOND; + level->double_speed = FALSE; + level->initial_gravity = FALSE; + level->em_slippery_gems = FALSE; + + level->use_custom_template = FALSE; -static void InitTapeDirectory(char *level_subdir) -{ - createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE); - createDirectory(getTapeDir(""), "main tape", PERMS_PRIVATE); - createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE); -} + for(i=0; iname[i] = '\0'; + for(i=0; iauthor[i] = '\0'; -static void InitScoreDirectory(char *level_subdir) -{ - createDirectory(getScoreDir(""), "main score", PERMS_PUBLIC); - createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC); -} + strcpy(level->name, NAMELESS_LEVEL_NAME); + strcpy(level->author, ANONYMOUS_NAME); -static void InitUserLevelDirectory(char *level_subdir) -{ - if (access(getUserLevelDir(level_subdir), F_OK) != 0) + for (i=0; i<4; i++) { - createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE); - createDirectory(getUserLevelDir(""), "main user level", PERMS_PRIVATE); - createDirectory(getUserLevelDir(level_subdir), "user level",PERMS_PRIVATE); - - SaveUserLevelInfo(); + level->envelope_text[i][0] = '\0'; + level->envelope_xsize[i] = MAX_ENVELOPE_XSIZE; + level->envelope_ysize[i] = MAX_ENVELOPE_YSIZE; } -} -static void InitLevelSetupDirectory(char *level_subdir) -{ - createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE); - createDirectory(getLevelSetupDir(""), "main level setup", PERMS_PRIVATE); - createDirectory(getLevelSetupDir(level_subdir), "level setup",PERMS_PRIVATE); -} + for(i=0; iscore[i] = 10; -static void ReadChunk_VERS(FILE *file, int *file_version, int *game_version) -{ - int file_version_major, file_version_minor, file_version_patch; - int game_version_major, game_version_minor, game_version_patch; + level->num_yamyam_contents = STD_ELEMENT_CONTENTS; + for(i=0; iyamyam_content[i][x][y] = + (i < STD_ELEMENT_CONTENTS ? EL_ROCK : EL_EMPTY); - file_version_major = fgetc(file); - file_version_minor = fgetc(file); - file_version_patch = fgetc(file); - fgetc(file); /* not used */ + level->field[0][0] = EL_PLAYER_1; + level->field[STD_LEV_FIELDX - 1][STD_LEV_FIELDY - 1] = EL_EXIT_CLOSED; - game_version_major = fgetc(file); - game_version_minor = fgetc(file); - game_version_patch = fgetc(file); - fgetc(file); /* not used */ + for (i=0; i < MAX_NUM_ELEMENTS; i++) + { + setElementChangePages(&element_info[i], 1); + setElementChangeInfoToDefaults(element_info[i].change); + } - *file_version = VERSION_IDENT(file_version_major, - file_version_minor, - file_version_patch); + for (i=0; i < NUM_CUSTOM_ELEMENTS; i++) + { + int element = EL_CUSTOM_START + i; - *game_version = VERSION_IDENT(game_version_major, - game_version_minor, - game_version_patch); -} + for(j=0; j < MAX_ELEMENT_NAME_LEN + 1; j++) + element_info[element].description[j] = '\0'; + if (element_info[element].custom_description != NULL) + strncpy(element_info[element].description, + element_info[element].custom_description, MAX_ELEMENT_NAME_LEN); + else + strcpy(element_info[element].description, + element_info[element].editor_description); -static void WriteChunk_VERS(FILE *file, int file_version, int game_version) -{ - int file_version_major = VERSION_MAJOR(file_version); - int file_version_minor = VERSION_MINOR(file_version); - int file_version_patch = VERSION_PATCH(file_version); - int game_version_major = VERSION_MAJOR(game_version); - int game_version_minor = VERSION_MINOR(game_version); - int game_version_patch = VERSION_PATCH(game_version); + element_info[element].use_gfx_element = FALSE; + element_info[element].gfx_element = EL_EMPTY_SPACE; - fputc(file_version_major, file); - fputc(file_version_minor, file); - fputc(file_version_patch, file); - fputc(0, file); /* not used */ + element_info[element].collect_score = 10; /* special default */ + element_info[element].collect_count = 1; /* special default */ - fputc(game_version_major, file); - fputc(game_version_minor, file); - fputc(game_version_patch, file); - fputc(0, file); /* not used */ -} + element_info[element].push_delay_fixed = -1; /* initialize later */ + element_info[element].push_delay_random = -1; /* initialize later */ + element_info[element].move_delay_fixed = 0; + element_info[element].move_delay_random = 0; -static void setLevelInfoToDefaults() -{ - int i, x, y; + element_info[element].move_pattern = MV_ALL_DIRECTIONS; + element_info[element].move_direction_initial = MV_NO_MOVING; + element_info[element].move_stepsize = TILEX / 8; - level.file_version = FILE_VERSION_ACTUAL; - level.game_version = GAME_VERSION_ACTUAL; + element_info[element].slippery_type = SLIPPERY_ANY_RANDOM; - level.encoding_16bit_field = FALSE; /* default: only 8-bit elements */ - level.encoding_16bit_yamyam = FALSE; /* default: only 8-bit elements */ - level.encoding_16bit_amoeba = FALSE; /* default: only 8-bit elements */ + for(x=0; x<3; x++) + for(y=0; y<3; y++) + element_info[element].content[x][y] = EL_EMPTY_SPACE; - lev_fieldx = level.fieldx = STD_LEV_FIELDX; - lev_fieldy = level.fieldy = STD_LEV_FIELDY; + element_info[element].access_type = 0; + element_info[element].access_layer = 0; + element_info[element].walk_to_action = 0; + element_info[element].smash_targets = 0; + element_info[element].deadliness = 0; + element_info[element].consistency = 0; - for(x=0; xno_level_file = FALSE; - BorderElement = EL_BETON; + if (leveldir_current == NULL) /* only when dumping level */ + return; /* try to determine better author name than 'anonymous' */ if (strcmp(leveldir_current->author, ANONYMOUS_NAME) != 0) { - strncpy(level.author, leveldir_current->author, MAX_LEVEL_AUTHOR_LEN); - level.author[MAX_LEVEL_AUTHOR_LEN] = '\0'; + strncpy(level->author, leveldir_current->author, MAX_LEVEL_AUTHOR_LEN); + level->author[MAX_LEVEL_AUTHOR_LEN] = '\0'; } else { switch (LEVELCLASS(leveldir_current)) { case LEVELCLASS_TUTORIAL: - strcpy(level.author, PROGRAM_AUTHOR_STRING); + strcpy(level->author, PROGRAM_AUTHOR_STRING); break; - case LEVELCLASS_CONTRIBUTION: - strncpy(level.author, leveldir_current->name,MAX_LEVEL_AUTHOR_LEN); - level.author[MAX_LEVEL_AUTHOR_LEN] = '\0'; + case LEVELCLASS_CONTRIB: + strncpy(level->author, leveldir_current->name, MAX_LEVEL_AUTHOR_LEN); + level->author[MAX_LEVEL_AUTHOR_LEN] = '\0'; break; - case LEVELCLASS_USER: - strncpy(level.author, getRealName(), MAX_LEVEL_AUTHOR_LEN); - level.author[MAX_LEVEL_AUTHOR_LEN] = '\0'; + case LEVELCLASS_PRIVATE: + strncpy(level->author, getRealName(), MAX_LEVEL_AUTHOR_LEN); + level->author[MAX_LEVEL_AUTHOR_LEN] = '\0'; break; default: @@ -445,20 +259,87 @@ static void setLevelInfoToDefaults() } } +static void ActivateLevelTemplate() +{ + /* Currently there is no special action needed to activate the template + data, because 'element_info' and 'Properties' overwrite the original + level data, while all other variables do not change. */ +} + +boolean LevelFileExists(int level_nr) +{ + char *filename = getLevelFilename(level_nr); + + return (access(filename, F_OK) == 0); +} + static int checkLevelElement(int element) { - if (element >= EL_FIRST_RUNTIME_EL) + /* map some (historic, now obsolete) elements */ + +#if 1 + switch (element) + { + case EL_PLAYER_OBSOLETE: + element = EL_PLAYER_1; + break; + + case EL_KEY_OBSOLETE: + element = EL_KEY_1; + + case EL_EM_KEY_1_FILE_OBSOLETE: + element = EL_EM_KEY_1; + break; + + case EL_EM_KEY_2_FILE_OBSOLETE: + element = EL_EM_KEY_2; + break; + + case EL_EM_KEY_3_FILE_OBSOLETE: + element = EL_EM_KEY_3; + break; + + case EL_EM_KEY_4_FILE_OBSOLETE: + element = EL_EM_KEY_4; + break; + + case EL_ENVELOPE_OBSOLETE: + element = EL_ENVELOPE_1; + break; + + case EL_SP_EMPTY: + element = EL_EMPTY; + break; + + default: + if (element >= NUM_FILE_ELEMENTS) + { + Error(ERR_WARN, "invalid level element %d", element); + + element = EL_CHAR_QUESTION; + } + break; + } +#else + if (element >= NUM_FILE_ELEMENTS) { Error(ERR_WARN, "invalid level element %d", element); - element = EL_CHAR_FRAGE; + + element = EL_CHAR_QUESTION; } + else if (element == EL_PLAYER_OBSOLETE) + element = EL_PLAYER_1; + else if (element == EL_KEY_OBSOLETE) + element = EL_KEY_1; +#endif return element; } static int LoadLevel_VERS(FILE *file, int chunk_size, struct LevelInfo *level) { - ReadChunk_VERS(file, &(level->file_version), &(level->game_version)); + level->file_version = getFileVersion(file); + level->game_version = getFileVersion(file); return chunk_size; } @@ -467,33 +348,35 @@ static int LoadLevel_HEAD(FILE *file, int chunk_size, struct LevelInfo *level) { int i, x, y; - lev_fieldx = level->fieldx = fgetc(file); - lev_fieldy = level->fieldy = fgetc(file); + level->fieldx = getFile8Bit(file); + level->fieldy = getFile8Bit(file); - level->time = getFile16BitInteger(file, BYTE_ORDER_BIG_ENDIAN); - level->gems_needed = getFile16BitInteger(file, BYTE_ORDER_BIG_ENDIAN); + level->time = getFile16BitBE(file); + level->gems_needed = getFile16BitBE(file); for(i=0; iname[i] = fgetc(file); + level->name[i] = getFile8Bit(file); level->name[MAX_LEVEL_NAME_LEN] = 0; for(i=0; iscore[i] = fgetc(file); + level->score[i] = getFile8Bit(file); - level->num_yam_contents = STD_ELEMENT_CONTENTS; + level->num_yamyam_contents = STD_ELEMENT_CONTENTS; for(i=0; iyam_content[i][x][y] = checkLevelElement(fgetc(file)); + level->yamyam_content[i][x][y] = checkLevelElement(getFile8Bit(file)); - level->amoeba_speed = fgetc(file); - level->time_magic_wall = fgetc(file); - level->time_wheel = fgetc(file); - level->amoeba_content = checkLevelElement(fgetc(file)); - level->double_speed = (fgetc(file) == 1 ? TRUE : FALSE); - level->gravity = (fgetc(file) == 1 ? TRUE : FALSE); - level->encoding_16bit_field = (fgetc(file) == 1 ? TRUE : FALSE); - level->em_slippery_gems = (fgetc(file) == 1 ? TRUE : FALSE); + level->amoeba_speed = getFile8Bit(file); + level->time_magic_wall = getFile8Bit(file); + level->time_wheel = getFile8Bit(file); + level->amoeba_content = checkLevelElement(getFile8Bit(file)); + level->double_speed = (getFile8Bit(file) == 1 ? TRUE : FALSE); + level->initial_gravity = (getFile8Bit(file) == 1 ? TRUE : FALSE); + level->encoding_16bit_field = (getFile8Bit(file) == 1 ? TRUE : FALSE); + level->em_slippery_gems = (getFile8Bit(file) == 1 ? TRUE : FALSE); + + level->use_custom_template = (getFile8Bit(file) == 1 ? TRUE : FALSE); ReadUnusedBytesFromFile(file, LEVEL_HEADER_UNUSED); @@ -505,18 +388,16 @@ static int LoadLevel_AUTH(FILE *file, int chunk_size, struct LevelInfo *level) int i; for(i=0; iauthor[i] = fgetc(file); + level->author[i] = getFile8Bit(file); level->author[MAX_LEVEL_NAME_LEN] = 0; return chunk_size; } -static int LoadLevel_CONT(FILE *file, int chunk_size, struct LevelInfo *level) +static int LoadLevel_BODY(FILE *file, int chunk_size, struct LevelInfo *level) { - int i, x, y; - int header_size = 4; - int content_size = MAX_ELEMENT_CONTENTS * 3 * 3; - int chunk_size_expected = header_size + content_size; + int x, y; + int chunk_size_expected = level->fieldx * level->fieldy; /* Note: "chunk_size" was wrong before version 2.0 when elements are stored with 16-bit encoding (and should be twice as big then). @@ -524,7 +405,7 @@ static int LoadLevel_CONT(FILE *file, int chunk_size, struct LevelInfo *level) contained 16-bit elements and vice versa. */ if (level->encoding_16bit_field && level->file_version >= FILE_VERSION_2_0) - chunk_size_expected += content_size; + chunk_size_expected *= 2; if (chunk_size_expected != chunk_size) { @@ -532,30 +413,20 @@ static int LoadLevel_CONT(FILE *file, int chunk_size, struct LevelInfo *level) return chunk_size_expected; } - fgetc(file); - level->num_yam_contents = fgetc(file); - fgetc(file); - fgetc(file); - - /* correct invalid number of content fields -- should never happen */ - if (level->num_yam_contents < 1 || - level->num_yam_contents > MAX_ELEMENT_CONTENTS) - level->num_yam_contents = STD_ELEMENT_CONTENTS; - - for(i=0; iyam_content[i][x][y] = - checkLevelElement(level->encoding_16bit_field ? - getFile16BitInteger(file, BYTE_ORDER_BIG_ENDIAN) : - fgetc(file)); + for(y=0; yfieldy; y++) + for(x=0; xfieldx; x++) + level->field[x][y] = + checkLevelElement(level->encoding_16bit_field ? getFile16BitBE(file) : + getFile8Bit(file)); return chunk_size; } -static int LoadLevel_BODY(FILE *file, int chunk_size, struct LevelInfo *level) +static int LoadLevel_CONT(FILE *file, int chunk_size, struct LevelInfo *level) { - int x, y; - int chunk_size_expected = level->fieldx * level->fieldy; + int i, x, y; + int header_size = 4; + int content_size = MAX_ELEMENT_CONTENTS * 3 * 3; + int chunk_size_expected = header_size + content_size; /* Note: "chunk_size" was wrong before version 2.0 when elements are stored with 16-bit encoding (and should be twice as big then). @@ -563,7 +434,7 @@ static int LoadLevel_BODY(FILE *file, int chunk_size, struct LevelInfo *level) contained 16-bit elements and vice versa. */ if (level->encoding_16bit_field && level->file_version >= FILE_VERSION_2_0) - chunk_size_expected *= 2; + chunk_size_expected += content_size; if (chunk_size_expected != chunk_size) { @@ -571,12 +442,22 @@ static int LoadLevel_BODY(FILE *file, int chunk_size, struct LevelInfo *level) return chunk_size_expected; } - for(y=0; yfieldy; y++) - for(x=0; xfieldx; x++) - Feld[x][y] = Ur[x][y] = - checkLevelElement(level->encoding_16bit_field ? - getFile16BitInteger(file, BYTE_ORDER_BIG_ENDIAN) : - fgetc(file)); + getFile8Bit(file); + level->num_yamyam_contents = getFile8Bit(file); + getFile8Bit(file); + getFile8Bit(file); + + /* correct invalid number of content fields -- should never happen */ + if (level->num_yamyam_contents < 1 || + level->num_yamyam_contents > MAX_ELEMENT_CONTENTS) + level->num_yamyam_contents = STD_ELEMENT_CONTENTS; + + for(i=0; iyamyam_content[i][x][y] = + checkLevelElement(level->encoding_16bit_field ? + getFile16BitBE(file) : getFile8Bit(file)); return chunk_size; } @@ -587,32 +468,32 @@ static int LoadLevel_CNT2(FILE *file, int chunk_size, struct LevelInfo *level) int num_contents, content_xsize, content_ysize; int content_array[MAX_ELEMENT_CONTENTS][3][3]; - element = checkLevelElement(getFile16BitInteger(file,BYTE_ORDER_BIG_ENDIAN)); - num_contents = fgetc(file); - content_xsize = fgetc(file); - content_ysize = fgetc(file); + element = checkLevelElement(getFile16BitBE(file)); + num_contents = getFile8Bit(file); + content_xsize = getFile8Bit(file); + content_ysize = getFile8Bit(file); + ReadUnusedBytesFromFile(file, LEVEL_CHUNK_CNT2_UNUSED); for(i=0; i MAX_ELEMENT_CONTENTS) num_contents = STD_ELEMENT_CONTENTS; - if (element == EL_MAMPFER) + if (element == EL_YAMYAM) { - level->num_yam_contents = num_contents; + level->num_yamyam_contents = num_contents; for(i=0; iyam_content[i][x][y] = content_array[i][x][y]; + level->yamyam_content[i][x][y] = content_array[i][x][y]; } - else if (element == EL_AMOEBE_BD) + else if (element == EL_BD_AMOEBA) { level->amoeba_content = content_array[0][0][0]; } @@ -624,112 +505,410 @@ static int LoadLevel_CNT2(FILE *file, int chunk_size, struct LevelInfo *level) return chunk_size; } -void LoadLevel(int level_nr) +static int LoadLevel_CNT3(FILE *file, int chunk_size, struct LevelInfo *level) { - char *filename = getLevelFilename(level_nr); - char cookie[MAX_LINE_LEN]; - char chunk_name[CHUNK_ID_LEN + 1]; - int chunk_size; - FILE *file; + int i; + int element; + int envelope_nr; + int envelope_len; + int chunk_size_expected; - /* always start with reliable default values */ - setLevelInfoToDefaults(); + element = checkLevelElement(getFile16BitBE(file)); + if (!IS_ENVELOPE(element)) + element = EL_ENVELOPE_1; - if (!(file = fopen(filename, MODE_READ))) + envelope_nr = element - EL_ENVELOPE_1; + + envelope_len = getFile16BitBE(file); + + level->envelope_xsize[envelope_nr] = getFile8Bit(file); + level->envelope_ysize[envelope_nr] = getFile8Bit(file); + + ReadUnusedBytesFromFile(file, LEVEL_CHUNK_CNT3_UNUSED); + + chunk_size_expected = LEVEL_CHUNK_CNT3_HEADER + envelope_len; + + if (chunk_size_expected != chunk_size) { - Error(ERR_WARN, "cannot read level '%s' - creating new level", filename); - return; + ReadUnusedBytesFromFile(file, chunk_size - LEVEL_CHUNK_CNT3_HEADER); + return chunk_size_expected; } - getFileChunk(file, chunk_name, NULL, BYTE_ORDER_BIG_ENDIAN); - if (strcmp(chunk_name, "RND1") == 0) - { - getFile32BitInteger(file, BYTE_ORDER_BIG_ENDIAN); /* not used */ + for(i=0; i < envelope_len; i++) + level->envelope_text[envelope_nr][i] = getFile8Bit(file); - getFileChunk(file, chunk_name, NULL, BYTE_ORDER_BIG_ENDIAN); - if (strcmp(chunk_name, "CAVE") != 0) - { - Error(ERR_WARN, "unknown format of level file '%s'", filename); - fclose(file); - return; - } + return chunk_size; +} + +static int LoadLevel_CUS1(FILE *file, int chunk_size, struct LevelInfo *level) +{ + int num_changed_custom_elements = getFile16BitBE(file); + int chunk_size_expected = 2 + num_changed_custom_elements * 6; + int i; + + if (chunk_size_expected != chunk_size) + { + ReadUnusedBytesFromFile(file, chunk_size - 2); + return chunk_size_expected; } - else /* check for pre-2.0 file format with cookie string */ + + for (i=0; i < num_changed_custom_elements; i++) { - strcpy(cookie, chunk_name); - fgets(&cookie[4], MAX_LINE_LEN - 4, file); - if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n') - cookie[strlen(cookie) - 1] = '\0'; + int element = getFile16BitBE(file); + int properties = getFile32BitBE(file); - if (!checkCookieString(cookie, LEVEL_COOKIE_TMPL)) - { - Error(ERR_WARN, "unknown format of level file '%s'", filename); - fclose(file); - return; - } + if (IS_CUSTOM_ELEMENT(element)) + Properties[element][EP_BITFIELD_BASE] = properties; + else + Error(ERR_WARN, "invalid custom element number %d", element); + } - if ((level.file_version = getFileVersionFromCookieString(cookie)) == -1) - { - Error(ERR_WARN, "unsupported version of level file '%s'", filename); - fclose(file); - return; - } + return chunk_size; +} - /* pre-2.0 level files have no game version, so use file version here */ - level.game_version = level.file_version; +static int LoadLevel_CUS2(FILE *file, int chunk_size, struct LevelInfo *level) +{ + int num_changed_custom_elements = getFile16BitBE(file); + int chunk_size_expected = 2 + num_changed_custom_elements * 4; + int i; + + if (chunk_size_expected != chunk_size) + { + ReadUnusedBytesFromFile(file, chunk_size - 2); + return chunk_size_expected; } - if (level.file_version < FILE_VERSION_1_2) + for (i=0; i < num_changed_custom_elements; i++) { - /* level files from versions before 1.2.0 without chunk structure */ - LoadLevel_HEAD(file, LEVEL_HEADER_SIZE, &level); - LoadLevel_BODY(file, level.fieldx * level.fieldy, &level); + int element = getFile16BitBE(file); + int custom_target_element = getFile16BitBE(file); + + if (IS_CUSTOM_ELEMENT(element)) + element_info[element].change->target_element = custom_target_element; + else + Error(ERR_WARN, "invalid custom element number %d", element); } - else + + return chunk_size; +} + +static int LoadLevel_CUS3(FILE *file, int chunk_size, struct LevelInfo *level) +{ + int num_changed_custom_elements = getFile16BitBE(file); + int chunk_size_expected = LEVEL_CHUNK_CUS3_SIZE(num_changed_custom_elements); + int i, j, x, y; + + if (chunk_size_expected != chunk_size) { - static struct + ReadUnusedBytesFromFile(file, chunk_size - 2); + return chunk_size_expected; + } + + for (i=0; i < num_changed_custom_elements; i++) + { + int element = getFile16BitBE(file); + + if (!IS_CUSTOM_ELEMENT(element)) { - char *name; - int size; - int (*loader)(FILE *, int, struct LevelInfo *); + Error(ERR_WARN, "invalid custom element number %d", element); + + element = EL_DEFAULT; /* dummy element used for artwork config */ } - chunk_info[] = - { - { "VERS", FILE_VERS_CHUNK_SIZE, LoadLevel_VERS }, - { "HEAD", LEVEL_HEADER_SIZE, LoadLevel_HEAD }, - { "AUTH", MAX_LEVEL_AUTHOR_LEN, LoadLevel_AUTH }, - { "CONT", -1, LoadLevel_CONT }, - { "BODY", -1, LoadLevel_BODY }, - { "CNT2", LEVEL_CHUNK_CNT2_SIZE, LoadLevel_CNT2 }, - { NULL, 0, NULL } - }; - while (getFileChunk(file, chunk_name, &chunk_size, BYTE_ORDER_BIG_ENDIAN)) - { - int i = 0; + for(j=0; jevents = getFile32BitBE(file); + + element_info[element].change->target_element = + checkLevelElement(getFile16BitBE(file)); + + element_info[element].change->delay_fixed = getFile16BitBE(file); + element_info[element].change->delay_random = getFile16BitBE(file); + element_info[element].change->delay_frames = getFile16BitBE(file); + + element_info[element].change->trigger_element = + checkLevelElement(getFile16BitBE(file)); + + element_info[element].change->explode = getFile8Bit(file); + element_info[element].change->use_content = getFile8Bit(file); + element_info[element].change->only_complete = getFile8Bit(file); + element_info[element].change->use_random_change = getFile8Bit(file); + + element_info[element].change->random = getFile8Bit(file); + element_info[element].change->power = getFile8Bit(file); + + for(y=0; y<3; y++) + for(x=0; x<3; x++) + element_info[element].change->content[x][y] = + checkLevelElement(getFile16BitBE(file)); + + element_info[element].slippery_type = getFile8Bit(file); + + /* some free bytes for future properties and padding */ + ReadUnusedBytesFromFile(file, LEVEL_CPART_CUS3_UNUSED); + + /* mark that this custom element has been modified */ + element_info[element].modified_settings = TRUE; + } + + return chunk_size; +} + +static int LoadLevel_CUS4(FILE *file, int chunk_size, struct LevelInfo *level) +{ + struct ElementInfo *ei; + int chunk_size_expected; + int element; + int i, x, y; + + element = getFile16BitBE(file); + + if (!IS_CUSTOM_ELEMENT(element)) + { + Error(ERR_WARN, "invalid custom element number %d", element); + + element = EL_DEFAULT; /* dummy element used for artwork config */ + } + + ei = &element_info[element]; + + for(i=0; i < MAX_ELEMENT_NAME_LEN; i++) + ei->description[i] = getFile8Bit(file); + ei->description[MAX_ELEMENT_NAME_LEN] = 0; + + Properties[element][EP_BITFIELD_BASE] = getFile32BitBE(file); + ReadUnusedBytesFromFile(file, 4); /* reserved for more base properties */ + + ei->num_change_pages = getFile8Bit(file); + + /* some free bytes for future base property values and padding */ + ReadUnusedBytesFromFile(file, 5); + + chunk_size_expected = LEVEL_CHUNK_CUS4_SIZE(ei->num_change_pages); + if (chunk_size_expected != chunk_size) + { + ReadUnusedBytesFromFile(file, chunk_size - 48); + return chunk_size_expected; + } + + /* read custom property values */ + + ei->use_gfx_element = getFile8Bit(file); + ei->gfx_element = checkLevelElement(getFile16BitBE(file)); + + ei->collect_score = getFile8Bit(file); + ei->collect_count = getFile8Bit(file); + + ei->push_delay_fixed = getFile16BitBE(file); + ei->push_delay_random = getFile16BitBE(file); + ei->move_delay_fixed = getFile16BitBE(file); + ei->move_delay_random = getFile16BitBE(file); + + ei->move_pattern = getFile16BitBE(file); + ei->move_direction_initial = getFile8Bit(file); + ei->move_stepsize = getFile8Bit(file); + + ei->slippery_type = getFile8Bit(file); + + for(y=0; y<3; y++) + for(x=0; x<3; x++) + ei->content[x][y] = checkLevelElement(getFile16BitBE(file)); + + /* some free bytes for future custom property values and padding */ + ReadUnusedBytesFromFile(file, 12); + + /* read change property values */ + + setElementChangePages(ei, ei->num_change_pages); + + for (i=0; i < ei->num_change_pages; i++) + { + struct ElementChangeInfo *change = &ei->change_page[i]; + + /* always start with reliable default values */ + setElementChangeInfoToDefaults(change); + + change->events = getFile32BitBE(file); + + change->target_element = checkLevelElement(getFile16BitBE(file)); + + change->delay_fixed = getFile16BitBE(file); + change->delay_random = getFile16BitBE(file); + change->delay_frames = getFile16BitBE(file); + + change->trigger_element = checkLevelElement(getFile16BitBE(file)); + + change->explode = getFile8Bit(file); + change->use_content = getFile8Bit(file); + change->only_complete = getFile8Bit(file); + change->use_random_change = getFile8Bit(file); + + change->random = getFile8Bit(file); + change->power = getFile8Bit(file); + + for(y=0; y<3; y++) + for(x=0; x<3; x++) + change->content[x][y] = checkLevelElement(getFile16BitBE(file)); + + change->can_change = getFile8Bit(file); + + change->sides = getFile8Bit(file); + + if (change->sides == CH_SIDE_NONE) /* correct empty sides field */ + change->sides = CH_SIDE_ANY; + + /* some free bytes for future change property values and padding */ + ReadUnusedBytesFromFile(file, 8); + } + + /* mark this custom element as modified */ + ei->modified_settings = TRUE; + + return chunk_size; +} + +void LoadLevelFromFilename(struct LevelInfo *level, char *filename) +{ + char cookie[MAX_LINE_LEN]; + char chunk_name[CHUNK_ID_LEN + 1]; + int chunk_size; + FILE *file; + + /* always start with reliable default values */ + setLevelInfoToDefaults(level); + + if (!(file = fopen(filename, MODE_READ))) + { + level->no_level_file = TRUE; + + if (level != &level_template) + Error(ERR_WARN, "cannot read level '%s' - creating new level", filename); + + return; + } + + getFileChunkBE(file, chunk_name, NULL); + if (strcmp(chunk_name, "RND1") == 0) + { + getFile32BitBE(file); /* not used */ + + getFileChunkBE(file, chunk_name, NULL); + if (strcmp(chunk_name, "CAVE") != 0) + { + Error(ERR_WARN, "unknown format of level file '%s'", filename); + fclose(file); + return; + } + } + else /* check for pre-2.0 file format with cookie string */ + { + strcpy(cookie, chunk_name); + fgets(&cookie[4], MAX_LINE_LEN - 4, file); + if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n') + cookie[strlen(cookie) - 1] = '\0'; + + if (!checkCookieString(cookie, LEVEL_COOKIE_TMPL)) + { + Error(ERR_WARN, "unknown format of level file '%s'", filename); + fclose(file); + return; + } + + if ((level->file_version = getFileVersionFromCookieString(cookie)) == -1) + { + Error(ERR_WARN, "unsupported version of level file '%s'", filename); + fclose(file); + return; + } + + /* pre-2.0 level files have no game version, so use file version here */ + level->game_version = level->file_version; + } + + if (level->file_version < FILE_VERSION_1_2) + { + /* level files from versions before 1.2.0 without chunk structure */ + LoadLevel_HEAD(file, LEVEL_HEADER_SIZE, level); + LoadLevel_BODY(file, level->fieldx * level->fieldy, level); + } + else + { + static struct + { + char *name; + int size; + int (*loader)(FILE *, int, struct LevelInfo *); + } + chunk_info[] = + { + { "VERS", FILE_VERS_CHUNK_SIZE, LoadLevel_VERS }, + { "HEAD", LEVEL_HEADER_SIZE, LoadLevel_HEAD }, + { "AUTH", MAX_LEVEL_AUTHOR_LEN, LoadLevel_AUTH }, + { "BODY", -1, LoadLevel_BODY }, + { "CONT", -1, LoadLevel_CONT }, + { "CNT2", LEVEL_CHUNK_CNT2_SIZE, LoadLevel_CNT2 }, + { "CNT3", -1, LoadLevel_CNT3 }, + { "CUS1", -1, LoadLevel_CUS1 }, + { "CUS2", -1, LoadLevel_CUS2 }, + { "CUS3", -1, LoadLevel_CUS3 }, + { "CUS4", -1, LoadLevel_CUS4 }, + { NULL, 0, NULL } + }; + + while (getFileChunkBE(file, chunk_name, &chunk_size)) + { + int i = 0; + + while (chunk_info[i].name != NULL && + strcmp(chunk_name, chunk_info[i].name) != 0) + i++; + + if (chunk_info[i].name == NULL) + { + Error(ERR_WARN, "unknown chunk '%s' in level file '%s'", + chunk_name, filename); + ReadUnusedBytesFromFile(file, chunk_size); + } + else if (chunk_info[i].size != -1 && + chunk_info[i].size != chunk_size) + { + Error(ERR_WARN, "wrong size (%d) of chunk '%s' in level file '%s'", + chunk_size, chunk_name, filename); + ReadUnusedBytesFromFile(file, chunk_size); + } else { /* call function to load this level chunk */ int chunk_size_expected = - (chunk_info[i].loader)(file, chunk_size, &level); + (chunk_info[i].loader)(file, chunk_size, level); /* the size of some chunks cannot be checked before reading other chunks first (like "HEAD" and "BODY") that contain some header @@ -744,12 +923,38 @@ void LoadLevel(int level_nr) } fclose(file); +} + +static void LoadLevel_InitVersion(struct LevelInfo *level, char *filename) +{ + if (leveldir_current == NULL) /* only when dumping level */ + return; - if (IS_LEVELCLASS_CONTRIBUTION(leveldir_current) || - IS_LEVELCLASS_USER(leveldir_current)) +#if 0 + printf("::: sort_priority: %d\n", leveldir_current->sort_priority); +#endif + + /* determine correct game engine version of current level */ +#if 1 + if (!leveldir_current->latest_engine) +#else + if (IS_LEVELCLASS_CONTRIB(leveldir_current) || + IS_LEVELCLASS_PRIVATE(leveldir_current) || + IS_LEVELCLASS_UNDEFINED(leveldir_current)) +#endif { - /* For user contributed and private levels, use the version of - the game engine the levels were created for. +#if 0 + printf("\n::: This level is private or contributed: '%s'\n", filename); +#endif + +#if 1 + printf("\n::: Use the stored game engine version for this level\n"); +#endif + + /* For all levels which are not forced to use the latest game engine + version (normally user contributed, private and undefined levels), + use the version of the game engine the levels were created for. + Since 2.0.1, the game engine version is now directly stored in the level file (chunk "VERS"), so there is no need anymore to set the game version from the file version (except for old, @@ -757,72 +962,264 @@ void LoadLevel(int level_nr) file format version used to store the level -- see above). */ /* do some special adjustments to support older level versions */ - if (level.file_version == FILE_VERSION_1_0) + if (level->file_version == FILE_VERSION_1_0) { Error(ERR_WARN, "level file '%s' has version number 1.0", filename); Error(ERR_WARN, "using high speed movement for player"); /* player was faster than monsters in (pre-)1.0 levels */ - level.double_speed = TRUE; + level->double_speed = TRUE; } + + /* Default behaviour for EM style gems was "slippery" only in 2.0.1 */ + if (level->game_version == VERSION_IDENT(2,0,1)) + level->em_slippery_gems = TRUE; } else { - /* Always use the latest version of the game engine for all but - user contributed and private levels; this allows for actual - corrections in the game engine to take effect for existing, - converted levels (from "classic" or other existing games) to - make the game emulation more accurate, while (hopefully) not - breaking existing levels created from other players. */ +#if 0 + printf("\n::: ALWAYS USE LATEST ENGINE FOR THIS LEVEL: [%d] '%s'\n", + leveldir_current->sort_priority, filename); +#endif + +#if 1 + printf("\n::: Use latest game engine version for this level.\n"); +#endif + + /* For all levels which are forced to use the latest game engine version + (normally all but user contributed, private and undefined levels), set + the game engine version to the actual version; this allows for actual + corrections in the game engine to take effect for existing, converted + levels (from "classic" or other existing games) to make the emulation + of the corresponding game more accurate, while (hopefully) not breaking + existing levels created from other players. */ + +#if 0 + printf("::: changing engine from %d to %d\n", + level->game_version, GAME_VERSION_ACTUAL); +#endif + + level->game_version = GAME_VERSION_ACTUAL; + + /* Set special EM style gems behaviour: EM style gems slip down from + normal, steel and growing wall. As this is a more fundamental change, + it seems better to set the default behaviour to "off" (as it is more + natural) and make it configurable in the level editor (as a property + of gem style elements). Already existing converted levels (neither + private nor contributed levels) are changed to the new behaviour. */ + + if (level->file_version < FILE_VERSION_2_0) + level->em_slippery_gems = TRUE; + } + +#if 0 + printf("::: => %d\n", level->game_version); +#endif +} + +static void LoadLevel_InitElements(struct LevelInfo *level, char *filename) +{ + int i, j; + + /* map custom element change events that have changed in newer versions + (these following values were accidentally changed in version 3.0.1) */ + if (level->game_version <= VERSION_IDENT(3,0,0)) + { + for (i=0; i < NUM_CUSTOM_ELEMENTS; i++) + { + int element = EL_CUSTOM_START + i; + + /* order of checking and copying events to be mapped is important */ + for (j=CE_BY_OTHER_ACTION; j >= CE_BY_PLAYER; j--) + { + if (HAS_CHANGE_EVENT(element, j - 2)) + { + SET_CHANGE_EVENT(element, j - 2, FALSE); + SET_CHANGE_EVENT(element, j, TRUE); + } + } + + /* order of checking and copying events to be mapped is important */ + for (j=CE_OTHER_GETS_COLLECTED; j >= CE_COLLISION; j--) + { + if (HAS_CHANGE_EVENT(element, j - 1)) + { + SET_CHANGE_EVENT(element, j - 1, FALSE); + SET_CHANGE_EVENT(element, j, TRUE); + } + } + } + } + + /* some custom element change events get mapped since version 3.0.3 */ + for (i=0; i < NUM_CUSTOM_ELEMENTS; i++) + { + int element = EL_CUSTOM_START + i; + + if (HAS_CHANGE_EVENT(element, CE_BY_PLAYER) || + HAS_CHANGE_EVENT(element, CE_BY_COLLISION)) + { + SET_CHANGE_EVENT(element, CE_BY_PLAYER, FALSE); + SET_CHANGE_EVENT(element, CE_BY_COLLISION, FALSE); + + SET_CHANGE_EVENT(element, CE_BY_DIRECT_ACTION, TRUE); + } + } + + /* initialize "can_change" field for old levels with only one change page */ + if (level->game_version <= VERSION_IDENT(3,0,2)) + { + for (i=0; i < NUM_CUSTOM_ELEMENTS; i++) + { + int element = EL_CUSTOM_START + i; + + if (CAN_CHANGE(element)) + element_info[element].change->can_change = TRUE; + } + } + + /* set default push delay values (corrected since version 3.0.7) */ + if (level->game_version < VERSION_IDENT(3,0,7)) + { + game.default_push_delay_fixed = 2; + game.default_push_delay_random = 8; + } + else + { + game.default_push_delay_fixed = 8; + game.default_push_delay_random = 8; + } + + /* set uninitialized push delay values of custom elements in older levels */ + for (i=0; i < NUM_CUSTOM_ELEMENTS; i++) + { + int element = EL_CUSTOM_START + i; + + if (element_info[element].push_delay_fixed == -1) + element_info[element].push_delay_fixed = game.default_push_delay_fixed; + if (element_info[element].push_delay_random == -1) + element_info[element].push_delay_random = game.default_push_delay_random; + } + + /* initialize element properties for level editor etc. */ + InitElementPropertiesEngine(level->game_version); +} + +static void LoadLevel_InitPlayfield(struct LevelInfo *level, char *filename) +{ + int x, y; + + /* map elements that have changed in newer versions */ + for(y=0; yfieldy; y++) + { + for(x=0; xfieldx; x++) + { + int element = level->field[x][y]; + + if (level->game_version <= VERSION_IDENT(2,2,0)) + { + /* map game font elements */ + element = (element == EL_CHAR('[') ? EL_CHAR_AUMLAUT : + element == EL_CHAR('\\') ? EL_CHAR_OUMLAUT : + element == EL_CHAR(']') ? EL_CHAR_UUMLAUT : + element == EL_CHAR('^') ? EL_CHAR_COPYRIGHT : element); + } + + if (level->game_version < VERSION_IDENT(3,0,0)) + { + /* map Supaplex gravity tube elements */ + element = (element == EL_SP_GRAVITY_PORT_LEFT ? EL_SP_PORT_LEFT : + element == EL_SP_GRAVITY_PORT_RIGHT ? EL_SP_PORT_RIGHT : + element == EL_SP_GRAVITY_PORT_UP ? EL_SP_PORT_UP : + element == EL_SP_GRAVITY_PORT_DOWN ? EL_SP_PORT_DOWN : + element); + } + + level->field[x][y] = element; + } + } + + /* copy elements to runtime playfield array */ + for(x=0; xfield[x][y]; + + /* initialize level size variables for faster access */ + lev_fieldx = level->fieldx; + lev_fieldy = level->fieldy; + + /* determine border element for this level */ + SetBorderElement(); +} + +void LoadLevelTemplate(int level_nr) +{ + char *filename = getLevelFilename(level_nr); + + LoadLevelFromFilename(&level_template, filename); - level.game_version = GAME_VERSION_ACTUAL; + LoadLevel_InitVersion(&level, filename); + LoadLevel_InitElements(&level, filename); - /* Set special EM style gems behaviour: EM style gems slip down from - normal, steel and growing wall. As this is a more fundamental change, - it seems better to set the default behaviour to "off" (as it is more - natural) and make it configurable in the level editor (as a property - of gem style elements). Already existing converted levels (neither - private nor contributed levels) are changed to the new behaviour. */ + ActivateLevelTemplate(); +} - if (level.file_version < FILE_VERSION_2_0) - level.em_slippery_gems = TRUE; - } +void LoadLevel(int level_nr) +{ + char *filename = getLevelFilename(level_nr); - /* determine border element for this level */ - SetBorderElement(); + LoadLevelFromFilename(&level, filename); + + if (level.use_custom_template) + LoadLevelTemplate(-1); + +#if 1 + LoadLevel_InitVersion(&level, filename); + LoadLevel_InitElements(&level, filename); + LoadLevel_InitPlayfield(&level, filename); +#else + LoadLevel_InitLevel(&level, filename); +#endif +} + +static void SaveLevel_VERS(FILE *file, struct LevelInfo *level) +{ + putFileVersion(file, level->file_version); + putFileVersion(file, level->game_version); } static void SaveLevel_HEAD(FILE *file, struct LevelInfo *level) { int i, x, y; - fputc(level->fieldx, file); - fputc(level->fieldy, file); + putFile8Bit(file, level->fieldx); + putFile8Bit(file, level->fieldy); - putFile16BitInteger(file, level->time, BYTE_ORDER_BIG_ENDIAN); - putFile16BitInteger(file, level->gems_needed, BYTE_ORDER_BIG_ENDIAN); + putFile16BitBE(file, level->time); + putFile16BitBE(file, level->gems_needed); for(i=0; iname[i], file); + putFile8Bit(file, level->name[i]); for(i=0; iscore[i], file); + putFile8Bit(file, level->score[i]); for(i=0; iencoding_16bit_yamyam ? EL_LEERRAUM : - level->yam_content[i][x][y]), - file); - fputc(level->amoeba_speed, file); - fputc(level->time_magic_wall, file); - fputc(level->time_wheel, file); - fputc((level->encoding_16bit_amoeba ? EL_LEERRAUM : level->amoeba_content), - file); - fputc((level->double_speed ? 1 : 0), file); - fputc((level->gravity ? 1 : 0), file); - fputc((level->encoding_16bit_field ? 1 : 0), file); - fputc((level->em_slippery_gems ? 1 : 0), file); + putFile8Bit(file, (level->encoding_16bit_yamyam ? EL_EMPTY : + level->yamyam_content[i][x][y])); + putFile8Bit(file, level->amoeba_speed); + putFile8Bit(file, level->time_magic_wall); + putFile8Bit(file, level->time_wheel); + putFile8Bit(file, (level->encoding_16bit_amoeba ? EL_EMPTY : + level->amoeba_content)); + putFile8Bit(file, (level->double_speed ? 1 : 0)); + putFile8Bit(file, (level->initial_gravity ? 1 : 0)); + putFile8Bit(file, (level->encoding_16bit_field ? 1 : 0)); + putFile8Bit(file, (level->em_slippery_gems ? 1 : 0)); + + putFile8Bit(file, (level->use_custom_template ? 1 : 0)); WriteUnusedBytesToFile(file, LEVEL_HEADER_UNUSED); } @@ -832,7 +1229,19 @@ static void SaveLevel_AUTH(FILE *file, struct LevelInfo *level) int i; for(i=0; iauthor[i], file); + putFile8Bit(file, level->author[i]); +} + +static void SaveLevel_BODY(FILE *file, struct LevelInfo *level) +{ + int x, y; + + for(y=0; yfieldy; y++) + for(x=0; xfieldx; x++) + if (level->encoding_16bit_field) + putFile16BitBE(file, level->field[x][y]); + else + putFile8Bit(file, level->field[x][y]); } #if 0 @@ -840,52 +1249,39 @@ static void SaveLevel_CONT(FILE *file, struct LevelInfo *level) { int i, x, y; - fputc(EL_MAMPFER, file); - fputc(level->num_yam_contents, file); - fputc(0, file); - fputc(0, file); + putFile8Bit(file, EL_YAMYAM); + putFile8Bit(file, level->num_yamyam_contents); + putFile8Bit(file, 0); + putFile8Bit(file, 0); for(i=0; iencoding_16bit_field) - putFile16BitInteger(file, level->yam_content[i][x][y], - BYTE_ORDER_BIG_ENDIAN); + putFile16BitBE(file, level->yamyam_content[i][x][y]); else - fputc(level->yam_content[i][x][y], file); + putFile8Bit(file, level->yamyam_content[i][x][y]); } #endif -static void SaveLevel_BODY(FILE *file, struct LevelInfo *level) -{ - int x, y; - - for(y=0; yfieldy; y++) - for(x=0; xfieldx; x++) - if (level->encoding_16bit_field) - putFile16BitInteger(file, Ur[x][y], BYTE_ORDER_BIG_ENDIAN); - else - fputc(Ur[x][y], file); -} - static void SaveLevel_CNT2(FILE *file, struct LevelInfo *level, int element) { int i, x, y; int num_contents, content_xsize, content_ysize; int content_array[MAX_ELEMENT_CONTENTS][3][3]; - if (element == EL_MAMPFER) + if (element == EL_YAMYAM) { - num_contents = level->num_yam_contents; + num_contents = level->num_yamyam_contents; content_xsize = 3; content_ysize = 3; for(i=0; iyam_content[i][x][y]; + content_array[i][x][y] = level->yamyam_content[i][x][y]; } - else if (element == EL_AMOEBE_BD) + else if (element == EL_BD_AMOEBA) { num_contents = 1; content_xsize = 1; @@ -894,7 +1290,7 @@ static void SaveLevel_CNT2(FILE *file, struct LevelInfo *level, int element) for(i=0; iamoeba_content; } else @@ -906,25 +1302,261 @@ static void SaveLevel_CNT2(FILE *file, struct LevelInfo *level, int element) return; } - putFile16BitInteger(file, element, BYTE_ORDER_BIG_ENDIAN); - fputc(num_contents, file); - fputc(content_xsize, file); - fputc(content_ysize, file); + putFile16BitBE(file, element); + putFile8Bit(file, num_contents); + putFile8Bit(file, content_xsize); + putFile8Bit(file, content_ysize); WriteUnusedBytesToFile(file, LEVEL_CHUNK_CNT2_UNUSED); for(i=0; ienvelope_text[envelope_nr]) + 1; + + putFile16BitBE(file, element); + putFile16BitBE(file, envelope_len); + putFile8Bit(file, level->envelope_xsize[envelope_nr]); + putFile8Bit(file, level->envelope_ysize[envelope_nr]); + + WriteUnusedBytesToFile(file, LEVEL_CHUNK_CNT3_UNUSED); + + for(i=0; i < envelope_len; i++) + putFile8Bit(file, level->envelope_text[envelope_nr][i]); +} + +#if 0 +static void SaveLevel_CUS1(FILE *file, struct LevelInfo *level, + int num_changed_custom_elements) +{ + int i, check = 0; + + putFile16BitBE(file, num_changed_custom_elements); + + for (i=0; i < NUM_CUSTOM_ELEMENTS; i++) + { + int element = EL_CUSTOM_START + i; + + if (Properties[element][EP_BITFIELD_BASE] != EP_BITMASK_DEFAULT) + { + if (check < num_changed_custom_elements) + { + putFile16BitBE(file, element); + putFile32BitBE(file, Properties[element][EP_BITFIELD_BASE]); + } + + check++; + } + } + + if (check != num_changed_custom_elements) /* should not happen */ + Error(ERR_WARN, "inconsistent number of custom element properties"); +} +#endif + +#if 0 +static void SaveLevel_CUS2(FILE *file, struct LevelInfo *level, + int num_changed_custom_elements) +{ + int i, check = 0; + + putFile16BitBE(file, num_changed_custom_elements); + + for (i=0; i < NUM_CUSTOM_ELEMENTS; i++) + { + int element = EL_CUSTOM_START + i; + + if (element_info[element].change->target_element != EL_EMPTY_SPACE) + { + if (check < num_changed_custom_elements) + { + putFile16BitBE(file, element); + putFile16BitBE(file, element_info[element].change->target_element); + } + + check++; + } + } + + if (check != num_changed_custom_elements) /* should not happen */ + Error(ERR_WARN, "inconsistent number of custom target elements"); +} +#endif + +#if 0 +static void SaveLevel_CUS3(FILE *file, struct LevelInfo *level, + int num_changed_custom_elements) +{ + int i, j, x, y, check = 0; + + putFile16BitBE(file, num_changed_custom_elements); + + for (i=0; i < NUM_CUSTOM_ELEMENTS; i++) + { + int element = EL_CUSTOM_START + i; + + if (element_info[element].modified_settings) + { + if (check < num_changed_custom_elements) + { + putFile16BitBE(file, element); + + for(j=0; jevents); + + putFile16BitBE(file, element_info[element].change->target_element); + + putFile16BitBE(file, element_info[element].change->delay_fixed); + putFile16BitBE(file, element_info[element].change->delay_random); + putFile16BitBE(file, element_info[element].change->delay_frames); + + putFile16BitBE(file, element_info[element].change->trigger_element); + + putFile8Bit(file, element_info[element].change->explode); + putFile8Bit(file, element_info[element].change->use_content); + putFile8Bit(file, element_info[element].change->only_complete); + putFile8Bit(file, element_info[element].change->use_random_change); + + putFile8Bit(file, element_info[element].change->random); + putFile8Bit(file, element_info[element].change->power); + + for(y=0; y<3; y++) + for(x=0; x<3; x++) + putFile16BitBE(file, element_info[element].change->content[x][y]); + + putFile8Bit(file, element_info[element].slippery_type); + + /* some free bytes for future properties and padding */ + WriteUnusedBytesToFile(file, LEVEL_CPART_CUS3_UNUSED); + } + + check++; + } + } + + if (check != num_changed_custom_elements) /* should not happen */ + Error(ERR_WARN, "inconsistent number of custom element properties"); +} +#endif + +static void SaveLevel_CUS4(FILE *file, struct LevelInfo *level, int element) { + struct ElementInfo *ei = &element_info[element]; int i, x, y; - char *filename = getLevelFilename(level_nr); + + putFile16BitBE(file, element); + + for(i=0; i < MAX_ELEMENT_NAME_LEN; i++) + putFile8Bit(file, ei->description[i]); + + putFile32BitBE(file, Properties[element][EP_BITFIELD_BASE]); + WriteUnusedBytesToFile(file, 4); /* reserved for more base properties */ + + putFile8Bit(file, ei->num_change_pages); + + /* some free bytes for future base property values and padding */ + WriteUnusedBytesToFile(file, 5); + + /* write custom property values */ + + putFile8Bit(file, ei->use_gfx_element); + putFile16BitBE(file, ei->gfx_element); + + putFile8Bit(file, ei->collect_score); + putFile8Bit(file, ei->collect_count); + + putFile16BitBE(file, ei->push_delay_fixed); + putFile16BitBE(file, ei->push_delay_random); + putFile16BitBE(file, ei->move_delay_fixed); + putFile16BitBE(file, ei->move_delay_random); + + putFile16BitBE(file, ei->move_pattern); + putFile8Bit(file, ei->move_direction_initial); + putFile8Bit(file, ei->move_stepsize); + + putFile8Bit(file, ei->slippery_type); + + for(y=0; y<3; y++) + for(x=0; x<3; x++) + putFile16BitBE(file, ei->content[x][y]); + + /* some free bytes for future custom property values and padding */ + WriteUnusedBytesToFile(file, 12); + + /* write change property values */ + + for (i=0; i < ei->num_change_pages; i++) + { + struct ElementChangeInfo *change = &ei->change_page[i]; + + putFile32BitBE(file, change->events); + + putFile16BitBE(file, change->target_element); + + putFile16BitBE(file, change->delay_fixed); + putFile16BitBE(file, change->delay_random); + putFile16BitBE(file, change->delay_frames); + + putFile16BitBE(file, change->trigger_element); + + putFile8Bit(file, change->explode); + putFile8Bit(file, change->use_content); + putFile8Bit(file, change->only_complete); + putFile8Bit(file, change->use_random_change); + + putFile8Bit(file, change->random); + putFile8Bit(file, change->power); + + for(y=0; y<3; y++) + for(x=0; x<3; x++) + putFile16BitBE(file, change->content[x][y]); + + putFile8Bit(file, change->can_change); + + putFile8Bit(file, change->sides); + + /* some free bytes for future change property values and padding */ + WriteUnusedBytesToFile(file, 8); + } +} + +static void SaveLevelFromFilename(struct LevelInfo *level, char *filename) +{ int body_chunk_size; + int i, x, y; FILE *file; if (!(file = fopen(filename, MODE_WRITE))) @@ -933,56 +1565,88 @@ void SaveLevel(int level_nr) return; } + level->file_version = FILE_VERSION_ACTUAL; + level->game_version = GAME_VERSION_ACTUAL; /* check level field for 16-bit elements */ - level.encoding_16bit_field = FALSE; - for(y=0; y 255) - level.encoding_16bit_field = TRUE; + level->encoding_16bit_field = FALSE; + for(y=0; yfieldy; y++) + for(x=0; xfieldx; x++) + if (level->field[x][y] > 255) + level->encoding_16bit_field = TRUE; /* check yamyam content for 16-bit elements */ - level.encoding_16bit_yamyam = FALSE; - for(i=0; iencoding_16bit_yamyam = FALSE; + for(i=0; inum_yamyam_contents; i++) for(y=0; y<3; y++) for(x=0; x<3; x++) - if (level.yam_content[i][x][y] > 255) - level.encoding_16bit_yamyam = TRUE; + if (level->yamyam_content[i][x][y] > 255) + level->encoding_16bit_yamyam = TRUE; /* check amoeba content for 16-bit elements */ - level.encoding_16bit_amoeba = FALSE; - if (level.amoeba_content > 255) - level.encoding_16bit_amoeba = TRUE; + level->encoding_16bit_amoeba = FALSE; + if (level->amoeba_content > 255) + level->encoding_16bit_amoeba = TRUE; + /* calculate size of "BODY" chunk */ body_chunk_size = - level.fieldx * level.fieldy * (level.encoding_16bit_field ? 2 : 1); + level->fieldx * level->fieldy * (level->encoding_16bit_field ? 2 : 1); + + putFileChunkBE(file, "RND1", CHUNK_SIZE_UNDEFINED); + putFileChunkBE(file, "CAVE", CHUNK_SIZE_NONE); + + putFileChunkBE(file, "VERS", FILE_VERS_CHUNK_SIZE); + SaveLevel_VERS(file, level); - putFileChunk(file, "RND1", CHUNK_SIZE_UNDEFINED, BYTE_ORDER_BIG_ENDIAN); - putFileChunk(file, "CAVE", CHUNK_SIZE_NONE, BYTE_ORDER_BIG_ENDIAN); + putFileChunkBE(file, "HEAD", LEVEL_HEADER_SIZE); + SaveLevel_HEAD(file, level); - putFileChunk(file, "VERS", FILE_VERS_CHUNK_SIZE, BYTE_ORDER_BIG_ENDIAN); - WriteChunk_VERS(file, FILE_VERSION_ACTUAL, GAME_VERSION_ACTUAL); + putFileChunkBE(file, "AUTH", MAX_LEVEL_AUTHOR_LEN); + SaveLevel_AUTH(file, level); - putFileChunk(file, "HEAD", LEVEL_HEADER_SIZE, BYTE_ORDER_BIG_ENDIAN); - SaveLevel_HEAD(file, &level); + putFileChunkBE(file, "BODY", body_chunk_size); + SaveLevel_BODY(file, level); - putFileChunk(file, "AUTH", MAX_LEVEL_AUTHOR_LEN, BYTE_ORDER_BIG_ENDIAN); - SaveLevel_AUTH(file, &level); + if (level->encoding_16bit_yamyam || + level->num_yamyam_contents != STD_ELEMENT_CONTENTS) + { + putFileChunkBE(file, "CNT2", LEVEL_CHUNK_CNT2_SIZE); + SaveLevel_CNT2(file, level, EL_YAMYAM); + } - putFileChunk(file, "BODY", body_chunk_size, BYTE_ORDER_BIG_ENDIAN); - SaveLevel_BODY(file, &level); + if (level->encoding_16bit_amoeba) + { + putFileChunkBE(file, "CNT2", LEVEL_CHUNK_CNT2_SIZE); + SaveLevel_CNT2(file, level, EL_BD_AMOEBA); + } - if (level.encoding_16bit_yamyam || - level.num_yam_contents != STD_ELEMENT_CONTENTS) + /* check for envelope content */ + for (i=0; i<4; i++) { - putFileChunk(file, "CNT2", LEVEL_CHUNK_CNT2_SIZE, BYTE_ORDER_BIG_ENDIAN); - SaveLevel_CNT2(file, &level, EL_MAMPFER); + if (strlen(level->envelope_text[i]) > 0) + { + int envelope_len = strlen(level->envelope_text[i]) + 1; + + putFileChunkBE(file, "CNT3", LEVEL_CHUNK_CNT3_HEADER + envelope_len); + SaveLevel_CNT3(file, level, EL_ENVELOPE_1 + i); + } } - if (level.encoding_16bit_amoeba) + /* check for non-default custom elements (unless using template level) */ + if (!level->use_custom_template) { - putFileChunk(file, "CNT2", LEVEL_CHUNK_CNT2_SIZE, BYTE_ORDER_BIG_ENDIAN); - SaveLevel_CNT2(file, &level, EL_AMOEBE_BD); + for (i=0; i < NUM_CUSTOM_ELEMENTS; i++) + { + int element = EL_CUSTOM_START + i; + + if (element_info[element].modified_settings) + { + int num_change_pages = element_info[element].num_change_pages; + + putFileChunkBE(file, "CUS4", LEVEL_CHUNK_CUS4_SIZE(num_change_pages)); + SaveLevel_CUS4(file, level, element); + } + } } fclose(file); @@ -990,13 +1654,59 @@ void SaveLevel(int level_nr) SetFilePermissions(filename, PERMS_PRIVATE); } +void SaveLevel(int level_nr) +{ + char *filename = getLevelFilename(level_nr); + + SaveLevelFromFilename(&level, filename); +} + +void SaveLevelTemplate() +{ + char *filename = getLevelFilename(-1); + + SaveLevelFromFilename(&level, filename); +} + +void DumpLevel(struct LevelInfo *level) +{ + printf_line("-", 79); + printf("Level xxx (file version %08d, game version %08d)\n", + level->file_version, level->game_version); + printf_line("-", 79); + + printf("Level Author: '%s'\n", level->author); + printf("Level Title: '%s'\n", level->name); + printf("\n"); + printf("Playfield Size: %d x %d\n", level->fieldx, level->fieldy); + printf("\n"); + printf("Level Time: %d seconds\n", level->time); + printf("Gems needed: %d\n", level->gems_needed); + printf("\n"); + printf("Time for Magic Wall: %d seconds\n", level->time_magic_wall); + printf("Time for Wheel: %d seconds\n", level->time_wheel); + printf("Time for Light: %d seconds\n", level->time_light); + printf("Time for Timegate: %d seconds\n", level->time_timegate); + printf("\n"); + printf("Amoeba Speed: %d\n", level->amoeba_speed); + printf("\n"); + printf("Gravity: %s\n", (level->initial_gravity ? "yes" : "no")); + printf("Double Speed Movement: %s\n", (level->double_speed ? "yes" : "no")); + printf("EM style slippery gems: %s\n", (level->em_slippery_gems ? "yes" : "no")); + + printf_line("-", 79); +} + + +/* ========================================================================= */ +/* tape file functions */ +/* ========================================================================= */ + static void setTapeInfoToDefaults() { int i; /* always start with reliable default values (empty tape) */ - tape.file_version = FILE_VERSION_ACTUAL; - tape.game_version = GAME_VERSION_ACTUAL; TapeErase(); /* default values (also for pre-1.2 tapes) with only the first player */ @@ -1018,7 +1728,8 @@ static void setTapeInfoToDefaults() static int LoadTape_VERS(FILE *file, int chunk_size, struct TapeInfo *tape) { - ReadChunk_VERS(file, &(tape->file_version), &(tape->game_version)); + tape->file_version = getFileVersion(file); + tape->game_version = getFileVersion(file); return chunk_size; } @@ -1027,16 +1738,15 @@ static int LoadTape_HEAD(FILE *file, int chunk_size, struct TapeInfo *tape) { int i; - tape->random_seed = getFile32BitInteger(file, BYTE_ORDER_BIG_ENDIAN); - tape->date = getFile32BitInteger(file, BYTE_ORDER_BIG_ENDIAN); - tape->length = getFile32BitInteger(file, BYTE_ORDER_BIG_ENDIAN); + tape->random_seed = getFile32BitBE(file); + tape->date = getFile32BitBE(file); + tape->length = getFile32BitBE(file); /* read header fields that are new since version 1.2 */ if (tape->file_version >= FILE_VERSION_1_2) { - byte store_participating_players = fgetc(file); - - ReadUnusedBytesFromFile(file, TAPE_HEADER_UNUSED); + byte store_participating_players = getFile8Bit(file); + int engine_version; /* since version 1.2, tapes store which players participate in the tape */ tape->num_participating_players = 0; @@ -1050,11 +1760,39 @@ static int LoadTape_HEAD(FILE *file, int chunk_size, struct TapeInfo *tape) tape->num_participating_players++; } } + + ReadUnusedBytesFromFile(file, TAPE_HEADER_UNUSED); + + engine_version = getFileVersion(file); + if (engine_version > 0) + tape->engine_version = engine_version; + else + tape->engine_version = tape->game_version; } return chunk_size; } +static int LoadTape_INFO(FILE *file, int chunk_size, struct TapeInfo *tape) +{ + int level_identifier_size; + int i; + + level_identifier_size = getFile16BitBE(file); + + tape->level_identifier = + checked_realloc(tape->level_identifier, level_identifier_size); + + for(i=0; i < level_identifier_size; i++) + tape->level_identifier[i] = getFile8Bit(file); + + tape->level_nr = getFile16BitBE(file); + + chunk_size = 2 + level_identifier_size + 2; + + return chunk_size; +} + static int LoadTape_BODY(FILE *file, int chunk_size, struct TapeInfo *tape) { int i, j; @@ -1077,10 +1815,10 @@ static int LoadTape_BODY(FILE *file, int chunk_size, struct TapeInfo *tape) tape->pos[i].action[j] = MV_NO_MOVING; if (tape->player_participates[j]) - tape->pos[i].action[j] = fgetc(file); + tape->pos[i].action[j] = getFile8Bit(file); } - tape->pos[i].delay = fgetc(file); + tape->pos[i].delay = getFile8Bit(file); if (tape->file_version == FILE_VERSION_1_0) { @@ -1111,6 +1849,8 @@ static int LoadTape_BODY(FILE *file, int chunk_size, struct TapeInfo *tape) } else if (tape->file_version < FILE_VERSION_2_0) { + /* convert pre-2.0 tapes to new tape format */ + if (tape->pos[i].delay > 1) { /* action part */ @@ -1137,9 +1877,8 @@ static int LoadTape_BODY(FILE *file, int chunk_size, struct TapeInfo *tape) return chunk_size; } -void LoadTape(int level_nr) +void LoadTapeFromFilename(char *filename) { - char *filename = getTapeFilename(level_nr); char cookie[MAX_LINE_LEN]; char chunk_name[CHUNK_ID_LEN + 1]; FILE *file; @@ -1151,12 +1890,12 @@ void LoadTape(int level_nr) if (!(file = fopen(filename, MODE_READ))) return; - getFileChunk(file, chunk_name, NULL, BYTE_ORDER_BIG_ENDIAN); + getFileChunkBE(file, chunk_name, NULL); if (strcmp(chunk_name, "RND1") == 0) { - getFile32BitInteger(file, BYTE_ORDER_BIG_ENDIAN); /* not used */ + getFile32BitBE(file); /* not used */ - getFileChunk(file, chunk_name, NULL, BYTE_ORDER_BIG_ENDIAN); + getFileChunkBE(file, chunk_name, NULL); if (strcmp(chunk_name, "TAPE") != 0) { Error(ERR_WARN, "unknown format of tape file '%s'", filename); @@ -1207,11 +1946,12 @@ void LoadTape(int level_nr) { { "VERS", FILE_VERS_CHUNK_SIZE, LoadTape_VERS }, { "HEAD", TAPE_HEADER_SIZE, LoadTape_HEAD }, + { "INFO", -1, LoadTape_INFO }, { "BODY", -1, LoadTape_BODY }, { NULL, 0, NULL } }; - while (getFileChunk(file, chunk_name, &chunk_size, BYTE_ORDER_BIG_ENDIAN)) + while (getFileChunkBE(file, chunk_name, &chunk_size)) { int i = 0; @@ -1253,6 +1993,24 @@ void LoadTape(int level_nr) fclose(file); tape.length_seconds = GetTapeLength(); + +#if 0 + printf("tape game version: %d\n", tape.game_version); + printf("tape engine version: %d\n", tape.engine_version); +#endif +} + +void LoadTape(int level_nr) +{ + char *filename = getTapeFilename(level_nr); + + LoadTapeFromFilename(filename); +} + +static void SaveTape_VERS(FILE *file, struct TapeInfo *tape) +{ + putFileVersion(file, tape->file_version); + putFileVersion(file, tape->game_version); } static void SaveTape_HEAD(FILE *file, struct TapeInfo *tape) @@ -1265,13 +2023,29 @@ static void SaveTape_HEAD(FILE *file, struct TapeInfo *tape) if (tape->player_participates[i]) store_participating_players |= (1 << i); - putFile32BitInteger(file, tape->random_seed, BYTE_ORDER_BIG_ENDIAN); - putFile32BitInteger(file, tape->date, BYTE_ORDER_BIG_ENDIAN); - putFile32BitInteger(file, tape->length, BYTE_ORDER_BIG_ENDIAN); + putFile32BitBE(file, tape->random_seed); + putFile32BitBE(file, tape->date); + putFile32BitBE(file, tape->length); - fputc(store_participating_players, file); + putFile8Bit(file, store_participating_players); + /* unused bytes not at the end here for 4-byte alignment of engine_version */ WriteUnusedBytesToFile(file, TAPE_HEADER_UNUSED); + + putFileVersion(file, tape->engine_version); +} + +static void SaveTape_INFO(FILE *file, struct TapeInfo *tape) +{ + int level_identifier_size = strlen(tape->level_identifier) + 1; + int i; + + putFile16BitBE(file, level_identifier_size); + + for(i=0; i < level_identifier_size; i++) + putFile8Bit(file, tape->level_identifier[i]); + + putFile16BitBE(file, tape->level_nr); } static void SaveTape_BODY(FILE *file, struct TapeInfo *tape) @@ -1282,20 +2056,21 @@ static void SaveTape_BODY(FILE *file, struct TapeInfo *tape) { for(j=0; jplayer_participates[j]) - fputc(tape->pos[i].action[j], file); + putFile8Bit(file, tape->pos[i].action[j]); - fputc(tape->pos[i].delay, file); + putFile8Bit(file, tape->pos[i].delay); } } void SaveTape(int level_nr) { - int i; char *filename = getTapeFilename(level_nr); FILE *file; boolean new_tape = TRUE; int num_participating_players = 0; + int info_chunk_size; int body_chunk_size; + int i; InitTapeDirectory(leveldir_current->filename); @@ -1313,23 +2088,30 @@ void SaveTape(int level_nr) return; } + tape.file_version = FILE_VERSION_ACTUAL; + tape.game_version = GAME_VERSION_ACTUAL; + /* count number of participating players */ for(i=0; ilevel_nr, tape->file_version, tape->game_version); - printf("-------------------------------------------------------------------------------\n"); + printf("Level series identifier: '%s'\n", tape->level_identifier); + printf_line("-", 79); for(i=0; ilength; i++) { if (i >= MAX_TAPELEN) break; + printf("%03d: ", i); + for(j=0; jplayer_participates[j]) @@ -1383,9 +2167,14 @@ void DumpTape(struct TapeInfo *tape) printf("(%03d)\n", tape->pos[i].delay); } - printf("-------------------------------------------------------------------------------\n"); + printf_line("-", 79); } + +/* ========================================================================= */ +/* score file functions */ +/* ========================================================================= */ + void LoadScore(int level_nr) { int i; @@ -1463,92 +2252,150 @@ void SaveScore(int level_nr) SetFilePermissions(filename, PERMS_PUBLIC); } -/* ------------------------------------------------------------------------- */ -/* setup file stuff */ -/* ------------------------------------------------------------------------- */ -#define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series" -#define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level" -#define TOKEN_STR_HANDICAP_LEVEL "handicap_level" -#define TOKEN_STR_PLAYER_PREFIX "player_" +/* ========================================================================= */ +/* setup file functions */ +/* ========================================================================= */ + +#define TOKEN_STR_PLAYER_PREFIX "player_" /* global setup */ -#define SETUP_TOKEN_PLAYER_NAME 0 -#define SETUP_TOKEN_SOUND 1 -#define SETUP_TOKEN_SOUND_LOOPS 2 -#define SETUP_TOKEN_SOUND_MUSIC 3 -#define SETUP_TOKEN_SOUND_SIMPLE 4 -#define SETUP_TOKEN_SCROLL_DELAY 5 -#define SETUP_TOKEN_SOFT_SCROLLING 6 -#define SETUP_TOKEN_FADING 7 -#define SETUP_TOKEN_AUTORECORD 8 -#define SETUP_TOKEN_QUICK_DOORS 9 -#define SETUP_TOKEN_TEAM_MODE 10 -#define SETUP_TOKEN_HANDICAP 11 -#define SETUP_TOKEN_TIME_LIMIT 12 -#define SETUP_TOKEN_FULLSCREEN 13 +#define SETUP_TOKEN_PLAYER_NAME 0 +#define SETUP_TOKEN_SOUND 1 +#define SETUP_TOKEN_SOUND_LOOPS 2 +#define SETUP_TOKEN_SOUND_MUSIC 3 +#define SETUP_TOKEN_SOUND_SIMPLE 4 +#define SETUP_TOKEN_TOONS 5 +#define SETUP_TOKEN_SCROLL_DELAY 6 +#define SETUP_TOKEN_SOFT_SCROLLING 7 +#define SETUP_TOKEN_FADING 8 +#define SETUP_TOKEN_AUTORECORD 9 +#define SETUP_TOKEN_QUICK_DOORS 10 +#define SETUP_TOKEN_TEAM_MODE 11 +#define SETUP_TOKEN_HANDICAP 12 +#define SETUP_TOKEN_TIME_LIMIT 13 +#define SETUP_TOKEN_FULLSCREEN 14 +#define SETUP_TOKEN_ASK_ON_ESCAPE 15 +#define SETUP_TOKEN_GRAPHICS_SET 16 +#define SETUP_TOKEN_SOUNDS_SET 17 +#define SETUP_TOKEN_MUSIC_SET 18 +#define SETUP_TOKEN_OVERRIDE_LEVEL_GRAPHICS 19 +#define SETUP_TOKEN_OVERRIDE_LEVEL_SOUNDS 20 +#define SETUP_TOKEN_OVERRIDE_LEVEL_MUSIC 21 + +#define NUM_GLOBAL_SETUP_TOKENS 22 + +/* editor setup */ +#define SETUP_TOKEN_EDITOR_EL_BOULDERDASH 0 +#define SETUP_TOKEN_EDITOR_EL_EMERALD_MINE 1 +#define SETUP_TOKEN_EDITOR_EL_MORE 2 +#define SETUP_TOKEN_EDITOR_EL_SOKOBAN 3 +#define SETUP_TOKEN_EDITOR_EL_SUPAPLEX 4 +#define SETUP_TOKEN_EDITOR_EL_DIAMOND_CAVES 5 +#define SETUP_TOKEN_EDITOR_EL_DX_BOULDERDASH 6 +#define SETUP_TOKEN_EDITOR_EL_CHARS 7 +#define SETUP_TOKEN_EDITOR_EL_CUSTOM 8 +#define SETUP_TOKEN_EDITOR_EL_CUSTOM_MORE 9 +#define SETUP_TOKEN_EDITOR_EL_HEADLINES 10 + +#define NUM_EDITOR_SETUP_TOKENS 11 + +/* shortcut setup */ +#define SETUP_TOKEN_SHORTCUT_SAVE_GAME 0 +#define SETUP_TOKEN_SHORTCUT_LOAD_GAME 1 +#define SETUP_TOKEN_SHORTCUT_TOGGLE_PAUSE 2 + +#define NUM_SHORTCUT_SETUP_TOKENS 3 /* player setup */ -#define SETUP_TOKEN_USE_JOYSTICK 14 -#define SETUP_TOKEN_JOY_DEVICE_NAME 15 -#define SETUP_TOKEN_JOY_XLEFT 16 -#define SETUP_TOKEN_JOY_XMIDDLE 17 -#define SETUP_TOKEN_JOY_XRIGHT 18 -#define SETUP_TOKEN_JOY_YUPPER 19 -#define SETUP_TOKEN_JOY_YMIDDLE 20 -#define SETUP_TOKEN_JOY_YLOWER 21 -#define SETUP_TOKEN_JOY_SNAP 22 -#define SETUP_TOKEN_JOY_BOMB 23 -#define SETUP_TOKEN_KEY_LEFT 24 -#define SETUP_TOKEN_KEY_RIGHT 25 -#define SETUP_TOKEN_KEY_UP 26 -#define SETUP_TOKEN_KEY_DOWN 27 -#define SETUP_TOKEN_KEY_SNAP 28 -#define SETUP_TOKEN_KEY_BOMB 29 - -/* level directory info */ -#define LEVELINFO_TOKEN_NAME 30 -#define LEVELINFO_TOKEN_NAME_SHORT 31 -#define LEVELINFO_TOKEN_NAME_SORTING 32 -#define LEVELINFO_TOKEN_AUTHOR 33 -#define LEVELINFO_TOKEN_IMPORTED_FROM 34 -#define LEVELINFO_TOKEN_LEVELS 35 -#define LEVELINFO_TOKEN_FIRST_LEVEL 36 -#define LEVELINFO_TOKEN_SORT_PRIORITY 37 -#define LEVELINFO_TOKEN_LEVEL_GROUP 38 -#define LEVELINFO_TOKEN_READONLY 39 - -#define FIRST_GLOBAL_SETUP_TOKEN SETUP_TOKEN_PLAYER_NAME -#define LAST_GLOBAL_SETUP_TOKEN SETUP_TOKEN_FULLSCREEN - -#define FIRST_PLAYER_SETUP_TOKEN SETUP_TOKEN_USE_JOYSTICK -#define LAST_PLAYER_SETUP_TOKEN SETUP_TOKEN_KEY_BOMB - -#define FIRST_LEVELINFO_TOKEN LEVELINFO_TOKEN_NAME -#define LAST_LEVELINFO_TOKEN LEVELINFO_TOKEN_READONLY +#define SETUP_TOKEN_PLAYER_USE_JOYSTICK 0 +#define SETUP_TOKEN_PLAYER_JOY_DEVICE_NAME 1 +#define SETUP_TOKEN_PLAYER_JOY_XLEFT 2 +#define SETUP_TOKEN_PLAYER_JOY_XMIDDLE 3 +#define SETUP_TOKEN_PLAYER_JOY_XRIGHT 4 +#define SETUP_TOKEN_PLAYER_JOY_YUPPER 5 +#define SETUP_TOKEN_PLAYER_JOY_YMIDDLE 6 +#define SETUP_TOKEN_PLAYER_JOY_YLOWER 7 +#define SETUP_TOKEN_PLAYER_JOY_SNAP 8 +#define SETUP_TOKEN_PLAYER_JOY_BOMB 9 +#define SETUP_TOKEN_PLAYER_KEY_LEFT 10 +#define SETUP_TOKEN_PLAYER_KEY_RIGHT 11 +#define SETUP_TOKEN_PLAYER_KEY_UP 12 +#define SETUP_TOKEN_PLAYER_KEY_DOWN 13 +#define SETUP_TOKEN_PLAYER_KEY_SNAP 14 +#define SETUP_TOKEN_PLAYER_KEY_BOMB 15 + +#define NUM_PLAYER_SETUP_TOKENS 16 + +/* system setup */ +#define SETUP_TOKEN_SYSTEM_SDL_AUDIODRIVER 0 +#define SETUP_TOKEN_SYSTEM_AUDIO_FRAGMENT_SIZE 1 + +#define NUM_SYSTEM_SETUP_TOKENS 2 + +/* options setup */ +#define SETUP_TOKEN_OPTIONS_VERBOSE 0 + +#define NUM_OPTIONS_SETUP_TOKENS 1 + static struct SetupInfo si; +static struct SetupEditorInfo sei; +static struct SetupShortcutInfo ssi; static struct SetupInputInfo sii; -static struct LevelDirInfo ldi; -static struct TokenInfo token_info[] = +static struct SetupSystemInfo syi; +static struct OptionInfo soi; + +static struct TokenInfo global_setup_tokens[] = { - /* global setup */ - { TYPE_STRING, &si.player_name, "player_name" }, - { TYPE_SWITCH, &si.sound, "sound" }, - { TYPE_SWITCH, &si.sound_loops, "repeating_sound_loops" }, - { TYPE_SWITCH, &si.sound_music, "background_music" }, - { TYPE_SWITCH, &si.sound_simple, "simple_sound_effects" }, - { TYPE_SWITCH, &si.scroll_delay, "scroll_delay" }, - { TYPE_SWITCH, &si.soft_scrolling, "soft_scrolling" }, - { TYPE_SWITCH, &si.fading, "screen_fading" }, - { TYPE_SWITCH, &si.autorecord, "automatic_tape_recording" }, - { TYPE_SWITCH, &si.quick_doors, "quick_doors" }, - { TYPE_SWITCH, &si.team_mode, "team_mode" }, - { TYPE_SWITCH, &si.handicap, "handicap" }, - { TYPE_SWITCH, &si.time_limit, "time_limit" }, - { TYPE_SWITCH, &si.fullscreen, "fullscreen" }, + { TYPE_STRING, &si.player_name, "player_name" }, + { TYPE_SWITCH, &si.sound, "sound" }, + { TYPE_SWITCH, &si.sound_loops, "repeating_sound_loops" }, + { TYPE_SWITCH, &si.sound_music, "background_music" }, + { TYPE_SWITCH, &si.sound_simple, "simple_sound_effects" }, + { TYPE_SWITCH, &si.toons, "toons" }, + { TYPE_SWITCH, &si.scroll_delay, "scroll_delay" }, + { TYPE_SWITCH, &si.soft_scrolling, "soft_scrolling" }, + { TYPE_SWITCH, &si.fading, "screen_fading" }, + { TYPE_SWITCH, &si.autorecord, "automatic_tape_recording" }, + { TYPE_SWITCH, &si.quick_doors, "quick_doors" }, + { TYPE_SWITCH, &si.team_mode, "team_mode" }, + { TYPE_SWITCH, &si.handicap, "handicap" }, + { TYPE_SWITCH, &si.time_limit, "time_limit" }, + { TYPE_SWITCH, &si.fullscreen, "fullscreen" }, + { TYPE_SWITCH, &si.ask_on_escape, "ask_on_escape" }, + { TYPE_STRING, &si.graphics_set, "graphics_set" }, + { TYPE_STRING, &si.sounds_set, "sounds_set" }, + { TYPE_STRING, &si.music_set, "music_set" }, + { TYPE_SWITCH, &si.override_level_graphics, "override_level_graphics" }, + { TYPE_SWITCH, &si.override_level_sounds, "override_level_sounds" }, + { TYPE_SWITCH, &si.override_level_music, "override_level_music" }, +}; - /* player setup */ +static struct TokenInfo editor_setup_tokens[] = +{ + { TYPE_SWITCH, &sei.el_boulderdash, "editor.el_boulderdash" }, + { TYPE_SWITCH, &sei.el_emerald_mine, "editor.el_emerald_mine" }, + { TYPE_SWITCH, &sei.el_more, "editor.el_more" }, + { TYPE_SWITCH, &sei.el_sokoban, "editor.el_sokoban" }, + { TYPE_SWITCH, &sei.el_supaplex, "editor.el_supaplex" }, + { TYPE_SWITCH, &sei.el_diamond_caves, "editor.el_diamond_caves" }, + { TYPE_SWITCH, &sei.el_dx_boulderdash,"editor.el_dx_boulderdash" }, + { TYPE_SWITCH, &sei.el_chars, "editor.el_chars" }, + { TYPE_SWITCH, &sei.el_custom, "editor.el_custom" }, + { TYPE_SWITCH, &sei.el_custom_more, "editor.el_custom_more" }, + { TYPE_SWITCH, &sei.el_headlines, "editor.el_headlines" }, +}; + +static struct TokenInfo shortcut_setup_tokens[] = +{ + { TYPE_KEY_X11, &ssi.save_game, "shortcut.save_game" }, + { TYPE_KEY_X11, &ssi.load_game, "shortcut.load_game" }, + { TYPE_KEY_X11, &ssi.toggle_pause, "shortcut.toggle_pause" } +}; + +static struct TokenInfo player_setup_tokens[] = +{ { TYPE_BOOLEAN, &sii.use_joystick, ".use_joystick" }, { TYPE_STRING, &sii.joy.device_name, ".joy.device_name" }, { TYPE_INTEGER, &sii.joy.xleft, ".joy.xleft" }, @@ -1559,96 +2406,45 @@ static struct TokenInfo token_info[] = { TYPE_INTEGER, &sii.joy.ylower, ".joy.ylower" }, { TYPE_INTEGER, &sii.joy.snap, ".joy.snap_field" }, { TYPE_INTEGER, &sii.joy.bomb, ".joy.place_bomb" }, - { TYPE_KEY, &sii.key.left, ".key.move_left" }, - { TYPE_KEY, &sii.key.right, ".key.move_right" }, - { TYPE_KEY, &sii.key.up, ".key.move_up" }, - { TYPE_KEY, &sii.key.down, ".key.move_down" }, - { TYPE_KEY, &sii.key.snap, ".key.snap_field" }, - { TYPE_KEY, &sii.key.bomb, ".key.place_bomb" }, - - /* level directory info */ - { TYPE_STRING, &ldi.name, "name" }, - { TYPE_STRING, &ldi.name_short, "name_short" }, - { TYPE_STRING, &ldi.name_sorting, "name_sorting" }, - { TYPE_STRING, &ldi.author, "author" }, - { TYPE_STRING, &ldi.imported_from, "imported_from" }, - { TYPE_INTEGER, &ldi.levels, "levels" }, - { TYPE_INTEGER, &ldi.first_level, "first_level" }, - { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" }, - { TYPE_BOOLEAN, &ldi.level_group, "level_group" }, - { TYPE_BOOLEAN, &ldi.readonly, "readonly" } + { TYPE_KEY_X11, &sii.key.left, ".key.move_left" }, + { TYPE_KEY_X11, &sii.key.right, ".key.move_right" }, + { TYPE_KEY_X11, &sii.key.up, ".key.move_up" }, + { TYPE_KEY_X11, &sii.key.down, ".key.move_down" }, + { TYPE_KEY_X11, &sii.key.snap, ".key.snap_field" }, + { TYPE_KEY_X11, &sii.key.bomb, ".key.place_bomb" } }; -static void setLevelDirInfoToDefaults(struct LevelDirInfo *ldi) -{ - ldi->filename = NULL; - ldi->fullpath = NULL; - ldi->basepath = NULL; - ldi->name = getStringCopy(ANONYMOUS_NAME); - ldi->name_short = NULL; - ldi->name_sorting = NULL; - ldi->author = getStringCopy(ANONYMOUS_NAME); - ldi->imported_from = NULL; - ldi->levels = 0; - ldi->first_level = 0; - ldi->last_level = 0; - ldi->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */ - ldi->level_group = FALSE; - ldi->parent_link = FALSE; - ldi->user_defined = FALSE; - ldi->readonly = TRUE; - ldi->color = 0; - ldi->class_desc = NULL; - ldi->handicap_level = 0; - ldi->cl_first = -1; - ldi->cl_cursor = -1; - - ldi->node_parent = NULL; - ldi->node_group = NULL; - ldi->next = NULL; -} - -static void setLevelDirInfoToDefaultsFromParent(struct LevelDirInfo *ldi, - struct LevelDirInfo *parent) +static struct TokenInfo system_setup_tokens[] = { - if (parent == NULL) - { - setLevelDirInfoToDefaults(ldi); - return; - } + { TYPE_STRING, &syi.sdl_audiodriver, "system.sdl_audiodriver" }, + { TYPE_INTEGER, &syi.audio_fragment_size,"system.audio_fragment_size" } +}; - /* first copy all values from the parent structure ... */ - *ldi = *parent; +static struct TokenInfo options_setup_tokens[] = +{ + { TYPE_BOOLEAN, &soi.verbose, "options.verbose" } +}; - /* ... then set all fields to default that cannot be inherited from parent. - This is especially important for all those fields that can be set from - the 'levelinfo.conf' config file, because the function 'setSetupInfo()' - calls 'free()' for all already set token values which requires that no - other structure's pointer may point to them! - */ +static char *get_corrected_login_name(char *login_name) +{ + /* needed because player name must be a fixed length string */ + char *login_name_new = checked_malloc(MAX_PLAYER_NAME_LEN + 1); - ldi->filename = NULL; - ldi->fullpath = NULL; - ldi->basepath = NULL; - ldi->name = getStringCopy(ANONYMOUS_NAME); - ldi->name_short = NULL; - ldi->name_sorting = NULL; - ldi->author = getStringCopy(parent->author); - ldi->imported_from = getStringCopy(parent->imported_from); + strncpy(login_name_new, login_name, MAX_PLAYER_NAME_LEN); + login_name_new[MAX_PLAYER_NAME_LEN] = '\0'; - ldi->level_group = FALSE; - ldi->parent_link = FALSE; + if (strlen(login_name) > MAX_PLAYER_NAME_LEN) /* name has been cut */ + if (strchr(login_name_new, ' ')) + *strchr(login_name_new, ' ') = '\0'; - ldi->node_parent = parent; - ldi->node_group = NULL; - ldi->next = NULL; + return login_name_new; } static void setSetupInfoToDefaults(struct SetupInfo *si) { int i; - si->player_name = getStringCopy(getLoginName()); + si->player_name = get_corrected_login_name(getLoginName()); si->sound = TRUE; si->sound_loops = TRUE; @@ -1666,11 +2462,36 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->handicap = TRUE; si->time_limit = TRUE; si->fullscreen = FALSE; + si->ask_on_escape = TRUE; + + si->graphics_set = getStringCopy(GFX_CLASSIC_SUBDIR); + si->sounds_set = getStringCopy(SND_CLASSIC_SUBDIR); + si->music_set = getStringCopy(MUS_CLASSIC_SUBDIR); + si->override_level_graphics = FALSE; + si->override_level_sounds = FALSE; + si->override_level_music = FALSE; + + si->editor.el_boulderdash = TRUE; + si->editor.el_emerald_mine = TRUE; + si->editor.el_more = TRUE; + si->editor.el_sokoban = TRUE; + si->editor.el_supaplex = TRUE; + si->editor.el_diamond_caves = TRUE; + si->editor.el_dx_boulderdash = TRUE; + si->editor.el_chars = TRUE; + si->editor.el_custom = TRUE; + si->editor.el_custom_more = FALSE; + + si->editor.el_headlines = TRUE; + + si->shortcut.save_game = DEFAULT_KEY_SAVE_GAME; + si->shortcut.load_game = DEFAULT_KEY_LOAD_GAME; + si->shortcut.toggle_pause = DEFAULT_KEY_TOGGLE_PAUSE; for (i=0; iinput[i].use_joystick = FALSE; - si->input[i].joy.device_name = getStringCopy(joystick_device_name[i]); + si->input[i].joy.device_name=getStringCopy(getDeviceNameFromJoystickNr(i)); si->input[i].joy.xleft = JOYSTICK_XLEFT; si->input[i].joy.xmiddle = JOYSTICK_XMIDDLE; si->input[i].joy.xright = JOYSTICK_XRIGHT; @@ -1686,57 +2507,42 @@ static void setSetupInfoToDefaults(struct SetupInfo *si) si->input[i].key.snap = (i == 0 ? DEFAULT_KEY_SNAP : KSYM_UNDEFINED); si->input[i].key.bomb = (i == 0 ? DEFAULT_KEY_BOMB : KSYM_UNDEFINED); } -} - -static void setSetupInfo(int token_nr, char *token_value) -{ - int token_type = token_info[token_nr].type; - void *setup_value = token_info[token_nr].value; - - if (token_value == NULL) - return; - - /* set setup field to corresponding token value */ - switch (token_type) - { - case TYPE_BOOLEAN: - case TYPE_SWITCH: - *(boolean *)setup_value = get_string_boolean_value(token_value); - break; - - case TYPE_KEY: - *(Key *)setup_value = getKeyFromX11KeyName(token_value); - break; - case TYPE_INTEGER: - *(int *)setup_value = get_string_integer_value(token_value); - break; - - case TYPE_STRING: - if (*(char **)setup_value != NULL) - free(*(char **)setup_value); - *(char **)setup_value = getStringCopy(token_value); - break; + si->system.sdl_audiodriver = getStringCopy(ARG_DEFAULT); + si->system.audio_fragment_size = DEFAULT_AUDIO_FRAGMENT_SIZE; - default: - break; - } + si->options.verbose = FALSE; } -static void decodeSetupFileList(struct SetupFileList *setup_file_list) +static void decodeSetupFileHash(SetupFileHash *setup_file_hash) { int i, pnr; - if (!setup_file_list) + if (!setup_file_hash) return; - /* handle global setup values */ + /* global setup */ si = setup; - for (i=FIRST_GLOBAL_SETUP_TOKEN; i<=LAST_GLOBAL_SETUP_TOKEN; i++) - setSetupInfo(i, getTokenValue(setup_file_list, token_info[i].text)); + for (i=0; iparent_link || entry2->parent_link) - compare_result = (entry1->parent_link ? -1 : +1); - else if (entry1->sort_priority == entry2->sort_priority) - { - char *name1 = getStringToLower(entry1->name_sorting); - char *name2 = getStringToLower(entry2->name_sorting); - - compare_result = strcmp(name1, name2); - - free(name1); - free(name2); - } - else if (LEVELSORTING(entry1) == LEVELSORTING(entry2)) - compare_result = entry1->sort_priority - entry2->sort_priority; - else - compare_result = LEVELSORTING(entry1) - LEVELSORTING(entry2); - - return compare_result; -} - -static void createParentLevelDirNode(struct LevelDirInfo *node_parent) -{ - struct LevelDirInfo *leveldir_new = newLevelDirInfo(); - - setLevelDirInfoToDefaults(leveldir_new); - - leveldir_new->node_parent = node_parent; - leveldir_new->parent_link = TRUE; - - leveldir_new->name = ".. (parent directory)"; - leveldir_new->name_short = getStringCopy(leveldir_new->name); - leveldir_new->name_sorting = getStringCopy(leveldir_new->name); - - leveldir_new->filename = ".."; - leveldir_new->fullpath = getStringCopy(node_parent->fullpath); - - leveldir_new->sort_priority = node_parent->sort_priority; - leveldir_new->class_desc = getLevelClassDescription(leveldir_new); - - pushLevelDirInfo(&node_parent->node_group, leveldir_new); -} - -static void LoadLevelInfoFromLevelDir(struct LevelDirInfo **node_first, - struct LevelDirInfo *node_parent, - char *level_directory) -{ - DIR *dir; - struct dirent *dir_entry; - boolean valid_entry_found = FALSE; - - if ((dir = opendir(level_directory)) == NULL) - { - Error(ERR_WARN, "cannot read level directory '%s'", level_directory); - return; - } - - while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */ - { - struct SetupFileList *setup_file_list = NULL; - struct stat file_status; - char *directory_name = dir_entry->d_name; - char *directory_path = getPath2(level_directory, directory_name); - char *filename = NULL; - - /* skip entries for current and parent directory */ - if (strcmp(directory_name, ".") == 0 || - strcmp(directory_name, "..") == 0) - { - free(directory_path); - continue; - } - - /* find out if directory entry is itself a directory */ - if (stat(directory_path, &file_status) != 0 || /* cannot stat file */ - (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */ - { - free(directory_path); - continue; - } - - filename = getPath2(directory_path, LEVELINFO_FILENAME); - setup_file_list = loadSetupFileList(filename); - - if (setup_file_list) - { - struct LevelDirInfo *leveldir_new = newLevelDirInfo(); - int i; - - checkSetupFileListIdentifier(setup_file_list, LEVELINFO_COOKIE); - setLevelDirInfoToDefaultsFromParent(leveldir_new, node_parent); - - /* set all structure fields according to the token/value pairs */ - ldi = *leveldir_new; - for (i=FIRST_LEVELINFO_TOKEN; i<=LAST_LEVELINFO_TOKEN; i++) - setSetupInfo(i, getTokenValue(setup_file_list, token_info[i].text)); - *leveldir_new = ldi; - - DrawInitText(leveldir_new->name, 150, FC_YELLOW); - - if (leveldir_new->name_short == NULL) - leveldir_new->name_short = getStringCopy(leveldir_new->name); - - if (leveldir_new->name_sorting == NULL) - leveldir_new->name_sorting = getStringCopy(leveldir_new->name); - - leveldir_new->filename = getStringCopy(directory_name); - - if (node_parent == NULL) /* top level group */ - { - leveldir_new->basepath = level_directory; - leveldir_new->fullpath = leveldir_new->filename; - } - else /* sub level group */ - { - leveldir_new->basepath = node_parent->basepath; - leveldir_new->fullpath = getPath2(node_parent->fullpath, - directory_name); - } - - if (leveldir_new->levels < 1) - leveldir_new->levels = 1; - - leveldir_new->last_level = - leveldir_new->first_level + leveldir_new->levels - 1; - - leveldir_new->user_defined = - (leveldir_new->basepath == options.level_directory ? FALSE : TRUE); - - leveldir_new->color = LEVELCOLOR(leveldir_new); - leveldir_new->class_desc = getLevelClassDescription(leveldir_new); - - leveldir_new->handicap_level = /* set handicap to default value */ - (leveldir_new->user_defined ? - leveldir_new->last_level : - leveldir_new->first_level); - - pushLevelDirInfo(node_first, leveldir_new); - - freeSetupFileList(setup_file_list); - valid_entry_found = TRUE; - - if (leveldir_new->level_group) - { - /* create node to link back to current level directory */ - createParentLevelDirNode(leveldir_new); - - /* step into sub-directory and look for more level series */ - LoadLevelInfoFromLevelDir(&leveldir_new->node_group, - leveldir_new, directory_path); - } - } - else - Error(ERR_WARN, "ignoring level directory '%s'", directory_path); - - free(directory_path); - free(filename); - } - - closedir(dir); - - if (!valid_entry_found) - Error(ERR_WARN, "cannot find any valid level series in directory '%s'", - level_directory); -} - -void LoadLevelInfo() -{ - InitUserLevelDirectory(getLoginName()); - - DrawInitText("Loading level series:", 120, FC_GREEN); - - LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory); - LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir("")); - - leveldir_current = getFirstValidLevelSeries(leveldir_first); - - if (leveldir_first == NULL) - Error(ERR_EXIT, "cannot find any valid level series in any directory"); - - sortLevelDirInfo(&leveldir_first, compareLevelDirInfoEntries); - -#if 0 - dumpLevelDirInfo(leveldir_first, 0); -#endif -} - -static void SaveUserLevelInfo() -{ - char *filename; - FILE *file; - int i; - - filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME); - - if (!(file = fopen(filename, MODE_WRITE))) - { - Error(ERR_WARN, "cannot write level info file '%s'", filename); - free(filename); - return; - } - - /* always start with reliable default values */ - setLevelDirInfoToDefaults(&ldi); - - ldi.name = getLoginName(); - ldi.author = getRealName(); - ldi.levels = 100; - ldi.first_level = 1; - ldi.sort_priority = LEVELCLASS_USER_START; - ldi.readonly = FALSE; - fprintf(file, "%s\n\n", - getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER, LEVELINFO_COOKIE)); - - for (i=FIRST_LEVELINFO_TOKEN; i<=LAST_LEVELINFO_TOKEN; i++) - if (i != LEVELINFO_TOKEN_NAME_SHORT && - i != LEVELINFO_TOKEN_NAME_SORTING && - i != LEVELINFO_TOKEN_IMPORTED_FROM) - fprintf(file, "%s\n", getSetupLine("", i)); - - fclose(file); - free(filename); - - SetFilePermissions(filename, PERMS_PRIVATE); + /* system setup */ + syi = setup.system; + for (i=0; i MAX_PLAYER_NAME_LEN) - setup.player_name[MAX_PLAYER_NAME_LEN] = '\0'; - else if (strlen(setup.player_name) < MAX_PLAYER_NAME_LEN) - { - char *new_name = checked_malloc(MAX_PLAYER_NAME_LEN + 1); - - strcpy(new_name, setup.player_name); - free(setup.player_name); - setup.player_name = new_name; - } + player_name_new = get_corrected_login_name(setup.player_name); + free(setup.player_name); + setup.player_name = player_name_new; } else Error(ERR_WARN, "using default setup values"); - - free(filename); -} - -static char *getSetupLine(char *prefix, int token_nr) -{ - int i; - static char entry[MAX_LINE_LEN]; - int token_type = token_info[token_nr].type; - void *setup_value = token_info[token_nr].value; - char *token_text = token_info[token_nr].text; - - /* start with the prefix, token and some spaces to format output line */ - sprintf(entry, "%s%s:", prefix, token_text); - for (i=strlen(entry); ifilename; - FILE *file; - - /* ----------------------------------------------------------------------- */ - /* ~/.rocksndiamonds/levelsetup.conf */ - /* ----------------------------------------------------------------------- */ - - InitUserDataDirectory(); - - filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME); - if (!(file = fopen(filename, MODE_WRITE))) - { - Error(ERR_WARN, "cannot write setup file '%s'", filename); - free(filename); - return; - } + /* system setup */ + syi = setup.system; + fprintf(file, "\n"); + for (i=0; iuser_defined ? - getUserLevelDir("") : - options.level_directory), - leveldir_current->fullpath); - - if ((dir = opendir(level_directory)) == NULL) - { - Error(ERR_WARN, "cannot read level directory '%s'", level_directory); - return; - } + char *filename = getCustomArtworkConfigFilename(ARTWORK_TYPE_GRAPHICS); + SetupFileHash *setup_file_hash; + int i; - while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */ + for (i=0; id_name) > 4 && - dir_entry->d_name[3] == '.' && - strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0) + if (element_info[i].custom_description != NULL) { - char levelnum_str[4]; - int levelnum_value; - - strncpy(levelnum_str, dir_entry->d_name, 3); - levelnum_str[3] = '\0'; - - levelnum_value = atoi(levelnum_str); - - if (levelnum_value < leveldir_current->first_level) - { - Error(ERR_WARN, "additional level %d found", levelnum_value); - leveldir_current->first_level = levelnum_value; - } - else if (levelnum_value > leveldir_current->last_level) - { - Error(ERR_WARN, "additional level %d found", levelnum_value); - leveldir_current->last_level = levelnum_value; - } + free(element_info[i].custom_description); + element_info[i].custom_description = NULL; } } - closedir(dir); -} - -void LoadLevelSetup_SeriesInfo() -{ - char *filename; - struct SetupFileList *level_setup_list = NULL; - char *level_subdir = leveldir_current->filename; - - /* always start with reliable default values */ - level_nr = leveldir_current->first_level; - - checkSeriesInfo(leveldir_current); - - /* ----------------------------------------------------------------------- */ - /* ~/.rocksndiamonds/levelsetup//levelsetup.conf */ - /* ----------------------------------------------------------------------- */ - - level_subdir = leveldir_current->filename; - - filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME); + if ((setup_file_hash = loadSetupFileHash(filename)) == NULL) + return; - if ((level_setup_list = loadSetupFileList(filename))) + for (i=0; ifirst_level) - level_nr = leveldir_current->first_level; - if (level_nr > leveldir_current->last_level) - level_nr = leveldir_current->last_level; - } - - token_value = getTokenValue(level_setup_list, TOKEN_STR_HANDICAP_LEVEL); - - if (token_value) - { - int level_nr = atoi(token_value); - - if (level_nr < leveldir_current->first_level) - level_nr = leveldir_current->first_level; - if (level_nr > leveldir_current->last_level + 1) - level_nr = leveldir_current->last_level; + char *token = getStringCat2(element_info[i].token_name, ".name"); + char *value = getHashEntry(setup_file_hash, token); - if (leveldir_current->user_defined) - level_nr = leveldir_current->last_level; + if (value != NULL) + element_info[i].custom_description = getStringCopy(value); - leveldir_current->handicap_level = level_nr; - } - - checkSetupFileListIdentifier(level_setup_list, LEVELSETUP_COOKIE); - - freeSetupFileList(level_setup_list); + free(token); } - else - Error(ERR_WARN, "using default setup values"); - free(filename); + freeSetupFileHash(setup_file_hash); } -void SaveLevelSetup_SeriesInfo() +void LoadSpecialMenuDesignSettings() { - char *filename; - char *level_subdir = leveldir_current->filename; - char *level_nr_str = int2str(level_nr, 0); - char *handicap_level_str = int2str(leveldir_current->handicap_level, 0); - FILE *file; - - /* ----------------------------------------------------------------------- */ - /* ~/.rocksndiamonds/levelsetup//levelsetup.conf */ - /* ----------------------------------------------------------------------- */ + char *filename = getCustomArtworkConfigFilename(ARTWORK_TYPE_GRAPHICS); + SetupFileHash *setup_file_hash; + int i, j; - InitLevelSetupDirectory(level_subdir); + /* always start with reliable default values from default config */ + for (i=0; image_config_vars[i].token != NULL; i++) + for (j=0; image_config[j].token != NULL; j++) + if (strcmp(image_config_vars[i].token, image_config[j].token) == 0) + *image_config_vars[i].value = + get_auto_parameter_value(image_config_vars[i].token, + image_config[j].value); - filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME); + if ((setup_file_hash = loadSetupFileHash(filename)) == NULL) + return; - if (!(file = fopen(filename, MODE_WRITE))) + /* special case: initialize with default values that may be overwritten */ + for (i=0; i < NUM_SPECIAL_GFX_ARGS; i++) { - Error(ERR_WARN, "cannot write setup file '%s'", filename); - free(filename); - return; + char *value_x = getHashEntry(setup_file_hash, "menu.draw_xoffset"); + char *value_y = getHashEntry(setup_file_hash, "menu.draw_yoffset"); + char *list_size = getHashEntry(setup_file_hash, "menu.list_size"); + + if (value_x != NULL) + menu.draw_xoffset[i] = get_integer_from_string(value_x); + if (value_y != NULL) + menu.draw_yoffset[i] = get_integer_from_string(value_y); + if (list_size != NULL) + menu.list_size[i] = get_integer_from_string(list_size); } - fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER, - LEVELSETUP_COOKIE)); - fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL, - level_nr_str)); - fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL, - handicap_level_str)); + /* read (and overwrite with) values that may be specified in config file */ + for (i=0; image_config_vars[i].token != NULL; i++) + { + char *value = getHashEntry(setup_file_hash, image_config_vars[i].token); - fclose(file); - free(filename); + if (value != NULL) + *image_config_vars[i].value = + get_auto_parameter_value(image_config_vars[i].token, value); + } - SetFilePermissions(filename, PERMS_PRIVATE); + freeSetupFileHash(setup_file_hash); }