// (c) 1995-2014 by Artsoft Entertainment
// Holger Schemel
// info@artsoft.org
-// http://www.artsoft.org/
+// https://www.artsoft.org/
// ----------------------------------------------------------------------------
// files.c
// ============================================================================
#include "tools.h"
#include "tape.h"
#include "config.h"
+#include "api.h"
#define ENABLE_UNUSED_CODE 0 // currently unused functions
#define ENABLE_HISTORIC_CHUNKS 0 // only for historic reference
// (element number only)
#define LEVEL_CHUNK_GRPX_UNCHANGED 2
+#define LEVEL_CHUNK_EMPX_UNCHANGED 2
#define LEVEL_CHUNK_NOTE_UNCHANGED 2
// (nothing at all if unchanged)
#define TAPE_CHUNK_VERS_SIZE 8 // size of file version chunk
#define TAPE_CHUNK_HEAD_SIZE 20 // size of tape file header
-#define TAPE_CHUNK_HEAD_UNUSED 2 // unused tape header bytes
+#define TAPE_CHUNK_HEAD_UNUSED 1 // unused tape header bytes
+#define TAPE_CHUNK_SCRN_SIZE 2 // size of screen size chunk
+
+#define SCORE_CHUNK_VERS_SIZE 8 // size of file version chunk
#define LEVEL_CHUNK_CNT3_SIZE(x) (LEVEL_CHUNK_CNT3_HEADER + (x))
#define LEVEL_CHUNK_CUS3_SIZE(x) (2 + (x) * LEVEL_CPART_CUS3_SIZE)
// 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 SCORE_COOKIE_TMPL "ROCKSNDIAMONDS_SCORE_FILE_VERSION_x.x"
// values for deciding when (not) to save configuration data
#define SAVE_CONF_NEVER 0
&li.solved_by_one_player, FALSE
},
+ {
+ -1, -1,
+ TYPE_INTEGER, CONF_VALUE_8_BIT(12),
+ &li.time_score_base, 1
+ },
+
+ {
+ -1, -1,
+ TYPE_BOOLEAN, CONF_VALUE_8_BIT(13),
+ &li.rate_time_over_score, FALSE
+ },
+
{
-1, -1,
-1, -1,
TYPE_BOOLEAN, CONF_VALUE_8_BIT(15),
&li.lazy_relocation, FALSE
},
+ {
+ EL_PLAYER_1, -1,
+ TYPE_BOOLEAN, CONF_VALUE_8_BIT(16),
+ &li.finish_dig_collect, TRUE
+ },
+ {
+ EL_PLAYER_1, -1,
+ TYPE_BOOLEAN, CONF_VALUE_8_BIT(17),
+ &li.keep_walkable_ce, FALSE
+ },
// (these values are different for each player)
{
&xx_ei.move_delay_random, 0,
&yy_ei.move_delay_random
},
+ {
+ -1, -1,
+ TYPE_INTEGER, CONF_VALUE_16_BIT(16),
+ &xx_ei.step_delay_fixed, 0,
+ &yy_ei.step_delay_fixed
+ },
+ {
+ -1, -1,
+ TYPE_INTEGER, CONF_VALUE_16_BIT(17),
+ &xx_ei.step_delay_random, 0,
+ &yy_ei.step_delay_random
+ },
{
-1, -1,
}
};
+static struct LevelFileConfigInfo chunk_config_EMPX[] =
+{
+ {
+ -1, -1,
+ TYPE_BOOLEAN, CONF_VALUE_8_BIT(1),
+ &xx_ei.use_gfx_element, FALSE
+ },
+ {
+ -1, -1,
+ TYPE_ELEMENT, CONF_VALUE_16_BIT(1),
+ &xx_ei.gfx_element_initial, EL_EMPTY_SPACE
+ },
+
+ {
+ -1, -1,
+ -1, -1,
+ NULL, -1
+ }
+};
+
static struct LevelFileConfigInfo chunk_config_CONF[] = // (OBSOLETE)
{
{
*group = xx_group;
}
+
+ if (IS_EMPTY_ELEMENT(element) ||
+ IS_INTERNAL_ELEMENT(element))
+ {
+ xx_ei = *ei; // copy element data into temporary buffer
+
+ setConfigToDefaultsFromConfigList(chunk_config_EMPX);
+
+ *ei = xx_ei;
+ }
}
clipboard_elements_initialized = TRUE;
if ((dir = openDirectory(directory)) == NULL)
{
- Error(ERR_WARN, "cannot read current level directory '%s'", directory);
+ Warn("cannot read current level directory '%s'", directory);
return basename;
}
// functions for loading R'n'D level
// ----------------------------------------------------------------------------
-static int getMappedElement(int element)
+int getMappedElement(int element)
{
// remap some (historic, now obsolete) elements
default:
if (element >= NUM_FILE_ELEMENTS)
{
- Error(ERR_WARN, "invalid level element %d", element);
+ Warn("invalid level element %d", element);
element = EL_UNKNOWN;
}
}
else
{
- Error(ERR_WARN, "cannot load content for element '%d'", element);
+ Warn("cannot load content for element '%d'", element);
}
return chunk_size;
if (IS_CUSTOM_ELEMENT(element))
element_info[element].properties[EP_BITFIELD_BASE_NR] = properties;
else
- Error(ERR_WARN, "invalid custom element number %d", element);
+ Warn("invalid custom element number %d", element);
// older game versions that wrote level files with CUS1 chunks used
// different default push delay values (not yet stored in level 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);
+ Warn("invalid custom element number %d", element);
}
level->file_has_custom_elements = TRUE;
if (!IS_CUSTOM_ELEMENT(element))
{
- Error(ERR_WARN, "invalid custom element number %d", element);
+ Warn("invalid custom element number %d", element);
element = EL_INTERNAL_DUMMY;
}
for (x = 0; x < 3; x++)
ei->content.e[x][y] = getMappedElement(getFile16BitBE(file));
+ // bits 0 - 31 of "has_event[]"
event_bits = getFile32BitBE(file);
- for (j = 0; j < NUM_CHANGE_EVENTS; j++)
+ for (j = 0; j < MIN(NUM_CHANGE_EVENTS, 32); j++)
if (event_bits & (1 << j))
ei->change->has_event[j] = TRUE;
if (!IS_CUSTOM_ELEMENT(element))
{
- Error(ERR_WARN, "invalid custom element number %d", element);
+ Warn("invalid custom element number %d", element);
ReadUnusedBytesFromFile(file, chunk_size - 2);
+
return chunk_size;
}
if (!IS_GROUP_ELEMENT(element))
{
- Error(ERR_WARN, "invalid group element number %d", element);
+ Warn("invalid group element number %d", element);
ReadUnusedBytesFromFile(file, chunk_size - 2);
+
return chunk_size;
}
if (num_entities > max_num_entities)
{
- Error(ERR_WARN,
- "truncating number of entities for element %d from %d to %d",
- element, num_entities, max_num_entities);
+ Warn("truncating number of entities for element %d from %d to %d",
+ element, num_entities, max_num_entities);
num_entities = max_num_entities;
}
data_type == TYPE_CONTENT_LIST))
{
// for element and content lists, zero entities are not allowed
- Error(ERR_WARN, "found empty list of entities for element %d",
- element);
+ Warn("found empty list of entities for element %d", element);
// do not set "num_entities" here to prevent reading behind buffer
value = getMappedElement(value);
if (data_type == TYPE_BOOLEAN)
- *(boolean *)(conf[i].value) = value;
+ *(boolean *)(conf[i].value) = (value ? TRUE : FALSE);
else
*(int *) (conf[i].value) = value;
int error_conf_chunk_token = conf_type & CONF_MASK_TOKEN;
int error_element = real_element;
- Error(ERR_WARN, "cannot load micro chunk '%s(%d)' value for element %d ['%s']",
- error_conf_chunk_bytes, error_conf_chunk_token,
- error_element, EL_NAME(error_element));
+ Warn("cannot load micro chunk '%s(%d)' value for element %d ['%s']",
+ error_conf_chunk_bytes, error_conf_chunk_token,
+ error_element, EL_NAME(error_element));
}
return micro_chunk_size;
if (ei->num_change_pages == -1)
{
- Error(ERR_WARN, "LoadLevel_CUSX(): missing 'num_change_pages' for '%s'",
- EL_NAME(element));
+ Warn("LoadLevel_CUSX(): missing 'num_change_pages' for '%s'",
+ EL_NAME(element));
ei->num_change_pages = 1;
return real_chunk_size;
}
+static int LoadLevel_EMPX(File *file, int chunk_size, struct LevelInfo *level)
+{
+ int element = getMappedElement(getFile16BitBE(file));
+ int real_chunk_size = 2;
+ struct ElementInfo *ei = &element_info[element];
+
+ xx_ei = *ei; // copy element data into temporary buffer
+
+ while (!checkEndOfFile(file))
+ {
+ real_chunk_size += LoadLevel_MicroChunk(file, chunk_config_EMPX,
+ -1, element);
+
+ if (real_chunk_size >= chunk_size)
+ break;
+ }
+
+ *ei = xx_ei;
+
+ level->file_has_custom_elements = TRUE;
+
+ return real_chunk_size;
+}
+
static void LoadLevelFromFileInfo_RND(struct LevelInfo *level,
struct LevelFileInfo *level_file_info,
boolean level_info_only)
if (level_info_only)
return;
- Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename);
+ Warn("cannot read level '%s' -- using empty level", filename);
if (!setup.editor.use_template_for_new_levels)
return;
{
level->no_valid_file = TRUE;
- Error(ERR_WARN, "unknown format of level file '%s'", filename);
+ Warn("unknown format of level file '%s'", filename);
closeFile(file);
{
level->no_valid_file = TRUE;
- Error(ERR_WARN, "unknown format of level file '%s'", filename);
+ Warn("unknown format of level file '%s'", filename);
closeFile(file);
{
level->no_valid_file = TRUE;
- Error(ERR_WARN, "unsupported version of level file '%s'", filename);
+ Warn("unsupported version of level file '%s'", filename);
closeFile(file);
{ "NOTE", -1, LoadLevel_NOTE },
{ "CUSX", -1, LoadLevel_CUSX },
{ "GRPX", -1, LoadLevel_GRPX },
+ { "EMPX", -1, LoadLevel_EMPX },
{ NULL, 0, NULL }
};
if (chunk_info[i].name == NULL)
{
- Error(ERR_WARN, "unknown chunk '%s' in level file '%s'",
- chunk_name, filename);
+ 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);
+ Warn("wrong size (%d) of chunk '%s' in level file '%s'",
+ chunk_size, chunk_name, filename);
+
ReadUnusedBytesFromFile(file, chunk_size);
}
else
// information, so check them here
if (chunk_size_expected != chunk_size)
{
- Error(ERR_WARN, "wrong size (%d) of chunk '%s' in level file '%s'",
- chunk_size, chunk_name, filename);
+ Warn("wrong size (%d) of chunk '%s' in level file '%s'",
+ chunk_size, chunk_name, filename);
}
}
}
cav->key_score = level->score[SC_KEY];
cav->exit_score = level->score[SC_TIME_BONUS];
+ cav->num_eater_arrays = level->num_yamyam_contents;
+
for (i = 0; i < MAX_ELEMENT_CONTENTS; i++)
for (y = 0; y < 3; y++)
for (x = 0; x < 3; x++)
// initialize player positions and delete players from the playfield
for (y = 0; y < cav->height; y++) for (x = 0; x < cav->width; x++)
{
- if (ELEM_IS_PLAYER(level->field[x][y]))
+ if (IS_PLAYER_ELEMENT(level->field[x][y]))
{
int player_nr = GET_PLAYER_NR(level->field[x][y]);
level->score[SC_KEY] = cav->key_score;
level->score[SC_TIME_BONUS] = cav->exit_score;
- level->num_yamyam_contents = MAX_ELEMENT_CONTENTS;
+ level->num_yamyam_contents = cav->num_eater_arrays;
- for (i = 0; i < level->num_yamyam_contents; i++)
+ for (i = 0; i < MAX_ELEMENT_CONTENTS; i++)
for (y = 0; y < 3; y++)
for (x = 0; x < 3; x++)
level->yamyam_content[i].e[x][y] =
if (jx != -1 && jy != -1)
level->field[jx][jy] = EL_PLAYER_1 + nr;
}
+
+ // time score is counted for each 10 seconds left in Emerald Mine levels
+ level->time_score_base = 10;
}
{
num_invalid_elements++;
- Error(ERR_DEBUG, "invalid element %d at position %d, %d",
+ Debug("level:native:SP", "invalid element %d at position %d, %d",
element_old, x, y);
}
}
if (num_invalid_elements > 0)
- Error(ERR_WARN, "found %d invalid elements%s", num_invalid_elements,
- (!options.debug ? " (use '--debug' for more details)" : ""));
+ Warn("found %d invalid elements%s", num_invalid_elements,
+ (!options.debug ? " (use '--debug' for more details)" : ""));
for (i = 0; i < MAX_PLAYERS; i++)
level->initial_player_gravity[i] =
if (port_x < 0 || port_x >= level->fieldx ||
port_y < 0 || port_y >= level->fieldy)
{
- Error(ERR_WARN, "special port position (%d, %d) out of bounds",
- port_x, port_y);
+ Warn("special port position (%d, %d) out of bounds", port_x, port_y);
continue;
}
if (port_element < EL_SP_GRAVITY_PORT_RIGHT ||
port_element > EL_SP_GRAVITY_PORT_UP)
{
- Error(ERR_WARN, "no special port at position (%d, %d)", port_x, port_y);
+ Warn("no special port at position (%d, %d)", port_x, port_y);
continue;
}
level->time_wheel = 0;
level->amoeba_content = EL_EMPTY;
-#if 1
- // original Supaplex does not use score values -- use default values
-#else
+ // original Supaplex does not use score values -- rate by playing time
for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++)
level->score[i] = 0;
-#endif
+
+ level->rate_time_over_score = TRUE;
// there are no yamyams in supaplex levels
for (i = 0; i < level->num_yamyam_contents; i++)
if (demo->length + demo_entries >= SP_MAX_TAPE_LEN)
{
- Error(ERR_WARN, "tape truncated: size exceeds maximum SP demo size %d",
- SP_MAX_TAPE_LEN);
+ Warn("tape truncated: size exceeds maximum SP demo size %d",
+ SP_MAX_TAPE_LEN);
break;
}
if (!success)
{
- Error(ERR_WARN, "SP demo truncated: size exceeds maximum tape size %d",
- MAX_TAPE_LEN);
+ Warn("SP demo truncated: size exceeds maximum tape size %d",
+ MAX_TAPE_LEN);
break;
}
break;
case 0x13f5:
- element = EL_YAMYAM;
+ element = EL_YAMYAM_UP;
break;
case 0x1425:
break;
case 0x1682: // secret gate (red)
- element = EL_GATE_1_GRAY;
+ element = EL_EM_GATE_1_GRAY;
break;
case 0x1683: // gate (yellow)
break;
case 0x1684: // secret gate (yellow)
- element = EL_GATE_2_GRAY;
+ element = EL_EM_GATE_2_GRAY;
break;
case 0x1685: // gate (blue)
break;
case 0x1686: // secret gate (blue)
- element = EL_GATE_4_GRAY;
+ element = EL_EM_GATE_4_GRAY;
break;
case 0x1687: // gate (green)
break;
case 0x1688: // secret gate (green)
- element = EL_GATE_3_GRAY;
+ element = EL_EM_GATE_3_GRAY;
break;
case 0x1689: // gate (white)
element = EL_INVISIBLE_SAND;
else
{
- Error(ERR_WARN, "unknown Diamond Caves element 0x%04x", element);
+ Warn("unknown Diamond Caves element 0x%04x", element);
+
element = EL_UNKNOWN;
}
break;
{
level->no_valid_file = TRUE;
- Error(ERR_WARN, "cannot decode level from stream -- using empty level");
+ Warn("cannot decode level from stream -- using empty level");
return;
}
level->extra_time = header[56] | (header[57] << 8);
level->shield_normal_time = header[58] | (header[59] << 8);
+ // shield and extra time elements do not have a score
+ level->score[SC_SHIELD] = 0;
+ level->extra_time_score = 0;
+
+ // set time for normal and deadly shields to the same value
+ level->shield_deadly_time = level->shield_normal_time;
+
// Diamond Caves has the same (strange) behaviour as Emerald Mine that gems
// can slip down from flat walls, like normal walls and steel walls
level->em_slippery_gems = TRUE;
+
+ // time score is counted for each 10 seconds left in Diamond Caves levels
+ level->time_score_base = 10;
}
static void LoadLevelFromFileInfo_DC(struct LevelInfo *level,
level->no_valid_file = TRUE;
if (!level_info_only)
- Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename);
+ Warn("cannot read level '%s' -- using empty level", filename);
return;
}
{
level->no_valid_file = TRUE;
- Error(ERR_WARN, "unknown DC level file '%s' -- using empty level",
- filename);
+ Warn("unknown DC level file '%s' -- using empty level", filename);
return;
}
{
level->no_valid_file = TRUE;
- Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level",
- filename);
+ Warn("cannot fseek in file '%s' -- using empty level", filename);
return;
}
{
level->no_valid_file = TRUE;
- Error(ERR_WARN, "unknown DC2 level file '%s' -- using empty level",
- filename);
+ Warn("unknown DC2 level file '%s' -- using empty level", filename);
return;
}
return EL_UNDEFINED;
}
+static void SetLevelSettings_SB(struct LevelInfo *level)
+{
+ // time settings
+ level->time = 0;
+ level->use_step_counter = TRUE;
+
+ // score settings
+ level->score[SC_TIME_BONUS] = 0;
+ level->time_score_base = 1;
+ level->rate_time_over_score = TRUE;
+
+ // game settings
+ level->auto_exit_sokoban = TRUE;
+}
+
static void LoadLevelFromFileInfo_SB(struct LevelInfo *level,
struct LevelFileInfo *level_file_info,
boolean level_info_only)
level->no_valid_file = TRUE;
if (!level_info_only)
- Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename);
+ Warn("cannot read level '%s' -- using empty level", filename);
return;
}
{
level->no_valid_file = TRUE;
- Error(ERR_WARN, "cannot read level '%s' -- using empty level", filename);
+ Warn("cannot read level '%s' -- using empty level", filename);
return;
}
}
// set special level settings for Sokoban levels
-
- level->time = 0;
- level->use_step_counter = TRUE;
+ SetLevelSettings_SB(level);
if (load_xsb_to_ces)
{
// special global settings can now be set in level template
-
level->use_custom_template = TRUE;
}
}
if (level->game_version < VERSION_IDENT(3,2,0,5))
{
// time bonus score was given for 10 s instead of 1 s before 3.2.0-5
- level->score[SC_TIME_BONUS] /= 10;
+ level->time_score_base = 10;
}
if (leveldir_current->latest_engine)
// only Sokoban fields (but not objects) had to be solved before 4.1.1.1
if (level->game_version < VERSION_IDENT(4,1,1,1))
level->sb_objects_needed = FALSE;
+
+ // CE actions were triggered by unfinished digging/collecting up to 4.2.2.0
+ if (level->game_version <= VERSION_IDENT(4,2,2,0))
+ level->finish_dig_collect = FALSE;
+
+ // CE changing to player was kept under the player if walkable up to 4.2.3.1
+ if (level->game_version <= VERSION_IDENT(4,2,3,1))
+ level->keep_walkable_ce = TRUE;
+}
+
+static void LoadLevel_InitSettings_SB(struct LevelInfo *level)
+{
+ boolean is_sokoban_level = TRUE; // unless non-Sokoban elements found
+ int x, y;
+
+ // check if this level is (not) a Sokoban level
+ for (y = 0; y < level->fieldy; y++)
+ for (x = 0; x < level->fieldx; x++)
+ if (!IS_SB_ELEMENT(Tile[x][y]))
+ is_sokoban_level = FALSE;
+
+ if (is_sokoban_level)
+ {
+ // set special level settings for Sokoban levels
+ SetLevelSettings_SB(level);
+ }
+}
+
+static void LoadLevel_InitSettings(struct LevelInfo *level)
+{
+ // adjust level settings for (non-native) Sokoban-style levels
+ LoadLevel_InitSettings_SB(level);
+
+ // rename levels with title "nameless level" or if renaming is forced
+ if (leveldir_current->empty_level_name != NULL &&
+ (strEqual(level->name, NAMELESS_LEVEL_NAME) ||
+ leveldir_current->force_level_name))
+ snprintf(level->name, MAX_LEVEL_NAME_LEN + 1,
+ leveldir_current->empty_level_name, level_nr);
}
static void LoadLevel_InitStandardElements(struct LevelInfo *level)
element_info[element].ignition_delay = 8;
}
}
+
+ // set mouse click change events to work for left/middle/right mouse button
+ if (level->game_version < VERSION_IDENT(4,2,3,0))
+ {
+ for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+ {
+ int element = EL_CUSTOM_START + i;
+ struct ElementInfo *ei = &element_info[element];
+
+ for (j = 0; j < ei->num_change_pages; j++)
+ {
+ struct ElementChangeInfo *change = &ei->change_page[j];
+
+ if (change->has_event[CE_CLICKED_BY_MOUSE] ||
+ change->has_event[CE_PRESSED_BY_MOUSE] ||
+ change->has_event[CE_MOUSE_CLICKED_ON_X] ||
+ change->has_event[CE_MOUSE_PRESSED_ON_X])
+ change->trigger_side = CH_SIDE_ANY;
+ }
+ }
+ }
}
static void LoadLevel_InitElements(struct LevelInfo *level)
// copy elements to runtime playfield array
for (x = 0; x < MAX_LEV_FIELDX; x++)
for (y = 0; y < MAX_LEV_FIELDY; y++)
- Feld[x][y] = level->field[x][y];
+ Tile[x][y] = level->field[x][y];
// initialize level size variables for faster access
lev_fieldx = level->fieldx;
LoadLevel_InitVersion(&level_template);
LoadLevel_InitElements(&level_template);
+ LoadLevel_InitSettings(&level_template);
ActivateLevelTemplate();
}
{
if (!fileExists(getGlobalLevelTemplateFilename()))
{
- Error(ERR_WARN, "no level template found for this level");
+ Warn("no level template found for this level");
return;
}
LoadLevel_InitVersion(&level);
LoadLevel_InitElements(&level);
LoadLevel_InitPlayfield(&level);
+ LoadLevel_InitSettings(&level);
LoadLevel_InitNativeEngines(&level);
}
int chunk_size = 0;
int x, y;
- for (y = 0; y < level->fieldy; y++)
- for (x = 0; x < level->fieldx; x++)
+ for (y = 0; y < level->fieldy; y++)
+ for (x = 0; x < level->fieldx; x++)
if (level->encoding_16bit_field)
chunk_size += putFile16BitBE(file, level->field[x][y]);
else
// chunk header already written -- write empty chunk data
WriteUnusedBytesToFile(file, LEVEL_CHUNK_CNT2_SIZE);
- Error(ERR_WARN, "cannot save content for element '%d'", element);
+ Warn("cannot save content for element '%d'", element);
+
return;
}
}
if (check != num_changed_custom_elements) // should not happen
- Error(ERR_WARN, "inconsistent number of custom element properties");
+ Warn("inconsistent number of custom element properties");
}
#endif
}
if (check != num_changed_custom_elements) // should not happen
- Error(ERR_WARN, "inconsistent number of custom target elements");
+ Warn("inconsistent number of custom target elements");
}
#endif
}
if (check != num_changed_custom_elements) // should not happen
- Error(ERR_WARN, "inconsistent number of custom element properties");
+ Warn("inconsistent number of custom element properties");
}
#endif
return chunk_size;
}
+static int SaveLevel_EMPX(FILE *file, struct LevelInfo *level, int element)
+{
+ struct ElementInfo *ei = &element_info[element];
+ int chunk_size = 0;
+ int i;
+
+ chunk_size += putFile16BitBE(file, element);
+
+ xx_ei = *ei; // copy element data into temporary buffer
+
+ for (i = 0; chunk_config_EMPX[i].data_type != -1; i++)
+ chunk_size += SaveLevel_MicroChunk(file, &chunk_config_EMPX[i], FALSE);
+
+ return chunk_size;
+}
+
static void SaveLevelFromFilename(struct LevelInfo *level, char *filename,
boolean save_as_template)
{
if (!(file = fopen(filename, MODE_WRITE)))
{
- Error(ERR_WARN, "cannot save level file '%s'", filename);
+ Warn("cannot save level file '%s'", filename);
+
return;
}
SaveLevel_GRPX(file, level, element);
}
}
+
+ for (i = 0; i < NUM_EMPTY_ELEMENTS_ALL; i++)
+ {
+ int element = GET_EMPTY_ELEMENT(i);
+
+ chunk_size = SaveLevel_EMPX(NULL, level, element);
+ if (chunk_size > LEVEL_CHUNK_EMPX_UNCHANGED) // save if changed
+ {
+ putFileChunkBE(file, "EMPX", chunk_size);
+ SaveLevel_EMPX(file, level, element);
+ }
+ }
}
fclose(file);
{
if (level->no_level_file || level->no_valid_file)
{
- Error(ERR_WARN, "cannot dump -- no valid level file found");
+ Warn("cannot dump -- no valid level file found");
return;
}
Print("SP player blocks last field: %s\n", (level->sp_block_last_field ? "yes" : "no"));
Print("use spring bug: %s\n", (level->use_spring_bug ? "yes" : "no"));
Print("use step counter: %s\n", (level->use_step_counter ? "yes" : "no"));
+ Print("rate time over score: %s\n", (level->rate_time_over_score ? "yes" : "no"));
+
+ if (options.debug)
+ {
+ int i, j;
+
+ for (i = 0; i < NUM_ENVELOPES; i++)
+ {
+ char *text = level->envelope[i].text;
+ int text_len = strlen(text);
+ boolean has_text = FALSE;
+
+ for (j = 0; j < text_len; j++)
+ if (text[j] != ' ' && text[j] != '\n')
+ has_text = TRUE;
+
+ if (has_text)
+ {
+ Print("\n");
+ Print("Envelope %d:\n'%s'\n", i + 1, text);
+ }
+ }
+ }
PrintLine("-", 79);
}
+void DumpLevels(void)
+{
+ static LevelDirTree *dumplevel_leveldir = NULL;
+
+ dumplevel_leveldir = getTreeInfoFromIdentifier(leveldir_first,
+ global.dumplevel_leveldir);
+
+ if (dumplevel_leveldir == NULL)
+ Fail("no such level identifier: '%s'", global.dumplevel_leveldir);
+
+ if (global.dumplevel_level_nr < dumplevel_leveldir->first_level ||
+ global.dumplevel_level_nr > dumplevel_leveldir->last_level)
+ Fail("no such level number: %d", global.dumplevel_level_nr);
+
+ leveldir_current = dumplevel_leveldir;
+
+ LoadLevel(global.dumplevel_level_nr);
+ DumpLevel(&level);
+
+ CloseAllAndExit(0);
+}
+
// ============================================================================
// tape file functions
// at least one (default: the first) player participates in every tape
tape.num_participating_players = 1;
+ tape.property_bits = TAPE_PROPERTY_NONE;
+
tape.level_nr = level_nr;
tape.counter = 0;
tape.changed = FALSE;
tape.playing = FALSE;
tape.pausing = FALSE;
+ tape.scr_fieldx = SCR_FIELDX_DEFAULT;
+ tape.scr_fieldy = SCR_FIELDY_DEFAULT;
+
+ tape.no_info_chunk = TRUE;
tape.no_valid_file = FALSE;
}
{
int tape_pos_size = 0;
- if (tape->event_mask == GAME_EVENTS_KEYS)
+ if (tape->use_key_actions)
tape_pos_size += tape->num_participating_players;
- else
+
+ if (tape->use_mouse_actions)
tape_pos_size += 3; // x and y position and mouse button mask
tape_pos_size += 1; // tape action delay value
return tape_pos_size;
}
-static int getGameEventMaskFromTapeEventValue(int value)
+static void setTapeActionFlags(struct TapeInfo *tape, int value)
{
- switch (value)
- {
- case TAPE_EVENTS_KEYS_ONLY:
- return GAME_EVENTS_KEYS;
+ tape->use_key_actions = FALSE;
+ tape->use_mouse_actions = FALSE;
- case TAPE_EVENTS_MOUSE_ONLY:
- return GAME_EVENTS_MOUSE;
+ if (value != TAPE_USE_MOUSE_ACTIONS_ONLY)
+ tape->use_key_actions = TRUE;
- case TAPE_EVENTS_KEYS_AND_MOUSE:
- return GAME_EVENTS_KEYS | GAME_EVENTS_MOUSE;
-
- default:
- return GAME_EVENTS_DEFAULT;
- }
+ if (value != TAPE_USE_KEY_ACTIONS_ONLY)
+ tape->use_mouse_actions = TRUE;
}
-static int getTapeEventValueFromGameEventMask(int mask)
+static int getTapeActionValue(struct TapeInfo *tape)
{
- switch (mask)
- {
- case GAME_EVENTS_KEYS:
- return TAPE_EVENTS_KEYS_ONLY;
-
- case GAME_EVENTS_MOUSE:
- return TAPE_EVENTS_MOUSE_ONLY;
-
- case GAME_EVENTS_KEYS | GAME_EVENTS_MOUSE:
- return TAPE_EVENTS_KEYS_AND_MOUSE;
-
- default:
- return TAPE_EVENTS_DEFAULT;
- }
+ return (tape->use_key_actions &&
+ tape->use_mouse_actions ? TAPE_USE_KEY_AND_MOUSE_ACTIONS :
+ tape->use_key_actions ? TAPE_USE_KEY_ACTIONS_ONLY :
+ tape->use_mouse_actions ? TAPE_USE_MOUSE_ACTIONS_ONLY :
+ TAPE_ACTIONS_DEFAULT);
}
static int LoadTape_VERS(File *file, int chunk_size, struct TapeInfo *tape)
}
}
- tape->event_mask = getGameEventMaskFromTapeEventValue(getFile8Bit(file));
+ setTapeActionFlags(tape, getFile8Bit(file));
+
+ tape->property_bits = getFile8Bit(file);
ReadUnusedBytesFromFile(file, TAPE_CHUNK_HEAD_UNUSED);
return chunk_size;
}
+static int LoadTape_SCRN(File *file, int chunk_size, struct TapeInfo *tape)
+{
+ tape->scr_fieldx = getFile8Bit(file);
+ tape->scr_fieldy = getFile8Bit(file);
+
+ return chunk_size;
+}
+
static int LoadTape_INFO(File *file, int chunk_size, struct TapeInfo *tape)
{
+ char *level_identifier = NULL;
int level_identifier_size;
int i;
+ tape->no_info_chunk = FALSE;
+
level_identifier_size = getFile16BitBE(file);
- tape->level_identifier =
- checked_realloc(tape->level_identifier, level_identifier_size);
+ level_identifier = checked_malloc(level_identifier_size);
for (i = 0; i < level_identifier_size; i++)
- tape->level_identifier[i] = getFile8Bit(file);
+ level_identifier[i] = getFile8Bit(file);
+
+ strncpy(tape->level_identifier, level_identifier, MAX_FILENAME_LEN);
+ tape->level_identifier[MAX_FILENAME_LEN] = '\0';
+
+ checked_free(level_identifier);
tape->level_nr = getFile16BitBE(file);
{
if (i >= MAX_TAPE_LEN)
{
- Error(ERR_WARN, "tape truncated -- size exceeds maximum tape size %d",
+ Warn("tape truncated -- size exceeds maximum tape size %d",
MAX_TAPE_LEN);
// tape too large; read and ignore remaining tape data from this chunk
break;
}
- if (tape->event_mask == GAME_EVENTS_MOUSE)
- {
- tape->pos[i].action[TAPE_ACTION_LX] = getFile8Bit(file);
- tape->pos[i].action[TAPE_ACTION_LY] = getFile8Bit(file);
- tape->pos[i].action[TAPE_ACTION_BUTTON] = getFile8Bit(file);
-
- tape->pos[i].action[TAPE_ACTION_UNUSED] = 0;
- }
- else
+ if (tape->use_key_actions)
{
for (j = 0; j < MAX_PLAYERS; j++)
{
}
}
+ if (tape->use_mouse_actions)
+ {
+ tape->pos[i].action[TAPE_ACTION_LX] = getFile8Bit(file);
+ tape->pos[i].action[TAPE_ACTION_LY] = getFile8Bit(file);
+ tape->pos[i].action[TAPE_ACTION_BUTTON] = getFile8Bit(file);
+ }
+
tape->pos[i].delay = getFile8Bit(file);
if (tape->file_version == FILE_VERSION_1_0)
default:
tape.no_valid_file = TRUE;
- Error(ERR_WARN, "unsupported Sokoban solution file '%s' ['%d']", filename, c);
+ Warn("unsupported Sokoban solution file '%s' ['%d']", filename, c);
break;
}
{
tape.no_valid_file = TRUE;
- Error(ERR_WARN, "unknown format of tape file '%s'", filename);
+ Warn("unknown format of tape file '%s'", filename);
closeFile(file);
{
tape.no_valid_file = TRUE;
- Error(ERR_WARN, "unknown format of tape file '%s'", filename);
+ Warn("unknown format of tape file '%s'", filename);
closeFile(file);
{
tape.no_valid_file = TRUE;
- Error(ERR_WARN, "unsupported version of tape file '%s'", filename);
+ Warn("unsupported version of tape file '%s'", filename);
closeFile(file);
{
{ "VERS", TAPE_CHUNK_VERS_SIZE, LoadTape_VERS },
{ "HEAD", TAPE_CHUNK_HEAD_SIZE, LoadTape_HEAD },
+ { "SCRN", TAPE_CHUNK_SCRN_SIZE, LoadTape_SCRN },
{ "INFO", -1, LoadTape_INFO },
{ "BODY", -1, LoadTape_BODY },
{ NULL, 0, NULL }
if (chunk_info[i].name == NULL)
{
- Error(ERR_WARN, "unknown chunk '%s' in tape file '%s'",
+ Warn("unknown chunk '%s' in tape 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 tape file '%s'",
+ Warn("wrong size (%d) of chunk '%s' in tape file '%s'",
chunk_size, chunk_name, filename);
+
ReadUnusedBytesFromFile(file, chunk_size);
}
else
// information, so check them here
if (chunk_size_expected != chunk_size)
{
- Error(ERR_WARN, "wrong size (%d) of chunk '%s' in tape file '%s'",
+ Warn("wrong size (%d) of chunk '%s' in tape file '%s'",
chunk_size, chunk_name, filename);
}
}
tape.length_seconds = GetTapeLengthSeconds();
#if 0
- printf("::: tape file version: %d\n", tape.file_version);
- printf("::: tape game version: %d\n", tape.game_version);
- printf("::: tape engine version: %d\n", tape.engine_version);
+ Debug("files:LoadTapeFromFilename", "tape file version: %d",
+ tape.file_version);
+ Debug("files:LoadTapeFromFilename", "tape game version: %d",
+ tape.game_version);
+ Debug("files:LoadTapeFromFilename", "tape engine version: %d",
+ tape.engine_version);
#endif
}
CopyNativeTape_SP_to_RND(&level);
}
+void LoadScoreTape(char *score_tape_basename, int nr)
+{
+ char *filename = getScoreTapeFilename(score_tape_basename, nr);
+
+ LoadTapeFromFilename(filename);
+}
+
+void LoadScoreCacheTape(char *score_tape_basename, int nr)
+{
+ char *filename = getScoreCacheTapeFilename(score_tape_basename, nr);
+
+ LoadTapeFromFilename(filename);
+}
+
+static boolean checkSaveTape_SCRN(struct TapeInfo *tape)
+{
+ // chunk required for team mode tapes with non-default screen size
+ return (tape->num_participating_players > 1 &&
+ (tape->scr_fieldx != SCR_FIELDX_DEFAULT ||
+ tape->scr_fieldy != SCR_FIELDY_DEFAULT));
+}
+
static void SaveTape_VERS(FILE *file, struct TapeInfo *tape)
{
putFileVersion(file, tape->file_version);
putFile8Bit(file, store_participating_players);
- putFile8Bit(file, getTapeEventValueFromGameEventMask(tape->event_mask));
+ putFile8Bit(file, getTapeActionValue(tape));
+
+ putFile8Bit(file, tape->property_bits);
// unused bytes not at the end here for 4-byte alignment of engine_version
WriteUnusedBytesToFile(file, TAPE_CHUNK_HEAD_UNUSED);
putFileVersion(file, tape->engine_version);
}
+static void SaveTape_SCRN(FILE *file, struct TapeInfo *tape)
+{
+ putFile8Bit(file, tape->scr_fieldx);
+ putFile8Bit(file, tape->scr_fieldy);
+}
+
static void SaveTape_INFO(FILE *file, struct TapeInfo *tape)
{
int level_identifier_size = strlen(tape->level_identifier) + 1;
for (i = 0; i < tape->length; i++)
{
- if (tape->event_mask == GAME_EVENTS_MOUSE)
- {
- putFile8Bit(file, tape->pos[i].action[TAPE_ACTION_LX]);
- putFile8Bit(file, tape->pos[i].action[TAPE_ACTION_LY]);
- putFile8Bit(file, tape->pos[i].action[TAPE_ACTION_BUTTON]);
- }
- else
+ if (tape->use_key_actions)
{
for (j = 0; j < MAX_PLAYERS; j++)
if (tape->player_participates[j])
putFile8Bit(file, tape->pos[i].action[j]);
}
+ if (tape->use_mouse_actions)
+ {
+ putFile8Bit(file, tape->pos[i].action[TAPE_ACTION_LX]);
+ putFile8Bit(file, tape->pos[i].action[TAPE_ACTION_LY]);
+ putFile8Bit(file, tape->pos[i].action[TAPE_ACTION_BUTTON]);
+ }
+
putFile8Bit(file, tape->pos[i].delay);
}
}
-void SaveTape(int nr)
+void SaveTapeToFilename(char *filename)
{
- char *filename = getTapeFilename(nr);
FILE *file;
int tape_pos_size;
int info_chunk_size;
int body_chunk_size;
- int i;
-
- InitTapeDirectory(leveldir_current->subdir);
if (!(file = fopen(filename, MODE_WRITE)))
{
- Error(ERR_WARN, "cannot save level recording file '%s'", filename);
+ Warn("cannot save level recording file '%s'", filename);
+
return;
}
- tape.file_version = FILE_VERSION_ACTUAL;
- tape.game_version = GAME_VERSION_ACTUAL;
+ tape_pos_size = getTapePosSize(&tape);
- tape.num_participating_players = 0;
-
- // count number of participating players
- for (i = 0; i < MAX_PLAYERS; i++)
- if (tape.player_participates[i])
- tape.num_participating_players++;
-
- tape_pos_size = getTapePosSize(&tape);
-
- info_chunk_size = 2 + (strlen(tape.level_identifier) + 1) + 2;
- body_chunk_size = tape_pos_size * tape.length;
+ info_chunk_size = 2 + (strlen(tape.level_identifier) + 1) + 2;
+ body_chunk_size = tape_pos_size * tape.length;
putFileChunkBE(file, "RND1", CHUNK_SIZE_UNDEFINED);
putFileChunkBE(file, "TAPE", CHUNK_SIZE_NONE);
putFileChunkBE(file, "HEAD", TAPE_CHUNK_HEAD_SIZE);
SaveTape_HEAD(file, &tape);
+ if (checkSaveTape_SCRN(&tape))
+ {
+ putFileChunkBE(file, "SCRN", TAPE_CHUNK_SCRN_SIZE);
+ SaveTape_SCRN(file, &tape);
+ }
+
putFileChunkBE(file, "INFO", info_chunk_size);
SaveTape_INFO(file, &tape);
fclose(file);
SetFilePermissions(filename, PERMS_PRIVATE);
+}
+
+static void SaveTapeExt(char *filename)
+{
+ int i;
+
+ tape.file_version = FILE_VERSION_ACTUAL;
+ tape.game_version = GAME_VERSION_ACTUAL;
+
+ tape.num_participating_players = 0;
+
+ // count number of participating players
+ for (i = 0; i < MAX_PLAYERS; i++)
+ if (tape.player_participates[i])
+ tape.num_participating_players++;
+
+ SaveTapeToFilename(filename);
tape.changed = FALSE;
}
+void SaveTape(int nr)
+{
+ char *filename = getTapeFilename(nr);
+
+ InitTapeDirectory(leveldir_current->subdir);
+
+ SaveTapeExt(filename);
+}
+
+void SaveScoreTape(int nr)
+{
+ char *filename = getScoreTapeFilename(tape.score_tape_basename, nr);
+
+ // used instead of "leveldir_current->subdir" (for network games)
+ InitScoreTapeDirectory(levelset.identifier, nr);
+
+ SaveTapeExt(filename);
+}
+
static boolean SaveTapeCheckedExt(int nr, char *msg_replace, char *msg_saved,
unsigned int req_state_added)
{
if (tape->no_valid_file)
{
- Error(ERR_WARN, "cannot dump -- no valid tape file found");
+ Warn("cannot dump -- no valid tape file found");
return;
}
PrintLine("-", 79);
+
Print("Tape of Level %03d (file version %08d, game version %08d)\n",
tape->level_nr, tape->file_version, tape->game_version);
Print(" (effective engine version %08d)\n",
tape->engine_version);
Print("Level series identifier: '%s'\n", tape->level_identifier);
+
+ Print("Special tape properties: ");
+ if (tape->property_bits == TAPE_PROPERTY_NONE)
+ Print("[none]");
+ if (tape->property_bits & TAPE_PROPERTY_EM_RANDOM_BUG)
+ Print("[em_random_bug]");
+ if (tape->property_bits & TAPE_PROPERTY_GAME_SPEED)
+ Print("[game_speed]");
+ if (tape->property_bits & TAPE_PROPERTY_PAUSE_MODE)
+ Print("[pause]");
+ if (tape->property_bits & TAPE_PROPERTY_SINGLE_STEP)
+ Print("[single_step]");
+ if (tape->property_bits & TAPE_PROPERTY_SNAPSHOT)
+ Print("[snapshot]");
+ if (tape->property_bits & TAPE_PROPERTY_REPLAYED)
+ Print("[replayed]");
+ if (tape->property_bits & TAPE_PROPERTY_TAS_KEYS)
+ Print("[tas_keys]");
+ if (tape->property_bits & TAPE_PROPERTY_SMALL_GRAPHICS)
+ Print("[small_graphics]");
+ Print("\n");
+
+ int year2 = tape->date / 10000;
+ int year4 = (year2 < 70 ? 2000 + year2 : 1900 + year2);
+ int month_index_raw = (tape->date / 100) % 100;
+ int month_index = month_index_raw % 12; // prevent invalid index
+ int month = month_index + 1;
+ int day = tape->date % 100;
+
+ Print("Tape date: %04d-%02d-%02d\n", year4, month, day);
+
PrintLine("-", 79);
tape_frame_counter = 0;
PrintLine("-", 79);
}
+void DumpTapes(void)
+{
+ static LevelDirTree *dumptape_leveldir = NULL;
+
+ dumptape_leveldir = getTreeInfoFromIdentifier(leveldir_first,
+ global.dumptape_leveldir);
+
+ if (dumptape_leveldir == NULL)
+ Fail("no such level identifier: '%s'", global.dumptape_leveldir);
+
+ if (global.dumptape_level_nr < dumptape_leveldir->first_level ||
+ global.dumptape_level_nr > dumptape_leveldir->last_level)
+ Fail("no such level number: %d", global.dumptape_level_nr);
+
+ leveldir_current = dumptape_leveldir;
+
+ if (options.mytapes)
+ LoadTape(global.dumptape_level_nr);
+ else
+ LoadSolutionTape(global.dumptape_level_nr);
+
+ DumpTape(&tape);
+
+ CloseAllAndExit(0);
+}
+
// ============================================================================
// score file functions
// ============================================================================
-void LoadScore(int nr)
+static void setScoreInfoToDefaultsExt(struct ScoreInfo *scores)
+{
+ int i;
+
+ for (i = 0; i < MAX_SCORE_ENTRIES; i++)
+ {
+ strcpy(scores->entry[i].tape_basename, UNDEFINED_FILENAME);
+ strcpy(scores->entry[i].name, EMPTY_PLAYER_NAME);
+ scores->entry[i].score = 0;
+ scores->entry[i].time = 0;
+
+ scores->entry[i].id = -1;
+ strcpy(scores->entry[i].tape_date, UNKNOWN_NAME);
+ strcpy(scores->entry[i].platform, UNKNOWN_NAME);
+ strcpy(scores->entry[i].version, UNKNOWN_NAME);
+ strcpy(scores->entry[i].country_name, UNKNOWN_NAME);
+ strcpy(scores->entry[i].country_code, "??");
+ }
+
+ scores->num_entries = 0;
+ scores->last_added = -1;
+ scores->last_added_local = -1;
+
+ scores->updated = FALSE;
+ scores->uploaded = FALSE;
+ scores->tape_downloaded = FALSE;
+ scores->force_last_added = FALSE;
+
+ // The following values are intentionally not reset here:
+ // - last_level_nr
+ // - last_entry_nr
+ // - next_level_nr
+ // - continue_playing
+ // - continue_on_return
+}
+
+static void setScoreInfoToDefaults(void)
+{
+ setScoreInfoToDefaultsExt(&scores);
+}
+
+static void setServerScoreInfoToDefaults(void)
+{
+ setScoreInfoToDefaultsExt(&server_scores);
+}
+
+static void LoadScore_OLD(int nr)
{
int i;
char *filename = getScoreFilename(nr);
char *line_ptr;
FILE *file;
- // always start with reliable default values
- for (i = 0; i < MAX_SCORE_ENTRIES; i++)
- {
- strcpy(highscore[i].Name, EMPTY_PLAYER_NAME);
- highscore[i].Score = 0;
- }
-
if (!(file = fopen(filename, MODE_READ)))
return;
if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n')
cookie[strlen(cookie) - 1] = '\0';
- if (!checkCookieString(cookie, SCORE_COOKIE))
+ if (!checkCookieString(cookie, SCORE_COOKIE_TMPL))
{
- Error(ERR_WARN, "unknown format of score file '%s'", filename);
+ Warn("unknown format of score file '%s'", filename);
+
fclose(file);
+
return;
}
for (i = 0; i < MAX_SCORE_ENTRIES; i++)
{
- if (fscanf(file, "%d", &highscore[i].Score) == EOF)
- Error(ERR_WARN, "fscanf() failed; %s", strerror(errno));
+ if (fscanf(file, "%d", &scores.entry[i].score) == EOF)
+ Warn("fscanf() failed; %s", strerror(errno));
+
if (fgets(line, MAX_LINE_LEN, file) == NULL)
line[0] = '\0';
{
if (*line_ptr != ' ' && *line_ptr != '\t' && *line_ptr != '\0')
{
- strncpy(highscore[i].Name, line_ptr, MAX_PLAYER_NAME_LEN);
- highscore[i].Name[MAX_PLAYER_NAME_LEN] = '\0';
+ strncpy(scores.entry[i].name, line_ptr, MAX_PLAYER_NAME_LEN);
+ scores.entry[i].name[MAX_PLAYER_NAME_LEN] = '\0';
break;
}
}
fclose(file);
}
-void SaveScore(int nr)
+static void ConvertScore_OLD(void)
+{
+ // only convert score to time for levels that rate playing time over score
+ if (!level.rate_time_over_score)
+ return;
+
+ // convert old score to playing time for score-less levels (like Supaplex)
+ int time_final_max = 999;
+ int i;
+
+ for (i = 0; i < MAX_SCORE_ENTRIES; i++)
+ {
+ int score = scores.entry[i].score;
+
+ if (score > 0 && score < time_final_max)
+ scores.entry[i].time = (time_final_max - score - 1) * FRAMES_PER_SECOND;
+ }
+}
+
+static int LoadScore_VERS(File *file, int chunk_size, struct ScoreInfo *scores)
+{
+ scores->file_version = getFileVersion(file);
+ scores->game_version = getFileVersion(file);
+
+ return chunk_size;
+}
+
+static int LoadScore_INFO(File *file, int chunk_size, struct ScoreInfo *scores)
+{
+ char *level_identifier = NULL;
+ int level_identifier_size;
+ int i;
+
+ level_identifier_size = getFile16BitBE(file);
+
+ level_identifier = checked_malloc(level_identifier_size);
+
+ for (i = 0; i < level_identifier_size; i++)
+ level_identifier[i] = getFile8Bit(file);
+
+ strncpy(scores->level_identifier, level_identifier, MAX_FILENAME_LEN);
+ scores->level_identifier[MAX_FILENAME_LEN] = '\0';
+
+ checked_free(level_identifier);
+
+ scores->level_nr = getFile16BitBE(file);
+ scores->num_entries = getFile16BitBE(file);
+
+ chunk_size = 2 + level_identifier_size + 2 + 2;
+
+ return chunk_size;
+}
+
+static int LoadScore_NAME(File *file, int chunk_size, struct ScoreInfo *scores)
+{
+ int i, j;
+
+ for (i = 0; i < scores->num_entries; i++)
+ {
+ for (j = 0; j < MAX_PLAYER_NAME_LEN; j++)
+ scores->entry[i].name[j] = getFile8Bit(file);
+
+ scores->entry[i].name[MAX_PLAYER_NAME_LEN] = '\0';
+ }
+
+ chunk_size = scores->num_entries * MAX_PLAYER_NAME_LEN;
+
+ return chunk_size;
+}
+
+static int LoadScore_SCOR(File *file, int chunk_size, struct ScoreInfo *scores)
+{
+ int i;
+
+ for (i = 0; i < scores->num_entries; i++)
+ scores->entry[i].score = getFile16BitBE(file);
+
+ chunk_size = scores->num_entries * 2;
+
+ return chunk_size;
+}
+
+static int LoadScore_SC4R(File *file, int chunk_size, struct ScoreInfo *scores)
+{
+ int i;
+
+ for (i = 0; i < scores->num_entries; i++)
+ scores->entry[i].score = getFile32BitBE(file);
+
+ chunk_size = scores->num_entries * 4;
+
+ return chunk_size;
+}
+
+static int LoadScore_TIME(File *file, int chunk_size, struct ScoreInfo *scores)
+{
+ int i;
+
+ for (i = 0; i < scores->num_entries; i++)
+ scores->entry[i].time = getFile32BitBE(file);
+
+ chunk_size = scores->num_entries * 4;
+
+ return chunk_size;
+}
+
+static int LoadScore_TAPE(File *file, int chunk_size, struct ScoreInfo *scores)
+{
+ int i, j;
+
+ for (i = 0; i < scores->num_entries; i++)
+ {
+ for (j = 0; j < MAX_SCORE_TAPE_BASENAME_LEN; j++)
+ scores->entry[i].tape_basename[j] = getFile8Bit(file);
+
+ scores->entry[i].tape_basename[MAX_SCORE_TAPE_BASENAME_LEN] = '\0';
+ }
+
+ chunk_size = scores->num_entries * MAX_SCORE_TAPE_BASENAME_LEN;
+
+ return chunk_size;
+}
+
+void LoadScore(int nr)
+{
+ char *filename = getScoreFilename(nr);
+ char cookie[MAX_LINE_LEN];
+ char chunk_name[CHUNK_ID_LEN + 1];
+ int chunk_size;
+ boolean old_score_file_format = FALSE;
+ File *file;
+
+ // always start with reliable default values
+ setScoreInfoToDefaults();
+
+ if (!(file = openFile(filename, MODE_READ)))
+ return;
+
+ getFileChunkBE(file, chunk_name, NULL);
+ if (strEqual(chunk_name, "RND1"))
+ {
+ getFile32BitBE(file); // not used
+
+ getFileChunkBE(file, chunk_name, NULL);
+ if (!strEqual(chunk_name, "SCOR"))
+ {
+ Warn("unknown format of score file '%s'", filename);
+
+ closeFile(file);
+
+ return;
+ }
+ }
+ else // check for old file format with cookie string
+ {
+ strcpy(cookie, chunk_name);
+ if (getStringFromFile(file, &cookie[4], MAX_LINE_LEN - 4) == NULL)
+ cookie[4] = '\0';
+ if (strlen(cookie) > 0 && cookie[strlen(cookie) - 1] == '\n')
+ cookie[strlen(cookie) - 1] = '\0';
+
+ if (!checkCookieString(cookie, SCORE_COOKIE_TMPL))
+ {
+ Warn("unknown format of score file '%s'", filename);
+
+ closeFile(file);
+
+ return;
+ }
+
+ old_score_file_format = TRUE;
+ }
+
+ if (old_score_file_format)
+ {
+ // score files from versions before 4.2.4.0 without chunk structure
+ LoadScore_OLD(nr);
+
+ // convert score to time, if possible (mainly for Supaplex levels)
+ ConvertScore_OLD();
+ }
+ else
+ {
+ static struct
+ {
+ char *name;
+ int size;
+ int (*loader)(File *, int, struct ScoreInfo *);
+ }
+ chunk_info[] =
+ {
+ { "VERS", SCORE_CHUNK_VERS_SIZE, LoadScore_VERS },
+ { "INFO", -1, LoadScore_INFO },
+ { "NAME", -1, LoadScore_NAME },
+ { "SCOR", -1, LoadScore_SCOR },
+ { "SC4R", -1, LoadScore_SC4R },
+ { "TIME", -1, LoadScore_TIME },
+ { "TAPE", -1, LoadScore_TAPE },
+
+ { NULL, 0, NULL }
+ };
+
+ while (getFileChunkBE(file, chunk_name, &chunk_size))
+ {
+ int i = 0;
+
+ while (chunk_info[i].name != NULL &&
+ !strEqual(chunk_name, chunk_info[i].name))
+ i++;
+
+ if (chunk_info[i].name == NULL)
+ {
+ Warn("unknown chunk '%s' in score file '%s'",
+ chunk_name, filename);
+
+ ReadUnusedBytesFromFile(file, chunk_size);
+ }
+ else if (chunk_info[i].size != -1 &&
+ chunk_info[i].size != chunk_size)
+ {
+ Warn("wrong size (%d) of chunk '%s' in score file '%s'",
+ chunk_size, chunk_name, filename);
+
+ ReadUnusedBytesFromFile(file, chunk_size);
+ }
+ else
+ {
+ // call function to load this score chunk
+ int chunk_size_expected =
+ (chunk_info[i].loader)(file, chunk_size, &scores);
+
+ // the size of some chunks cannot be checked before reading other
+ // chunks first (like "HEAD" and "BODY") that contain some header
+ // information, so check them here
+ if (chunk_size_expected != chunk_size)
+ {
+ Warn("wrong size (%d) of chunk '%s' in score file '%s'",
+ chunk_size, chunk_name, filename);
+ }
+ }
+ }
+ }
+
+ closeFile(file);
+}
+
+#if ENABLE_HISTORIC_CHUNKS
+void SaveScore_OLD(int nr)
{
int i;
- int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
char *filename = getScoreFilename(nr);
FILE *file;
if (!(file = fopen(filename, MODE_WRITE)))
{
- Error(ERR_WARN, "cannot save score for level %d", nr);
+ Warn("cannot save score for level %d", nr);
+
return;
}
fprintf(file, "%s\n\n", SCORE_COOKIE);
for (i = 0; i < MAX_SCORE_ENTRIES; i++)
- fprintf(file, "%d %s\n", highscore[i].Score, highscore[i].Name);
+ fprintf(file, "%d %s\n", scores.entry[i].score, scores.entry[i].name);
+
+ fclose(file);
+
+ SetFilePermissions(filename, PERMS_PRIVATE);
+}
+#endif
+
+static void SaveScore_VERS(FILE *file, struct ScoreInfo *scores)
+{
+ putFileVersion(file, scores->file_version);
+ putFileVersion(file, scores->game_version);
+}
+
+static void SaveScore_INFO(FILE *file, struct ScoreInfo *scores)
+{
+ int level_identifier_size = strlen(scores->level_identifier) + 1;
+ int i;
+
+ putFile16BitBE(file, level_identifier_size);
+
+ for (i = 0; i < level_identifier_size; i++)
+ putFile8Bit(file, scores->level_identifier[i]);
+
+ putFile16BitBE(file, scores->level_nr);
+ putFile16BitBE(file, scores->num_entries);
+}
+
+static void SaveScore_NAME(FILE *file, struct ScoreInfo *scores)
+{
+ int i, j;
+
+ for (i = 0; i < scores->num_entries; i++)
+ {
+ int name_size = strlen(scores->entry[i].name);
+
+ for (j = 0; j < MAX_PLAYER_NAME_LEN; j++)
+ putFile8Bit(file, (j < name_size ? scores->entry[i].name[j] : 0));
+ }
+}
+
+static void SaveScore_SCOR(FILE *file, struct ScoreInfo *scores)
+{
+ int i;
+
+ for (i = 0; i < scores->num_entries; i++)
+ putFile16BitBE(file, scores->entry[i].score);
+}
+
+static void SaveScore_SC4R(FILE *file, struct ScoreInfo *scores)
+{
+ int i;
+
+ for (i = 0; i < scores->num_entries; i++)
+ putFile32BitBE(file, scores->entry[i].score);
+}
+
+static void SaveScore_TIME(FILE *file, struct ScoreInfo *scores)
+{
+ int i;
+
+ for (i = 0; i < scores->num_entries; i++)
+ putFile32BitBE(file, scores->entry[i].time);
+}
+
+static void SaveScore_TAPE(FILE *file, struct ScoreInfo *scores)
+{
+ int i, j;
+
+ for (i = 0; i < scores->num_entries; i++)
+ {
+ int size = strlen(scores->entry[i].tape_basename);
+
+ for (j = 0; j < MAX_SCORE_TAPE_BASENAME_LEN; j++)
+ putFile8Bit(file, (j < size ? scores->entry[i].tape_basename[j] : 0));
+ }
+}
+
+static void SaveScoreToFilename(char *filename)
+{
+ FILE *file;
+ int info_chunk_size;
+ int name_chunk_size;
+ int scor_chunk_size;
+ int sc4r_chunk_size;
+ int time_chunk_size;
+ int tape_chunk_size;
+ boolean has_large_score_values;
+ int i;
+
+ if (!(file = fopen(filename, MODE_WRITE)))
+ {
+ Warn("cannot save score file '%s'", filename);
+
+ return;
+ }
+
+ info_chunk_size = 2 + (strlen(scores.level_identifier) + 1) + 2 + 2;
+ name_chunk_size = scores.num_entries * MAX_PLAYER_NAME_LEN;
+ scor_chunk_size = scores.num_entries * 2;
+ sc4r_chunk_size = scores.num_entries * 4;
+ time_chunk_size = scores.num_entries * 4;
+ tape_chunk_size = scores.num_entries * MAX_SCORE_TAPE_BASENAME_LEN;
+
+ has_large_score_values = FALSE;
+ for (i = 0; i < scores.num_entries; i++)
+ if (scores.entry[i].score > 0xffff)
+ has_large_score_values = TRUE;
+
+ putFileChunkBE(file, "RND1", CHUNK_SIZE_UNDEFINED);
+ putFileChunkBE(file, "SCOR", CHUNK_SIZE_NONE);
+
+ putFileChunkBE(file, "VERS", SCORE_CHUNK_VERS_SIZE);
+ SaveScore_VERS(file, &scores);
+
+ putFileChunkBE(file, "INFO", info_chunk_size);
+ SaveScore_INFO(file, &scores);
+
+ putFileChunkBE(file, "NAME", name_chunk_size);
+ SaveScore_NAME(file, &scores);
+
+ if (has_large_score_values)
+ {
+ putFileChunkBE(file, "SC4R", sc4r_chunk_size);
+ SaveScore_SC4R(file, &scores);
+ }
+ else
+ {
+ putFileChunkBE(file, "SCOR", scor_chunk_size);
+ SaveScore_SCOR(file, &scores);
+ }
+
+ putFileChunkBE(file, "TIME", time_chunk_size);
+ SaveScore_TIME(file, &scores);
+
+ putFileChunkBE(file, "TAPE", tape_chunk_size);
+ SaveScore_TAPE(file, &scores);
fclose(file);
- SetFilePermissions(filename, permissions);
+ SetFilePermissions(filename, PERMS_PRIVATE);
+}
+
+void SaveScore(int nr)
+{
+ char *filename = getScoreFilename(nr);
+ int i;
+
+ // used instead of "leveldir_current->subdir" (for network games)
+ InitScoreDirectory(levelset.identifier);
+
+ scores.file_version = FILE_VERSION_ACTUAL;
+ scores.game_version = GAME_VERSION_ACTUAL;
+
+ strncpy(scores.level_identifier, levelset.identifier, MAX_FILENAME_LEN);
+ scores.level_identifier[MAX_FILENAME_LEN] = '\0';
+ scores.level_nr = level_nr;
+
+ for (i = 0; i < MAX_SCORE_ENTRIES; i++)
+ if (scores.entry[i].score == 0 &&
+ scores.entry[i].time == 0 &&
+ strEqual(scores.entry[i].name, EMPTY_PLAYER_NAME))
+ break;
+
+ scores.num_entries = i;
+
+ if (scores.num_entries == 0)
+ return;
+
+ SaveScoreToFilename(filename);
+}
+
+static void LoadServerScoreFromCache(int nr)
+{
+ struct ScoreEntry score_entry;
+ struct
+ {
+ void *value;
+ boolean is_string;
+ int string_size;
+ }
+ score_mapping[] =
+ {
+ { &score_entry.score, FALSE, 0 },
+ { &score_entry.time, FALSE, 0 },
+ { score_entry.name, TRUE, MAX_PLAYER_NAME_LEN },
+ { score_entry.tape_basename, TRUE, MAX_FILENAME_LEN },
+ { score_entry.tape_date, TRUE, MAX_ISO_DATE_LEN },
+ { &score_entry.id, FALSE, 0 },
+ { score_entry.platform, TRUE, MAX_PLATFORM_TEXT_LEN },
+ { score_entry.version, TRUE, MAX_VERSION_TEXT_LEN },
+ { score_entry.country_code, TRUE, MAX_COUNTRY_CODE_LEN },
+ { score_entry.country_name, TRUE, MAX_COUNTRY_NAME_LEN },
+
+ { NULL, FALSE, 0 }
+ };
+ char *filename = getScoreCacheFilename(nr);
+ SetupFileHash *score_hash = loadSetupFileHash(filename);
+ int i, j;
+
+ server_scores.num_entries = 0;
+
+ if (score_hash == NULL)
+ return;
+
+ for (i = 0; i < MAX_SCORE_ENTRIES; i++)
+ {
+ score_entry = server_scores.entry[i];
+
+ for (j = 0; score_mapping[j].value != NULL; j++)
+ {
+ char token[10];
+
+ sprintf(token, "%02d.%d", i, j);
+
+ char *value = getHashEntry(score_hash, token);
+
+ if (value == NULL)
+ continue;
+
+ if (score_mapping[j].is_string)
+ {
+ char *score_value = (char *)score_mapping[j].value;
+ int value_size = score_mapping[j].string_size;
+
+ strncpy(score_value, value, value_size);
+ score_value[value_size] = '\0';
+ }
+ else
+ {
+ int *score_value = (int *)score_mapping[j].value;
+
+ *score_value = atoi(value);
+ }
+
+ server_scores.num_entries = i + 1;
+ }
+
+ server_scores.entry[i] = score_entry;
+ }
+
+ freeSetupFileHash(score_hash);
+}
+
+void LoadServerScore(int nr, boolean download_score)
+{
+ if (!setup.use_api_server)
+ return;
+
+ // always start with reliable default values
+ setServerScoreInfoToDefaults();
+
+ // 1st step: load server scores from cache file (which may not exist)
+ // (this should prevent reading it while the thread is writing to it)
+ LoadServerScoreFromCache(nr);
+
+ if (download_score && runtime.use_api_server)
+ {
+ // 2nd step: download server scores from score server to cache file
+ // (as thread, as it might time out if the server is not reachable)
+ ApiGetScoreAsThread(nr);
+ }
+}
+
+void PrepareScoreTapesForUpload(char *leveldir_subdir)
+{
+ MarkTapeDirectoryUploadsAsIncomplete(leveldir_subdir);
+
+ // if score tape not uploaded, ask for uploading missing tapes later
+ if (!setup.has_remaining_tapes)
+ setup.ask_for_remaining_tapes = TRUE;
+
+ setup.provide_uploading_tapes = TRUE;
+ setup.has_remaining_tapes = TRUE;
+
+ SaveSetup_ServerSetup();
+}
+
+void SaveServerScore(int nr, boolean tape_saved)
+{
+ if (!runtime.use_api_server)
+ {
+ PrepareScoreTapesForUpload(leveldir_current->subdir);
+
+ return;
+ }
+
+ ApiAddScoreAsThread(nr, tape_saved, NULL);
+}
+
+void SaveServerScoreFromFile(int nr, boolean tape_saved,
+ char *score_tape_filename)
+{
+ if (!runtime.use_api_server)
+ return;
+
+ ApiAddScoreAsThread(nr, tape_saved, score_tape_filename);
+}
+
+void LoadLocalAndServerScore(int nr, boolean download_score)
+{
+ int last_added_local = scores.last_added_local;
+ boolean force_last_added = scores.force_last_added;
+
+ // needed if only showing server scores
+ setScoreInfoToDefaults();
+
+ if (!strEqual(setup.scores_in_highscore_list, STR_SCORES_TYPE_SERVER_ONLY))
+ LoadScore(nr);
+
+ // restore last added local score entry (before merging server scores)
+ scores.last_added = scores.last_added_local = last_added_local;
+
+ if (setup.use_api_server &&
+ !strEqual(setup.scores_in_highscore_list, STR_SCORES_TYPE_LOCAL_ONLY))
+ {
+ // load server scores from cache file and trigger update from server
+ LoadServerScore(nr, download_score);
+
+ // merge local scores with scores from server
+ MergeServerScore();
+ }
+
+ if (force_last_added)
+ scores.force_last_added = force_last_added;
}
TYPE_STRING,
&setup.player_name, "player_name"
},
+ {
+ TYPE_SWITCH,
+ &setup.multiple_users, "multiple_users"
+ },
{
TYPE_SWITCH,
&setup.sound, "sound"
TYPE_SWITCH,
&setup.autorecord, "automatic_tape_recording"
},
+ {
+ TYPE_SWITCH,
+ &setup.auto_pause_on_start, "auto_pause_on_start"
+ },
{
TYPE_SWITCH,
&setup.show_titlescreen, "show_titlescreen"
},
{
TYPE_SWITCH,
- &setup.skip_scores_after_game, "skip_scores_after_game"
+ &setup.count_score_after_game, "count_score_after_game"
+ },
+ {
+ TYPE_SWITCH,
+ &setup.show_scores_after_game, "show_scores_after_game"
},
{
TYPE_SWITCH,
TYPE_SWITCH,
&setup.ask_on_game_over, "ask_on_game_over"
},
+ {
+ TYPE_SWITCH,
+ &setup.ask_on_quit_game, "ask_on_quit_game"
+ },
+ {
+ TYPE_SWITCH,
+ &setup.ask_on_quit_program, "ask_on_quit_program"
+ },
{
TYPE_SWITCH,
&setup.quick_switch, "quick_player_switch"
TYPE_SWITCH,
&setup.prefer_aga_graphics, "prefer_aga_graphics"
},
+ {
+ TYPE_SWITCH,
+ &setup.prefer_lowpass_sounds, "prefer_lowpass_sounds"
+ },
+ {
+ TYPE_SWITCH,
+ &setup.prefer_extra_panel_items, "prefer_extra_panel_items"
+ },
{
TYPE_SWITCH,
&setup.game_speed_extended, "game_speed_extended"
},
{
TYPE_SWITCH,
- &setup.show_snapshot_buttons, "show_snapshot_buttons"
+ &setup.show_load_save_buttons, "show_load_save_buttons"
+ },
+ {
+ TYPE_SWITCH,
+ &setup.show_undo_redo_buttons, "show_undo_redo_buttons"
+ },
+ {
+ TYPE_STRING,
+ &setup.scores_in_highscore_list, "scores_in_highscore_list"
},
{
TYPE_STRING,
TYPE_INTEGER,
&setup.touch.grid_ysize[1], "touch.virtual_buttons.1.ysize"
},
+ {
+ TYPE_SWITCH,
+ &setup.touch.overlay_buttons, "touch.overlay_buttons"
+ },
};
static struct TokenInfo auto_setup_tokens[] =
{
{
- TYPE_INTEGER,
- &setup.auto_setup.editor_zoom_tilesize, "editor.zoom_tilesize"
+ TYPE_INTEGER,
+ &setup.auto_setup.editor_zoom_tilesize, "editor.zoom_tilesize"
+ },
+};
+
+static struct TokenInfo server_setup_tokens[] =
+{
+ {
+ TYPE_STRING,
+ &setup.player_uuid, "player_uuid"
+ },
+ {
+ TYPE_INTEGER,
+ &setup.player_version, "player_version"
+ },
+ {
+ TYPE_SWITCH,
+ &setup.use_api_server, TEST_PREFIX "use_api_server"
+ },
+ {
+ TYPE_STRING,
+ &setup.api_server_hostname, TEST_PREFIX "api_server_hostname"
+ },
+ {
+ TYPE_STRING,
+ &setup.api_server_password, TEST_PREFIX "api_server_password"
+ },
+ {
+ TYPE_SWITCH,
+ &setup.ask_for_uploading_tapes, TEST_PREFIX "ask_for_uploading_tapes"
+ },
+ {
+ TYPE_SWITCH,
+ &setup.ask_for_remaining_tapes, TEST_PREFIX "ask_for_remaining_tapes"
+ },
+ {
+ TYPE_SWITCH,
+ &setup.provide_uploading_tapes, TEST_PREFIX "provide_uploading_tapes"
+ },
+ {
+ TYPE_SWITCH,
+ &setup.ask_for_using_api_server,TEST_PREFIX "ask_for_using_api_server"
+ },
+ {
+ TYPE_SWITCH,
+ &setup.has_remaining_tapes, TEST_PREFIX "has_remaining_tapes"
},
};
TYPE_SWITCH,
&setup.editor.show_element_token, "editor.show_element_token"
},
+ {
+ TYPE_SWITCH,
+ &setup.editor.show_read_only_warning, "editor.show_read_only_warning"
+ },
};
static struct TokenInfo editor_cascade_setup_tokens[] =
TYPE_SWITCH,
&setup.editor_cascade.el_ge, "editor.cascade.el_ge"
},
+ {
+ TYPE_SWITCH,
+ &setup.editor_cascade.el_es, "editor.cascade.el_es"
+ },
{
TYPE_SWITCH,
&setup.editor_cascade.el_ref, "editor.cascade.el_ref"
TYPE_KEY_X11,
&setup.shortcut.load_game, "shortcut.load_game"
},
+ {
+ TYPE_KEY_X11,
+ &setup.shortcut.restart_game, "shortcut.restart_game"
+ },
+ {
+ TYPE_KEY_X11,
+ &setup.shortcut.pause_before_end, "shortcut.pause_before_end"
+ },
{
TYPE_KEY_X11,
&setup.shortcut.toggle_pause, "shortcut.toggle_pause"
static struct TokenInfo system_setup_tokens[] =
{
+ {
+ TYPE_STRING,
+ &setup.system.sdl_renderdriver, "system.sdl_renderdriver"
+ },
{
TYPE_STRING,
&setup.system.sdl_videodriver, "system.sdl_videodriver"
TYPE_BOOLEAN,
&setup.internal.menu_save_and_exit, "menu_save_and_exit"
},
+ {
+ TYPE_BOOLEAN,
+ &setup.internal.info_title, "info_title"
+ },
+ {
+ TYPE_BOOLEAN,
+ &setup.internal.info_elements, "info_elements"
+ },
+ {
+ TYPE_BOOLEAN,
+ &setup.internal.info_music, "info_music"
+ },
+ {
+ TYPE_BOOLEAN,
+ &setup.internal.info_credits, "info_credits"
+ },
+ {
+ TYPE_BOOLEAN,
+ &setup.internal.info_program, "info_program"
+ },
+ {
+ TYPE_BOOLEAN,
+ &setup.internal.info_version, "info_version"
+ },
+ {
+ TYPE_BOOLEAN,
+ &setup.internal.info_levelset, "info_levelset"
+ },
+ {
+ TYPE_BOOLEAN,
+ &setup.internal.info_exit, "info_exit"
+ },
};
static struct TokenInfo debug_setup_tokens[] =
TYPE_BOOLEAN,
&setup.debug.show_frames_per_second, "debug.show_frames_per_second"
},
+ {
+ TYPE_SWITCH3,
+ &setup.debug.xsn_mode, "debug.xsn_mode"
+ },
+ {
+ TYPE_INTEGER,
+ &setup.debug.xsn_percent, "debug.xsn_percent"
+ },
};
static struct TokenInfo options_setup_tokens[] =
},
};
-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);
-
- strncpy(login_name_new, login_name, MAX_PLAYER_NAME_LEN);
- login_name_new[MAX_PLAYER_NAME_LEN] = '\0';
-
- if (strlen(login_name) > MAX_PLAYER_NAME_LEN) // name has been cut
- if (strchr(login_name_new, ' '))
- *strchr(login_name_new, ' ') = '\0';
-
- return login_name_new;
-}
-
static void setSetupInfoToDefaults(struct SetupInfo *si)
{
int i;
- si->player_name = get_corrected_login_name(getLoginName());
+ si->player_name = getStringCopy(getDefaultUserName(user.nr));
+
+ si->multiple_users = TRUE;
si->sound = TRUE;
si->sound_loops = TRUE;
si->engine_snapshot_memory = SNAPSHOT_MEMORY_DEFAULT;
si->fade_screens = TRUE;
si->autorecord = TRUE;
+ si->auto_pause_on_start = FALSE;
si->show_titlescreen = TRUE;
si->quick_doors = FALSE;
si->team_mode = FALSE;
si->skip_levels = TRUE;
si->increment_levels = TRUE;
si->auto_play_next_level = TRUE;
- si->skip_scores_after_game = FALSE;
+ si->count_score_after_game = TRUE;
+ si->show_scores_after_game = TRUE;
si->time_limit = TRUE;
si->fullscreen = FALSE;
si->window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
si->ask_on_escape = TRUE;
si->ask_on_escape_editor = TRUE;
si->ask_on_game_over = TRUE;
+ si->ask_on_quit_game = TRUE;
+ si->ask_on_quit_program = TRUE;
si->quick_switch = FALSE;
si->input_on_focus = FALSE;
si->prefer_aga_graphics = TRUE;
+ si->prefer_lowpass_sounds = FALSE;
+ si->prefer_extra_panel_items = TRUE;
si->game_speed_extended = FALSE;
si->game_frame_delay = GAME_FRAME_DELAY;
si->sp_show_border_elements = FALSE;
si->small_game_graphics = FALSE;
- si->show_snapshot_buttons = FALSE;
+ si->show_load_save_buttons = FALSE;
+ si->show_undo_redo_buttons = FALSE;
+ si->scores_in_highscore_list = getStringCopy(STR_SCORES_TYPE_DEFAULT);
si->graphics_set = getStringCopy(GFX_CLASSIC_SUBDIR);
si->sounds_set = getStringCopy(SND_CLASSIC_SUBDIR);
si->touch.grid_initialized = video.initialized;
+ si->touch.overlay_buttons = FALSE;
+
si->editor.el_boulderdash = TRUE;
si->editor.el_emerald_mine = TRUE;
si->editor.el_emerald_mine_club = TRUE;
si->editor.show_element_token = FALSE;
+ si->editor.show_read_only_warning = TRUE;
+
si->editor.use_template_for_new_levels = TRUE;
si->shortcut.save_game = DEFAULT_KEY_SAVE_GAME;
si->shortcut.load_game = DEFAULT_KEY_LOAD_GAME;
+ si->shortcut.restart_game = DEFAULT_KEY_RESTART_GAME;
+ si->shortcut.pause_before_end = DEFAULT_KEY_PAUSE_BEFORE_END;
si->shortcut.toggle_pause = DEFAULT_KEY_TOGGLE_PAUSE;
si->shortcut.focus_player[0] = DEFAULT_KEY_FOCUS_PLAYER_1;
si->input[i].key.drop = (i == 0 ? DEFAULT_KEY_DROP : KSYM_UNDEFINED);
}
+ si->system.sdl_renderdriver = getStringCopy(ARG_DEFAULT);
si->system.sdl_videodriver = getStringCopy(ARG_DEFAULT);
si->system.sdl_audiodriver = getStringCopy(ARG_DEFAULT);
si->system.audio_fragment_size = DEFAULT_AUDIO_FRAGMENT_SIZE;
si->debug.show_frames_per_second = FALSE;
+ si->debug.xsn_mode = AUTO;
+ si->debug.xsn_percent = 0;
+
si->options.verbose = FALSE;
#if defined(PLATFORM_ANDROID)
si->fullscreen = TRUE;
+ si->touch.overlay_buttons = TRUE;
#endif
+
+ setHideSetupEntry(&setup.debug.xsn_mode);
}
static void setSetupInfoToDefaults_AutoSetup(struct SetupInfo *si)
si->auto_setup.editor_zoom_tilesize = MINI_TILESIZE;
}
+static void setSetupInfoToDefaults_ServerSetup(struct SetupInfo *si)
+{
+ si->player_uuid = NULL; // (will be set later)
+ si->player_version = 1; // (will be set later)
+
+ si->use_api_server = TRUE;
+ si->api_server_hostname = getStringCopy(API_SERVER_HOSTNAME);
+ si->api_server_password = getStringCopy(UNDEFINED_PASSWORD);
+ si->ask_for_uploading_tapes = TRUE;
+ si->ask_for_remaining_tapes = FALSE;
+ si->provide_uploading_tapes = TRUE;
+ si->ask_for_using_api_server = TRUE;
+ si->has_remaining_tapes = FALSE;
+}
+
static void setSetupInfoToDefaults_EditorCascade(struct SetupInfo *si)
{
si->editor_cascade.el_bd = TRUE;
si->editor_cascade.el_steel_chars = FALSE;
si->editor_cascade.el_ce = FALSE;
si->editor_cascade.el_ge = FALSE;
+ si->editor_cascade.el_es = FALSE;
si->editor_cascade.el_ref = FALSE;
si->editor_cascade.el_user = FALSE;
si->editor_cascade.el_dynamic = FALSE;
{
char *hide_setup_token = getHideSetupToken(setup_value);
+ if (hide_setup_hash == NULL)
+ hide_setup_hash = newSetupFileHash();
+
if (setup_value != NULL)
setHashEntry(hide_setup_hash, hide_setup_token, "");
}
+void removeHideSetupEntry(void *setup_value)
+{
+ char *hide_setup_token = getHideSetupToken(setup_value);
+
+ if (setup_value != NULL)
+ removeHashEntry(hide_setup_hash, hide_setup_token);
+}
+
boolean hideSetupEntry(void *setup_value)
{
char *hide_setup_token = getHideSetupToken(setup_value);
// check if this setup option should be hidden in the setup menu
if (token_hide_value != NULL && get_boolean_from_string(token_hide_value))
setHideSetupEntry(token_info[token_nr].value);
+
+ free(token_hide_text);
}
static void setSetupInfoFromTokenInfo(SetupFileHash *setup_file_hash,
token_info[token_nr].text);
}
-static void decodeSetupFileHash(SetupFileHash *setup_file_hash)
+static void decodeSetupFileHash_Default(SetupFileHash *setup_file_hash)
{
int i, pnr;
if (!setup_file_hash)
return;
- if (hide_setup_hash == NULL)
- hide_setup_hash = newSetupFileHash();
-
for (i = 0; i < ARRAY_SIZE(global_setup_tokens); i++)
setSetupInfoFromTokenInfo(setup_file_hash, global_setup_tokens, i);
auto_setup_tokens[i].text));
}
+static void decodeSetupFileHash_ServerSetup(SetupFileHash *setup_file_hash)
+{
+ int i;
+
+ if (!setup_file_hash)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(server_setup_tokens); i++)
+ setSetupInfo(server_setup_tokens, i,
+ getHashEntry(setup_file_hash,
+ server_setup_tokens[i].text));
+}
+
static void decodeSetupFileHash_EditorCascade(SetupFileHash *setup_file_hash)
{
int i;
editor_cascade_setup_tokens[i].text));
}
+void LoadUserNames(void)
+{
+ int last_user_nr = user.nr;
+ int i;
+
+ if (global.user_names != NULL)
+ {
+ for (i = 0; i < MAX_PLAYER_NAMES; i++)
+ checked_free(global.user_names[i]);
+
+ checked_free(global.user_names);
+ }
+
+ global.user_names = checked_calloc(MAX_PLAYER_NAMES * sizeof(char *));
+
+ for (i = 0; i < MAX_PLAYER_NAMES; i++)
+ {
+ user.nr = i;
+
+ SetupFileHash *setup_file_hash = loadSetupFileHash(getSetupFilename());
+
+ if (setup_file_hash)
+ {
+ char *player_name = getHashEntry(setup_file_hash, "player_name");
+
+ global.user_names[i] = getFixedUserName(player_name);
+
+ freeSetupFileHash(setup_file_hash);
+ }
+
+ if (global.user_names[i] == NULL)
+ global.user_names[i] = getStringCopy(getDefaultUserName(i));
+ }
+
+ user.nr = last_user_nr;
+}
+
void LoadSetupFromFilename(char *filename)
{
SetupFileHash *setup_file_hash = loadSetupFileHash(filename);
if (setup_file_hash)
{
- decodeSetupFileHash(setup_file_hash);
+ decodeSetupFileHash_Default(setup_file_hash);
freeSetupFileHash(setup_file_hash);
}
else
{
- Error(ERR_DEBUG, "using default setup values");
+ Debug("setup", "using default setup values");
}
}
char *player_name_new;
// needed to work around problems with fixed length strings
- player_name_new = get_corrected_login_name(setup.player_name);
+ player_name_new = getFixedUserName(setup.player_name);
free(setup.player_name);
setup.player_name = player_name_new;
MIN(MAX(MIN_SCROLL_DELAY, setup.scroll_delay_value), MAX_SCROLL_DELAY);
}
-void LoadSetup(void)
+void LoadSetup_Default(void)
{
char *filename;
// try to load setup values from default setup file
filename = getDefaultSetupFilename();
+ if (fileExists(filename))
+ LoadSetupFromFilename(filename);
+
+ // try to load setup values from platform setup file
+ filename = getPlatformSetupFilename();
+
if (fileExists(filename))
LoadSetupFromFilename(filename);
free(filename);
}
+void LoadSetup_ServerSetup(void)
+{
+ char *filename = getPath2(getSetupDir(), SERVERSETUP_FILENAME);
+ SetupFileHash *setup_file_hash = NULL;
+
+ // always start with reliable default values
+ setSetupInfoToDefaults_ServerSetup(&setup);
+
+ setup_file_hash = loadSetupFileHash(filename);
+
+ if (setup_file_hash)
+ {
+ decodeSetupFileHash_ServerSetup(setup_file_hash);
+
+ freeSetupFileHash(setup_file_hash);
+ }
+
+ free(filename);
+
+ if (setup.player_uuid == NULL)
+ {
+ // player UUID does not yet exist in setup file
+ setup.player_uuid = getStringCopy(getUUID());
+ setup.player_version = 2;
+
+ SaveSetup_ServerSetup();
+ }
+}
+
void LoadSetup_EditorCascade(void)
{
char *filename = getPath2(getSetupDir(), EDITORCASCADE_FILENAME);
free(filename);
}
+void LoadSetup(void)
+{
+ LoadSetup_Default();
+ LoadSetup_AutoSetup();
+ LoadSetup_ServerSetup();
+ LoadSetup_EditorCascade();
+}
+
static void addGameControllerMappingToHash(SetupFileHash *mappings_hash,
char *mapping_line)
{
if (!(file = fopen(filename, MODE_READ)))
{
- Error(ERR_WARN, "cannot read game controller mappings file '%s'", filename);
+ Warn("cannot read game controller mappings file '%s'", filename);
return;
}
fclose(file);
}
-void SaveSetup(void)
+void SaveSetup_Default(void)
{
char *filename = getSetupFilename();
FILE *file;
if (!(file = fopen(filename, MODE_WRITE)))
{
- Error(ERR_WARN, "cannot write setup file '%s'", filename);
+ Warn("cannot write setup file '%s'", filename);
+
return;
}
for (i = 0; i < ARRAY_SIZE(global_setup_tokens); i++)
{
// just to make things nicer :)
- if (global_setup_tokens[i].value == &setup.sound ||
+ if (global_setup_tokens[i].value == &setup.multiple_users ||
+ global_setup_tokens[i].value == &setup.sound ||
global_setup_tokens[i].value == &setup.graphics_set ||
global_setup_tokens[i].value == &setup.volume_simple ||
global_setup_tokens[i].value == &setup.network_mode ||
fprintf(file, "\n");
for (i = 0; i < ARRAY_SIZE(debug_setup_tokens); i++)
- fprintf(file, "%s\n", getSetupLine(debug_setup_tokens, "", i));
+ if (!strPrefix(debug_setup_tokens[i].text, "debug.xsn_") ||
+ setup.debug.xsn_mode != AUTO)
+ fprintf(file, "%s\n", getSetupLine(debug_setup_tokens, "", i));
fprintf(file, "\n");
for (i = 0; i < ARRAY_SIZE(options_setup_tokens); i++)
if (!(file = fopen(filename, MODE_WRITE)))
{
- Error(ERR_WARN, "cannot write auto setup file '%s'", filename);
+ Warn("cannot write auto setup file '%s'", filename);
+
free(filename);
+
return;
}
free(filename);
}
+void SaveSetup_ServerSetup(void)
+{
+ char *filename = getPath2(getSetupDir(), SERVERSETUP_FILENAME);
+ FILE *file;
+ int i;
+
+ InitUserDataDirectory();
+
+ if (!(file = fopen(filename, MODE_WRITE)))
+ {
+ Warn("cannot write server setup file '%s'", filename);
+
+ free(filename);
+
+ return;
+ }
+
+ fprintFileHeader(file, SERVERSETUP_FILENAME);
+
+ for (i = 0; i < ARRAY_SIZE(server_setup_tokens); i++)
+ {
+ // just to make things nicer :)
+ if (server_setup_tokens[i].value == &setup.use_api_server)
+ fprintf(file, "\n");
+
+ fprintf(file, "%s\n", getSetupLine(server_setup_tokens, "", i));
+ }
+
+ fclose(file);
+
+ SetFilePermissions(filename, PERMS_PRIVATE);
+
+ free(filename);
+}
+
void SaveSetup_EditorCascade(void)
{
char *filename = getPath2(getSetupDir(), EDITORCASCADE_FILENAME);
if (!(file = fopen(filename, MODE_WRITE)))
{
- Error(ERR_WARN, "cannot write editor cascade state file '%s'", filename);
+ Warn("cannot write editor cascade state file '%s'", filename);
+
free(filename);
+
return;
}
free(filename);
}
+void SaveSetup(void)
+{
+ SaveSetup_Default();
+ SaveSetup_AutoSetup();
+ SaveSetup_ServerSetup();
+ SaveSetup_EditorCascade();
+}
+
static void SaveSetup_WriteGameControllerMappings(SetupFileHash *mappings_hash,
char *filename)
{
if (!(file = fopen(filename, MODE_WRITE)))
{
- Error(ERR_WARN, "cannot write game controller mappings file '%s'",filename);
+ Warn("cannot write game controller mappings file '%s'", filename);
return;
}
if (value != NULL)
return atoi(value);
- Error(ERR_WARN, "unknown element token '%s'", token);
+ Warn("unknown element token '%s'", token);
return EL_UNDEFINED;
}
static int get_anim_action_parameter_value(char *token)
{
+ // check most common default case first to massively speed things up
+ if (strEqual(token, ARG_UNDEFINED))
+ return ANIM_EVENT_ACTION_NONE;
+
int result = getImageIDFromToken(token);
if (result == -1)
result = -(int)key;
}
+ if (result == -1)
+ {
+ if (isURL(token))
+ {
+ result = get_hash_from_key(token); // unsigned int => int
+ result = ABS(result); // may be negative now
+ result += (result < MAX_IMAGE_FILES ? MAX_IMAGE_FILES : 0);
+
+ setHashEntry(anim_url_hash, int2str(result, 0), token);
+ }
+ }
+
if (result == -1)
result = ANIM_EVENT_ACTION_NONE;
string_has_parameter(value, "pingpong") ? ANIM_PINGPONG :
string_has_parameter(value, "pingpong2") ? ANIM_PINGPONG2 :
string_has_parameter(value, "random") ? ANIM_RANDOM :
+ string_has_parameter(value, "random_static") ? ANIM_RANDOM_STATIC :
string_has_parameter(value, "ce_value") ? ANIM_CE_VALUE :
string_has_parameter(value, "ce_score") ? ANIM_CE_SCORE :
string_has_parameter(value, "ce_delay") ? ANIM_CE_DELAY :
string_has_parameter(value, "vertical") ? ANIM_VERTICAL :
string_has_parameter(value, "centered") ? ANIM_CENTERED :
string_has_parameter(value, "all") ? ANIM_ALL :
+ string_has_parameter(value, "tiled") ? ANIM_TILED :
ANIM_DEFAULT);
if (string_has_parameter(value, "once"))
if (string_has_parameter(value, "reverse"))
result |= STYLE_REVERSE;
+ if (string_has_parameter(value, "leftmost_position"))
+ result |= STYLE_LEFTMOST_POSITION;
+
if (string_has_parameter(value, "block_clicks"))
result |= STYLE_BLOCK;
return get_parameter_value(value_raw, suffix, TYPE_INTEGER);
}
-void InitMenuDesignSettings_Static(void)
+void InitMenuDesignSettings_FromHash(SetupFileHash *setup_file_hash,
+ boolean ignore_defaults)
{
int i;
- // always start with reliable default values from static default config
for (i = 0; image_config_vars[i].token != NULL; i++)
{
- char *value = getHashEntry(image_config_hash, image_config_vars[i].token);
+ char *value = getHashEntry(setup_file_hash, image_config_vars[i].token);
+
+ // (ignore definitions set to "[DEFAULT]" which are already initialized)
+ if (ignore_defaults && strEqual(value, ARG_DEFAULT))
+ continue;
if (value != NULL)
*image_config_vars[i].value =
}
}
+void InitMenuDesignSettings_Static(void)
+{
+ // always start with reliable default values from static default config
+ InitMenuDesignSettings_FromHash(image_config_hash, FALSE);
+}
+
static void InitMenuDesignSettings_SpecialPreProcessing(void)
{
int i;
*menu_config_players[i].value = TRUE;
// 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);
-
- // (ignore definitions set to "[DEFAULT]" which are already initialized)
- if (value != NULL && !strEqual(value, ARG_DEFAULT))
- *image_config_vars[i].value =
- get_token_parameter_value(image_config_vars[i].token, value);
- }
+ InitMenuDesignSettings_FromHash(setup_file_hash, TRUE);
freeSetupFileHash(setup_file_hash);
}
{
if (num_unknown_tokens == 0)
{
- Error(ERR_INFO_LINE, "-");
- Error(ERR_INFO, "warning: unknown token(s) found in config file:");
- Error(ERR_INFO, "- config file: '%s'", filename);
+ Warn("---");
+ Warn("unknown token(s) found in config file:");
+ Warn("- config file: '%s'", filename);
num_unknown_tokens++;
}
- Error(ERR_INFO, "- token: '%s'", list->token);
+ Warn("- token: '%s'", list->token);
}
}
if (num_unknown_tokens > 0)
- Error(ERR_INFO_LINE, "-");
+ Warn("---");
while (*num_elements % 4) // pad with empty elements, if needed
(*elements)[(*num_elements)++] = EL_EMPTY;
#if 0
for (i = 0; i < *num_elements; i++)
- printf("editor: element '%s' [%d]\n",
- element_info[(*elements)[i]].token_name, (*elements)[i]);
+ Debug("editor", "element '%s' [%d]\n",
+ element_info[(*elements)[i]].token_name, (*elements)[i]);
#endif
}
if ((dir = openDirectory(music_directory)) == NULL)
{
- Error(ERR_WARN, "cannot read music directory '%s'", music_directory);
+ Warn("cannot read music directory '%s'", music_directory);
+
return;
}
new = &(*new)->next;
}
}
+
+ // add pointers to previous list nodes
+
+ struct MusicFileInfo *node = music_file_info;
+
+ while (node != NULL)
+ {
+ if (node->next)
+ node->next->prev = node;
+
+ node = node->next;
+ }
}
static void add_helpanim_entry(int element, int action, int direction,
{
if (token_nr == 0)
{
- Error(ERR_INFO_LINE, "-");
- Error(ERR_INFO, "warning: unknown token(s) found in config file:");
- Error(ERR_INFO, "- config file: '%s'", filename);
+ Warn("---");
+ Warn("unknown token(s) found in config file:");
+ Warn("- config file: '%s'", filename);
}
- Error(ERR_INFO, "- token: '%s'", token);
+ Warn("- token: '%s'", token);
}
static void print_unknown_token_end(int token_nr)
{
if (token_nr > 0)
- Error(ERR_INFO_LINE, "-");
+ Warn("---");
}
void LoadHelpAnimInfo(void)
#if 0
for (i = 0; i < num_list_entries; i++)
- printf("::: '%s': %d, %d, %d => %d\n",
- EL_NAME(helpanim_info[i].element),
- helpanim_info[i].element,
- helpanim_info[i].action,
- helpanim_info[i].direction,
- helpanim_info[i].delay);
+ Debug("files:LoadHelpAnimInfo", "'%s': %d, %d, %d => %d",
+ EL_NAME(helpanim_info[i].element),
+ helpanim_info[i].element,
+ helpanim_info[i].action,
+ helpanim_info[i].direction,
+ helpanim_info[i].delay);
#endif
}
#if 0
BEGIN_HASH_ITERATION(helptext_info, itr)
{
- printf("::: '%s' => '%s'\n",
- HASH_ITERATION_TOKEN(itr), HASH_ITERATION_VALUE(itr));
+ Debug("files:LoadHelpTextInfo", "'%s' => '%s'",
+ HASH_ITERATION_TOKEN(itr), HASH_ITERATION_VALUE(itr));
}
END_HASH_ITERATION(hash, itr)
#endif
global.convert_leveldir);
if (convert_leveldir == NULL)
- Error(ERR_EXIT, "no such level identifier: '%s'",
- global.convert_leveldir);
+ Fail("no such level identifier: '%s'", global.convert_leveldir);
leveldir_current = convert_leveldir;
Print("converting level ... ");
+#if 0
+ // special case: conversion of some EMC levels as requested by ACME
+ level.game_engine_type = GAME_ENGINE_TYPE_RND;
+#endif
+
level_filename = getDefaultLevelFilename(level_nr);
new_level = !fileExists(level_filename);
sprintf(basename1, "%04d.bmp", i);
sprintf(basename2, "%04ds.bmp", i);
- filename1 = getPath2(global.create_images_dir, basename1);
- filename2 = getPath2(global.create_images_dir, basename2);
+ filename1 = getPath2(global.create_sketch_images_dir, basename1);
+ filename2 = getPath2(global.create_sketch_images_dir, basename2);
DrawSizedElement(0, 0, element, TILESIZE);
BlitBitmap(drawto, bitmap1, SX, SY, TILEX, TILEY, 0, 0);
if (SDL_SaveBMP(bitmap1->surface, filename1) != 0)
- Error(ERR_EXIT, "cannot save level sketch image file '%s'", filename1);
+ Fail("cannot save level sketch image file '%s'", filename1);
DrawSizedElement(0, 0, element, MINI_TILESIZE);
BlitBitmap(drawto, bitmap2, SX, SY, MINI_TILEX, MINI_TILEY, 0, 0);
if (SDL_SaveBMP(bitmap2->surface, filename2) != 0)
- Error(ERR_EXIT, "cannot save level sketch image file '%s'", filename2);
+ Fail("cannot save level sketch image file '%s'", filename2);
free(filename1);
free(filename2);
if (options.debug)
fprintf(stderr, "\n");
- Error(ERR_INFO, "%d normal and small images created", NUM_FILE_ELEMENTS);
+ Info("%d normal and small images created", NUM_FILE_ELEMENTS);
+
+ CloseAllAndExit(0);
+}
+
+
+// ----------------------------------------------------------------------------
+// create and save images for element collecting animations (raw BMP format)
+// ----------------------------------------------------------------------------
+
+static boolean createCollectImage(int element)
+{
+ return (IS_COLLECTIBLE(element) && !IS_SP_ELEMENT(element));
+}
+
+void CreateCollectElementImages(void)
+{
+ int i, j;
+ int num_steps = 8;
+ int anim_frames = num_steps - 1;
+ int tile_size = TILESIZE;
+ int anim_width = tile_size * anim_frames;
+ int anim_height = tile_size;
+ int num_collect_images = 0;
+ int pos_collect_images = 0;
+
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ if (createCollectImage(i))
+ num_collect_images++;
+
+ Info("Creating %d element collecting animation images ...",
+ num_collect_images);
+
+ int dst_width = anim_width * 2;
+ int dst_height = anim_height * num_collect_images / 2;
+ Bitmap *dst_bitmap = CreateBitmap(dst_width, dst_height, DEFAULT_DEPTH);
+ char *basename = "RocksCollect.bmp";
+ char *filename = getPath2(global.create_collect_images_dir, basename);
+
+ for (i = 0; i < MAX_NUM_ELEMENTS; i++)
+ {
+ if (!createCollectImage(i))
+ continue;
+
+ int dst_x = (pos_collect_images / (num_collect_images / 2)) * anim_width;
+ int dst_y = (pos_collect_images % (num_collect_images / 2)) * anim_height;
+ int graphic = el2img(i);
+ char *token_name = element_info[i].token_name;
+ Bitmap *tmp_bitmap = CreateBitmap(tile_size, tile_size, DEFAULT_DEPTH);
+ Bitmap *src_bitmap;
+ int src_x, src_y;
+
+ Info("- creating collecting image for '%s' ...", token_name);
+
+ getGraphicSource(graphic, 0, &src_bitmap, &src_x, &src_y);
+
+ BlitBitmap(src_bitmap, tmp_bitmap, src_x, src_y,
+ tile_size, tile_size, 0, 0);
+
+ tmp_bitmap->surface_masked = tmp_bitmap->surface;
+
+ for (j = 0; j < anim_frames; j++)
+ {
+ int frame_size_final = tile_size * (anim_frames - j) / num_steps;
+ int frame_size = frame_size_final * num_steps;
+ int offset = (tile_size - frame_size_final) / 2;
+ Bitmap *frame_bitmap = ZoomBitmap(tmp_bitmap, frame_size, frame_size);
+
+ while (frame_size > frame_size_final)
+ {
+ frame_size /= 2;
+
+ Bitmap *half_bitmap = ZoomBitmap(frame_bitmap, frame_size, frame_size);
+
+ FreeBitmap(frame_bitmap);
+
+ frame_bitmap = half_bitmap;
+ }
+
+ BlitBitmap(frame_bitmap, dst_bitmap, 0, 0,
+ frame_size_final, frame_size_final,
+ dst_x + j * tile_size + offset, dst_y + offset);
+
+ FreeBitmap(frame_bitmap);
+ }
+
+ tmp_bitmap->surface_masked = NULL;
+
+ FreeBitmap(tmp_bitmap);
+
+ pos_collect_images++;
+ }
+
+ if (SDL_SaveBMP(dst_bitmap->surface, filename) != 0)
+ Fail("cannot save element collecting image file '%s'", filename);
+
+ FreeBitmap(dst_bitmap);
+
+ Info("Done.");
CloseAllAndExit(0);
}
}
if (SDL_SaveBMP(bitmap->surface, dst_filename) != 0)
- Error(ERR_EXIT, "cannot save CE graphics file '%s'", dst_filename);
+ Fail("cannot save CE graphics file '%s'", dst_filename);
FreeBitmap(bitmap);