#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
#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 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
TYPE_INTEGER, CONF_VALUE_16_BIT(1),
&li.mm_time_bomb, 75
},
+
{
EL_MM_GRAY_BALL, -1,
TYPE_INTEGER, CONF_VALUE_16_BIT(1),
&li.mm_time_ball, 75
},
+ {
+ EL_MM_GRAY_BALL, -1,
+ TYPE_INTEGER, CONF_VALUE_8_BIT(1),
+ &li.mm_ball_choice_mode, ANIM_RANDOM
+ },
+ {
+ EL_MM_GRAY_BALL, -1,
+ TYPE_ELEMENT_LIST, CONF_VALUE_BYTES(1),
+ &li.mm_ball_content, EL_EMPTY, NULL,
+ &li.num_mm_ball_contents, 8, MAX_MM_BALL_CONTENTS
+ },
+ {
+ EL_MM_GRAY_BALL, -1,
+ TYPE_BOOLEAN, CONF_VALUE_8_BIT(1),
+ &li.rotate_mm_ball_content, TRUE
+ },
+ {
+ EL_MM_GRAY_BALL, -1,
+ TYPE_BOOLEAN, CONF_VALUE_8_BIT(2),
+ &li.explode_mm_ball, FALSE
+ },
+
{
EL_MM_STEEL_BLOCK, -1,
TYPE_INTEGER, CONF_VALUE_16_BIT(1),
int element = i;
struct ElementInfo *ei = &element_info[element];
+ if (element == EL_MM_GRAY_BALL)
+ {
+ struct LevelInfo_MM *level_mm = level->native_mm_level;
+ int j;
+
+ for (j = 0; j < level->num_mm_ball_contents; j++)
+ level->mm_ball_content[j] =
+ map_element_MM_to_RND(level_mm->ball_content[j]);
+ }
+
// never initialize clipboard elements after the very first time
// (to be able to use clipboard elements between several levels)
if (IS_CLIPBOARD_ELEMENT(element) && clipboard_elements_initialized)
// bits 0 - 31 of "has_event[]"
event_bits = getFile32BitBE(file);
for (j = 0; j < MIN(NUM_CHANGE_EVENTS, 32); j++)
- if (event_bits & (1 << j))
+ if (event_bits & (1u << j))
ei->change->has_event[j] = TRUE;
ei->change->target_element = getMappedElement(getFile16BitBE(file));
// bits 0 - 31 of "has_event[]" ...
event_bits = getFile32BitBE(file);
for (j = 0; j < MIN(NUM_CHANGE_EVENTS, 32); j++)
- if (event_bits & (1 << j))
+ if (event_bits & (1u << j))
change->has_event[j] = TRUE;
change->target_element = getMappedElement(getFile16BitBE(file));
// ... bits 32 - 39 of "has_event[]" (not nice, but downward compatible)
event_bits = getFile8Bit(file);
for (j = 32; j < NUM_CHANGE_EVENTS; j++)
- if (event_bits & (1 << (j - 32)))
+ if (event_bits & (1u << (j - 32)))
change->has_event[j] = TRUE;
}
while (!checkEndOfFile(file))
{
+ // level file might contain invalid change page number
+ if (xx_current_change_page >= ei->num_change_pages)
+ break;
+
struct ElementChangeInfo *change = &ei->change_page[xx_current_change_page];
xx_change = *change; // copy change data into temporary buffer
struct ElementInfo *ei = &element_info[element];
struct ElementGroupInfo *group = ei->group;
+ if (group == NULL)
+ return -1;
+
xx_ei = *ei; // copy element data into temporary buffer
xx_group = *group; // copy group data into temporary buffer
int chunk_size_expected =
(chunk_info[i].loader)(file, chunk_size, level);
+ if (chunk_size_expected < 0)
+ {
+ Warn("error reading chunk '%s' in level file '%s'",
+ chunk_name, filename);
+
+ break;
+ }
+
// 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
{
Warn("wrong size (%d) of chunk '%s' in level file '%s'",
chunk_size, chunk_name, filename);
+
+ break;
}
}
}
static void CopyNativeLevel_RND_to_MM(struct LevelInfo *level)
{
struct LevelInfo_MM *level_mm = level->native_mm_level;
- int x, y;
+ int i, x, y;
level_mm->fieldx = MIN(level->fieldx, MM_MAX_PLAYFIELD_WIDTH);
level_mm->fieldy = MIN(level->fieldy, MM_MAX_PLAYFIELD_HEIGHT);
level_mm->kettles_needed = level->gems_needed;
level_mm->auto_count_kettles = level->auto_count_gems;
- level_mm->laser_red = level->mm_laser_red;
- level_mm->laser_green = level->mm_laser_green;
- level_mm->laser_blue = level->mm_laser_blue;
+ level_mm->mm_laser_red = level->mm_laser_red;
+ level_mm->mm_laser_green = level->mm_laser_green;
+ level_mm->mm_laser_blue = level->mm_laser_blue;
+
+ level_mm->df_laser_red = level->df_laser_red;
+ level_mm->df_laser_green = level->df_laser_green;
+ level_mm->df_laser_blue = level->df_laser_blue;
strcpy(level_mm->name, level->name);
strcpy(level_mm->author, level->author);
level_mm->time_ball = level->mm_time_ball;
level_mm->time_block = level->mm_time_block;
+ level_mm->num_ball_contents = level->num_mm_ball_contents;
+ level_mm->ball_choice_mode = level->mm_ball_choice_mode;
+ level_mm->rotate_ball_content = level->rotate_mm_ball_content;
+ level_mm->explode_ball = level->explode_mm_ball;
+
+ for (i = 0; i < level->num_mm_ball_contents; i++)
+ level_mm->ball_content[i] =
+ map_element_RND_to_MM(level->mm_ball_content[i]);
+
for (x = 0; x < level->fieldx; x++)
for (y = 0; y < level->fieldy; y++)
Ur[x][y] =
static void CopyNativeLevel_MM_to_RND(struct LevelInfo *level)
{
struct LevelInfo_MM *level_mm = level->native_mm_level;
- int x, y;
+ int i, x, y;
level->fieldx = MIN(level_mm->fieldx, MAX_LEV_FIELDX);
level->fieldy = MIN(level_mm->fieldy, MAX_LEV_FIELDY);
level->gems_needed = level_mm->kettles_needed;
level->auto_count_gems = level_mm->auto_count_kettles;
- level->mm_laser_red = level_mm->laser_red;
- level->mm_laser_green = level_mm->laser_green;
- level->mm_laser_blue = level_mm->laser_blue;
+ level->mm_laser_red = level_mm->mm_laser_red;
+ level->mm_laser_green = level_mm->mm_laser_green;
+ level->mm_laser_blue = level_mm->mm_laser_blue;
+
+ level->df_laser_red = level_mm->df_laser_red;
+ level->df_laser_green = level_mm->df_laser_green;
+ level->df_laser_blue = level_mm->df_laser_blue;
strcpy(level->name, level_mm->name);
level->mm_time_ball = level_mm->time_ball;
level->mm_time_block = level_mm->time_block;
+ level->num_mm_ball_contents = level_mm->num_ball_contents;
+ level->mm_ball_choice_mode = level_mm->ball_choice_mode;
+ level->rotate_mm_ball_content = level_mm->rotate_ball_content;
+ level->explode_mm_ball = level_mm->explode_ball;
+
+ for (i = 0; i < level->num_mm_ball_contents; i++)
+ level->mm_ball_content[i] =
+ map_element_MM_to_RND(level_mm->ball_content[i]);
+
for (x = 0; x < level->fieldx; x++)
for (y = 0; y < level->fieldy; y++)
level->field[x][y] = map_element_MM_to_RND(level_mm->field[x][y]);
return getMappedElement(element);
}
-static void LoadLevelFromFileStream_DC(File *file, struct LevelInfo *level,
- int nr)
+static void LoadLevelFromFileStream_DC(File *file, struct LevelInfo *level)
{
byte header[DC_LEVEL_HEADER_SIZE];
int envelope_size;
}
}
- LoadLevelFromFileStream_DC(file, level, level_file_info->nr);
+ LoadLevelFromFileStream_DC(file, level);
closeFile(file);
}
{
// 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)
event_bits = 0;
for (j = 0; j < MIN(NUM_CHANGE_EVENTS, 32); j++)
if (change->has_event[j])
- event_bits |= (1 << j);
+ event_bits |= (1u << j);
putFile32BitBE(file, event_bits);
putFile16BitBE(file, change->target_element);
event_bits = 0;
for (j = 32; j < NUM_CHANGE_EVENTS; j++)
if (change->has_event[j])
- event_bits |= (1 << (j - 32));
+ event_bits |= (1u << (j - 32));
putFile8Bit(file, event_bits);
}
}
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);
}
tape.level_nr = level_nr;
tape.counter = 0;
tape.changed = FALSE;
+ tape.solved = FALSE;
tape.recording = FALSE;
tape.playing = FALSE;
setTapeActionFlags(tape, getFile8Bit(file));
tape->property_bits = getFile8Bit(file);
-
- ReadUnusedBytesFromFile(file, TAPE_CHUNK_HEAD_UNUSED);
+ tape->solved = getFile8Bit(file);
engine_version = getFileVersion(file);
if (engine_version > 0)
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
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);
+ putFile8Bit(file, tape->solved);
putFileVersion(file, tape->engine_version);
}
tape->engine_version);
Print("Level series identifier: '%s'\n", tape->level_identifier);
+ Print("Solution tape: %s\n",
+ tape->solved ? "yes" :
+ tape->game_version < VERSION_IDENT(4,3,2,3) ? "unknown" : "no");
+
Print("Special tape properties: ");
if (tape->property_bits == TAPE_PROPERTY_NONE)
Print("[none]");
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->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)
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;
{ "INFO", -1, LoadScore_INFO },
{ "NAME", -1, LoadScore_NAME },
{ "SCOR", -1, LoadScore_SCOR },
+ { "SC4R", -1, LoadScore_SC4R },
{ "TIME", -1, LoadScore_TIME },
{ "TAPE", -1, LoadScore_TAPE },
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;
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)))
{
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, "NAME", name_chunk_size);
SaveScore_NAME(file, &scores);
- putFileChunkBE(file, "SCOR", scor_chunk_size);
- SaveScore_SCOR(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);
SaveScoreToFilename(filename);
}
-void ExecuteAsThread(SDL_ThreadFunction function, char *name, void *data,
- char *error)
-{
-#if defined(PLATFORM_EMSCRIPTEN)
- // threads currently not fully supported by Emscripten/SDL and some browsers
- function(data);
-#else
- SDL_Thread *thread = SDL_CreateThread(function, name, data);
-
- if (thread != NULL)
- SDL_DetachThread(thread);
- else
- Error("Cannot create thread to %s!", error);
-
- // nasty kludge to lower probability of intermingled thread error messages
- Delay(1);
-#endif
-}
-
-char *getPasswordJSON(char *password)
-{
- static char password_json[MAX_FILENAME_LEN] = "";
- static boolean initialized = FALSE;
-
- if (!initialized)
- {
- if (password != NULL &&
- !strEqual(password, "") &&
- !strEqual(password, UNDEFINED_PASSWORD))
- snprintf(password_json, MAX_FILENAME_LEN,
- " \"password\": \"%s\",\n",
- setup.api_server_password);
-
- initialized = TRUE;
- }
-
- return password_json;
-}
-
-struct ApiGetScoreThreadData
-{
- int level_nr;
- char *score_cache_filename;
-};
-
-static void *CreateThreadData_ApiGetScore(int nr)
-{
- struct ApiGetScoreThreadData *data =
- checked_malloc(sizeof(struct ApiGetScoreThreadData));
- char *score_cache_filename = getScoreCacheFilename(nr);
-
- data->level_nr = nr;
- data->score_cache_filename = getStringCopy(score_cache_filename);
-
- return data;
-}
-
-static void FreeThreadData_ApiGetScore(void *data_raw)
-{
- struct ApiGetScoreThreadData *data = data_raw;
-
- checked_free(data->score_cache_filename);
- checked_free(data);
-}
-
-static boolean SetRequest_ApiGetScore(struct HttpRequest *request,
- void *data_raw)
-{
- struct ApiGetScoreThreadData *data = data_raw;
- int level_nr = data->level_nr;
-
- request->hostname = setup.api_server_hostname;
- request->port = API_SERVER_PORT;
- request->method = API_SERVER_METHOD;
- request->uri = API_SERVER_URI_GET;
-
- char *levelset_identifier = getEscapedJSON(leveldir_current->identifier);
- char *levelset_name = getEscapedJSON(leveldir_current->name);
-
- snprintf(request->body, MAX_HTTP_BODY_SIZE,
- "{\n"
- "%s"
- " \"game_version\": \"%s\",\n"
- " \"game_platform\": \"%s\",\n"
- " \"levelset_identifier\": \"%s\",\n"
- " \"levelset_name\": \"%s\",\n"
- " \"level_nr\": \"%d\"\n"
- "}\n",
- getPasswordJSON(setup.api_server_password),
- getProgramRealVersionString(),
- getProgramPlatformString(),
- levelset_identifier,
- levelset_name,
- level_nr);
-
- checked_free(levelset_identifier);
- checked_free(levelset_name);
-
- ConvertHttpRequestBodyToServerEncoding(request);
-
- return TRUE;
-}
-
-static void HandleResponse_ApiGetScore(struct HttpResponse *response,
- void *data_raw)
-{
- struct ApiGetScoreThreadData *data = data_raw;
-
- if (response->body_size == 0)
- {
- // no scores available for this level
-
- return;
- }
-
- ConvertHttpResponseBodyToClientEncoding(response);
-
- char *filename = data->score_cache_filename;
- FILE *file;
- int i;
-
- // used instead of "leveldir_current->subdir" (for network games)
- InitScoreCacheDirectory(levelset.identifier);
-
- if (!(file = fopen(filename, MODE_WRITE)))
- {
- Warn("cannot save score cache file '%s'", filename);
-
- return;
- }
-
- for (i = 0; i < response->body_size; i++)
- fputc(response->body[i], file);
-
- fclose(file);
-
- SetFilePermissions(filename, PERMS_PRIVATE);
-
- server_scores.updated = TRUE;
-}
-
-#if defined(PLATFORM_EMSCRIPTEN)
-static void Emscripten_ApiGetScore_Loaded(unsigned handle, void *data_raw,
- void *buffer, unsigned int size)
-{
- struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size);
-
- if (response != NULL)
- {
- HandleResponse_ApiGetScore(response, data_raw);
-
- checked_free(response);
- }
- else
- {
- Error("server response too large to handle (%d bytes)", size);
- }
-
- FreeThreadData_ApiGetScore(data_raw);
-}
-
-static void Emscripten_ApiGetScore_Failed(unsigned handle, void *data_raw,
- int code, const char *status)
-{
- Error("server failed to handle request: %d %s", code, status);
-
- FreeThreadData_ApiGetScore(data_raw);
-}
-
-static void Emscripten_ApiGetScore_Progress(unsigned handle, void *data_raw,
- int bytes, int size)
-{
- // nothing to do here
-}
-
-static void Emscripten_ApiGetScore_HttpRequest(struct HttpRequest *request,
- void *data_raw)
-{
- if (!SetRequest_ApiGetScore(request, data_raw))
- {
- FreeThreadData_ApiGetScore(data_raw);
-
- return;
- }
-
- emscripten_async_wget2_data(request->uri,
- request->method,
- request->body,
- data_raw,
- TRUE,
- Emscripten_ApiGetScore_Loaded,
- Emscripten_ApiGetScore_Failed,
- Emscripten_ApiGetScore_Progress);
-}
-
-#else
-
-static void ApiGetScore_HttpRequestExt(struct HttpRequest *request,
- struct HttpResponse *response,
- void *data_raw)
-{
- if (!SetRequest_ApiGetScore(request, data_raw))
- return;
-
- if (!DoHttpRequest(request, response))
- {
- Error("HTTP request failed: %s", GetHttpError());
-
- return;
- }
-
- if (!HTTP_SUCCESS(response->status_code))
- {
- // do not show error message if no scores found for this level set
- if (response->status_code == 404)
- return;
-
- Error("server failed to handle request: %d %s",
- response->status_code,
- response->status_text);
-
- return;
- }
-
- HandleResponse_ApiGetScore(response, data_raw);
-}
-
-static void ApiGetScore_HttpRequest(struct HttpRequest *request,
- struct HttpResponse *response,
- void *data_raw)
-{
- ApiGetScore_HttpRequestExt(request, response, data_raw);
-
- FreeThreadData_ApiGetScore(data_raw);
-}
-#endif
-
-static int ApiGetScoreThread(void *data_raw)
-{
- struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest));
- struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse));
-
- program.api_thread_count++;
-
-#if defined(PLATFORM_EMSCRIPTEN)
- Emscripten_ApiGetScore_HttpRequest(request, data_raw);
-#else
- ApiGetScore_HttpRequest(request, response, data_raw);
-#endif
-
- program.api_thread_count--;
-
- checked_free(request);
- checked_free(response);
-
- return 0;
-}
-
-static void ApiGetScoreAsThread(int nr)
-{
- struct ApiGetScoreThreadData *data = CreateThreadData_ApiGetScore(nr);
-
- ExecuteAsThread(ApiGetScoreThread,
- "ApiGetScore", data,
- "download scores from server");
-}
-
static void LoadServerScoreFromCache(int nr)
{
struct ScoreEntry score_entry;
{ &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 }
};
}
}
-static char *get_file_base64(char *filename)
-{
- struct stat file_status;
-
- if (stat(filename, &file_status) != 0)
- {
- Error("cannot stat file '%s'", filename);
-
- return NULL;
- }
-
- int buffer_size = file_status.st_size;
- byte *buffer = checked_malloc(buffer_size);
- FILE *file;
- int i;
-
- if (!(file = fopen(filename, MODE_READ)))
- {
- Error("cannot open file '%s'", filename);
-
- checked_free(buffer);
-
- return NULL;
- }
-
- for (i = 0; i < buffer_size; i++)
- {
- int c = fgetc(file);
-
- if (c == EOF)
- {
- Error("cannot read from input file '%s'", filename);
-
- fclose(file);
- checked_free(buffer);
-
- return NULL;
- }
-
- buffer[i] = (byte)c;
- }
-
- fclose(file);
-
- int buffer_encoded_size = base64_encoded_size(buffer_size);
- char *buffer_encoded = checked_malloc(buffer_encoded_size);
-
- base64_encode(buffer_encoded, buffer, buffer_size);
-
- checked_free(buffer);
-
- return buffer_encoded;
-}
-
-static void PrepareScoreTapesForUpload(char *leveldir_subdir)
+void PrepareScoreTapesForUpload(char *leveldir_subdir)
{
MarkTapeDirectoryUploadsAsIncomplete(leveldir_subdir);
SaveSetup_ServerSetup();
}
-struct ApiAddScoreThreadData
-{
- int level_nr;
- boolean tape_saved;
- char *leveldir_subdir;
- char *score_tape_filename;
- struct ScoreEntry score_entry;
-};
-
-static void *CreateThreadData_ApiAddScore(int nr, boolean tape_saved,
- char *score_tape_filename)
-{
- struct ApiAddScoreThreadData *data =
- checked_malloc(sizeof(struct ApiAddScoreThreadData));
- struct ScoreEntry *score_entry = &scores.entry[scores.last_added];
-
- if (score_tape_filename == NULL)
- score_tape_filename = getScoreTapeFilename(score_entry->tape_basename, nr);
-
- data->level_nr = nr;
- data->tape_saved = tape_saved;
- data->leveldir_subdir = getStringCopy(leveldir_current->subdir);
- data->score_tape_filename = getStringCopy(score_tape_filename);
- data->score_entry = *score_entry;
-
- return data;
-}
-
-static void FreeThreadData_ApiAddScore(void *data_raw)
-{
- struct ApiAddScoreThreadData *data = data_raw;
-
- checked_free(data->leveldir_subdir);
- checked_free(data->score_tape_filename);
- checked_free(data);
-}
-
-static boolean SetRequest_ApiAddScore(struct HttpRequest *request,
- void *data_raw)
-{
- struct ApiAddScoreThreadData *data = data_raw;
- struct ScoreEntry *score_entry = &data->score_entry;
- char *score_tape_filename = data->score_tape_filename;
- boolean tape_saved = data->tape_saved;
- int level_nr = data->level_nr;
-
- request->hostname = setup.api_server_hostname;
- request->port = API_SERVER_PORT;
- request->method = API_SERVER_METHOD;
- request->uri = API_SERVER_URI_ADD;
-
- char *tape_base64 = get_file_base64(score_tape_filename);
-
- if (tape_base64 == NULL)
- {
- Error("loading and base64 encoding score tape file failed");
-
- return FALSE;
- }
-
- char *player_name_raw = score_entry->name;
- char *player_uuid_raw = setup.player_uuid;
-
- if (options.player_name != NULL && global.autoplay_leveldir != NULL)
- {
- player_name_raw = options.player_name;
- player_uuid_raw = "";
- }
-
- char *levelset_identifier = getEscapedJSON(leveldir_current->identifier);
- char *levelset_name = getEscapedJSON(leveldir_current->name);
- char *levelset_author = getEscapedJSON(leveldir_current->author);
- char *level_name = getEscapedJSON(level.name);
- char *level_author = getEscapedJSON(level.author);
- char *player_name = getEscapedJSON(player_name_raw);
- char *player_uuid = getEscapedJSON(player_uuid_raw);
-
- snprintf(request->body, MAX_HTTP_BODY_SIZE,
- "{\n"
- "%s"
- " \"game_version\": \"%s\",\n"
- " \"game_platform\": \"%s\",\n"
- " \"batch_time\": \"%d\",\n"
- " \"levelset_identifier\": \"%s\",\n"
- " \"levelset_name\": \"%s\",\n"
- " \"levelset_author\": \"%s\",\n"
- " \"levelset_num_levels\": \"%d\",\n"
- " \"levelset_first_level\": \"%d\",\n"
- " \"level_nr\": \"%d\",\n"
- " \"level_name\": \"%s\",\n"
- " \"level_author\": \"%s\",\n"
- " \"use_step_counter\": \"%d\",\n"
- " \"rate_time_over_score\": \"%d\",\n"
- " \"player_name\": \"%s\",\n"
- " \"player_uuid\": \"%s\",\n"
- " \"score\": \"%d\",\n"
- " \"time\": \"%d\",\n"
- " \"tape_basename\": \"%s\",\n"
- " \"tape_saved\": \"%d\",\n"
- " \"tape\": \"%s\"\n"
- "}\n",
- getPasswordJSON(setup.api_server_password),
- getProgramRealVersionString(),
- getProgramPlatformString(),
- (int)global.autoplay_time,
- levelset_identifier,
- levelset_name,
- levelset_author,
- leveldir_current->levels,
- leveldir_current->first_level,
- level_nr,
- level_name,
- level_author,
- level.use_step_counter,
- level.rate_time_over_score,
- player_name,
- player_uuid,
- score_entry->score,
- score_entry->time,
- score_entry->tape_basename,
- tape_saved,
- tape_base64);
-
- checked_free(tape_base64);
-
- checked_free(levelset_identifier);
- checked_free(levelset_name);
- checked_free(levelset_author);
- checked_free(level_name);
- checked_free(level_author);
- checked_free(player_name);
- checked_free(player_uuid);
-
- ConvertHttpRequestBodyToServerEncoding(request);
-
- return TRUE;
-}
-
-static void HandleResponse_ApiAddScore(struct HttpResponse *response,
- void *data_raw)
-{
- server_scores.uploaded = TRUE;
-}
-
-static void HandleFailure_ApiAddScore(void *data_raw)
-{
- struct ApiAddScoreThreadData *data = data_raw;
-
- PrepareScoreTapesForUpload(data->leveldir_subdir);
-}
-
-#if defined(PLATFORM_EMSCRIPTEN)
-static void Emscripten_ApiAddScore_Loaded(unsigned handle, void *data_raw,
- void *buffer, unsigned int size)
-{
- struct HttpResponse *response = GetHttpResponseFromBuffer(buffer, size);
-
- if (response != NULL)
- {
- HandleResponse_ApiAddScore(response, data_raw);
-
- checked_free(response);
- }
- else
- {
- Error("server response too large to handle (%d bytes)", size);
-
- HandleFailure_ApiAddScore(data_raw);
- }
-
- FreeThreadData_ApiAddScore(data_raw);
-}
-
-static void Emscripten_ApiAddScore_Failed(unsigned handle, void *data_raw,
- int code, const char *status)
-{
- Error("server failed to handle request: %d %s", code, status);
-
- HandleFailure_ApiAddScore(data_raw);
-
- FreeThreadData_ApiAddScore(data_raw);
-}
-
-static void Emscripten_ApiAddScore_Progress(unsigned handle, void *data_raw,
- int bytes, int size)
-{
- // nothing to do here
-}
-
-static void Emscripten_ApiAddScore_HttpRequest(struct HttpRequest *request,
- void *data_raw)
-{
- if (!SetRequest_ApiAddScore(request, data_raw))
- {
- FreeThreadData_ApiAddScore(data_raw);
-
- return;
- }
-
- emscripten_async_wget2_data(request->uri,
- request->method,
- request->body,
- data_raw,
- TRUE,
- Emscripten_ApiAddScore_Loaded,
- Emscripten_ApiAddScore_Failed,
- Emscripten_ApiAddScore_Progress);
-}
-
-#else
-
-static void ApiAddScore_HttpRequestExt(struct HttpRequest *request,
- struct HttpResponse *response,
- void *data_raw)
-{
- if (!SetRequest_ApiAddScore(request, data_raw))
- return;
-
- if (!DoHttpRequest(request, response))
- {
- Error("HTTP request failed: %s", GetHttpError());
-
- HandleFailure_ApiAddScore(data_raw);
-
- return;
- }
-
- if (!HTTP_SUCCESS(response->status_code))
- {
- Error("server failed to handle request: %d %s",
- response->status_code,
- response->status_text);
-
- HandleFailure_ApiAddScore(data_raw);
-
- return;
- }
-
- HandleResponse_ApiAddScore(response, data_raw);
-}
-
-static void ApiAddScore_HttpRequest(struct HttpRequest *request,
- struct HttpResponse *response,
- void *data_raw)
-{
- ApiAddScore_HttpRequestExt(request, response, data_raw);
-
- FreeThreadData_ApiAddScore(data_raw);
-}
-#endif
-
-static int ApiAddScoreThread(void *data_raw)
-{
- struct HttpRequest *request = checked_calloc(sizeof(struct HttpRequest));
- struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse));
-
- program.api_thread_count++;
-
-#if defined(PLATFORM_EMSCRIPTEN)
- Emscripten_ApiAddScore_HttpRequest(request, data_raw);
-#else
- ApiAddScore_HttpRequest(request, response, data_raw);
-#endif
-
- program.api_thread_count--;
-
- checked_free(request);
- checked_free(response);
-
- return 0;
-}
-
-static void ApiAddScoreAsThread(int nr, boolean tape_saved,
- char *score_tape_filename)
-{
- struct ApiAddScoreThreadData *data =
- CreateThreadData_ApiAddScore(nr, tape_saved, score_tape_filename);
-
- ExecuteAsThread(ApiAddScoreThread,
- "ApiAddScore", data,
- "upload score to server");
-}
-
void SaveServerScore(int nr, boolean tape_saved)
{
if (!runtime.use_api_server)
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();
// merge local scores with scores from server
MergeServerScore();
}
+
+ if (force_last_added)
+ scores.force_last_added = force_last_added;
}
TYPE_SWITCH,
&setup.autorecord, "automatic_tape_recording"
},
+ {
+ TYPE_SWITCH,
+ &setup.autorecord_after_replay, "autorecord_after_replay"
+ },
+ {
+ TYPE_SWITCH,
+ &setup.auto_pause_on_start, "auto_pause_on_start"
+ },
{
TYPE_SWITCH,
&setup.show_titlescreen, "show_titlescreen"
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_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"
TYPE_BOOLEAN,
&setup.internal.create_user_levelset, "create_user_levelset"
},
+ {
+ TYPE_BOOLEAN,
+ &setup.internal.info_screens_from_main, "info_screens_from_main"
+ },
{
TYPE_BOOLEAN,
&setup.internal.menu_game, "menu_game"
},
+ {
+ TYPE_BOOLEAN,
+ &setup.internal.menu_engines, "menu_engines"
+ },
{
TYPE_BOOLEAN,
&setup.internal.menu_editor, "menu_editor"
TYPE_BOOLEAN,
&setup.internal.menu_save_and_exit, "menu_save_and_exit"
},
+ {
+ TYPE_BOOLEAN,
+ &setup.internal.menu_shortcuts_various, "menu_shortcuts_various"
+ },
+ {
+ TYPE_BOOLEAN,
+ &setup.internal.menu_shortcuts_focus, "menu_shortcuts_focus"
+ },
+ {
+ TYPE_BOOLEAN,
+ &setup.internal.menu_shortcuts_tape, "menu_shortcuts_tape"
+ },
+ {
+ TYPE_BOOLEAN,
+ &setup.internal.menu_shortcuts_sound, "menu_shortcuts_sound"
+ },
+ {
+ TYPE_BOOLEAN,
+ &setup.internal.menu_shortcuts_snap, "menu_shortcuts_snap"
+ },
+ {
+ 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.options.verbose, "options.verbose"
},
+ {
+ TYPE_BOOLEAN,
+ &setup.options.debug, "options.debug"
+ },
+ {
+ TYPE_STRING,
+ &setup.options.debug_mode, "options.debug_mode"
+ },
};
static void setSetupInfoToDefaults(struct SetupInfo *si)
si->engine_snapshot_memory = SNAPSHOT_MEMORY_DEFAULT;
si->fade_screens = TRUE;
si->autorecord = TRUE;
+ si->autorecord_after_replay = TRUE;
+ si->auto_pause_on_start = FALSE;
si->show_titlescreen = TRUE;
si->quick_doors = FALSE;
si->team_mode = FALSE;
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->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->internal.choose_from_top_leveldir = FALSE;
si->internal.show_scaling_in_title = TRUE;
si->internal.create_user_levelset = TRUE;
+ si->internal.info_screens_from_main = FALSE;
si->internal.default_window_width = WIN_XSIZE_DEFAULT;
si->internal.default_window_height = WIN_YSIZE_DEFAULT;
si->debug.xsn_percent = 0;
si->options.verbose = FALSE;
+ si->options.debug = FALSE;
+ si->options.debug_mode = getStringCopy(ARG_UNDEFINED_STRING);
#if defined(PLATFORM_ANDROID)
si->fullscreen = TRUE;
+ si->touch.overlay_buttons = TRUE;
#endif
setHideSetupEntry(&setup.debug.xsn_mode);
// 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);
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;
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);
}
void LoadMusicInfo(void)
{
- char *music_directory = getCustomMusicDirectory();
+ char *music_directory = getCustomMusicDirectory_NoConf();
int num_music = getMusicListSize();
int num_music_noconf = 0;
int num_sounds = getSoundListSize();
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,
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);
+ char *basename_bmp = "RocksCollect.bmp";
+ char *basename_png = "RocksCollect.png";
+ char *filename_bmp = getPath2(global.create_collect_images_dir, basename_bmp);
+ char *filename_png = getPath2(global.create_collect_images_dir, basename_png);
+ int len_filename_bmp = strlen(filename_bmp);
+ int len_filename_png = strlen(filename_png);
+ int max_command_len = MAX_FILENAME_LEN + len_filename_bmp + len_filename_png;
+ char cmd_convert[max_command_len];
+
+ snprintf(cmd_convert, max_command_len, "convert \"%s\" \"%s\"",
+ filename_bmp,
+ filename_png);
+
+ // force using RGBA surface for destination bitmap
+ SDL_SetColorKey(dst_bitmap->surface, SET_TRANSPARENT_PIXEL,
+ SDL_MapRGB(dst_bitmap->surface->format, 0x00, 0x00, 0x00));
+
+ dst_bitmap->surface =
+ SDL_ConvertSurfaceFormat(dst_bitmap->surface, SDL_PIXELFORMAT_ARGB8888, 0);
for (i = 0; i < MAX_NUM_ELEMENTS; i++)
{
BlitBitmap(src_bitmap, tmp_bitmap, src_x, src_y,
tile_size, tile_size, 0, 0);
+ // force using RGBA surface for temporary bitmap (using transparent black)
+ SDL_SetColorKey(tmp_bitmap->surface, SET_TRANSPARENT_PIXEL,
+ SDL_MapRGB(tmp_bitmap->surface->format, 0x00, 0x00, 0x00));
+
+ tmp_bitmap->surface =
+ SDL_ConvertSurfaceFormat(tmp_bitmap->surface, SDL_PIXELFORMAT_ARGB8888, 0);
+
tmp_bitmap->surface_masked = tmp_bitmap->surface;
for (j = 0; j < anim_frames; j++)
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);
+ BlitBitmapMasked(frame_bitmap, dst_bitmap, 0, 0,
+ frame_size_final, frame_size_final,
+ dst_x + j * tile_size + offset, dst_y + offset);
FreeBitmap(frame_bitmap);
}
pos_collect_images++;
}
- if (SDL_SaveBMP(dst_bitmap->surface, filename) != 0)
- Fail("cannot save element collecting image file '%s'", filename);
+ if (SDL_SaveBMP(dst_bitmap->surface, filename_bmp) != 0)
+ Fail("cannot save element collecting image file '%s'", filename_bmp);
FreeBitmap(dst_bitmap);
+ Info("Converting image file from BMP to PNG ...");
+
+ if (system(cmd_convert) != 0)
+ Fail("converting image file failed");
+
+ unlink(filename_bmp);
+
Info("Done.");
CloseAllAndExit(0);