-/*
+/*
* Copyright (C) 2003 Robert Kooima
*
* NEVERBALL is free software; you can redistribute it and/or modify
*/
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include <math.h>
+#include <errno.h>
#include "level.h"
-#include "glext.h"
-#include "image.h"
-#include "game.h"
-#include "geom.h"
-#include "demo.h"
-#include "hud.h"
-#include "audio.h"
-#include "config.h"
+#include "solid.h"
/*---------------------------------------------------------------------------*/
-struct score
+void score_init_hs(struct score *s, int timer, int coins)
{
- char time_n[4][MAXNAM];
- int time_t[4];
- int time_c[4];
-
- char coin_n[4][MAXNAM];
- int coin_t[4];
- int coin_c[4];
-};
-
-struct level
-{
- char file[MAXSTR];
- char back[MAXSTR];
- char grad[MAXSTR];
- char shot[MAXSTR];
- char song[MAXSTR];
- int time;
- int goal;
-
- GLuint text;
-};
-
-static int score; /* Current coin total */
-static int coins; /* Current coin count */
-static int balls; /* Current life count */
-static int goal; /* Current goal count */
-
-static int level; /* Current level number */
-static int count; /* Number of levels */
-static int limit; /* Last opened (locked) level */
-static int status; /* Status of current level */
-
-static int mode; /* Current play mode */
-
-static int level_total;
-static int coins_total;
-static int times_total;
-
-static struct level level_v[MAXLVL];
-static struct score score_v[MAXLVL];
-
-static char scores_file[MAXSTR];
-
-/*---------------------------------------------------------------------------*/
-
-static void level_store_hs(const char *filename)
-{
- FILE *fout;
-
- if ((fout = fopen(config_user(filename), "w")))
- {
- int i;
- int j;
-
- for (i = 0; i < limit; i++)
- for (j = 0; j < 3; j++)
- {
- if (strlen(score_v[i].time_n[j]) == 0)
- strcpy(score_v[i].time_n[j], DEFAULT_PLAYER);
- if (strlen(score_v[i].coin_n[j]) == 0)
- strcpy(score_v[i].coin_n[j], DEFAULT_PLAYER);
-
- fprintf(fout, "%d %d %s\n",
- score_v[i].time_t[j],
- score_v[i].time_c[j],
- score_v[i].time_n[j]);
- fprintf(fout, "%d %d %s\n",
- score_v[i].coin_t[j],
- score_v[i].coin_c[j],
- score_v[i].coin_n[j]);
- }
-
- fclose(fout);
- }
-}
-
-static void level_load_hs(const char *filename)
-{
- FILE *fin;
+ int i;
- limit = 1;
+ strcpy(s->player[0], "Hard");
+ strcpy(s->player[1], "Medium");
+ strcpy(s->player[2], "Easy");
+ strcpy(s->player[3], "");
- if ((fin = fopen(config_user(filename), "r")))
+ for (i = 0; i < NSCORE + 1; i++)
{
- int i;
-
- for (i = 0; i < count; i++)
- {
- if (fscanf(fin, "%d %d %s",
- &score_v[i].time_t[0],
- &score_v[i].time_c[0],
- score_v[i].time_n[0]) == 3 &&
- fscanf(fin, "%d %d %s",
- &score_v[i].coin_t[0],
- &score_v[i].coin_c[0],
- score_v[i].coin_n[0]) == 3 &&
- fscanf(fin, "%d %d %s",
- &score_v[i].time_t[1],
- &score_v[i].time_c[1],
- score_v[i].time_n[1]) == 3 &&
- fscanf(fin, "%d %d %s",
- &score_v[i].coin_t[1],
- &score_v[i].coin_c[1],
- score_v[i].coin_n[1]) == 3 &&
- fscanf(fin, "%d %d %s",
- &score_v[i].time_t[2],
- &score_v[i].time_c[2],
- score_v[i].time_n[2]) == 3 &&
- fscanf(fin, "%d %d %s",
- &score_v[i].coin_t[2],
- &score_v[i].coin_c[2],
- score_v[i].coin_n[2]) == 3)
- limit = i + 1;
- }
-
- fclose(fin);
+ s->timer[i] = timer;
+ s->coins[i] = coins;
}
}
/*---------------------------------------------------------------------------*/
-static void level_init_rc(const char *filename)
+static int level_scan_metadata(struct level *l, char *av)
{
- FILE *fin;
- char buf[MAXSTR];
+#define CASE(x) (strcmp((x), c) == 0)
+ char *c = av;
+ char *stop = av + strlen(av);
+ char *v, *e;
- count = 0;
- level = 0;
- coins = 0;
- score = 0;
- balls = 0;
-
- /* Load the levels list. */
-
- if ((fin = fopen(config_data(filename), "r")))
+ while (c < stop)
{
- while (count < MAXLVL && fgets(buf, MAXSTR, fin))
+ /* look for the start of the value */
+ v = strchr(c, '=');
+ if (v == NULL)
+ return 0;
+ *v = '\0';
+ v++;
+
+ /* look the end of the value */
+ e = strchr(v, '\n');
+ if (e == NULL)
+ return 0;
+ *e = '\0';
+ e++;
+
+ /* test metadata */
+ if (CASE("message"))
+ strcpy(l->message, v);
+ else if (CASE("back"))
+ strcpy(l->back, v);
+ else if (CASE("song"))
+ strcpy(l->song, v);
+ else if (CASE("grad"))
+ strcpy(l->grad, v);
+ else if (CASE("shot"))
+ strcpy(l->shot, v);
+ else if (CASE("goal"))
{
- sscanf(buf, "%s %s %s %s %d %d %s",
- level_v[count].file,
- level_v[count].back,
- level_v[count].shot,
- level_v[count].grad,
- &level_v[count].time,
- &level_v[count].goal,
- level_v[count].song);
- count++;
+ l->goal = atoi(v);
+ l->score.most_coins.coins[2] = l->goal;
}
- fclose(fin);
- }
-}
-
-static void level_init_hs(const char *filename)
-{
- char buf[MAXSTR];
- FILE *fin;
- int i = 0;
-
- /* Set some sane values in case the scores file is missing. */
-
- for (i = 0; i < MAXLVL; i++)
- {
- strcpy(score_v[i].time_n[0], "Hard");
- strcpy(score_v[i].time_n[1], "Medium");
- strcpy(score_v[i].time_n[2], "Easy");
-
- score_v[i].time_t[0] = i ? 59999 : 359999;
- score_v[i].time_t[1] = i ? 59999 : 359999;
- score_v[i].time_t[2] = i ? 59999 : 359999;
-
- score_v[i].time_c[0] = 0;
- score_v[i].time_c[1] = 0;
- score_v[i].time_c[2] = 0;
-
- strcpy(score_v[i].coin_n[0], "Hard");
- strcpy(score_v[i].coin_n[1], "Medium");
- strcpy(score_v[i].coin_n[2], "Easy");
-
- score_v[i].coin_t[0] = i ? 59999 : 359999;
- score_v[i].coin_t[1] = i ? 59999 : 359999;
- score_v[i].coin_t[2] = i ? 59999 : 359999;
-
- score_v[i].coin_c[0] = 0;
- score_v[i].coin_c[1] = 0;
- score_v[i].coin_c[2] = 0;
- }
-
- /* Load the default high scores file. */
-
- if ((fin = fopen(config_data(filename), "r")))
- {
- for (i = 0; i < MAXLVL && fgets(buf, MAXSTR, fin); i++)
- sscanf(buf, "%d %d %d %d %d %d",
- &score_v[i].time_t[0], &score_v[i].coin_c[0],
- &score_v[i].time_t[1], &score_v[i].coin_c[1],
- &score_v[i].time_t[2], &score_v[i].coin_c[2]);
-
- fclose(fin);
+ else if (CASE("time"))
+ {
+ l->time = atoi(v);
+ l->score.best_times.timer[2] = l->time;
+ l->score.unlock_goal.timer[2] = l->time;
+ }
+ else if (CASE("time_hs"))
+ sscanf(v, "%d %d",
+ &l->score.best_times.timer[0],
+ &l->score.best_times.timer[1]);
+ else if (CASE("goal_hs"))
+ sscanf(v, "%d %d",
+ &l->score.unlock_goal.timer[0],
+ &l->score.unlock_goal.timer[1]);
+ else if (CASE("coin_hs"))
+ sscanf(v, "%d %d",
+ &l->score.most_coins.coins[0],
+ &l->score.most_coins.coins[1]);
+ else if (CASE("version"))
+ strcpy(l->version, v);
+ else if (CASE("author"))
+ strcpy(l->author, v);
+ else if (CASE("special"))
+ l->is_bonus = atoi(v);
+
+ c = e;
}
+ return 1;
}
-/*---------------------------------------------------------------------------*/
+/* Load the sol file 'filename' and fill the 'level' structure. Return 1 on
+ * success, 0 on error. */
-const char *level_shot(int i)
+int level_load(const char *filename, struct level *level)
{
- return level_v[i].shot;
-}
+ struct s_file sol;
-const char *level_time_n(int i, int j)
-{
- return score_v[i].time_n[j];
-}
-
-const char *level_coin_n(int i, int j)
-{
- return score_v[i].coin_n[j];
-}
-
-/*---------------------------------------------------------------------------*/
-/* Return the coin count for the Most Coins or Best Time score. */
-
-int level_coin_c(int i, int j)
-{
- if (j < 0)
- return score;
- else
- return score_v[i].coin_c[j];
-}
-
-int level_time_c(int i, int j)
-{
- return score_v[i].time_c[j];
-}
-
-/*---------------------------------------------------------------------------*/
-/* Return the time for the Most Coins or Best Time score. */
-
-int level_coin_t(int i, int j)
-{
- return score_v[i].coin_t[j];
-}
-
-int level_time_t(int i, int j)
-{
- if (j < 0)
- return level_v[i].time - curr_clock();
- else
- return score_v[i].time_t[j];
-}
-
-/*---------------------------------------------------------------------------*/
-
-void level_init(const char *init_levels,
- const char *init_scores,
- const char *user_scores)
-{
- memset(level_v, 0, sizeof (struct level) * MAXLVL);
- memset(score_v, 0, sizeof (struct score) * MAXLVL);
-
- level_init_rc(init_levels);
- level_init_hs(init_scores);
- level_load_hs(user_scores);
-
- strncpy(scores_file, user_scores, MAXSTR);
-
- score = 0;
- coins = 0;
- balls = 2;
- level = 0;
-
- level_total = 0;
- coins_total = 0;
- times_total = 0;
-}
-
-void level_cheat(void)
-/* Open each level of the set */
-{
- limit = count;
-}
-
-void level_free(void)
-{
+ int money;
int i;
- level_store_hs(scores_file);
-
- for (i = 0; i < count; i++)
- if (glIsTexture(level_v[i].text))
- glDeleteTextures(1, &level_v[i].text);
-
- count = 0;
-}
-
-int level_exists(int i)
-{
- return (0 < i && i < count);
-}
-
-int level_opened(int i)
-{
- return level_exists(i) && (0 < i && i < count && i <= limit);
-}
-
-int level_locked(int i)
-{
- return level_opened(i) && (i == limit) && (level_v[i].goal > 0);
-}
-
-/*---------------------------------------------------------------------------*/
-
-int curr_times_total(void) { return times_total; }
-int curr_coins_total(void) { return coins_total; }
-
-int curr_count(void) { return count; }
-int curr_score(void) { return score; }
-int curr_coins(void) { return coins; }
-int curr_balls(void) { return balls; }
-int curr_level(void) { return level; }
-int curr_goal (void) { return goal; }
-
-/*---------------------------------------------------------------------------*/
-
-static int score_time_comp(const struct score *S, int i, int j)
-{
- if (S->time_t[i] < S->time_t[j])
- return 1;
-
- if (S->time_t[i] == S->time_t[j] &&
- S->time_c[i] > S->time_c[j])
- return 1;
-
- return 0;
-}
-
-static int score_coin_comp(const struct score *S, int i, int j)
-{
- if (S->coin_c[i] > S->coin_c[j])
- return 1;
-
- if (S->coin_c[i] == S->coin_c[j] &&
- S->coin_t[i] < S->coin_t[j])
- return 1;
+ memset(level, 0, sizeof (struct level));
+ memset(&sol, 0, sizeof (sol));
- return 0;
-}
-
-/*---------------------------------------------------------------------------*/
-
-static void score_time_swap(struct score *S, int i, int j)
-{
- char n[MAXNAM];
- int t;
- int c;
-
- strncpy(n, S->time_n[i], MAXNAM);
- strncpy(S->time_n[i], S->time_n[j], MAXNAM);
- strncpy(S->time_n[j], n, MAXNAM);
-
- t = S->time_t[i];
- S->time_t[i] = S->time_t[j];
- S->time_t[j] = t;
-
- c = S->time_c[i];
- S->time_c[i] = S->time_c[j];
- S->time_c[j] = c;
-}
-
-static void score_coin_swap(struct score *S, int i, int j)
-{
- char n[MAXNAM];
- int t;
- int c;
-
- strncpy(n, S->coin_n[i], MAXNAM);
- strncpy(S->coin_n[i], S->coin_n[j], MAXNAM);
- strncpy(S->coin_n[j], n, MAXNAM);
-
- t = S->coin_t[i];
- S->coin_t[i] = S->coin_t[j];
- S->coin_t[j] = t;
-
- c = S->coin_c[i];
- S->coin_c[i] = S->coin_c[j];
- S->coin_c[j] = c;
-}
-
-/*---------------------------------------------------------------------------*/
-
-int level_replay(const char *filename)
-{
- status = GAME_NONE;
-
- return demo_replay_init(filename, &score, &coins, &balls, &goal);
-}
-
-int level_play(const char *filename, int i, int m)
-{
- status = GAME_NONE;
- mode = m;
-
- if (i >= 0)
- {
- level = i;
- goal = level_v[level].goal;
- }
- return demo_play_init(USER_REPLAY_FILE,
- level_v[level].file,
- level_v[level].back,
- level_v[level].grad,
- level_v[level].song,
- level_v[level].shot,
- level_v[level].time,
- level_auto_opened() ? 0 : goal, score, coins, balls);
-}
-
-/*---------------------------------------------------------------------------*/
-
-void level_stat(int s)
-{
- if ((status = s) == GAME_GOAL)
+ /* Try to load the sol file */
+ if (!sol_load_only_head(&sol, config_data(filename)))
{
- coins_total += coins;
- level_total += 1;
- }
-
- demo_play_stat(curr_coins(), level_v[level].time - curr_clock());
-}
-
-int level_dead(void)
-{
- return (balls <= 0);
-}
-
-int level_last(void)
-{
- return (level + 1 == count);
-}
-
-int level_auto_opened(void)
-/* Is the level automatically opened */
-{
- return mode != MODE_CHALLENGE && level != limit;
-}
-
-int level_exit(const char *filename, int next)
-{
- times_total += level_v[level].time - curr_clock();
-
- demo_play_stop(filename);
-
- switch (status)
- {
- case GAME_GOAL:
- if (next) level++;
- if (limit < level)
- limit = level;
-
- level_store_hs(scores_file);
- break;
-
- case GAME_TIME:
- case GAME_FALL:
- if (mode == MODE_CHALLENGE)
- balls--;
- break;
- }
-
- /* Load the next level. */
-
- if (status && level < count && balls >= 0)
- {
- goal = level_v[level].goal;
- coins = 0;
- status = GAME_NONE;
-
- return demo_play_init(USER_REPLAY_FILE,
- level_v[level].file,
- level_v[level].back,
- level_v[level].grad,
- level_v[level].song,
- level_v[level].shot,
- level_v[level].time,
- level_auto_opened() ? 0 : goal, score, coins, balls);
+ fprintf(stderr,
+ _("Error while loading level file '%s': %s\n"), filename,
+ errno ? strerror(errno) : _("Not a valid level file"));
+ return 0;
}
- return 0;
-}
-
-int level_sort(int *time_i, int *coin_i)
-{
- int i, clock = level_v[level].time - curr_clock();
- char player[MAXNAM];
+ strcpy(level->file, filename);
- config_get_s(CONFIG_PLAYER, player, MAXNAM);
+ /* Init hs with default values */
+ score_init_hs(&level->score.best_times, 59999, 0);
+ score_init_hs(&level->score.unlock_goal, 59999, 0);
+ score_init_hs(&level->score.most_coins, 59999, 0);
- /* Insert the time record into the high score list. */
+ /* Compute money and default max money */
+ money = 0;
+ for (i = 0; i < sol.hc; i++)
+ if (sol.hv[i].t == ITEM_COIN)
+ money += sol.hv[i].n;
+ level->score.most_coins.coins[0] = money;
- strncpy(score_v[level].time_n[3], player, MAXNAM);
- score_v[level].time_c[3] = coins;
- score_v[level].time_t[3] = clock;
+ /* Scan sol metadata */
+ if (sol.ac > 0)
+ level_scan_metadata(level, sol.av);
- for (i = 2; i >= 0 && score_time_comp(score_v + level, i + 1, i); i--)
- {
- score_time_swap(score_v + level, i + 1, i);
- *time_i = i;
- }
+ /* Compute initial hs default values */
- /* Insert the coin record into the high score list. */
+#define HOP(t, c) \
+ if (t[2] c t[0]) \
+ t[0] = t[1] = t[2]; \
+ else if (t[2] c t[1]) \
+ t[1] = (t[0] + t[2]) / 2
- strncpy(score_v[level].coin_n[3], player, MAXNAM);
- score_v[level].coin_c[3] = coins;
- score_v[level].coin_t[3] = clock;
+ HOP(level->score.best_times.timer, <=);
+ HOP(level->score.unlock_goal.timer, <=);
+ HOP(level->score.most_coins.coins, >=);
- for (i = 2; i >= 0 && score_coin_comp(score_v + level, i + 1, i); i--)
- {
- score_coin_swap(score_v + level, i + 1, i);
- *coin_i = i;
- }
+ sol_free(&sol);
- return (*time_i < 3 || *coin_i < 3);
+ return 1;
}
-int level_done(int *time_i, int *coin_i)
-{
- int i;
- char player[MAXNAM];
-
- config_get_s(CONFIG_PLAYER, player, MAXNAM);
-
- /* Note a global high score. */
-
- strncpy(score_v[0].time_n[3], player, MAXNAM);
- score_v[0].time_c[3] = coins_total;
- score_v[0].time_t[3] = times_total;
-
- strncpy(score_v[0].coin_n[3], player, MAXNAM);
- score_v[0].coin_c[3] = coins_total;
- score_v[0].coin_t[3] = times_total;
-
- if (level == count && level_total == count - 1)
- {
- /* Insert the time record into the global high score list. */
-
- for (i = 2; i >= 0 && score_time_comp(score_v, i + 1, i); i--)
- {
- score_time_swap(score_v, i + 1, i);
- *time_i = i;
- }
-
- /* Insert the coin record into the global high score list. */
-
- for (i = 2; i >= 0 && score_coin_comp(score_v, i + 1, i); i--)
- {
- score_coin_swap(score_v, i + 1, i);
- *coin_i = i;
- }
- }
+/*---------------------------------------------------------------------------*/
- return (*time_i < 3 || *coin_i < 3);
+void level_dump_info(const struct level *l)
+{
+ printf("filename: %s\n"
+ "version: %s\n"
+ "author: %s\n"
+ "time limit: %d\n"
+ "goal count: %d\n"
+ "time hs: %d %d %d\n"
+ "goal hs: %d %d %d\n"
+ "coin hs: %d %d %d\n"
+ "message: %s\n"
+ "background: %s\n"
+ "gradiant: %s\n"
+ "screenshot: %s\n"
+ "song: %s\n",
+ l->file,
+ l->version,
+ l->author,
+ l->time,
+ l->goal,
+ l->score.best_times.timer[0],
+ l->score.best_times.timer[1],
+ l->score.best_times.timer[2],
+ l->score.unlock_goal.timer[0],
+ l->score.unlock_goal.timer[1],
+ l->score.unlock_goal.timer[2],
+ l->score.most_coins.coins[0],
+ l->score.most_coins.coins[1],
+ l->score.most_coins.coins[2],
+ l->message,
+ l->back,
+ l->grad,
+ l->shot,
+ l->song);
}
-int level_score(int n)
-{
- int sound = AUD_COIN;
- int value = level_auto_opened();
-
- coins += n;
-
- /* Pulse the coin counter based on the value of the grabbed coin. */
-
- if (n >= 10) hud_coin_pulse(2.00f);
- else if (n >= 5) hud_coin_pulse(1.50f);
- else hud_coin_pulse(1.25f);
-
- /* Check for goal open. */
-
- if (goal > 0)
- {
- if (n >= 10) hud_goal_pulse(2.00f);
- else if (n >= 5) hud_goal_pulse(1.50f);
- else hud_goal_pulse(1.25f);
-
- if (goal - n <= 0)
- {
- sound = AUD_SWITCH;
- value = 1;
- hud_goal_pulse(2.0f);
- }
-
- goal = (goal > n) ? (goal - n) : 0;
- }
-
- audio_play(sound, 1.f);
- return value;
-}
+/*---------------------------------------------------------------------------*/
-int level_count(void)
+const char *mode_to_str(int m)
{
- if (mode != MODE_CHALLENGE)
- return 0;
- if (coins > 0)
+ switch (m)
{
- score++;
- coins--;
-
- if (score % 100 == 0)
- {
- balls += 1;
- audio_play(AUD_BALL, 1.0f);
- }
- return 1;
+ case MODE_CHALLENGE: return _("Challenge");
+ case MODE_NORMAL: return _("Normal");
+ case MODE_PRACTICE: return _("Practice");
+ case MODE_SINGLE: return _("Single");
+ default: return _("Unknown");
}
- return 0;
}
/*---------------------------------------------------------------------------*/
-void level_name(int i, const char *name, int time_i, int coin_i)
+const char *state_to_str(int m)
{
- strncpy(score_v[i].time_n[time_i], name, MAXNAM);
- strncpy(score_v[i].coin_n[coin_i], name, MAXNAM);
-}
-
-void level_snap(int i)
-{
- char filename[MAXSTR];
-
- /* Convert the level name to a BMP filename. */
-
- memset(filename, 0, MAXSTR);
- strncpy(filename, level_v[i].file, strcspn(level_v[i].file, "."));
- strcat(filename, ".bmp");
-
- /* Initialize the game for a snapshot. */
-
- if (game_init(level_v[i].file, level_v[i].back, level_v[i].grad, 0, 1))
+ switch (m)
{
- /* Render the level and grab the screen. */
-
- config_clear();
- game_set_fly(1.f);
- game_kill_fade();
- game_draw(1, 0);
- SDL_GL_SwapBuffers();
-
- image_snap(filename);
+ case GAME_NONE: return _("Aborted");
+ case GAME_TIME: return _("Time-out");
+ case GAME_SPEC:
+ case GAME_GOAL: return _("Success");
+ case GAME_FALL: return _("Fall-out");
+ default: return _("Unknown");
}
}
/*---------------------------------------------------------------------------*/
-
-int level_mode(void)
-{
- return mode;
-}
-