share/solid.o \
share/binary.o \
share/base_config.o \
+ share/config.o \
+ share/sync.o \
share/mapc.o
BALL_OBJS := \
share/lang.o \
share/tilt.o \
share/common.o \
ball/hud.o \
- ball/mode.o \
ball/game.o \
ball/score.o \
ball/level.o \
- ball/levels.o \
+ ball/progress.o \
ball/set.o \
ball/demo.o \
ball/util.o \
putt/hole.o \
putt/course.o \
putt/st_all.o \
+ putt/st_balt.o \
putt/st_conf.o \
putt/main.o
$(CC) $(ALL_CFLAGS) -o $(PUTT_TARG) $(PUTT_OBJS) $(LDFLAGS) $(ALL_LIBS)
$(MAPC_TARG) : $(MAPC_OBJS)
- $(CC) $(ALL_CFLAGS) -o $(MAPC_TARG) $(MAPC_OBJS) $(LDFLAGS) $(BASE_LIBS)
+ $(CC) $(ALL_CFLAGS) -o $(MAPC_TARG) $(MAPC_OBJS) $(LDFLAGS) $(ALL_LIBS)
# Work around some extremely helpful sdl-config scripts.
#include "binary.h"
#include "text.h"
#include "common.h"
+#include "level.h"
/*---------------------------------------------------------------------------*/
#define MAGIC 0x52424EAF
-#define DEMO_VERSION 5
+#define DEMO_VERSION 6
#define DATELEN 20
"Player: %s\n"
"Shot: %s\n"
"Level: %s\n"
- " Back: %s\n"
- " Grad: %s\n"
- " Song: %s\n"
"Time: %d\n"
"Goal: %d\n"
+ "Goal enabled: %d\n"
"Score: %d\n"
"Balls: %d\n"
"Total Time: %d\n",
d->name, d->filename,
d->timer, d->coins, d->mode, d->status, ctime(&d->date),
d->player,
- d->shot, d->file, d->back, d->grad, d->song,
- d->time, d->goal, d->score, d->balls, d->times);
+ d->shot, d->file,
+ d->time, d->goal, d->goal_e, d->score, d->balls, d->times);
}
static int demo_header_read(FILE *fp, struct demo *d)
get_index(fp, &d->time);
get_index(fp, &d->goal);
+ get_index(fp, &d->goal_e);
get_index(fp, &d->score);
get_index(fp, &d->balls);
get_index(fp, &d->times);
put_index(fp, &d->time);
put_index(fp, &d->goal);
+ put_index(fp, &d->goal_e);
put_index(fp, &d->score);
put_index(fp, &d->balls);
put_index(fp, &d->times);
/*---------------------------------------------------------------------------*/
-int demo_play_init(const char *name,
- const struct level *level,
- const struct level_game *lg)
+int demo_play_init(const char *name, const struct level *level,
+ int mode, int t, int g, int e, int s, int b, int tt)
{
struct demo demo;
strncpy(demo.filename, config_user(name), MAXSTR);
strcat(demo.filename, REPLAY_EXT);
- demo.mode = lg->mode;
+ demo.mode = mode;
demo.date = time(NULL);
config_get_s(CONFIG_PLAYER, demo.player, MAXNAM);
strncpy(demo.shot, level->shot, PATHMAX);
strncpy(demo.file, level->file, PATHMAX);
- strncpy(demo.back, level->back, PATHMAX);
- strncpy(demo.grad, level->grad, PATHMAX);
- strncpy(demo.song, level->song, PATHMAX);
- demo.time = lg->time;
- demo.goal = lg->goal;
- demo.score = lg->score;
- demo.balls = lg->balls;
- demo.times = lg->times;
+ demo.time = t;
+ demo.goal = g;
+ demo.goal_e = e;
+ demo.score = s;
+ demo.balls = b;
+ demo.times = tt;
if ((demo_fp = fopen(demo.filename, FMODE_WB)))
{
demo_header_write(demo_fp, &demo);
audio_music_fade_to(2.0f, level->song);
- return game_init(level, lg->time, lg->goal);
+ return game_init(level->file, t, e);
}
return 0;
}
input_put(demo_fp);
}
-void demo_play_stat(const struct level_game *lg)
+void demo_play_stat(int status, int coins, int timer)
{
if (demo_fp)
{
fseek(demo_fp, 8, SEEK_SET);
- put_index(demo_fp, &lg->timer);
- put_index(demo_fp, &lg->coins);
- put_index(demo_fp, &lg->status);
+ put_index(demo_fp, &timer);
+ put_index(demo_fp, &coins);
+ put_index(demo_fp, &status);
fseek(demo_fp, pos, SEEK_SET);
}
/*---------------------------------------------------------------------------*/
-static int demo_load_level(const struct demo *demo, struct level *level)
-{
- if (level_load(demo->file, level))
- {
- level->time = demo->time;
- level->goal = demo->goal;
- return 1;
- }
- return 0;
-}
-
static struct demo demo_replay; /* The current demo */
static struct level demo_level_replay; /* The current level demo-ed*/
static int demo_status = GAME_NONE;
-int demo_replay_init(const char *name, struct level_game *lg)
+int demo_replay_init(const char *name, int *g, int *m, int *b, int *s, int *tt)
{
demo_status = GAME_NONE;
demo_fp = fopen(name, FMODE_RB);
base_name(text_from_locale(demo_replay.filename), REPLAY_EXT),
PATHMAX);
- if (!demo_load_level(&demo_replay, &demo_level_replay))
+ if (level_load(demo_replay.file, &demo_level_replay))
+ {
+ demo_level_replay.time = demo_replay.time;
+ demo_level_replay.goal = demo_replay.goal;
+ }
+ else
return 0;
- if (lg)
- {
- lg->mode = demo_replay.mode;
- lg->score = demo_replay.score;
- lg->times = demo_replay.times;
- lg->time = demo_replay.time;
- lg->goal = demo_replay.goal;
+ if (g) *g = demo_replay.goal;
+ if (m) *m = demo_replay.mode;
+ if (b) *b = demo_replay.balls;
+ if (s) *s = demo_replay.score;
+ if (tt) *tt = demo_replay.times;
- /* A normal replay demo */
+ if (g)
+ {
audio_music_fade_to(0.5f, demo_level_replay.song);
- return game_init(&demo_level_replay, demo_replay.time,
- demo_replay.goal);
+
+ return game_init(demo_level_replay.file,
+ demo_level_replay.time,
+ demo_replay.goal_e);
}
- else /* A title screen demo */
- return game_init(&demo_level_replay, demo_replay.time, 0);
+ else
+ return game_init(demo_level_replay.file,
+ demo_level_replay.time, 1);
}
return 0;
}
struct demo
{
- char name[PATHMAX]; /* demo basename */
- char filename[MAXSTR]; /* demo path */
-
- int timer; /* elapsed time */
- int coins; /* coin number */
- int status; /* how the replay end */
- int mode; /* game mode */
- time_t date; /* date of creation */
- char player[MAXNAM]; /* player name */
+ char name[PATHMAX]; /* demo basename */
+ char filename[MAXSTR]; /* demo path */
+
+ char player[MAXNAM];
+ time_t date;
+
+ int timer;
+ int coins;
+ int status;
+ int mode;
+
char shot[PATHMAX]; /* image filename */
char file[PATHMAX]; /* level filename */
- char back[PATHMAX]; /* level bg filename */
- char grad[PATHMAX]; /* level gradient filename */
- char song[PATHMAX]; /* level song filename */
- int time; /* time limit (! training mode) */
- int goal; /* coin to open the goal (! training mode) */
- int score; /* sum of coins (challenge mode) */
- int balls; /* number of balls (challenge mode) */
- int times; /* total time (challenge mode) */
+
+ int time; /* time limit */
+ int goal; /* coin limit */
+ int goal_e; /* goal enabled flag */
+ int score; /* total coins */
+ int balls; /* number of balls */
+ int times; /* total time */
};
/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/
int demo_play_init(const char *, const struct level *,
- const struct level_game *);
+ int, int, int, int, int, int, int);
void demo_play_step(void);
-void demo_play_stat(const struct level_game *);
+void demo_play_stat(int, int, int);
void demo_play_stop(void);
int demo_saved (void);
/*---------------------------------------------------------------------------*/
-int demo_replay_init(const char *, struct level_game *);
+int demo_replay_init(const char *, int *, int *, int *, int *, int *);
int demo_replay_step(float);
void demo_replay_stop(int);
void demo_replay_dump_info(void);
static float view_k;
static int coins = 0; /* Collected coins */
-static int goal_c = 0; /* Goal coins remaining (0 = open) */
+static int goal_e = 0; /* Goal enabled flag */
static float goal_k = 0; /* Goal animation */
static int jump_e = 1; /* Jumping enabled flag */
static int jump_b = 0; /* Jump-in-progress flag */
static float jump_dt; /* Jump duration */
+static int jump_y = 0; /* Which arbitrary ball is jumping? */
static float jump_p[3]; /* Jump destination */
static float fade_k = 0.0; /* Fade in/out level */
static float fade_d = 0.0; /* Fade in/out direction */
view_e[2][2] = 1.f;
}
-int game_init(const struct level *level, int t, int g)
+int game_init(const char *file_name, int t, int e)
{
+ char *back_name = NULL, *grad_name = NULL;
+
+ int i;
+
timer = (float) t / 100.f;
timer_down = (t > 0);
coins = 0;
if (game_state)
game_free();
- if (!sol_load_gl(&file, config_data(level->file),
+ if (!sol_load_gl(&file, config_data(file_name),
config_get_d(CONFIG_TEXTURES),
config_get_d(CONFIG_SHADOW)))
return (game_state = 0);
jump_e = 1;
jump_b = 0;
+ jump_y = 0;
- goal_c = g;
- goal_k = (g == 0) ? 1.0f : 0.0f;
+ goal_e = e ? 1 : 0;
+ goal_k = e ? 1.0f : 0.0f;
/* Initialise the level, background, particles, fade, and view. */
fade_k = 1.0f;
fade_d = -2.0f;
+ for (i = 0; i < file.dc; i++)
+ {
+ char *k = file.av + file.dv[i].ai;
+ char *v = file.av + file.dv[i].aj;
+
+ if (strcmp(k, "back") == 0) back_name = v;
+ if (strcmp(k, "grad") == 0) grad_name = v;
+ }
+
part_reset(GOAL_HEIGHT);
view_init();
- back_init(level->grad, config_get_d(CONFIG_GEOMETRY));
+ back_init(grad_name, config_get_d(CONFIG_GEOMETRY));
- sol_load_gl(&back, config_data(level->back),
+ sol_load_gl(&back, config_data(back_name),
config_get_d(CONFIG_TEXTURES), 0);
/* Initialize ball size tracking... */
return coins;
}
-int curr_goal(void)
+/*---------------------------------------------------------------------------*/
+
+void game_check_balls(struct s_file *fp)
{
- return goal_c;
-}
+ float z[3] = {0.0f, 0.0f, 0.0f};
+ int i;
-/*---------------------------------------------------------------------------*/
+ for (i = 0; i < fp->yc; i++)
+ {
+ struct s_ball *yp = fp->yv + i;
+
+ /*
+ * Test and deal with any fall-outs
+ */
+ if (yp->p[1] < fp->vv[0].p[1] && yp->n)
+ {
+ v_cpy(yp->p, yp->O);
+ v_cpy(yp->v, z);
+ v_cpy(yp->w, z);
+ }
+ }
+}
static void game_draw_balls(const struct s_file *fp,
const float *bill_M, float t)
{
- float c[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
+ float c[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
float ball_M[16];
float pend_M[16];
+ int yi;
+
+ for (yi = 0; yi < fp->yc; yi++)
+ {
+ float ball_M[16];
+ float pend_M[16];
+
+ m_basis(ball_M, fp->yv[yi].e[0], fp->yv[yi].e[1], fp->yv[yi].e[2]);
+ m_basis(pend_M, fp->yv[yi].E[0], fp->yv[yi].E[1], fp->yv[yi].E[2]);
+
+ glPushMatrix();
+ {
+ glTranslatef(fp->yv[yi].p[0],
+ fp->yv[yi].p[1] + BALL_FUDGE,
+ fp->yv[yi].p[2]);
+ glScalef(fp->yv[yi].r,
+ fp->yv[yi].r,
+ fp->yv[yi].r);
+
+ glColor4fv(c);
+ ball_draw(ball_M, pend_M, bill_M, t);
+ }
+ glPopMatrix();
+ }
+
m_basis(ball_M, fp->uv[0].e[0], fp->uv[0].e[1], fp->uv[0].e[2]);
m_basis(pend_M, fp->uv[0].E[0], fp->uv[0].E[1], fp->uv[0].E[2]);
static void game_draw_goals(const struct s_file *fp, const float *M, float t)
{
- if (goal_c == 0)
+ if (goal_e)
{
int zi;
{
float fov = view_fov;
- if (jump_b) fov *= 2.f * fabsf(jump_dt - 0.5);
+ if (jump_b && jump_y)
+ fov /= 1.9f * fabsf(jump_dt - 0.5f);
+
+ else if (jump_b)
+ fov *= 2.0f * fabsf(jump_dt - 0.5f);
if (game_state)
{
static void game_update_time(float dt, int b)
{
- if (goal_c == 0 && goal_k < 1.0f)
+ if (goal_e && goal_k < 1.0f)
goal_k += dt;
/* The ticking clock. */
static int game_update_state(int bt)
{
struct s_file *fp = &file;
- struct s_goal *zp;
struct s_item *hp;
float p[3];
float c[3];
+ int i;
+
/* Test for an item. */
if (bt && (hp = sol_item_test(fp, p, COIN_RADIUS)))
{
- const char *sound = AUD_COIN;
-
item_color(hp, c);
part_burst(p, c);
grow_init(fp, hp->t);
if (hp->t == ITEM_COIN)
- {
coins += hp->n;
- /* Check for goal open. */
-
- if (goal_c > 0)
- {
- goal_c -= hp->n;
- if (goal_c <= 0)
- {
- sound = AUD_SWITCH;
- goal_c = 0;
- }
- }
- }
- audio_play(sound, 1.f);
-
- /* Reset item type. */
-
hp->t = ITEM_NONE;
}
/* Test for a switch. */
- if (sol_swch_test(fp, 0))
+ if (sol_swch_test(fp))
audio_play(AUD_SWITCH, 1.f);
/* Test for a jump. */
- if (jump_e == 1 && jump_b == 0 && sol_jump_test(fp, jump_p, 0) == 1)
+ if (!jump_y && jump_e == 1 && jump_b == 0 && sol_jump_test(fp, jump_p, 0) == 1)
{
jump_b = 1;
jump_e = 0;
jump_dt = 0.f;
+ jump_y = 0;
audio_play(AUD_JUMP, 1.f);
}
if (jump_e == 0 && jump_b == 0 && sol_jump_test(fp, jump_p, 0) == 0)
jump_e = 1;
+ if (!jump_b && !jump_y && sol_jump_test(fp, jump_p, 0) == 0)
+ jump_y = 0;
+
+ for (i = 0; i < fp->yc; i++)
+ {
+ if (!jump_y && jump_e == 1
+ && jump_b == 0
+ && sol_jump_test(fp, jump_p, fp->yv + i - fp->uv) == 1)
+ {
+ jump_b = 1;
+ jump_e = 0;
+ jump_dt = 0.f;
+ jump_y = i + 1;
+
+ audio_play(AUD_JUMP, 1.f);
+ }
+ if (jump_e == 0 && jump_b == 0
+ && sol_jump_test(fp, jump_p, fp->yv + i - fp->uv) == 0)
+ jump_e = 1;
+ if (!jump_b && jump_y && i == jump_y - 1
+ && sol_jump_test(fp, jump_p, fp->yv + i - fp->uv) == 0)
+ jump_y = 0;
+ }
/* Test for a goal. */
- if (bt && goal_c == 0 && (zp = sol_goal_test(fp, p, 0)))
+ if (bt && goal_e && (sol_goal_test(fp, p, 0)))
{
audio_play(AUD_GOAL, 1.0f);
return GAME_GOAL;
if (0.5f < jump_dt)
{
- fp->uv[0].p[0] = jump_p[0];
- fp->uv[0].p[1] = jump_p[1];
- fp->uv[0].p[2] = jump_p[2];
+ if (jump_y)
+ {
+ fp->yv[jump_y - 1].p[0] = jump_p[0];
+ fp->yv[jump_y - 1].p[1] = jump_p[1];
+ fp->yv[jump_y - 1].p[2] = jump_p[2];
+ }
+
+ else
+ {
+ fp->uv[0].p[0] = jump_p[0];
+ fp->uv[0].p[1] = jump_p[1];
+ fp->uv[0].p[2] = jump_p[2];
+ }
}
if (1.0f < jump_dt)
jump_b = 0;
float b = sol_step(fp, h, dt, 0, NULL);
+ game_check_balls(fp);
+
/* Mix the sound of a ball bounce. */
if (b > 0.5f)
/*---------------------------------------------------------------------------*/
+void game_set_goal(void)
+{
+ audio_play(AUD_SWITCH, 1.0f);
+ goal_e = 1;
+}
+
+void game_clr_goal(void)
+{
+ goal_e = 0;
+}
+
+/*---------------------------------------------------------------------------*/
+
void game_set_x(int k)
{
input_set_x(-ANGLE_BOUND * k / JOY_MAX);
}
/*---------------------------------------------------------------------------*/
+
+const char *status_to_str(int s)
+{
+ switch (s)
+ {
+ case GAME_NONE: return _("Aborted");
+ case GAME_TIME: return _("Time-out");
+ case GAME_GOAL: return _("Success");
+ case GAME_FALL: return _("Fall-out");
+ default: return _("Unknown");
+ }
+}
+
+/*---------------------------------------------------------------------------*/
#include <stdio.h>
-#include "level.h"
-#include "mode.h"
+#include "lang.h"
/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/
-int game_init(const struct level *, int, int);
+int game_init(const char *, int, int);
void game_free(void);
int curr_clock(void);
int curr_coins(void);
-int curr_goal(void);
void game_draw(int, float);
int game_step(const float[3], float, int);
+void game_set_goal(void);
+void game_clr_goal(void);
+
void game_set_ang(int, int);
void game_set_pos(int, int);
void game_set_x (int);
/*---------------------------------------------------------------------------*/
+#define GAME_NONE 0
+#define GAME_TIME 1
+#define GAME_GOAL 2
+#define GAME_FALL 3
+
+const char *status_to_str(int);
+
+/*---------------------------------------------------------------------------*/
+
#endif
#include "hud.h"
#include "gui.h"
#include "game.h"
-#include "levels.h"
+#include "progress.h"
#include "config.h"
#include "audio.h"
static int Lhud_id;
static int Rhud_id;
-static int Rhud2_id;
static int time_id;
static int coin_id;
-static int coin2_id;
static int ball_id;
static int scor_id;
static int goal_id;
gui_layout(Rhud_id, +1, -1);
}
- if ((Rhud2_id = gui_hstack(0)))
- {
- gui_label(Rhud2_id, _("Coins"), GUI_SML, 0, gui_wht, gui_wht);
- coin2_id = gui_count(Rhud2_id, 100, GUI_SML, GUI_NW);
- gui_layout(Rhud2_id, +1, -1);
- }
-
if ((Lhud_id = gui_hstack(0)))
{
if ((id = gui_vstack(Lhud_id)))
void hud_paint(void)
{
- int mode = curr_lg()->mode;
-
- if (mode == MODE_CHALLENGE)
+ if (curr_mode() == MODE_CHALLENGE)
gui_paint(Lhud_id);
- if (mode == MODE_PRACTICE)
- gui_paint(Rhud2_id);
- else
- gui_paint(Rhud_id);
-
+ gui_paint(Rhud_id);
gui_paint(time_id);
if (config_get_d(CONFIG_FPS))
void hud_update(int pulse)
{
- const struct level_game *lg = curr_lg();
-
int clock = curr_clock();
int coins = curr_coins();
int goal = curr_goal();
- int balls = lg->balls;
- int score = lg->score;
- int mode = lg->mode;
+ int balls = curr_balls();
+ int score = curr_score();
int c_id;
int last;
{
gui_set_clock(time_id, clock);
- if (last > clock && pulse && mode != MODE_PRACTICE)
+ if (last > clock && pulse)
{
if (clock <= 1000 && (last / 100) > (clock / 100))
{
/* balls and score + select coin widget */
- switch (mode)
+ switch (curr_mode())
{
case MODE_CHALLENGE:
if (gui_value(ball_id) != balls) gui_set_count(ball_id, balls);
c_id = coin_id;
break;
- case MODE_NORMAL:
- c_id = coin_id;
- break;
-
default:
- c_id = coin2_id;
+ c_id = coin_id;
break;
}
#include <string.h>
#include <math.h>
#include <errno.h>
+#include <assert.h>
+#include "solid.h"
#include "config.h"
-#include "demo.h"
-#include "text.h"
#include "level.h"
-#include "mode.h"
#include "set.h"
-#include "solid.h"
/*---------------------------------------------------------------------------*/
if (strcmp(k, "message") == 0)
strncpy(l->message, v, MAXSTR);
- else if (strcmp(k, "back") == 0)
- strncpy(l->back, v, PATHMAX);
else if (strcmp(k, "song") == 0)
strncpy(l->song, v, PATHMAX);
- else if (strcmp(k, "grad") == 0)
- strncpy(l->grad, v, PATHMAX);
else if (strcmp(k, "shot") == 0)
strncpy(l->shot, v, PATHMAX);
else if (strcmp(k, "goal") == 0)
"goal hs: %d %d %d\n"
"coin hs: %d %d %d\n"
"message: %s\n"
- "background: %s\n"
- "gradient: %s\n"
"screenshot: %s\n"
"song: %s\n",
l->file,
l->score.most_coins.coins[1],
l->score.most_coins.coins[2],
l->message,
- l->back,
- l->grad,
l->shot,
l->song);
}
/*---------------------------------------------------------------------------*/
-static unsigned int do_level_init = 1;
-
-int level_replay(const char *filename)
+int level_exists(int i)
{
- return demo_replay_init(filename, curr_lg());
+ return set_level_exists(curr_set(), i);
}
-int level_play(const struct level *l, int m)
+void level_open(int i)
{
- struct level_game *lg = curr_lg();
-
- if (do_level_init)
- {
- memset(lg, 0, sizeof (struct level_game));
-
- lg->mode = m;
- lg->level = l;
- lg->balls = 3;
- }
-
- lg->goal = (lg->mode == MODE_PRACTICE) ? 0 : lg->level->goal;
- lg->time = (lg->mode == MODE_PRACTICE) ? 0 : lg->level->time;
-
- /* Clear other fields. */
-
- lg->status = GAME_NONE;
- lg->coins = 0;
- lg->timer = lg->time;
- lg->coin_rank = lg->goal_rank = lg->time_rank =
- lg->score_rank = lg->times_rank = 3;
-
- lg->win = lg->dead = lg->unlock = 0;
- lg->next_level = NULL;
- lg->bonus = 0;
-
- return demo_play_init(USER_REPLAY_FILE, lg->level, lg);
+ if (level_exists(i))
+ get_level(i)->is_locked = 0;
}
-void level_stat(int status, int clock, int coins)
+int level_opened(int i)
{
- struct level_game *lg = curr_lg();
-
- int mode = lg->mode;
- int timer = (mode == MODE_PRACTICE) ? clock : lg->time - clock;
-
- char player[MAXNAM];
-
- config_get_s(CONFIG_PLAYER, player, MAXNAM);
-
- lg->status = status;
- lg->coins = coins;
- lg->timer = timer;
-
- if (mode == MODE_CHALLENGE)
- {
- /* sum time */
- lg->times += timer;
+ return level_exists(i) && !get_level(i)->is_locked;
+}
- /* sum coins an earn extra balls */
- if (status == GAME_GOAL || lg->level->is_bonus)
- {
- lg->balls += count_extra_balls(lg->score, coins);
- lg->score += coins;
- }
+void level_complete(int i)
+{
+ if (level_exists(i))
+ get_level(i)->is_completed = 1;
+}
- /* lose ball and game */
- else
- {
- lg->dead = (lg->balls <= 0);
- lg->balls--;
- }
- }
+int level_completed(int i)
+{
+ return level_exists(i) && get_level(i)->is_completed;
+}
- set_finish_level(lg, player);
+int level_time (int i)
+{
+ assert(level_exists(i));
+ return get_level(i)->time;
+}
- demo_play_stat(lg);
+int level_goal (int i)
+{
+ assert(level_exists(i));
+ return get_level(i)->goal;
}
-void level_stop(void)
+int level_bonus(int i)
{
- demo_play_stop();
- do_level_init = 1;
+ return level_exists(i) && get_level(i)->is_bonus;
}
-int level_next(void)
+const char *level_shot(int i)
{
- struct level_game *lg = curr_lg();
+ return level_exists(i) ? get_level(i)->shot : NULL;
+}
- level_stop();
- lg->level = lg->next_level;
- do_level_init = 0;
+const char *level_file(int i)
+{
+ return level_exists(i) ? get_level(i)->file : NULL;
+}
- return level_play(lg->level, lg->mode);
+const char *level_repr(int i)
+{
+ return level_exists(i) ? get_level(i)->repr : NULL;
}
-int level_same(void)
+const char *level_msg(int i)
{
- level_stop();
- do_level_init = 0;
- return level_play(curr_lg()->level, curr_lg()->mode);
+ if (level_exists(i) && strlen(get_level(i)->message) > 0)
+ return _(get_level(i)->message);
+
+ return NULL;
}
/*---------------------------------------------------------------------------*/
-int count_extra_balls(int old_score, int coins)
+int level_score_update(int level,
+ int timer,
+ int coins,
+ int *time_rank,
+ int *goal_rank,
+ int *coin_rank)
{
- return ((old_score % 100) + coins) / 100;
-}
+ struct level *l = get_level(level);
+ char player[MAXSTR] = "";
-void level_update_player_name(void)
-{
- char player[MAXNAM];
+ config_get_s(CONFIG_PLAYER, player, MAXSTR);
- config_get_s(CONFIG_PLAYER, player, MAXNAM);
+ if (time_rank)
+ *time_rank = score_time_insert(&l->score.best_times,
+ player, timer, coins);
- score_change_name(curr_lg(), player);
-}
+ if (goal_rank)
+ *goal_rank = score_time_insert(&l->score.unlock_goal,
+ player, timer, coins);
-/*---------------------------------------------------------------------------*/
+ if (coin_rank)
+ *coin_rank = score_coin_insert(&l->score.most_coins,
+ player, timer, coins);
-const char *status_to_str(int m)
+ if ((time_rank && *time_rank < 3) ||
+ (goal_rank && *goal_rank < 3) ||
+ (coin_rank && *coin_rank < 3))
+ return 1;
+ else
+ return 0;
+}
+
+void level_rename_player(int level,
+ int time_rank,
+ int goal_rank,
+ int coin_rank,
+ const char *player)
{
- switch (m)
- {
- case GAME_NONE: return _("Aborted");
- case GAME_TIME: return _("Time-out");
- case GAME_GOAL: return _("Success");
- case GAME_FALL: return _("Fall-out");
- default: return _("Unknown");
- }
+ struct level *l = get_level(level);
+
+ strncpy(l->score.best_times.player [time_rank], player, MAXNAM);
+ strncpy(l->score.unlock_goal.player[goal_rank], player, MAXNAM);
+ strncpy(l->score.most_coins.player [coin_rank], player, MAXNAM);
}
/*---------------------------------------------------------------------------*/
+
#include "base_config.h"
#include "score.h"
-#include "levels.h"
+#include "progress.h"
/*---------------------------------------------------------------------------*/
struct level
{
+ /* TODO: turn into an internal structure. */
+
char file[PATHMAX];
- char back[PATHMAX];
- char grad[PATHMAX];
char shot[PATHMAX];
char song[PATHMAX];
int level_load(const char *, struct level *);
void level_dump(const struct level *);
-int level_replay(const char *);
-int level_play(const struct level *, int);
-void level_stat(int, int, int);
-void level_stop(void);
-int level_next(void);
-int level_same(void);
-
/*---------------------------------------------------------------------------*/
-int count_extra_balls(int, int);
+int level_exists(int);
+
+void level_open (int);
+int level_opened(int);
-void level_update_player_name(void);
+void level_complete (int);
+int level_completed(int);
+
+int level_time(int);
+int level_goal(int);
+int level_bonus(int);
+
+const char *level_shot(int);
+const char *level_file(int);
+const char *level_repr(int);
+const char *level_msg (int);
/*---------------------------------------------------------------------------*/
-#define GAME_NONE 0 /* No event (or aborted) */
-#define GAME_TIME 1 /* Time's up */
-#define GAME_GOAL 2 /* Goal reached */
-#define GAME_FALL 3 /* Fall out */
+int level_score_update (int, int, int, int *, int *, int *);
+void level_rename_player(int, int, int, int, const char *);
-const char *status_to_str(int);
+/*---------------------------------------------------------------------------*/
#endif
#include "image.h"
#include "audio.h"
#include "demo.h"
-#include "levels.h"
+#include "progress.h"
#include "game.h"
#include "gui.h"
#include "set.h"
if (display_info)
{
- if (!level_replay(demo_path))
+ if (!progress_replay(demo_path))
{
fprintf(stderr, L_("Replay file '%s': %s\n"), demo_path,
errno ? strerror(errno) : L_("Not a replay file"));
/* Initialise demo playback. */
- if (replay_demo)
+ if (replay_demo && progress_replay(demo_path))
{
- level_replay(demo_path);
demo_play_goto(1);
goto_state(&st_demo_play);
}
char user_scores[PATHMAX]; /* User high-score file */
- struct score time_score; /* Challenge score */
struct score coin_score; /* Challenge score */
+ struct score time_score; /* Challenge score */
/* Level info */
int j;
for (j = 0; j < NSCORE; j++)
- fprintf(fp, "%d %d %s\n", s->timer[j], s->coins[j], s->player[j]);
+ fprintf(fp, "%d %d %s\n", s->timer[j], s->coins[j], s->player[j]);
}
-/* Store the score of the set. */
-static void set_store_hs(void)
+void set_store_hs(void)
{
const struct set *s = &set_v[set];
FILE *fout;
{
l = &level_v[i];
res = get_score(fin, &l->score.best_times) &&
- get_score(fin, &l->score.unlock_goal) &&
- get_score(fin, &l->score.most_coins);
+ get_score(fin, &l->score.unlock_goal) &&
+ get_score(fin, &l->score.most_coins);
}
fclose(fin);
return set;
}
-const struct level *get_level(int i)
+struct level *get_level(int i)
{
return (i >= 0 && i < set_v[set].count) ? &level_v[i] : NULL;
}
/*---------------------------------------------------------------------------*/
-/* Update the level score rank according to coins and timer. */
-static int level_score_update(struct level_game *lg, const char *player)
-{
- int timer = lg->timer;
- int coins = lg->coins;
- struct level *l = &level_v[lg->level->number];
-
- lg->time_rank = score_time_insert(&l->score.best_times,
- player, timer, coins);
-
- if (lg->mode == MODE_CHALLENGE || lg->mode == MODE_NORMAL)
- lg->goal_rank = score_time_insert(&l->score.unlock_goal,
- player, timer, coins);
- else
- lg->goal_rank = 3;
-
- lg->coin_rank = score_coin_insert(&l->score.most_coins,
- player, timer, coins);
-
- return (lg->time_rank < 3 || lg->goal_rank < 3 || lg->coin_rank < 3);
-}
-
-/* Update the set score rank according to score and times. */
-static int set_score_update(struct level_game *lg, const char *player)
+int set_score_update(int timer, int coins, int *score_rank, int *times_rank)
{
- int timer = lg->times;
- int coins = lg->score;
struct set *s = &set_v[set];
+ char player[MAXSTR] = "";
- lg->score_rank = score_time_insert(&s->time_score, player, timer, coins);
- lg->times_rank = score_time_insert(&s->coin_score, player, timer, coins);
-
- return (lg->score_rank < 3 || lg->times_rank < 3);
-}
-
-/* Update the player name for set and level high-score. */
-void score_change_name(struct level_game *lg, const char *player)
-{
- struct set *s = &set_v[set];
- struct level *l = &level_v[lg->level->number];
-
- strncpy(l->score.best_times.player [lg->time_rank], player, MAXNAM);
- strncpy(l->score.unlock_goal.player[lg->goal_rank], player, MAXNAM);
- strncpy(l->score.most_coins.player [lg->coin_rank], player, MAXNAM);
-
- strncpy(s->coin_score.player[lg->score_rank], player, MAXNAM);
- strncpy(s->time_score.player[lg->times_rank], player, MAXNAM);
+ config_get_s(CONFIG_PLAYER, player, MAXSTR);
- set_store_hs();
-}
-
-static struct level *next_level(int i)
-{
- return set_level_exists(set, i + 1) ? &level_v[i + 1] : NULL;
-}
+ if (score_rank)
+ *score_rank = score_coin_insert(&s->coin_score, player, timer, coins);
-static struct level *next_normal_level(int i)
-{
- for (i++; i < set_v[set].count; i++)
- if (!level_v[i].is_bonus)
- return &level_v[i];
+ if (times_rank)
+ *times_rank = score_time_insert(&s->time_score, player, timer, coins);
- return NULL;
+ if ((score_rank && *score_rank < 3) || (times_rank && *times_rank < 3))
+ return 1;
+ else
+ return 0;
}
-/*---------------------------------------------------------------------------*/
-
-void set_finish_level(struct level_game *lg, const char *player)
+void set_rename_player(int score_rank, int times_rank, const char *player)
{
struct set *s = &set_v[set];
- int ln = lg->level->number; /* Current level number */
- struct level *cl = &level_v[ln]; /* Current level */
- struct level *nl = NULL; /* Next level */
- int dirty = 0; /* Should the score be saved? */
-
- assert(s == cl->set);
-
- /* if no set, no next level */
- if (s == NULL)
- {
- /* if no set, return */
- lg->next_level = NULL;
- return;
- }
-
- /* On level completed */
- if (lg->status == GAME_GOAL)
- {
- /* Update level scores */
- dirty = level_score_update(lg, player);
-
- /* Complete the level */
- if (lg->mode == MODE_CHALLENGE || lg->mode == MODE_NORMAL)
- {
- /* Complete the level */
- if (!cl->is_completed)
- {
- cl->is_completed = 1;
- dirty = 1;
- }
- }
- }
-
- /* On goal reached */
- if (lg->status == GAME_GOAL)
- {
- /* Identify the following level */
-
- nl = next_level(ln);
-
- if (nl != NULL)
- {
- /* Skip bonuses if unlocked in any mode */
-
- if (nl->is_bonus)
- {
- if (lg->mode == MODE_CHALLENGE && nl->is_locked)
- {
- nl->is_locked = 0;
-
- lg->bonus = 1;
- lg->bonus_repr = nl->repr;
- }
-
- nl = next_normal_level(nl->number);
-
- if (nl == NULL && lg->mode == MODE_CHALLENGE)
- {
- lg->win = 1;
- }
- }
- }
- else if (lg->mode == MODE_CHALLENGE)
- lg->win = 1;
- }
- else if (cl->is_bonus || lg->mode != MODE_CHALLENGE)
- {
- /* On fail, identify the next level (only in bonus for challenge) */
- nl = next_normal_level(ln);
- /* Next level may be unavailable */
- if (!cl->is_bonus && nl != NULL && nl->is_locked)
- nl = NULL;
- /* Fail a bonus level but win the set! */
- else if (nl == NULL && lg->mode == MODE_CHALLENGE)
- lg->win = 1;
- }
-
- /* Win ! */
- if (lg->win)
- {
- /* update set score */
- set_score_update(lg, player);
- /* unlock all levels */
- set_cheat();
- dirty = 1;
- }
-
- /* unlock the next level if needed */
- if (nl != NULL && nl->is_locked)
- {
- if (lg->mode == MODE_CHALLENGE || lg->mode == MODE_NORMAL)
- {
- lg->unlock = 1;
- nl->is_locked = 0;
- dirty = 1;
- }
- else
- nl = NULL;
- }
-
- /* got the next level */
- lg->next_level = nl;
- /* Update file */
- if (dirty)
- set_store_hs();
+ strncpy(s->coin_score.player[score_rank], player, MAXNAM);
+ strncpy(s->time_score.player[times_rank], player, MAXNAM);
}
/*---------------------------------------------------------------------------*/
/* Initialize the game for a snapshot. */
- if (game_init(&level_v[i], 0, 0))
+ if (game_init(level_v[i].file, 0, 1))
{
/* Render the level and grab the screen. */
const struct score *set_time_score(int);
const struct score *set_coin_score(int);
+int set_score_update (int, int, int *, int *);
+void set_rename_player(int, int, const char *);
+
+void set_store_hs(void);
/*---------------------------------------------------------------------------*/
int set_level_exists(int, int);
-const struct level *get_level(int);
-
-void set_finish_level(struct level_game *, const char *);
-void score_change_name(struct level_game *, const char *);
+struct level *get_level(int);
void level_snap(int);
void set_cheat(void);
#include "set.h"
#include "game.h"
#include "demo.h"
-#include "levels.h"
+#include "progress.h"
#include "audio.h"
#include "solid.h"
#include "config.h"
break;
default:
- if (level_replay(demo_get(i)->filename))
+ if (progress_replay(demo_get(i)->filename))
{
last_viewed = i;
demo_play_goto(0);
return goto_state(&st_demo_play);
}
+ break;
}
return 1;
}
demo_paused = 0;
goto_state(&st_demo_end);
}
+ else
+ progress_step();
}
static int demo_play_keybd(int c, int d)
return 0;
case DEMO_REPLAY:
demo_replay_stop(0);
- level_replay(curr_demo_replay()->filename);
+ progress_replay(curr_demo_replay()->filename);
return goto_state(&st_demo_play);
case DEMO_CONTINUE:
return goto_state(&st_demo_play);
#include "game.h"
#include "util.h"
#include "demo.h"
-#include "levels.h"
+#include "progress.h"
#include "audio.h"
#include "config.h"
#include "st_shared.h"
/* Bread crumbs. */
static int new_name;
+static int resume;
static int done_action(int i)
{
case DONE_NAME:
new_name = 1;
return goto_name(&st_done, &st_done, 0);
+
+ case GUI_MOST_COINS:
+ case GUI_BEST_TIMES:
+ case GUI_UNLOCK_GOAL:
+ set_score_type(i);
+ resume = 1;
+ return goto_state(&st_done);
}
return 1;
}
int id, jd;
- int high = (curr_lg()->times_rank < 3) || (curr_lg()->score_rank < 3);
+ int high = progress_set_high();
if (new_name)
{
- level_update_player_name();
+ progress_rename();
new_name = 0;
}
gui_space(id);
- if ((jd = gui_harray(id)))
- {
- gui_most_coins(jd, 1);
- gui_best_times(jd, 1);
- }
+ if ((jd = gui_hstack(id)))
+ gui_score_board(jd, 1);
gui_space(id);
if ((jd = gui_harray(id)))
{
- /* FIXME, I'm ugly. */
+ gui_start(jd, _("Select Level"), GUI_SML, DONE_OK, 0);
if (high)
- gui_state(jd, _("Change Player Name"), GUI_SML, DONE_NAME, 0);
-
- gui_start(jd, _("OK"), GUI_SML, DONE_OK, 0);
+ gui_state(jd, _("Change Name"), GUI_SML, DONE_NAME, 0);
}
+ if (!resume)
+ gui_pulse(gid, 1.2f);
+
gui_layout(id, 0, 0);
- gui_pulse(gid, 1.2f);
}
- set_best_times(set_time_score(curr_set()), curr_lg()->times_rank, 0);
- set_most_coins(set_coin_score(curr_set()), curr_lg()->score_rank);
+ set_score_board(set_coin_score(curr_set()), progress_score_rank(),
+ set_time_score(curr_set()), progress_times_rank(),
+ set_time_score(curr_set()), progress_times_rank());
+
+ /* Reset hack. */
+ resume = 0;
return id;
}
#include "gui.h"
#include "game.h"
#include "util.h"
-#include "levels.h"
+#include "progress.h"
#include "audio.h"
#include "config.h"
#include "demo.h"
#define FALL_OUT_BACK 4
#define FALL_OUT_OVER 5
-static int be_back_soon;
+static int resume;
static int fall_out_action(int i)
{
/* Fall through. */
case FALL_OUT_OVER:
- level_stop();
+ progress_stop();
return goto_state(&st_over);
case FALL_OUT_SAVE:
- be_back_soon = 1;
+ resume = 1;
- level_stop();
+ progress_stop();
return goto_save(&st_fall_out, &st_fall_out);
case FALL_OUT_NEXT:
- level_next();
- return goto_state(&st_level);
+ if (progress_next())
+ return goto_state(&st_level);
+ break;
case FALL_OUT_SAME:
- level_same();
- return goto_state(&st_level);
+ if (progress_same())
+ return goto_state(&st_level);
+ break;
}
return 1;
{
int id, jd, kd;
- const struct level_game *lg = curr_lg();
-
/* Reset hack. */
- be_back_soon = 0;
+ resume = 0;
if ((id = gui_vstack(0)))
{
if ((jd = gui_harray(id)))
{
- int next_id = 0, retry_id = 0;
-
- next_id = gui_maybe(jd, _("Next Level"), FALL_OUT_NEXT,
- lg->next_level != NULL);
-
- if (lg->dead)
- {
- gui_start(jd, _("Game Over"), GUI_SML, FALL_OUT_OVER, 0);
- }
- else
- {
- retry_id = gui_state(jd, _("Retry Level"), GUI_SML,
- FALL_OUT_SAME, 0);
- }
+ if (progress_dead())
+ gui_start(jd, _("Exit"), GUI_SML, FALL_OUT_OVER, 0);
- gui_maybe(jd, _("Save Replay"), FALL_OUT_SAVE, demo_saved());
+ if (progress_next_avail())
+ gui_start(jd, _("Next Level"), GUI_SML, FALL_OUT_NEXT, 0);
- /* Default is next if the next level is newly unlocked. */
+ if (progress_same_avail())
+ gui_start(jd, _("Retry Level"), GUI_SML, FALL_OUT_SAME, 0);
- if (next_id && lg->unlock)
- gui_focus(next_id);
- else if (retry_id)
- gui_focus(retry_id);
+ if (demo_saved())
+ gui_state(jd, _("Save Replay"), GUI_SML, FALL_OUT_SAVE, 0);
}
gui_space(id);
{
if (d)
{
- if (config_tst_d(CONFIG_KEY_RESTART, c) && !curr_lg()->dead)
+ if (config_tst_d(CONFIG_KEY_RESTART, c) && progress_same_avail())
return fall_out_action(FALL_OUT_SAME);
}
return 1;
static void fall_out_leave(int id)
{
/* HACK: don't run animation if only "visiting" a state. */
- st_fall_out.timer = be_back_soon ? shared_timer : fall_out_timer;
+ st_fall_out.timer = resume ? shared_timer : fall_out_timer;
gui_delete(id);
}
#include "gui.h"
#include "game.h"
#include "util.h"
-#include "levels.h"
+#include "progress.h"
#include "audio.h"
#include "config.h"
#include "demo.h"
/* Bread crumbs. */
static int new_name;
-static int be_back_soon;
+static int resume;
static int goal_action(int i)
{
/* Fall through. */
case GOAL_OVER:
- level_stop();
+ progress_stop();
return goto_state(&st_over);
case GOAL_SAVE:
- be_back_soon = 1;
+ resume = 1;
- level_stop();
+ progress_stop();
return goto_save(&st_goal, &st_goal);
case GOAL_NAME:
new_name = 1;
- be_back_soon = 1;
+ resume = 1;
- level_stop();
+ progress_stop();
return goto_name(&st_goal, &st_goal, 0);
case GOAL_DONE:
- level_stop();
+ progress_stop();
return goto_state(&st_done);
+ case GUI_MOST_COINS:
+ case GUI_BEST_TIMES:
+ case GUI_UNLOCK_GOAL:
+ set_score_type(i);
+ resume = 1;
+ return goto_state(&st_goal);
+
case GOAL_NEXT:
- level_next();
- return goto_state(&st_level);
+ if (progress_next())
+ return goto_state(&st_level);
+ break;
case GOAL_SAME:
- level_same();
- return goto_state(&st_level);
+ if (progress_same())
+ return goto_state(&st_level);
+ break;
}
return 1;
{
const char *s1 = _("New Record");
const char *s2 = _("GOAL");
- const char *s3 = _("Congratulations!");
int id, jd, kd;
- struct level_game *lg = curr_lg();
- const struct level *l = lg->level;
-
- int high;
+ const struct level *l = get_level(curr_level());
- high = (lg->time_rank < 3) || (lg->goal_rank < 3) || (lg->coin_rank < 3);
-
- /* Reset hack. */
- be_back_soon = 0;
+ int high = progress_lvl_high();
if (new_name)
{
- level_update_player_name();
+ progress_rename();
new_name = 0;
}
{
int gid;
- if (lg->mode == MODE_CHALLENGE && lg->bonus)
- {
- char buf[MAXSTR];
-
- sprintf(buf, _("You have unlocked bonus level %s!"),
- lg->bonus_repr);
-
- gid = gui_label(id, s3, GUI_MED, GUI_ALL, gui_grn, gui_red);
- gid = gui_label(id, buf, GUI_SML, GUI_ALL, gui_blu, gui_grn);
-
- lg->bonus = 0;
- lg->bonus_repr = NULL;
- }
-
if (high)
gid = gui_label(id, s1, GUI_MED, GUI_ALL, gui_grn, gui_grn);
else
gui_space(id);
- if (lg->mode == MODE_CHALLENGE)
+ if (curr_mode() == MODE_CHALLENGE)
{
- int coins = lg->coins;
- int score = lg->score - coins;
- int balls = lg->balls - count_extra_balls(score, coins);
+ int coins, score, balls;
+ char msg[MAXSTR] = "";
+ int i;
+
+ /* Reverse-engineer initial score and balls. */
+
+ if (resume)
+ {
+ coins = 0;
+ score = curr_score();
+ balls = curr_balls();
+ }
+ else
+ {
+ coins = curr_coins();
+ score = curr_score() - coins;
+ balls = curr_balls();
+
+ for (i = curr_score(); i > score; i--)
+ if (progress_reward_ball(i))
+ balls--;
+ }
+
+ sprintf(msg, ngettext("%d new bonus level",
+ "%d new bonus levels",
+ curr_bonus()), curr_bonus());
if ((jd = gui_hstack(id)))
{
+
if ((kd = gui_harray(jd)))
{
- balls_id = gui_count(kd, 100, GUI_MED, GUI_RGT);
- gui_label(kd, _("Balls"), GUI_SML, GUI_LFT, gui_wht, gui_wht);
+ balls_id = gui_count(kd, 100, GUI_MED, GUI_NE);
+ gui_label(kd, _("Balls"), GUI_SML, 0, gui_wht, gui_wht);
}
if ((kd = gui_harray(jd)))
{
- score_id = gui_count(kd, 1000, GUI_MED, GUI_RGT);
- gui_label(kd, _("Score"), GUI_SML, GUI_LFT, gui_wht, gui_wht);
+ score_id = gui_count(kd, 1000, GUI_MED, 0);
+ gui_label(kd, _("Score"), GUI_SML, 0, gui_wht, gui_wht);
}
if ((kd = gui_harray(jd)))
{
- coins_id = gui_count(kd, 100, GUI_MED, GUI_RGT);
- gui_label(kd, _("Coins"), GUI_SML, GUI_LFT, gui_wht, gui_wht);
+ coins_id = gui_count(kd, 100, GUI_MED, 0);
+ gui_label(kd, _("Coins"), GUI_SML, GUI_NW, gui_wht, gui_wht);
}
gui_set_count(balls_id, balls);
gui_set_count(score_id, score);
gui_set_count(coins_id, coins);
+
}
+
+ gui_label(id, msg, GUI_SML, GUI_BOT, 0, 0);
+
+ gui_space(id);
}
else
{
balls_id = score_id = coins_id = 0;
}
- gui_space(id);
-
- if ((jd = gui_harray(id)))
- {
- gui_most_coins(jd, 1);
- gui_best_times(jd, 1);
- }
+ if ((jd = gui_hstack(id)))
+ gui_score_board(jd, 1);
gui_space(id);
if ((jd = gui_harray(id)))
{
- int next_id = 0, retry_id = 0;
-
- if (lg->win)
+ if (progress_done())
gui_start(jd, _("Finish"), GUI_SML, GOAL_DONE, 0);
- else
- next_id = gui_maybe(jd, _("Next Level"), GOAL_NEXT,
- lg->next_level != NULL);
- if (lg->dead)
- gui_start(jd, _("Game Over"), GUI_SML, GOAL_OVER, 0);
- else
- {
- retry_id = gui_maybe(jd, _("Retry Level"), GOAL_SAME,
- lg->mode != MODE_CHALLENGE);
- }
+ if (progress_next_avail())
+ gui_start(jd, _("Next Level"), GUI_SML, GOAL_NEXT, 0);
- gui_maybe(jd, _("Save Replay"), GOAL_SAVE, demo_saved());
+ if (progress_same_avail())
+ gui_start(jd, _("Retry Level"), GUI_SML, GOAL_SAME, 0);
- /* Default is next if the next level is newly unlocked. */
+ if (demo_saved())
+ gui_state(jd, _("Save Replay"), GUI_SML, GOAL_SAVE, 0);
- if (next_id && lg->unlock)
- gui_focus(next_id);
- else if (lg->mode != MODE_CHALLENGE)
- gui_focus(retry_id);
+ if (high)
+ gui_state(id, _("Change Name"), GUI_SML, GOAL_NAME, 0);
}
- /* FIXME, I'm ugly. */
+ if (!resume)
+ gui_pulse(gid, 1.2f);
- if (high)
- gui_state(id, _("Change Player Name"), GUI_SML, GOAL_NAME, 0);
-
- gui_pulse(gid, 1.2f);
gui_layout(id, 0, 0);
}
- set_most_coins(&l->score.most_coins, lg->coin_rank);
-
- if (lg->mode == MODE_CHALLENGE || lg->mode == MODE_NORMAL)
- set_best_times(&l->score.unlock_goal, lg->goal_rank, 1);
- else
- set_best_times(&l->score.best_times, lg->time_rank, 0);
+ set_score_board(&l->score.most_coins, progress_coin_rank(),
+ &l->score.best_times, progress_time_rank(),
+ &l->score.unlock_goal, progress_goal_rank());
audio_music_fade_out(2.0f);
config_clr_grab();
+ /* Reset hack. */
+ resume = 0;
+
return id;
}
gui_set_count(score_id, score + 1);
gui_pulse(score_id, 1.1f);
- if ((score + 1) % 100 == 0)
+ if (progress_reward_ball(score + 1))
{
gui_set_count(balls_id, balls + 1);
gui_pulse(balls_id, 2.0f);
static void goal_leave(int id)
{
/* HACK: don't run animation if only "visiting" a state. */
- st_goal.timer = be_back_soon ? shared_timer : goal_timer;
+ st_goal.timer = resume ? shared_timer : goal_timer;
gui_delete(id);
}
break;
case HELP_DEMO_1:
- if (demo_replay_init(config_data("gui/demo1.nbr"), NULL))
+ if (demo_replay_init(config_data("gui/demo1.nbr"),
+ NULL, NULL, NULL, NULL, NULL))
return goto_state(&st_help_demo);
break;
case HELP_DEMO_2:
- if (demo_replay_init(config_data("gui/demo2.nbr"), NULL))
+ if (demo_replay_init(config_data("gui/demo2.nbr"),
+ NULL, NULL, NULL, NULL, NULL))
return goto_state(&st_help_demo);
break;
gui_space(kd);
- gui_label(kd, _("Practice Mode"), GUI_SML, GUI_TOP, 0, 0);
- gui_multi(kd,
- _("Play without time limit or coin constraint.\\"
- "Levels cannot be unlocked in this mode."),
- GUI_SML, GUI_BOT, gui_wht, gui_wht);
-
- gui_space(kd);
-
gui_label(kd, _("Challenge Mode"), GUI_SML, GUI_TOP, 0, 0);
gui_multi(kd,
_("Start playing from the first level of the set.\\"
#include "gui.h"
#include "game.h"
#include "set.h"
-#include "levels.h"
+#include "progress.h"
#include "audio.h"
#include "config.h"
#include "st_shared.h"
{
int id, jd, kd, ld;
const char *ln;
- const struct level_game *lg = curr_lg();
int b;
const float *textcol1, *textcol2;
{
if ((jd = gui_hstack(id)))
{
- ln = lg->level->repr;
- b = lg->level->is_bonus;
+ ln = level_repr (curr_level());
+ b = level_bonus(curr_level());
+
textcol1 = b ? gui_wht : 0;
textcol2 = b ? gui_grn : 0;
}
}
- gui_label(kd, mode_to_str(lg->mode, 1), GUI_SML, GUI_BOT,
+ gui_label(kd, mode_to_str(curr_mode(), 1), GUI_SML, GUI_BOT,
gui_wht, gui_wht);
}
}
gui_space(id);
- if (strlen(lg->level->message) != 0)
- gui_multi(id, _(lg->level->message), GUI_SML, GUI_ALL, gui_wht,
- gui_wht);
+ gui_multi(id, level_msg(curr_level()),
+ GUI_SML, GUI_ALL,
+ gui_wht, gui_wht);
gui_layout(id, 0, 0);
}
}
if (config_tst_d(CONFIG_JOYSTICK_BUTTON_EXIT, b))
{
- level_stat(GAME_NONE, curr_clock(), curr_coins());
- level_stop();
+ progress_stop();
return goto_state(&st_over);
}
}
#include "gui.h"
#include "set.h"
#include "game.h"
-#include "levels.h"
+#include "progress.h"
#include "audio.h"
#include "config.h"
#include "demo.h"
{
int id;
- if (curr_lg()->mode != MODE_CHALLENGE)
+ if (curr_mode() != MODE_CHALLENGE)
return 0;
if ((id = gui_label(0, _("GAME OVER"), GUI_LRG, GUI_ALL, gui_gry, gui_red)))
static void over_timer(int id, float dt)
{
- if (curr_lg()->mode != MODE_CHALLENGE || time_state() > 3.f)
+ if (curr_mode() != MODE_CHALLENGE || time_state() > 3.f)
goto_state(&st_start);
gui_timer(id, dt);
#include "gui.h"
#include "config.h"
#include "game.h"
-#include "levels.h"
+#include "progress.h"
#include "level.h"
#include "audio.h"
#include "hud.h"
return goto_state(st_continue);
case PAUSE_RESTART:
- level_same();
- clear_pause();
- SDL_PauseAudio(0);
- config_set_grab(1);
- return goto_state(&st_play_ready);
+ if (progress_same())
+ {
+ clear_pause();
+ SDL_PauseAudio(0);
+ config_set_grab(1);
+ return goto_state(&st_play_ready);
+ }
+ break;
case PAUSE_EXIT:
- level_stat(GAME_NONE, curr_clock(), curr_coins());
- level_stop();
+ progress_exit(GAME_NONE);
clear_pause();
SDL_PauseAudio(0);
audio_music_stop();
{
gui_state(jd, _("Quit"), GUI_SML, PAUSE_EXIT, 0);
- if (curr_lg()->mode != MODE_CHALLENGE)
+ if (progress_same_avail())
gui_state(jd, _("Restart"), GUI_SML, PAUSE_RESTART, 0);
- else
- {
- int ld = gui_state(jd, _("Restart"), GUI_SML, 0, 0);
- gui_set_color(ld, gui_gry, gui_gry);
- }
gui_start(jd, _("Continue"), GUI_SML, PAUSE_CONTINUE, 1);
}
if (config_tst_d(CONFIG_KEY_PAUSE, c))
return pause_action(PAUSE_CONTINUE);
- if (config_tst_d(CONFIG_KEY_RESTART, c)
- && curr_lg()->mode != MODE_CHALLENGE)
+ if (config_tst_d(CONFIG_KEY_RESTART, c) &&
+ curr_mode() != MODE_CHALLENGE)
return pause_action(PAUSE_RESTART);
}
return 1;
#include "hud.h"
#include "game.h"
#include "demo.h"
-#include "levels.h"
+#include "progress.h"
#include "audio.h"
#include "config.h"
#include "st_shared.h"
{
if (SDL_GetModState() & KMOD_SHIFT)
{
- level_stat(GAME_NONE, curr_clock(), curr_coins());
- level_stop();
+ progress_exit(GAME_NONE);
config_clr_grab();
return goto_state(&st_over);
switch (game_step(g, dt, 1))
{
case GAME_GOAL:
- level_stat(GAME_GOAL, curr_clock(), curr_coins());
+ progress_stat(GAME_GOAL);
gui_stuck();
goto_state(&st_goal);
break;
case GAME_FALL:
- level_stat(GAME_FALL, curr_clock(), curr_coins());
+ progress_stat(GAME_FALL);
gui_stuck();
goto_state(&st_fall_out);
break;
case GAME_TIME:
- level_stat(GAME_TIME, curr_clock(), curr_coins());
+ progress_stat(GAME_TIME);
gui_stuck();
goto_state(&st_time_out);
break;
default:
+ progress_step();
break;
}
}
hud_view_pulse(2);
}
if (config_tst_d(CONFIG_KEY_RESTART, c) &&
- curr_lg()->mode != MODE_CHALLENGE)
+ progress_same_avail())
{
- level_same();
- goto_state(&st_play_ready);
+ if (progress_same())
+ goto_state(&st_play_ready);
}
if (config_tst_d(CONFIG_KEY_PAUSE, c))
goto_pause();
if (d && c == SDLK_c && config_cheat())
{
- level_stat(GAME_GOAL, curr_clock(), curr_coins());
+ progress_stat(GAME_GOAL);
return goto_state(&st_goal);
}
return 1;
#include "audio.h"
#include "config.h"
#include "demo.h"
-#include "levels.h"
+#include "progress.h"
#include "text.h"
#include "st_shared.h"
#include "gui.h"
#include "set.h"
-#include "levels.h"
+#include "progress.h"
#include "game.h"
#include "audio.h"
#include "config.h"
#include "set.h"
#include "util.h"
#include "game.h"
-#include "levels.h"
+#include "progress.h"
#include "audio.h"
#include "config.h"
#include "st_shared.h"
/*---------------------------------------------------------------------------*/
-#define START_BACK -1
-#define START_PRACTICE -2
-#define START_NORMAL -3
-#define START_CHALLENGE -4
+#define START_BACK -1
+#define START_CHALLENGE -2
+#define START_OPEN_GOALS -3
+#define START_LOCK_GOALS -4
static int shot_id;
-static int status_id;
/*---------------------------------------------------------------------------*/
static void gui_level(int id, int i)
{
- const GLfloat *fore, *back;
- const struct level *l;
+ const GLfloat *fore = 0, *back = 0;
int jd;
- if (!set_level_exists(curr_set(), i))
+ if (!level_exists(i))
{
- gui_space(id);
+ gui_label(id, " ", GUI_SML, GUI_ALL, gui_blk, gui_blk);
return;
}
- l = get_level(i);
-
- if (!l->is_locked)
+ if (level_opened(i))
{
- fore = l->is_bonus ? gui_grn : gui_wht;
- back = l->is_completed ? fore : gui_yel;
+ fore = level_bonus(i) ? gui_grn : gui_wht;
+ back = level_completed(i) ? fore : gui_yel;
}
- else
- fore = back = gui_gry;
- jd = gui_label(id, l->repr, GUI_SML, GUI_ALL, back, fore);
+ jd = gui_label(id, level_repr(i), GUI_SML, GUI_ALL, back, fore);
- gui_active(jd, i, 0);
+ if (level_opened(i) || config_cheat())
+ gui_active(jd, i, 0);
}
static void start_over_level(int i)
{
- const struct level *l = get_level(i);
- if (!l->is_locked || config_cheat())
+ if (level_opened(i) || config_cheat())
{
- gui_set_image(shot_id, l->shot);
+ gui_set_image(shot_id, level_shot(i));
- set_most_coins(&l->score.most_coins, -1);
-
- if (curr_mode() == MODE_PRACTICE)
- {
- set_best_times(&l->score.best_times, -1, 0);
- if (l->is_bonus)
- gui_set_label(status_id,
- _("Play this bonus level in practice mode"));
- else
- gui_set_label(status_id,
- _("Play this level in practice mode"));
- }
- else
- {
- set_best_times(&l->score.unlock_goal, -1, 1);
- if (l->is_bonus)
- gui_set_label(status_id,
- _("Play this bonus level in normal mode"));
- else
- gui_set_label(status_id, _("Play this level in normal mode"));
- }
- if (config_cheat())
- {
- gui_set_label(status_id, l->file);
- }
- return;
+ set_score_board(&get_level(i)->score.most_coins, -1,
+ &get_level(i)->score.best_times, -1,
+ &get_level(i)->score.unlock_goal, -1);
}
- else if (l->is_bonus)
- gui_set_label(status_id,
- _("Play in challenge mode to unlock extra bonus levels"));
- else
- gui_set_label(status_id,
- _("Finish previous levels to unlock this level"));
}
static void start_over(int id)
{
int i;
- gui_pulse(id, 1.2f);
if (id == 0)
return;
- i = gui_token(id);
+ gui_pulse(id, 1.2f);
+ i = gui_token(id);
- switch (i)
+ if (i == START_CHALLENGE || i == START_BACK)
{
- case START_CHALLENGE:
gui_set_image(shot_id, set_shot(curr_set()));
- set_most_coins(set_coin_score(curr_set()), -1);
- set_best_times(set_time_score(curr_set()), -1, 0);
- gui_set_label(status_id, _("Challenge all levels from the first one"));
- break;
-
- case START_NORMAL:
- gui_set_label(status_id, _("Collect coins and unlock next level"));
- break;
- case START_PRACTICE:
- gui_set_label(status_id, _("Train yourself without time nor coin"));
- break;
+ set_score_board(set_coin_score(curr_set()), -1,
+ set_time_score(curr_set()), -1,
+ set_time_score(curr_set()), -1);
}
if (i >= 0)
static int start_action(int i)
{
- int mode = curr_mode();
-
audio_play(AUD_MENU, 1.0f);
switch (i)
{
case START_BACK:
return goto_state(&st_set);
- case START_NORMAL:
- mode_set(MODE_NORMAL);
- return goto_state(&st_start);
- case START_PRACTICE:
- mode_set(MODE_PRACTICE);
+
+ case START_CHALLENGE:
+ progress_init(MODE_CHALLENGE);
+ return config_cheat() ? 1 : start_action(0);
+
+ case GUI_MOST_COINS:
+ case GUI_BEST_TIMES:
+ case GUI_UNLOCK_GOAL:
+ set_score_type(i);
return goto_state(&st_start);
- }
- if (i == START_CHALLENGE)
- {
- /* On cheat, start challenge mode where you want */
- if (config_cheat())
- {
- mode_set(MODE_CHALLENGE);
- return goto_state(&st_start);
- }
- i = 0;
- mode = MODE_CHALLENGE;
- }
+ case START_OPEN_GOALS:
+ config_set_d(CONFIG_LOCK_GOALS, 0);
+ return goto_state(&st_start);
- if (i >= 0)
- {
- const struct level *l = get_level(i);
+ case START_LOCK_GOALS:
+ config_set_d(CONFIG_LOCK_GOALS, 1);
+ return goto_state(&st_start);
- if (!l->is_locked || config_cheat())
- {
- if (level_play(l, mode))
- {
- return goto_state(&st_level);
- }
- else
- {
- level_stop();
- return 1;
- }
- }
+ default:
+ if (progress_play(i))
+ return goto_state(&st_level);
+ break;
}
+
return 1;
}
{
int w = config_get_d(CONFIG_WIDTH);
int h = config_get_d(CONFIG_HEIGHT);
- int m = curr_mode();
int i, j;
int id, jd, kd, ld;
- /* Deactivate cheat */
-
- if (m == MODE_CHALLENGE && !config_cheat())
- {
- mode_set(MODE_NORMAL);
- m = MODE_NORMAL;
- }
+ progress_init(MODE_NORMAL);
if ((id = gui_vstack(0)))
{
gui_start(jd, _("Back"), GUI_SML, START_BACK, 0);
}
-
if ((jd = gui_harray(id)))
{
shot_id = gui_image(jd, set_shot(curr_set()), 7 * w / 16, 7 * h / 16);
if ((kd = gui_varray(jd)))
{
- if ((ld = gui_harray(kd)))
- {
- gui_state(ld, _("Practice"), GUI_SML, START_PRACTICE,
- m == MODE_PRACTICE);
- gui_state(ld, _("Normal"), GUI_SML, START_NORMAL,
- m == MODE_NORMAL);
- }
for (i = 0; i < 5; i++)
if ((ld = gui_harray(kd)))
for (j = 4; j >= 0; j--)
gui_level(ld, i * 5 + j);
gui_state(kd, _("Challenge"), GUI_SML, START_CHALLENGE,
- m == MODE_CHALLENGE);
+ curr_mode() == MODE_CHALLENGE);
}
}
gui_space(id);
- if ((jd = gui_harray(id)))
- {
- gui_most_coins(jd, 0);
- gui_best_times(jd, 0);
- }
+ if ((jd = gui_hstack(id)))
+ gui_score_board(jd, 0);
+
gui_space(id);
- status_id = gui_label(id, _("Choose a level to play"), GUI_SML, GUI_ALL,
- gui_yel, gui_wht);
+ if ((jd = gui_hstack(id)))
+ {
+ gui_filler(jd);
+
+ if ((kd = gui_harray(jd)))
+ {
+ /* TODO, replace the whitespace hack with something sane. */
+
+ gui_state(kd,
+ /* TRANSLATORS: adjust the amount of whitespace here
+ * as necessary for the buttons to look good. */
+ _(" No "), GUI_SML, START_OPEN_GOALS,
+ config_get_d(CONFIG_LOCK_GOALS) == 0);
+
+ gui_state(kd, _("Yes"), GUI_SML, START_LOCK_GOALS,
+ config_get_d(CONFIG_LOCK_GOALS) == 1);
+ }
+
+ gui_space(jd);
+
+ gui_label(jd, _("Lock Goals of Completed Levels?"),
+ GUI_SML, GUI_ALL, 0, 0);
+
+ gui_filler(jd);
+ }
gui_layout(id, 0, 0);
- set_most_coins(NULL, -1);
- set_best_times(NULL, -1, m != MODE_PRACTICE);
+ set_score_board(NULL, -1, NULL, -1, NULL, -1);
}
audio_music_fade_to(0.5f, "bgm/inter.ogg");
/* Iterate over all levels, taking a screenshot of each. */
for (i = 0; i < MAXLVL; i++)
- if (set_level_exists(curr_set(), i))
+ if (level_exists(i))
level_snap(i);
}
#include "game.h"
#include "util.h"
-#include "levels.h"
+#include "progress.h"
#include "demo.h"
#include "audio.h"
#include "gui.h"
/* Fall through. */
case TIME_OUT_OVER:
- level_stop();
+ progress_stop();
return goto_state(&st_over);
case TIME_OUT_SAVE:
- level_stop();
+ progress_stop();
return goto_save(&st_time_out, &st_time_out);
case TIME_OUT_NEXT:
- level_next();
- return goto_state(&st_level);
+ if (progress_next())
+ return goto_state(&st_level);
+ break;
case TIME_OUT_SAME:
- level_same();
- return goto_state(&st_level);
+ if (progress_same())
+ return goto_state(&st_level);
+ break;
}
return 1;
{
int id, jd, kd;
- const struct level_game *lg = curr_lg();
-
if ((id = gui_vstack(0)))
{
kd = gui_label(id, _("Time's Up!"), GUI_LRG, GUI_ALL, gui_gry, gui_red);
if ((jd = gui_harray(id)))
{
- int next_id = 0, retry_id = 0;
-
- next_id = gui_maybe(jd, _("Next Level"), TIME_OUT_NEXT,
- lg->next_level != NULL);
-
- if (lg->dead)
- gui_start(jd, _("Game Over"), GUI_SML, TIME_OUT_OVER, 0);
- else
- {
- retry_id = gui_state(jd, _("Retry Level"), GUI_SML,
- TIME_OUT_SAME, 0);
- }
+ if (progress_dead())
+ gui_start(jd, _("Exit"), GUI_SML, TIME_OUT_OVER, 0);
- gui_maybe(jd, _("Save Replay"), TIME_OUT_SAVE, demo_saved());
+ if (progress_next_avail())
+ gui_start(jd, _("Next Level"), GUI_SML, TIME_OUT_NEXT, 0);
- /* Default is next if the next level is newly unlocked. */
+ if (progress_same_avail())
+ gui_start(jd, _("Retry Level"), GUI_SML, TIME_OUT_SAME, 0);
- if (next_id && lg->unlock)
- gui_focus(next_id);
- else if (retry_id)
- gui_focus(retry_id);
+ if (demo_saved())
+ gui_state(jd, _("Save Replay"), GUI_SML, TIME_OUT_SAVE, 0);
}
gui_space(id);
{
if (d)
{
- if (config_tst_d(CONFIG_KEY_RESTART, c) && !curr_lg()->dead)
+ if (config_tst_d(CONFIG_KEY_RESTART, c) && progress_same_avail())
return time_out_action(TIME_OUT_SAME);
}
return 1;
return 1;
}
-static struct level title_level;
-
static int title_enter(void)
{
int id, jd, kd;
audio_music_fade_to(0.5f, "bgm/title.ogg");
/* Initialize the title level for display. */
- level_load("map-medium/title.sol", &title_level);
- game_init(&title_level, 0, 0);
+
+ game_init("map-medium/title.sol", 0, 1);
real_time = 0.0f;
mode = 0;
{
if ((demo = demo_pick()))
{
- demo_replay_init(demo, NULL);
+ demo_replay_init(demo, NULL, NULL, NULL, NULL, NULL);
game_set_fly(0.0f);
real_time = 0.0f;
mode = 2;
if (real_time > 1.0f)
{
- game_init(&title_level, 0, 0);
+ game_init("map-medium/title.sol", 0, 1);
+
real_time = 0.0f;
mode = 0;
}
/* Build a Most Coins top three list with default values. */
-void gui_most_coins(int id, int e)
+static void gui_most_coins(int id, int e)
{
const char *s = "1234567";
/* Set the Most Coins top three list values for level i. */
-void set_most_coins(const struct score *s, int hilight)
+static void set_most_coins(const struct score *s, int hilight)
{
int j, spe;
const char *name;
/* Build a Best Times top three list with default values. */
-void gui_best_times(int id, int e)
+static void gui_best_times(int id, int e)
{
const char *s = "1234567";
}
}
-/* Set the Best Times top three list values for level i. */
+/* Set the Best Times top three list values. */
-void set_best_times(const struct score *s, int hilight, int goal)
+static void set_best_times(const struct score *s, int hilight, int goal)
{
int j, spe;
const char *name;
gui_set_color(time_name[j], 0, 0);
else if (j != hilight)
gui_set_color(time_name[j], gui_yel, gui_wht);
- else if (j>= NSCORE)
+ else if (j >= NSCORE)
gui_set_color(time_name[j], gui_red, gui_red);
else
gui_set_color(time_name[j], gui_grn, gui_grn);
/*---------------------------------------------------------------------------*/
+static int score_type = GUI_MOST_COINS;
+
+void gui_score_board(int id, int e)
+{
+ int jd, kd;
+
+ gui_filler(id);
+
+ if ((jd = gui_hstack(id)))
+ {
+ gui_filler(jd);
+
+ if ((kd = gui_vstack(jd)))
+ {
+ gui_filler(kd);
+
+ gui_state(kd, _("Most Coins"), GUI_SML, GUI_MOST_COINS,
+ score_type == GUI_MOST_COINS);
+ gui_state(kd, _("Best Times"), GUI_SML, GUI_BEST_TIMES,
+ score_type == GUI_BEST_TIMES);
+ gui_state(kd, _("Unlock Goal"), GUI_SML, GUI_UNLOCK_GOAL,
+ score_type == GUI_UNLOCK_GOAL);
+
+ gui_filler(kd);
+ }
+
+ gui_filler(jd);
+ }
+
+ gui_filler(id);
+
+ switch (score_type)
+ {
+ case GUI_MOST_COINS:
+ gui_most_coins(id, e);
+ break;
+
+ case GUI_BEST_TIMES:
+ gui_best_times(id, e);
+ break;
+
+ case GUI_UNLOCK_GOAL:
+ gui_best_times(id, e);
+ break;
+ }
+
+ gui_filler(id);
+}
+
+void set_score_board(const struct score *smc, int hmc,
+ const struct score *sbt, int hbt,
+ const struct score *sug, int hug)
+{
+ switch (score_type)
+ {
+ case GUI_MOST_COINS:
+ set_most_coins(smc, hmc);
+ break;
+
+ case GUI_BEST_TIMES:
+ set_best_times(sbt, hbt, 0);
+ break;
+
+ case GUI_UNLOCK_GOAL:
+ set_best_times(sug, hug, 1);
+ break;
+ }
+}
+
+void set_score_type(int t)
+{
+ score_type = t;
+}
+
+/*---------------------------------------------------------------------------*/
+
static int lock = 1;
static int keyd[127];
#define GUI_BS -104
#define GUI_CL -105
-void gui_most_coins(int, int);
-void set_most_coins(const struct score *, int);
-void gui_best_times(int, int);
-void set_best_times(const struct score *, int, int);
+#define GUI_MOST_COINS -106
+#define GUI_BEST_TIMES -107
+#define GUI_UNLOCK_GOAL -108
+
+void set_score_type(int);
+void gui_score_board(int, int);
+void set_score_board(const struct score *, int,
+ const struct score *, int,
+ const struct score *, int);
void gui_keyboard(int);
void gui_keyboard_lock(void);
Mehdi Yousfi-Monod (mym)
Michael Middleton (slippifishi)
Florian Priester
+ Byron James Johnson (Krabby Krap)
* TRANSLATORS
- Feature ideas and testing
Byron James Johnson
- Platform acceleration toggle
+ - Neverputt ball vs. ball collision
Laurent Moussault (Lorant)
- Menu navigation improvements
- Layout improvements in several screens
- Map compiler speed-ups for debugging large maps
Uoti Urpala
- Bounces with moving objects
+ Georg Wachter
+ - Generic sphere-vs-sphere collision
Mehdi Yousfi-Monod
- Neverball Hall of Fame management
- Windows packaging
Joystick exit button
+ gamma 0.78
+
+ gamma gives the Coefficient of restitution of a sphere-sphere
+ collision. Its square gives the fraction of kinetic energy
+ (in the center-of-mass system) that will be conserved in the
+ collision, so possible values range from 0 (completely
+ inelastic) to 1 (completely elastic) collision. Values greater
+ than 1 mean that in each collision, extra energy is generated.
+
+ putt_collisions 0
+
+ This key allows balls to collide with other balls in
+ Neverputt.
+
Contact: <robert.kooima@gmail.com>
static float jump_e = 1; /* Jumping enabled flag */
static float jump_b = 0; /* Jump-in-progress flag */
+static int jump_u = 0; /* Which ball is jumping? */
static float jump_dt; /* Jump duration */
static float jump_p[3]; /* Jump destination */
{
jump_e = 1;
jump_b = 0;
+ jump_u = 0;
view_init();
sol_load_gl(&file, config_data(s), config_get_d(CONFIG_TEXTURES),
/*---------------------------------------------------------------------------*/
+int game_check_balls(struct s_file *fp)
+{
+ float z[3] = {0.0f, 0.0f, 0.0f};
+ int i, j;
+
+ for (i = 1; i < fp->uc && config_get_d(CONFIG_BALL_COLLISIONS); i++)
+ {
+ struct s_ball *up = fp->uv + i;
+
+ /*
+ * If a ball falls out, return the ball to the camera marker
+ * and reset the play state for fair play
+ */
+ if (i != ball && up->p[1] < -10.f && (up->p[1] > -199.9f || up->p[1] < -599.9f))
+ {
+ up->P = 0;
+ v_cpy(up->p, fp->uv->p);
+ v_cpy(up->v, z);
+ v_cpy(up->w, z);
+ }
+
+ if (i == ball && up->p[1] < -30.0f)
+ {
+ v_cpy(up->p, fp->uv->p);
+ v_cpy(up->v, z);
+ v_cpy(up->w, z);
+ }
+
+ /*
+ * If an OTHER ball stops in a hole, mark it as done
+ * and drop it -200.0 units to allow room for more balls
+ */
+ if (i != ball && !(v_len(up->v) > 0.0f))
+ {
+ const float *ball_p = up->p;
+ const float ball_r = up->r;
+ int zi;
+ for (zi = 0; zi < fp->zc; zi++)
+ {
+ float r[3];
+
+ r[0] = ball_p[0] - fp->zv[zi].p[0];
+ r[1] = ball_p[2] - fp->zv[zi].p[2];
+ r[2] = 0;
+
+ if (v_len(r) < fp->zv[zi].r * 1.1 - ball_r &&
+ ball_p[1] > fp->zv[zi].p[1] &&
+ ball_p[1] < fp->zv[zi].p[1] + GOAL_HEIGHT / 2)
+ {
+ up->p[1] = -200.0f;
+ v_cpy(up->v, z);
+ v_cpy(up->w, z);
+ return i;
+ }
+ }
+ }
+
+ /*
+ * Check for intesecting balls.
+ * If there are any, reset the proper
+ * ball's play state
+ */
+ for (j = i + 1; j < fp->uc && config_get_d(CONFIG_BALL_COLLISIONS); j++)
+ {
+ struct s_ball *u2p = fp->uv + j;
+ float d[3];
+ v_sub(d, up->p, u2p->p);
+ if (v_len(up->v) > 0.005f || v_len(u2p->v) > 0.005f)
+ continue;
+ if (v_len(d) < (fsqrtf((up->r + u2p->r) * (up->r + u2p->r))) * 1.0f && i != ball)
+ up->P = 0;
+ else if (v_len(d) < (fsqrtf((up->r + u2p->r) * (up->r + u2p->r)) * 1.0f))
+ u2p->P = 0;
+ }
+ }
+
+ for (i = 0; i < fp->yc; i++)
+ {
+ struct s_ball *yp = fp->yv + i;
+
+ if (yp->p[1] < -20.0f && yp->n)
+ {
+ v_cpy(yp->p, yp->O);
+ v_cpy(yp->v, z);
+ v_cpy(yp->w, z);
+ }
+
+ if (!(v_len(yp->v) > 0.0f))
+ {
+ const float *ball_p = yp->p;
+ const float ball_r = yp->r;
+ int zi;
+ for (zi = 0; zi < fp->zc; zi++)
+ {
+ float r[3];
+
+ r[0] = ball_p[0] - fp->zv[zi].p[0];
+ r[1] = ball_p[2] - fp->zv[zi].p[2];
+ r[2] = 0;
+
+ if (v_len(r) < fp->zv[zi].r * 1.1 - ball_r &&
+ ball_p[1] > fp->zv[zi].p[1] &&
+ ball_p[1] < fp->zv[zi].p[1] + GOAL_HEIGHT / 2)
+ {
+ v_cpy(yp->p, yp->O);
+ v_cpy(yp->v, z);
+ v_cpy(yp->w, z);
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
static void game_draw_vect_prim(const struct s_file *fp, GLenum mode)
{
float p[3];
static void game_draw_balls(const struct s_file *fp,
const float *bill_M, float t)
{
- static const GLfloat color[5][4] = {
+ static const GLfloat color[6][4] = {
{ 1.0f, 1.0f, 1.0f, 0.7f },
{ 1.0f, 0.0f, 0.0f, 1.0f },
{ 0.0f, 1.0f, 0.0f, 1.0f },
{ 0.0f, 0.0f, 1.0f, 1.0f },
{ 1.0f, 1.0f, 0.0f, 1.0f },
+ { 0.1f, 0.1f, 0.1f, 1.0f },
};
- int ui;
+ int ui, yi;
+
+ for (yi = 0; yi < fp->yc; yi++)
+ {
+ float M[16];
+
+ if (!config_get_d(CONFIG_BALL_COLLISIONS) && fp->yv[yi].c)
+ continue;
+
+ m_basis(M, fp->yv[yi].e[0], fp->yv[yi].e[1], fp->yv[yi].e[2]);
+
+ glPushMatrix();
+ {
+ glTranslatef(fp->yv[yi].p[0],
+ fp->yv[yi].p[1] + BALL_FUDGE,
+ fp->yv[yi].p[2]);
+ glMultMatrixf(M);
+ glScalef(fp->yv[yi].r,
+ fp->yv[yi].r,
+ fp->yv[yi].r);
+
+ glColor4fv(color[5]);
+ oldball_draw(1);
+ }
+ glPopMatrix();
+ }
for (ui = curr_party(); ui > 0; ui--)
{
- if (ui == ball)
+ if (ui == ball || (config_get_d(CONFIG_BALL_COLLISIONS) && fp->uv[ui].P))
{
- float ball_M[16];
- float pend_M[16];
+ float M[16];
- m_basis(ball_M, fp->uv[ui].e[0], fp->uv[ui].e[1], fp->uv[ui].e[2]);
- m_basis(pend_M, fp->uv[ui].E[0], fp->uv[ui].E[1], fp->uv[ui].E[2]);
+ m_basis(M, fp->uv[ui].e[0], fp->uv[ui].e[1], fp->uv[ui].e[2]);
glPushMatrix();
{
glTranslatef(fp->uv[ui].p[0],
fp->uv[ui].p[1] + BALL_FUDGE,
fp->uv[ui].p[2]);
+ glMultMatrixf(M);
glScalef(fp->uv[ui].r,
fp->uv[ui].r,
fp->uv[ui].r);
glColor4fv(color[ui]);
- ball_draw(ball_M, pend_M, bill_M, t);
+ oldball_draw(0);
}
glPopMatrix();
}
float fov = FOV;
- if (jump_b) fov *= 2.0f * fabsf(jump_dt - 0.5f);
+ int i = 0;
+
+ if (config_get_d(CONFIG_BALL_COLLISIONS) && jump_b && jump_u != ball * 2)
+ fov /= 1.9f * fabsf(jump_dt - 0.5f);
+
+ else if (jump_b)
+ fov *= 2.0f * fabsf(jump_dt - 0.5f);
config_push_persp(fov, 0.1f, FAR_DIST);
glPushAttrib(GL_LIGHTING_BIT);
if (config_get_d(CONFIG_SHADOW) && !pose)
{
+ for (i = 0; i < fp->yc; i++)
+ {
+ shad_draw_set(fp->yv[i].p, fp->yv[i].r);
+ sol_shad(fp);
+ shad_draw_clr();
+ }
+
+ for (i = 0; i < fp->uc; i++)
+ {
+ if (fp->uv[i].P)
+ {
+ shad_draw_set(fp->uv[i].p, fp->uv[i].r);
+ sol_shad(fp);
+ shad_draw_clr();
+ }
+ }
+
shad_draw_set(fp->uv[ball].p, fp->uv[ball].r);
sol_shad(fp);
shad_draw_clr();
struct s_file *fp = &file;
float p[3];
+ int i;
+
if (dt > 0.f)
t += dt;
else
t = 0.f;
/* Test for a switch. */
-
- if (sol_swch_test(fp, ball))
+ if (sol_swch_test(fp))
audio_play(AUD_SWITCH, 1.f);
/* Test for a jump. */
- if (jump_e == 1 && jump_b == 0 && sol_jump_test(fp, jump_p, ball) == 1)
+ if (config_get_d(CONFIG_BALL_COLLISIONS))
+ {
+ for (i = 1; i < curr_party() + 1; i++)
+ {
+ if (!jump_u && jump_e == 1 && jump_b == 0 && sol_jump_test(fp, jump_p, i) == 1)
+ {
+ jump_b = 1;
+ jump_e = 0;
+ jump_dt = 0.f;
+ jump_u = i * 2;
+
+ audio_play(AUD_JUMP, 1.f);
+ }
+ if (jump_e == 0 && jump_b == 0 && sol_jump_test(fp, jump_p, i) == 0)
+ jump_e = 1;
+ if (!jump_b && jump_u && i == jump_u / 2 && sol_jump_test(fp, jump_p, i) == 0)
+ jump_u = 0;
+ }
+
+ for (i = 0; i < fp->yc; i++)
+ {
+ if (!jump_u && jump_e == 1 && jump_b == 0 && sol_jump_test(fp, jump_p, fp->yv + i - fp->uv) == 1)
+ {
+ jump_b = 1;
+ jump_e = 0;
+ jump_dt = 0.f;
+ jump_u = i * 2 + 1;
+
+ audio_play(AUD_JUMP, 1.f);
+ }
+ if (jump_e == 0 && jump_b == 0 && sol_jump_test(fp, jump_p, fp->yv + i - fp->uv) == 0)
+ jump_e = 1;
+ if (!jump_b && jump_u && i == jump_u / 2 && sol_jump_test(fp, jump_p, fp->yv + i - fp->uv) == 0)
+ jump_u = 0;
+ }
+ }
+ else
{
- jump_b = 1;
- jump_e = 0;
- jump_dt = 0.f;
+ if (jump_e == 1 && jump_b == 0 && sol_jump_test(fp, jump_p, ball) == 1)
+ {
+ jump_b = 1;
+ jump_e = 0;
+ jump_dt = 0.f;
- audio_play(AUD_JUMP, 1.f);
+ audio_play(AUD_JUMP, 1.f);
+ }
+ if (jump_e == 0 && jump_b == 0 && sol_jump_test(fp, jump_p, ball) == 0)
+ jump_e = 1;
}
- if (jump_e == 0 && jump_b == 0 && sol_jump_test(fp, jump_p, ball) == 0)
- jump_e = 1;
/* Test for fall-out. */
- if (fp->uv[ball].p[1] < -10.f)
+ if (fp->uv[ball].p[1] < -10.0f)
return GAME_FALL;
/* Test for a goal or stop. */
- if (t > 1.f)
+ if (t > 1.0f)
{
- t = 0.f;
+ t = 0.0f;
+
+ if (config_get_d(CONFIG_BALL_COLLISIONS))
+ {
+ switch (sol_goal_test(fp, p, ball))
+ {
+ case 2: /* The player's ball landed in the goal and the all of the other balls have stopped */
+ t = 0.0f;
+ return GAME_GOAL;
+ break;
+ case 1: /* All balls have stopped */
+ t = 0.0f;
+ return GAME_STOP;
+ break;
+ case 0: /* Game still running; there may be a ball that has not yet stopped */
+ return GAME_NONE;
+ break;
+ default: /* Should never reach this */
+ break;
+ }
+ }
- if (sol_goal_test(fp, p, ball))
- return GAME_GOAL;
else
- return GAME_STOP;
+ {
+ if (sol_goal_test(fp, p, ball))
+ return GAME_GOAL;
+ else
+ return GAME_STOP;
+ }
}
return GAME_NONE;
}
+void game_set_played(int b)
+{
+ if (ball)
+ file.uv[ball].P = b;
+ if (!b)
+ {
+ file.uv[0].P = 0;
+ file.uv[1].P = 0;
+ file.uv[2].P = 0;
+ file.uv[3].P = 0;
+ file.uv[4].P = 0;
+ }
+}
+
/*
* On most hardware, rendering requires much more computing power than
* physics. Since physics takes less time than graphics, it make sense to
if (jump_b)
{
- jump_dt += dt;
+ if (config_get_d(CONFIG_BALL_COLLISIONS))
+ {
+ jump_dt += dt;
- /* Handle a jump. */
+ /* Handle a jump. */
- if (0.5 < jump_dt)
+ if (0.5 < jump_dt)
+ {
+ if (jump_u % 2)
+ {
+ fp->yv[jump_u / 2].p[0] = jump_p[0];
+ fp->yv[jump_u / 2].p[1] = jump_p[1];
+ fp->yv[jump_u / 2].p[2] = jump_p[2];
+ }
+
+ else
+ {
+ fp->uv[jump_u / 2].p[0] = jump_p[0];
+ fp->uv[jump_u / 2].p[1] = jump_p[1];
+ fp->uv[jump_u / 2].p[2] = jump_p[2];
+ }
+ }
+ if (1.f < jump_dt)
+ {
+ jump_b = 0;
+ }
+ }
+
+ else
{
- fp->uv[ball].p[0] = jump_p[0];
- fp->uv[ball].p[1] = jump_p[1];
- fp->uv[ball].p[2] = jump_p[2];
+ jump_dt += dt;
+
+ /* Handle a jump. */
+
+ if (0.5 < jump_dt)
+ {
+ fp->uv[ball].p[0] = jump_p[0];
+ fp->uv[ball].p[1] = jump_p[1];
+ fp->uv[ball].p[2] = jump_p[2];
+ }
+ if (1.f < jump_dt)
+ jump_b = 0;
}
- if (1.f < jump_dt)
- jump_b = 0;
}
else
{
for (i = 0; i < n; i++)
{
+ int ball_in_hole = 0;
+
d = sol_step(fp, g, t, ball, &m);
+ if ((ball_in_hole = game_check_balls(fp)))
+ hole_goal(ball_in_hole);
+
if (b < d)
b = d;
if (m)
* friction too early and stopping the ball prematurely.
*/
- file.uv[ball].v[0] = -4.f * view_e[2][0] * view_m;
- file.uv[ball].v[1] = -4.f * view_e[2][1] * view_m + BALL_FUDGE;
- file.uv[ball].v[2] = -4.f * view_e[2][2] * view_m;
+ if (config_get_d(CONFIG_BALL_COLLISIONS))
+ {
+ file.uv[ball].v[0] = -4.f * view_e[2][0] * view_m;
+ file.uv[ball].v[1] = -4.f * view_e[2][1] * view_m + BALL_FUDGE;
+ file.uv[ball].v[2] = -4.f * view_e[2][2] * view_m;
+ }
+
+ else
+ {
+ file.uv[ball].v[0] = -4.f * view_e[2][0] * view_m;
+ file.uv[ball].v[1] = -4.f * view_e[2][1] * view_m + BALL_FUDGE;
+ file.uv[ball].v[2] = -4.f * view_e[2][2] * view_m;
+ }
view_m = 0.f;
}
void game_update_view(float);
+void game_set_played(int);
+
void game_set_rot(int);
void game_clr_mag(void);
void game_set_mag(int);
if (p <= party)
{
- for (h = 1; h <= hole && h < count; h++)
+ for (h = 1; h <= hole && h <= 18; h++)
T += score_v[h][p];
sprintf(str, "%d", T);
if (p <= party)
{
- for (h = 1; h <= hole && h <= count / 2; h++)
+ for (h = 1; h <= hole && h <= 9; h++)
T += score_v[h][p];
sprintf(str, "%d", T);
static char str[MAXSTR];
int h, T = 0;
- int out = count / 2;
- if (hole > out && p <= party)
+ if (hole > 9 && p <= party)
{
- for (h = out + 1; h <= hole && h < count; h++)
+ for (h = 10; h <= hole && h <= 18; h++)
T += score_v[h][p];
sprintf(str, "%d", T);
return 0;
}
-void hole_goal(void)
+void hole_goal(int playerid)
{
- score_v[hole][player]++;
+ if (playerid)
+ {
+ /* HACK: If the player has already beaten the hole, return */
+ if (stat_v[playerid] == 1)
+ return;
+
+ if (score_v[hole][playerid] == 1)
+ audio_play(AUD_ONE, 1.0f);
+
+ else if (score_v[hole][playerid] == score_v[hole][0] - 2)
+ audio_play(AUD_EAGLE, 1.0f);
+ else if (score_v[hole][playerid] == score_v[hole][0] - 1)
+ audio_play(AUD_BIRDIE, 1.0f);
+ else if (score_v[hole][playerid] == score_v[hole][0])
+ audio_play(AUD_PAR, 1.0f);
+ else if (score_v[hole][playerid] == score_v[hole][0] + 1)
+ audio_play(AUD_BOGEY, 1.0f);
+ else if (score_v[hole][playerid] == score_v[hole][0] + 2)
+ audio_play(AUD_DOUBLE, 1.0f);
+ else
+ audio_play(AUD_SUCCESS, 1.0f);
+
+ stat_v[playerid] = 1;
+ done++;
+
+ if (done == party)
+ audio_music_fade_out(2.0f);
+ }
- if (score_v[hole][player] == 1)
- audio_play(AUD_ONE, 1.0f);
-
- else if (score_v[hole][player] == score_v[hole][0] - 2)
- audio_play(AUD_EAGLE, 1.0f);
- else if (score_v[hole][player] == score_v[hole][0] - 1)
- audio_play(AUD_BIRDIE, 1.0f);
- else if (score_v[hole][player] == score_v[hole][0])
- audio_play(AUD_PAR, 1.0f);
- else if (score_v[hole][player] == score_v[hole][0] + 1)
- audio_play(AUD_BOGEY, 1.0f);
- else if (score_v[hole][player] == score_v[hole][0] + 2)
- audio_play(AUD_DOUBLE, 1.0f);
else
- audio_play(AUD_SUCCESS, 1.0f);
+ {
+ score_v[hole][player]++;
+
+ if (score_v[hole][player] == 1)
+ audio_play(AUD_ONE, 1.0f);
+
+ else if (score_v[hole][player] == score_v[hole][0] - 2)
+ audio_play(AUD_EAGLE, 1.0f);
+ else if (score_v[hole][player] == score_v[hole][0] - 1)
+ audio_play(AUD_BIRDIE, 1.0f);
+ else if (score_v[hole][player] == score_v[hole][0])
+ audio_play(AUD_PAR, 1.0f);
+ else if (score_v[hole][player] == score_v[hole][0] + 1)
+ audio_play(AUD_BOGEY, 1.0f);
+ else if (score_v[hole][player] == score_v[hole][0] + 2)
+ audio_play(AUD_DOUBLE, 1.0f);
+ else
+ audio_play(AUD_SUCCESS, 1.0f);
- stat_v[player] = 1;
- done++;
+ stat_v[player] = 1;
+ done++;
- if (done == party)
- audio_music_fade_out(2.0f);
+ if (done == party)
+ audio_music_fade_out(2.0f);
+ }
}
void hole_stop(void)
void hole_goto(int, int);
int hole_next(void);
int hole_move(void);
-void hole_goal(void);
+void hole_goal(int);
void hole_stop(void);
void hole_fall(void);
static void title_timer(int id, float dt)
{
- float g[3] = { 0.f, 0.f, 0.f };
+ float g[3] = { 0.0f, -9.8f, 0.0f };
game_step(g, dt);
game_set_fly(fcosf(time_state() / 10.f));
if (paused)
paused = 0;
+ game_set_played(1);
+
return 0;
}
static void stroke_timer(int id, float dt)
{
- float g[3] = { 0.f, 0.f, 0.f };
+ float g[3] = { 0.0f, -9.8f, 0.0f };
float k;
if (paused)
paused = 0;
else
- hole_goal();
+ hole_goal(0);
hud_init();
static void stop_timer(int id, float dt)
{
- float g[3] = { 0.f, 0.f, 0.f };
+ float g[3] = { 0.0f, -9.8f, 0.0f };
game_update_view(dt);
game_step(g, dt);
if (paused)
paused = 0;
+ game_set_played(0);
+
return score_card(_("Scores"), gui_yel, gui_red);
}
--- /dev/null
+/*
+ * Copyright (C) 2003 Robert Kooima - 2008 Byron Johnson
+ * Part of the Neverball Project http://icculus.org/neverball/
+ *
+ * NEVERBALL is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+
+#include "gui.h"
+#include "back.h"
+#include "part.h"
+#include "game.h"
+#include "audio.h"
+#include "config.h"
+
+#include "st_balt.h"
+
+extern struct state st_conf;
+extern struct state st_null;
+
+/*---------------------------------------------------------------------------*/
+
+enum {
+ BALT_BACK = 1,
+ BALT_GOLF,
+ BALT_BILL,
+ BALT_CRAZ
+};
+
+static int balt_action(int i)
+{
+ int r = 1;
+
+ audio_play(AUD_MENU, 1.0f);
+
+ switch (i)
+ {
+ case BALT_BACK:
+ goto_state(&st_conf);
+ break;
+
+ case BALT_GOLF:
+ goto_state(&st_null);
+ config_set_s(CONFIG_BALL_GAMMA, "0.78");
+ goto_state(&st_balt);
+ break;
+
+ case BALT_BILL:
+ goto_state(&st_null);
+ config_set_s(CONFIG_BALL_GAMMA, "1.00");
+ goto_state(&st_balt);
+ break;
+
+ case BALT_CRAZ:
+ goto_state(&st_null);
+ config_set_s(CONFIG_BALL_GAMMA, "1.50");
+ goto_state(&st_balt);
+ break;
+
+ default:
+ break;
+ }
+
+ return r;
+}
+
+static int balt_enter(void)
+{
+ int id, jd;
+
+ char gamma[MAXNAM];
+
+ config_get_s(CONFIG_BALL_GAMMA, gamma, MAXNAM);
+
+ back_init("back/gui.png", config_get_d(CONFIG_GEOMETRY));
+
+ if ((id = gui_vstack(0)))
+ {
+ if ((jd = gui_harray(id)))
+ {
+ gui_space(jd);
+ gui_space(jd);
+ gui_start(jd, _("Back"), GUI_SML, BALT_BACK, 0);
+ }
+
+ gui_space(id);
+ gui_space(id);
+
+ if ((jd = gui_harray(id)))
+ {
+ gui_state(jd, _("Billiards"), GUI_SML, BALT_BILL,
+ strcmp(gamma, "1.00") == 0 ||
+ strcmp(gamma, "1.0") == 0);
+ gui_state(jd, _("Golf Balls"), GUI_SML, BALT_GOLF,
+ strcmp(gamma, "0.78") == 0);
+ }
+
+ if ((jd = gui_harray(id)))
+ {
+ gui_state(jd, _("Crazy Balls"), GUI_SML, BALT_CRAZ,
+ strcmp(gamma, "1.50") == 0 ||
+ strcmp(gamma, "1.5") == 0);
+ }
+
+ gui_layout(id, 0, 0);
+ }
+
+ audio_music_fade_to(0.5f, "bgm/inter.ogg");
+
+ return id;
+}
+
+static void balt_leave(int id)
+{
+ back_free();
+ gui_delete(id);
+}
+
+static void balt_paint(int id, float st)
+{
+ config_push_persp((float) config_get_d(CONFIG_VIEW_FOV), 0.1f, FAR_DIST);
+ {
+ back_draw(0);
+ }
+ config_pop_matrix();
+ gui_paint(id);
+}
+
+static void balt_timer(int id, float dt)
+{
+ gui_timer(id, dt);
+}
+
+static void balt_point(int id, int x, int y, int dx, int dy)
+{
+ gui_pulse(gui_point(id, x, y), 1.2f);
+}
+
+static void balt_stick(int id, int a, int v)
+{
+ if (config_tst_d(CONFIG_JOYSTICK_AXIS_X, a))
+ gui_pulse(gui_stick(id, v, 0), 1.2f);
+ if (config_tst_d(CONFIG_JOYSTICK_AXIS_Y, a))
+ gui_pulse(gui_stick(id, 0, v), 1.2f);
+}
+
+static int balt_click(int b, int d)
+{
+ if (b < 0 && d == 1)
+ return balt_action(gui_token(gui_click()));
+ return 1;
+}
+
+static int balt_keybd(int c, int d)
+{
+ return (d && c == SDLK_ESCAPE) ? goto_state(&st_conf) : 1;
+}
+
+static int balt_buttn(int b, int d)
+{
+ if (d)
+ {
+ if (config_tst_d(CONFIG_JOYSTICK_BUTTON_A, b))
+ return balt_action(gui_token(gui_click()));
+ if (config_tst_d(CONFIG_JOYSTICK_BUTTON_B, b))
+ return goto_state(&st_conf);
+ if (config_tst_d(CONFIG_JOYSTICK_BUTTON_EXIT, b))
+ return goto_state(&st_conf);
+ }
+ return 1;
+}
+
+/*---------------------------------------------------------------------------*/
+
+
+struct state st_balt = {
+ balt_enter,
+ balt_leave,
+ balt_paint,
+ balt_timer,
+ balt_point,
+ balt_stick,
+ NULL,
+ balt_click,
+ balt_keybd,
+ balt_buttn,
+ 1, 0
+};
+
--- /dev/null
+#ifndef ST_BALT_H
+#define ST_BALT_H
+
+#include "state.h"
+
+extern struct state st_balt;
+
+#endif
#include "st_conf.h"
#include "st_all.h"
#include "st_resol.h"
+#include "st_balt.h"
/*---------------------------------------------------------------------------*/
CONF_SHDON,
CONF_SHDOF,
CONF_BACK,
- CONF_RESOL
+ CONF_RESOL,
+ CONF_BALT,
+ CONF_BCLON,
+ CONF_BCLOF
};
static int music_id[11];
goto_state(&st_resol);
break;
+ case CONF_BALT:
+ goto_state(&st_balt);
+ break;
+
+ case CONF_BCLON:
+ goto_state(&st_null);
+ config_set_d(CONFIG_BALL_COLLISIONS, 1);
+ goto_state(&st_conf);
+ break;
+
+ case CONF_BCLOF:
+ goto_state(&st_null);
+ config_set_d(CONFIG_BALL_COLLISIONS, 0);
+ goto_state(&st_conf);
+ break;
+
default:
if (100 <= i && i <= 110)
{
if ((id = gui_vstack(0)))
{
int f = config_get_d(CONFIG_FULLSCREEN);
+ int c = config_get_d(CONFIG_BALL_COLLISIONS);
int t = config_get_d(CONFIG_TEXTURES);
int g = config_get_d(CONFIG_GEOMETRY);
int h = config_get_d(CONFIG_SHADOW);
int s = config_get_d(CONFIG_SOUND_VOLUME);
int m = config_get_d(CONFIG_MUSIC_VOLUME);
+ char gamma[MAXNAM];
+
char resolution[20];
+ char balt[22];
+
+ config_get_s(CONFIG_BALL_GAMMA, gamma, MAXNAM);
+
sprintf(resolution, "%d x %d",
config_get_d(CONFIG_WIDTH),
config_get_d(CONFIG_HEIGHT));
+ if (strcmp(gamma, "0.78") == 0)
+ strcpy(balt, "Golf Balls");
+
+ else if (strcmp(gamma, "1.00") == 0 ||
+ strcmp(gamma, "1.0") == 0)
+ strcpy(balt, "Billiards");
+
+ else if (strcmp(gamma, "1.50") == 0 ||
+ strcmp(gamma, "1.5") == 0)
+ strcpy(balt, "Crazy Balls");
+
+ else
+ sprintf(balt, "Custom");
+
if ((jd = gui_harray(id)))
{
gui_label(jd, _("Options"), GUI_SML, GUI_ALL, 0, 0);
if ((jd = gui_harray(id)) &&
(kd = gui_harray(jd)))
{
- gui_state(kd, resolution, GUI_SML, CONF_RESOL, 0);
+ gui_state(kd, resolution, GUI_SML, CONF_RESOL, 0);
gui_label(jd, _("Resolution"), GUI_SML, GUI_ALL, 0, 0);
}
if ((jd = gui_harray(id)) &&
(kd = gui_harray(jd)))
{
- gui_state(kd, _("Low"), GUI_SML, CONF_TEXLO, (t == 2));
- gui_state(kd, _("High"), GUI_SML, CONF_TEXHI, (t == 1));
+ gui_state(kd, _("Off"), GUI_SML, CONF_BCLOF, (c == 0));
+ gui_state(kd, _("On"), GUI_SML, CONF_BCLON, (c == 1));
+
+ gui_label(jd, _("Ball Collisions"), GUI_SML, GUI_ALL, 0, 0);
+ }
+
+ if ((jd = gui_harray(id)) &&
+ (kd = gui_harray(jd)))
+ {
+ if (c == 1)
+ {
+ gui_state(kd, balt, GUI_SML, CONF_BALT, 0);
+ gui_space(jd);
+ }
+ else
+ {
+ gui_space(kd);
+ gui_space(jd);
+ gui_space(id);
+ }
+ }
+
+ gui_space(id);
+
+ if ((jd = gui_harray(id)) &&
+ (kd = gui_harray(jd)))
+ {
+ gui_state(kd, _("Low"), GUI_SML, CONF_TEXLO, (t == 2));
+ gui_state(kd, _("High"), GUI_SML, CONF_TEXHI, (t == 1));
- gui_label(jd, _("Textures"), GUI_SML, GUI_ALL, 0, 0);
+ gui_label(jd, _("Textures"), GUI_SML, GUI_ALL, 0, 0);
}
if ((jd = gui_harray(id)) &&
(kd = gui_harray(jd)))
{
- gui_state(kd, _("Low"), GUI_SML, CONF_GEOLO, (g == 0));
- gui_state(kd, _("High"), GUI_SML, CONF_GEOHI, (g == 1));
+ gui_state(kd, _("Low"), GUI_SML, CONF_GEOLO, (g == 0));
+ gui_state(kd, _("High"), GUI_SML, CONF_GEOHI, (g == 1));
- gui_label(jd, _("Geometry"), GUI_SML, GUI_ALL, 0, 0);
+ gui_label(jd, _("Geometry"), GUI_SML, GUI_ALL, 0, 0);
}
if ((jd = gui_harray(id)) &&
(kd = gui_harray(jd)))
{
- gui_state(kd, _("Off"), GUI_SML, CONF_SHDOF, (h == 0));
- gui_state(kd, _("On"), GUI_SML, CONF_SHDON, (h == 1));
+ gui_state(kd, _("Off"), GUI_SML, CONF_SHDOF, (h == 0));
+ gui_state(kd, _("On"), GUI_SML, CONF_SHDON, (h == 1));
- gui_label(jd, _("Shadow"), GUI_SML, GUI_ALL, 0, 0);
+ gui_label(jd, _("Shadow"), GUI_SML, GUI_ALL, 0, 0);
}
gui_space(id);
jump_free();
flag_free();
mark_free();
+ oldball_free();
ball_free();
shad_free();
shad_init();
ball_init();
+ oldball_init(g);
mark_init(g);
flag_init(g);
jump_init(g);
#include "glext.h"
#include "config.h"
#include "solid_gl.h"
+#include "image.h"
/*---------------------------------------------------------------------------*/
+#define IMG_DEFAULT "ball/default.png"
+#define IMG_ARBBALL "ball/arbball.png"
+
static int has_solid = 0;
static int has_inner = 0;
static int has_outer = 0;
static float inner_alpha;
static float outer_alpha;
+static GLuint oldball_list;
+static GLuint oldball_text;
+static GLuint arbball_list;
+static GLuint arbball_text;
+
+/*---------------------------------------------------------------------------*/
+
+/* These are the faces of an octahedron in positive longitude/latitude. */
+
+static float oldball_octahedron[8][3][2] = {
+ {{ 0.0f, 90.0f }, { 0.0f, 0.0f }, { 90.0f, 0.0f }},
+ {{ 90.0f, 90.0f }, { 90.0f, 0.0f }, { 180.0f, 0.0f }},
+ {{ 180.0f, 90.0f }, { 180.0f, 0.0f }, { 270.0f, 0.0f }},
+ {{ 270.0f, 90.0f }, { 270.0f, 0.0f }, { 360.0f, 0.0f }},
+ {{ 0.0f, -90.0f }, { 90.0f, 0.0f }, { 0.0f, 0.0f }},
+ {{ 90.0f, -90.0f }, { 180.0f, 0.0f }, { 90.0f, 0.0f }},
+ {{ 180.0f, -90.0f }, { 270.0f, 0.0f }, { 180.0f, 0.0f }},
+ {{ 270.0f, -90.0f }, { 360.0f, 0.0f }, { 270.0f, 0.0f }},
+};
+
+static void oldball_midpoint(float *P, const float *A, const float *B)
+{
+ float D[2];
+
+ /* The haversine midpoint method. */
+
+ D[0] = fcosf(B[1]) * fcosf(B[0] - A[0]);
+ D[1] = fcosf(B[1]) * fsinf(B[0] - A[0]);
+
+ P[0] = A[0] + fatan2f(D[1], fcosf(A[1]) + D[0]);
+
+ P[1] = fatan2f(fsinf(A[1]) +
+ fsinf(B[1]),
+ fsqrtf((fcosf(A[1]) + D[0]) *
+ (fcosf(A[1]) + D[0]) + D[1] * D[1]));
+}
+
+static void oldball_vertex(const float *p)
+{
+ /* Draw a vertex with normal and texture coordinate at the given lon/lat. */
+
+ const float x = fsinf(p[0]) * fcosf(p[1]);
+ const float y = fsinf(p[1]);
+ const float z = fcosf(p[0]) * fcosf(p[1]);
+
+ glTexCoord2f((+p[0] ) / V_RAD(360.0f),
+ (-p[1] + V_RAD(90.0f)) / V_RAD(180.0f));
+
+ glNormal3f(x, y, z);
+ glVertex3f(x, y, z);
+}
+
+static void oldball_subdiv(const float *a,
+ const float *b,
+ const float *c, int D)
+{
+ if (D > 0)
+ {
+ /* Recursively subdivide the given triangle. */
+
+ float d[2];
+ float e[2];
+ float f[2];
+
+ oldball_midpoint(d, a, b);
+ oldball_midpoint(e, b, c);
+ oldball_midpoint(f, c, a);
+
+ oldball_subdiv(a, d, f, D - 1);
+ oldball_subdiv(d, b, e, D - 1);
+ oldball_subdiv(f, e, c, D - 1);
+ oldball_subdiv(d, e, f, D - 1);
+ }
+ else
+ {
+ /* Draw the given triangle. */
+
+ oldball_vertex(a);
+ oldball_vertex(b);
+ oldball_vertex(c);
+ }
+}
+
+void oldball_init(int b)
+{
+ char name[MAXSTR];
+
+ strncpy(name, IMG_DEFAULT, MAXSTR - 12);
+
+ if ((oldball_text = make_image_from_file(name)))
+ {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ }
+
+ oldball_list = glGenLists(1);
+
+ glNewList(oldball_list, GL_COMPILE);
+ {
+#if 1
+ int i, d = b ? 4 : 3;
+
+ glBegin(GL_TRIANGLES);
+ {
+ for (i = 0; i < 8; ++i)
+ {
+ float a[2];
+ float b[2];
+ float c[2];
+
+ a[0] = V_RAD(oldball_octahedron[i][0][0]);
+ a[1] = V_RAD(oldball_octahedron[i][0][1]);
+
+ b[0] = V_RAD(oldball_octahedron[i][1][0]);
+ b[1] = V_RAD(oldball_octahedron[i][1][1]);
+
+ c[0] = V_RAD(oldball_octahedron[i][2][0]);
+ c[1] = V_RAD(oldball_octahedron[i][2][1]);
+
+ oldball_subdiv(a, b, c, d);
+ }
+ }
+ glEnd();
+#else
+ int i, slices = b ? 32 : 16;
+ int j, stacks = b ? 16 : 8;
+
+ for (i = 0; i < stacks; i++)
+ {
+ float k0 = (float) i / stacks;
+ float k1 = (float) (i + 1) / stacks;
+
+ float s0 = fsinf(V_PI * (k0 - 0.5));
+ float c0 = fcosf(V_PI * (k0 - 0.5));
+ float s1 = fsinf(V_PI * (k1 - 0.5));
+ float c1 = fcosf(V_PI * (k1 - 0.5));
+
+ glBegin(GL_QUAD_STRIP);
+ {
+ for (j = 0; j <= slices; j++)
+ {
+ float k = (float) j / slices;
+ float s = fsinf(V_PI * k * 2.0);
+ float c = fcosf(V_PI * k * 2.0);
+
+ glTexCoord2f(k, k0);
+ glNormal3f(s * c0, c * c0, s0);
+ glVertex3f(s * c0, c * c0, s0);
+
+ glTexCoord2f(k, k1);
+ glNormal3f(s * c1, c * c1, s1);
+ glVertex3f(s * c1, c * c1, s1);
+ }
+ }
+ glEnd();
+ }
+#endif
+ }
+ glEndList();
+
+ strncpy(name, IMG_ARBBALL, MAXSTR - 12);
+
+ if ((arbball_text = make_image_from_file(name)))
+ {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ }
+
+ arbball_list = glGenLists(1);
+
+ glNewList(arbball_list, GL_COMPILE);
+ {
+#if 1
+ int i, d = b ? 4 : 3;
+
+ glBegin(GL_TRIANGLES);
+ {
+ for (i = 0; i < 8; ++i)
+ {
+ float a[2];
+ float b[2];
+ float c[2];
+
+ a[0] = V_RAD(oldball_octahedron[i][0][0]);
+ a[1] = V_RAD(oldball_octahedron[i][0][1]);
+
+ b[0] = V_RAD(oldball_octahedron[i][1][0]);
+ b[1] = V_RAD(oldball_octahedron[i][1][1]);
+
+ c[0] = V_RAD(oldball_octahedron[i][2][0]);
+ c[1] = V_RAD(oldball_octahedron[i][2][1]);
+
+ oldball_subdiv(a, b, c, d);
+ }
+ }
+ glEnd();
+#else
+ int i, slices = b ? 32 : 16;
+ int j, stacks = b ? 16 : 8;
+
+ for (i = 0; i < stacks; i++)
+ {
+ float k0 = (float) i / stacks;
+ float k1 = (float) (i + 1) / stacks;
+
+ float s0 = fsinf(V_PI * (k0 - 0.5));
+ float c0 = fcosf(V_PI * (k0 - 0.5));
+ float s1 = fsinf(V_PI * (k1 - 0.5));
+ float c1 = fcosf(V_PI * (k1 - 0.5));
+
+ glBegin(GL_QUAD_STRIP);
+ {
+ for (j = 0; j <= slices; j++)
+ {
+ float k = (float) j / slices;
+ float s = fsinf(V_PI * k * 2.0);
+ float c = fcosf(V_PI * k * 2.0);
+
+ glTexCoord2f(k, k0);
+ glNormal3f(s * c0, c * c0, s0);
+ glVertex3f(s * c0, c * c0, s0);
+
+ glTexCoord2f(k, k1);
+ glNormal3f(s * c1, c * c1, s1);
+ glVertex3f(s * c1, c * c1, s1);
+ }
+ }
+ glEnd();
+ }
+#endif
+ }
+ glEndList();
+}
+
+void oldball_free(void)
+{
+ if (glIsList(oldball_list))
+ glDeleteLists(oldball_list, 1);
+
+ if (glIsTexture(oldball_text))
+ glDeleteTextures(1, &oldball_text);
+
+ if (glIsList(arbball_list))
+ glDeleteLists(arbball_list, 1);
+
+ if (glIsTexture(arbball_text))
+ glDeleteTextures(1, &arbball_text);
+
+ oldball_list = 0;
+ oldball_text = 0;
+
+ arbball_list = 0;
+ arbball_text = 0;
+}
+
+void oldball_draw(int arb)
+{
+ static const float a[4] = { 0.2f, 0.2f, 0.2f, 1.0f };
+ static const float s[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
+ static const float e[4] = { 0.2f, 0.2f, 0.2f, 1.0f };
+ static const float h[1] = { 20.0f };
+
+ glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, a);
+ glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, s);
+ glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, e);
+ glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, h);
+
+ glEnable(GL_COLOR_MATERIAL);
+ {
+ glBindTexture(GL_TEXTURE_2D, (arb) ? (arbball_text) : (oldball_text));
+
+ /* Render the ball back to front in case it is translucent. */
+
+ glDepthMask(GL_FALSE);
+ {
+ glCullFace(GL_FRONT);
+ glCallList((arb) ? (arbball_list) : (oldball_list));
+ glCullFace(GL_BACK);
+ glCallList(oldball_list);
+ }
+ glDepthMask(GL_TRUE);
+
+ /* Render the ball into the depth buffer. */
+
+ glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+ {
+ glCallList((arb) ? (arbball_list) : (oldball_list));
+ }
+ glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+
+ /* Ensure the ball is visible even when obscured by geometry. */
+
+ glDisable(GL_DEPTH_TEST);
+ {
+ glColor4f(1.0f, 1.0f, 1.0f, 0.1f);
+ glCallList((arb) ? (arbball_list) : (oldball_list));
+ }
+ glEnable(GL_DEPTH_TEST);
+ }
+ glDisable(GL_COLOR_MATERIAL);
+}
+
/*---------------------------------------------------------------------------*/
#define SET(B, v, b) ((v) ? ((B) | (b)) : ((B) & ~(b)))
{
int T = config_get_d(CONFIG_TEXTURES);
+ char ball_file[PATHMAX];
char solid_file[PATHMAX];
char inner_file[PATHMAX];
char outer_file[PATHMAX];
- config_get_s(CONFIG_BALL, solid_file, PATHMAX - 12);
- config_get_s(CONFIG_BALL, inner_file, PATHMAX - 12);
- config_get_s(CONFIG_BALL, outer_file, PATHMAX - 12);
+ config_get_s(CONFIG_BALL, ball_file, PATHMAX / 2 - 12);
+
+ strncpy(solid_file, "ball/", PATHMAX);
+ strncpy(inner_file, "ball/", PATHMAX);
+ strncpy(outer_file, "ball/", PATHMAX);
+
+ strcat(solid_file, ball_file);
+ strcat(inner_file, ball_file);
+ strcat(outer_file, ball_file);
+
+ strcat(solid_file, "/");
+ strcat(inner_file, "/");
+ strcat(outer_file, "/");
+
+ strcat(solid_file, ball_file);
+ strcat(inner_file, ball_file);
+ strcat(outer_file, ball_file);
strcat(solid_file, "-solid.sol");
strcat(inner_file, "-inner.sol");
void ball_init(void);
void ball_free(void);
+void oldball_init(int);
+void oldball_free(void);
void ball_draw(const float *,
const float *,
const float *, float);
+void oldball_draw(int);
/*---------------------------------------------------------------------------*/
config_set_d(CONFIG_CHEAT, DEFAULT_CHEAT);
config_set_d(CONFIG_STATS, DEFAULT_STATS);
config_set_d(CONFIG_UNIFORM, DEFAULT_UNIFORM);
+ config_set_d(CONFIG_LOCK_GOALS, DEFAULT_LOCK_GOALS);
+ config_set_d(CONFIG_BALL_COLLISIONS, DEFAULT_BALL_COLLISIONS);
+ config_set_s(CONFIG_BALL_GAMMA, DEFAULT_BALL_GAMMA);
config_set_d(CONFIG_KEY_FORWARD, DEFAULT_KEY_FORWARD);
config_set_d(CONFIG_KEY_BACKWARD, DEFAULT_KEY_BACKWARD);
config_set_d(CONFIG_KEY_LEFT, DEFAULT_KEY_LEFT);
else if (strcmp(key, "wiimote_addr") == 0)
config_set_s(CONFIG_WIIMOTE_ADDR, val);
- else if (strcmp(key, "cheat") == 0)
- config_set_d(CONFIG_CHEAT, atoi(val));
- else if (strcmp(key, "stats") == 0)
- config_set_d(CONFIG_STATS, atoi(val));
- else if (strcmp(key, "uniform") == 0)
+ else if (strcmp(key, "cheat") == 0)
+ config_set_d(CONFIG_CHEAT, atoi(val));
+ else if (strcmp(key, "stats") == 0)
+ config_set_d(CONFIG_STATS, atoi(val));
+ else if (strcmp(key, "uniform") == 0)
config_set_d(CONFIG_UNIFORM, atoi(val));
+
+ else if (strcmp(key, "lock_goals") == 0)
+ config_set_d(CONFIG_LOCK_GOALS, atoi(val));
+
+ else if (strcmp(key, "ball_collisions") == 0)
+ config_set_d(CONFIG_BALL_COLLISIONS, atoi(val));
+ else if (strcmp(key, "gamma") == 0)
+ config_set_s(CONFIG_BALL_GAMMA, val);
}
fclose(fp);
SDL_GetKeyName((SDLKey) option_d[CONFIG_KEY_RESTART]));
if (strlen(option_s[CONFIG_PLAYER]) > 0)
- fprintf(fp, "player %s\n", option_s[CONFIG_PLAYER]);
+ fprintf(fp, "player %s\n", option_s[CONFIG_PLAYER]);
if (strlen(option_s[CONFIG_BALL]) > 0)
- fprintf(fp, "ball_file %s\n", option_s[CONFIG_BALL]);
+ fprintf(fp, "ball_file %s\n", option_s[CONFIG_BALL]);
if (strlen(option_s[CONFIG_WIIMOTE_ADDR]) > 0)
- fprintf(fp, "wiimote_addr %s\n", option_s[CONFIG_WIIMOTE_ADDR]);
-
- fprintf(fp, "stats %d\n",
- option_d[CONFIG_STATS]);
- fprintf(fp, "uniform %d\n",
- option_d[CONFIG_UNIFORM]);
- if (config_cheat())
- fprintf(fp, "cheat %d\n", option_d[CONFIG_CHEAT]);
+ fprintf(fp, "wiimote_addr %s\n", option_s[CONFIG_WIIMOTE_ADDR]);
+
+ fprintf(fp, "stats %d\n", option_d[CONFIG_STATS]);
+ fprintf(fp, "uniform %d\n", option_d[CONFIG_UNIFORM]);
+ fprintf(fp, "lock_goals %d\n", option_d[CONFIG_LOCK_GOALS]);
+ fprintf(fp, "ball_collisions %d\n", option_d[CONFIG_BALL_COLLISIONS]);
+
+ if (strlen(option_s[CONFIG_BALL_GAMMA]) > 0)
+ fprintf(fp, "gamma %s\n", option_s[CONFIG_BALL_GAMMA]);
fclose(fp);
}
CONFIG_CHEAT,
CONFIG_STATS,
CONFIG_UNIFORM,
+ CONFIG_LOCK_GOALS,
+ CONFIG_BALL_COLLISIONS,
CONFIG_OPTION_D_COUNT
};
CONFIG_PLAYER,
CONFIG_BALL,
CONFIG_WIIMOTE_ADDR,
+ CONFIG_BALL_GAMMA,
CONFIG_OPTION_S_COUNT
};
#define DEFAULT_ROTATE_SLOW 100
#define DEFAULT_ROTATE_FAST 200
#define DEFAULT_PLAYER ""
-#define DEFAULT_BALL "ball/basic-ball/basic-ball"
+#define DEFAULT_BALL "basic-ball"
#define DEFAULT_CHEAT 0
#define DEFAULT_KEY_FORWARD SDLK_UP
#define DEFAULT_KEY_BACKWARD SDLK_DOWN
#define DEFAULT_KEY_RESTART SDLK_r
#define DEFAULT_STATS 0
#define DEFAULT_UNIFORM 0
+#define DEFAULT_LOCK_GOALS 0
+#define DEFAULT_BALL_COLLISIONS 0
+#define DEFAULT_BALL_GAMMA "0.78"
/*---------------------------------------------------------------------------*/
#define MAXX 1024
#define MAXR 2048
#define MAXU 1024
+#define MAXY 1024
#define MAXW 1024
#define MAXD 1024
#define MAXA 16384
return (fp->uc < MAXU) ? fp->uc++ : overflow("ball");
}
+static int incy(struct s_file *fp)
+{
+ return (fp->yc < MAXY) ? fp->yc++ : overflow("ball");
+}
+
static int incw(struct s_file *fp)
{
return (fp->wc < MAXW) ? fp->wc++ : overflow("view");
fp->xc = 0;
fp->rc = 0;
fp->uc = 0;
+ fp->yc = 0;
fp->wc = 0;
fp->dc = 0;
fp->ac = 0;
fp->xv = (struct s_swch *) calloc(MAXX, sizeof (struct s_swch));
fp->rv = (struct s_bill *) calloc(MAXR, sizeof (struct s_bill));
fp->uv = (struct s_ball *) calloc(MAXU, sizeof (struct s_ball));
+ fp->yv = (struct s_ball *) calloc(MAXY, sizeof (struct s_ball));
fp->wv = (struct s_view *) calloc(MAXW, sizeof (struct s_view));
fp->dv = (struct s_dict *) calloc(MAXD, sizeof (struct s_dict));
fp->av = (char *) calloc(MAXA, sizeof (char));
up->p[1] = 0.0f;
up->p[2] = 0.0f;
up->r = 0.25f;
+ up->m = 1;
+ up->n = 0;
+ up->c = 0;
for (i = 0; i < c; i++)
{
up->p[1] = +(float) (z - 24) / SCALE;
up->p[2] = -(float) (y) / SCALE;
}
+
+ if (strcmp(k[i], "mobile") == 0)
+ sscanf(v[i], "%d", &up->m);
+
+ if (strcmp(k[i], "return") == 0)
+ sscanf(v[i], "%d", &up->n);
+
+ if (strcmp(k[i], "collisions") == 0)
+ sscanf(v[i], "%d", &up->c);
}
up->p[1] += up->r + SMALL;
}
+static void make_abal(struct s_file *fp,
+ char k[][MAXSTR],
+ char v[][MAXSTR], int c)
+{
+ int i, yi = incy(fp);
+
+ struct s_ball *yp = fp->yv + yi;
+
+ yp->p[0] = 0.0f;
+ yp->p[1] = 0.0f;
+ yp->p[2] = 0.0f;
+ yp->r = 0.25f;
+ yp->m = 1;
+ yp->n = 0;
+ yp->c = 0;
+
+ for (i = 0; i < c; i++)
+ {
+ if (strcmp(k[i], "radius") == 0)
+ sscanf(v[i], "%f", &yp->r);
+
+ if (strcmp(k[i], "origin") == 0)
+ {
+ int x = 0, y = 0, z = 0;
+
+ sscanf(v[i], "%d %d %d", &x, &y, &z);
+
+ yp->p[0] = +(float) (x) / SCALE;
+ yp->p[1] = +(float) (z - 24) / SCALE;
+ yp->p[2] = -(float) (y) / SCALE;
+ }
+
+ if (strcmp(k[i], "mobile") == 0)
+ sscanf(v[i], "%d", &yp->m);
+
+ if (strcmp(k[i], "return") == 0)
+ sscanf(v[i], "%d", &yp->n);
+
+ if (strcmp(k[i], "collisions") == 0)
+ sscanf(v[i], "%d", &yp->c);
+ }
+
+ yp->p[1] += yp->r + SMALL;
+}
+
/*---------------------------------------------------------------------------*/
static void read_ent(struct s_file *fp, FILE *fin)
if (!strcmp(v[i], "info_null")) make_bill(fp, k, v, c);
if (!strcmp(v[i], "path_corner")) make_path(fp, k, v, c);
if (!strcmp(v[i], "info_player_start")) make_ball(fp, k, v, c);
+ if (!strcmp(v[i], "info_notnull")) make_abal(fp, k, v, c);
if (!strcmp(v[i], "info_player_intermission")) make_view(fp, k, v, c);
if (!strcmp(v[i], "info_player_deathmatch")) make_goal(fp, k, v, c);
if (!strcmp(v[i], "target_teleporter")) make_jump(fp, k, v, c);
name, n, m, c,
#endif
name, n, c,
- p->mc, p->vc, p->ec, p->sc, p->tc,
- p->gc, p->lc, p->pc, p->nc, p->bc,
- p->hc, p->zc, p->wc, p->jc, p->xc,
- p->rc, p->uc, p->ac, p->dc, p->ic);
+ p->mc, p->vc, p->ec, p->sc, p->tc,
+ p->gc, p->lc, p->pc, p->nc, p->bc,
+ p->hc, p->zc, p->wc, p->jc, p->xc,
+ p->rc, p->uc + p->yc, p->ac, p->dc, p->ic);
}
int main(int argc, char *argv[])
#include "solid.h"
#include "base_config.h"
#include "binary.h"
+#include "config.h"
#define MAGIC 0x4c4f53af
-#define SOL_VERSION 6
+#define SOL_VERSION 7
#define LARGE 1.0e+5f
#define SMALL 1.0e-3f
+static int ball_collision_flag = 0;
+
/*---------------------------------------------------------------------------*/
static float erp(float t)
{
get_array(fin, bp->p, 3);
get_float(fin, &bp->r);
+ get_index(fin, &bp->m);
+ get_index(fin, &bp->n);
+ get_index(fin, &bp->c);
+
+ v_cpy(bp->O, bp->p);
bp->e[0][0] = bp->E[0][0] = 1.0f;
bp->e[0][1] = bp->E[0][1] = 0.0f;
get_index(fin, &fp->xc);
get_index(fin, &fp->rc);
get_index(fin, &fp->uc);
+ get_index(fin, &fp->yc);
get_index(fin, &fp->wc);
get_index(fin, &fp->ic);
fp->rv = (struct s_bill *) calloc(fp->rc, sizeof (struct s_bill));
if (fp->uc)
fp->uv = (struct s_ball *) calloc(fp->uc, sizeof (struct s_ball));
+ if (fp->yc)
+ fp->yv = (struct s_ball *) calloc(fp->yc, sizeof (struct s_ball));
if (fp->wc)
fp->wv = (struct s_view *) calloc(fp->wc, sizeof (struct s_view));
if (fp->dc)
for (i = 0; i < fp->xc; i++) sol_load_swch(fin, fp->xv + i);
for (i = 0; i < fp->rc; i++) sol_load_bill(fin, fp->rv + i);
for (i = 0; i < fp->uc; i++) sol_load_ball(fin, fp->uv + i);
+ for (i = 0; i < fp->yc; i++) sol_load_ball(fin, fp->yv + i);
for (i = 0; i < fp->wc; i++) sol_load_view(fin, fp->wv + i);
for (i = 0; i < fp->ic; i++) get_index(fin, fp->iv + i);
get_index(fin, &fp->xc);
get_index(fin, &fp->rc);
get_index(fin, &fp->uc);
+ get_index(fin, &fp->yc);
get_index(fin, &fp->wc);
get_index(fin, &fp->ic);
#endif
- fseek(fin, 18 * 4, SEEK_CUR);
+ fseek(fin, 19 * 4, SEEK_CUR);
if (fp->ac)
{
{
put_array(fout, bp->p, 3);
put_float(fout, &bp->r);
+ put_index(fout, &bp->m);
+ put_index(fout, &bp->n);
+ put_index(fout, &bp->c);
}
static void sol_stor_view(FILE *fout, struct s_view *wp)
put_index(fin, &fp->xc);
put_index(fin, &fp->rc);
put_index(fin, &fp->uc);
+ put_index(fin, &fp->yc);
put_index(fin, &fp->wc);
put_index(fin, &fp->ic);
for (i = 0; i < fp->xc; i++) sol_stor_swch(fin, fp->xv + i);
for (i = 0; i < fp->rc; i++) sol_stor_bill(fin, fp->rv + i);
for (i = 0; i < fp->uc; i++) sol_stor_ball(fin, fp->uv + i);
+ for (i = 0; i < fp->yc; i++) sol_stor_ball(fin, fp->yv + i);
for (i = 0; i < fp->wc; i++) sol_stor_view(fin, fp->wv + i);
for (i = 0; i < fp->ic; i++) put_index(fin, fp->iv + i);
}
if (fp->xv) free(fp->xv);
if (fp->rv) free(fp->rv);
if (fp->uv) free(fp->uv);
+ if (fp->yv) free(fp->yv);
if (fp->wv) free(fp->wv);
if (fp->dv) free(fp->dv);
if (fp->iv) free(fp->iv);
return t;
}
+static float v_ball(float Q[3],
+ const float o[3],
+ const float q[3],
+ const float w[3],
+ const float p[3],
+ const float v[3], float r, float r2)
+{
+ float O[3], P[3], V[3];
+ float t = LARGE;
+
+ v_add(O, o, q);
+ v_sub(P, p, O);
+ v_sub(V, v, w);
+
+ if (v_dot(P, V) < 0.0f)
+ {
+ t = v_sol(P, V, r + r2);
+
+ if (t < LARGE)
+ v_mad(Q, O, w, t);
+ }
+
+ return t;
+
+}
+
/*
* Compute the earliest time and position of the intersection of a
* sphere and an edge.
v_mad(p, q, n, up->r);
/* Return the "energy" of the impact, to determine the sound amplitude. */
-
return fabsf(v_dot(n, d));
}
/*
+ * Compute the new linear velocities of two colliding balls.
+ * t gives the time after which they collide.
+ */
+static float sol_bounce_sphere(const struct s_file *fp,
+ struct s_ball *up,
+ struct s_ball *u2p,
+ const float t)
+{
+ float r_rel[3], v_rel[3], v1_par[3], v1_perp[3], v2_par[3], v2_perp[3],
+ u[3];
+ float v11[3], v12[3], v21[3], v22[3];
+ float *p1 = up->p, *v1 = up->v, *p2 = u2p->p, *v2 = u2p->v;
+ float inertia, factor, gamma;
+ const int u1 = up->m;
+ const int u2 = u2p->m;
+
+ {
+ char gamma_str[MAXNAM];
+ config_get_s(CONFIG_BALL_GAMMA, gamma_str, MAXNAM);
+ gamma = atof(gamma_str);
+ }
+
+ /* Correct positions up to the collision */
+ v_mad(p1, p1, v1, t);
+ v_mad(p2, p2, v2, t);
+
+ /* Floating point precision */
+ if (!(p1[1] - p2[1] > 0.001f ) && !(p2[1] - p1[1] > 0.001f))
+ {
+ if (p1[1] > p2[1])
+ p2[1] = p1[1];
+ else
+ p1[1] = p2[1];
+ }
+
+ /* Hack: prevent losing balls */
+ v_sub(v_rel, v2, v1);
+ if (v_len(v_rel) < 0.001f)
+ {
+ return 0.0f;
+ }
+
+ /* r_rel is the unit vector from p1 to p2 */
+ v_sub(r_rel, p2, p1);
+ v_nrm(r_rel, r_rel);
+
+ /*
+ * project velocities upon r_rel to get components parallel
+ * to r_rel - only these will be changed in the collision
+ */
+ factor = v_dot(v1, r_rel);
+ v_scl(v1_par, r_rel, factor);
+ v_sub(v1_perp, v1, v1_par);
+
+ factor = v_dot(v2, r_rel);
+ v_scl(v2_par, r_rel, factor);
+ v_sub(v2_perp, v2, v2_par);
+
+ /* u is used to calculate the "energy" of the impact */
+ v_sub(u, v2_par, v1_par);
+
+ /* Ensure immobile balls don't travel inside of each other */
+ if (!up->m || !u2p->m)
+ gamma = 0.78f;
+
+ /*
+ * New parallel velocities follow from momentum conservation,
+ * coefficient of restitution GAMMA, mass ratio inertia
+ */
+ inertia = pow(up->r / u2p->r, 3);
+
+ if (!u2p->m)
+ {
+ v_scl(v11, v1_par, -gamma);
+ v_scl(v12, v2_par, gamma + 1.0f);
+ v_add(v1_par, v11, v12);
+ }
+
+ else if (!up->m)
+ {
+ v_scl(v21, v1_par, gamma + 1.0f);
+ v_scl(v22, v2_par, -gamma);
+ v_add(v2_par, v21, v22);
+ }
+
+ else
+ {
+ v_scl(v11, v1_par, (inertia - gamma) / (inertia + 1.0f));
+ v_scl(v12, v1_par, (gamma + 1.0f) * inertia / (inertia + 1.0f));
+ v_scl(v21, v2_par, (gamma + 1.0f) / (inertia + 1.0f));
+ v_scl(v22, v2_par, (1.0f - gamma * inertia) / (inertia + 1.0f));
+ v_add(v1_par, v11, v21);
+ v_add(v2_par, v12, v22);
+ }
+
+ if (up->m)
+ v_add(v1, v1_par, v1_perp);
+ if (u2p->m)
+ v_add(v2, v2_par, v2_perp);
+
+ /* Hack: prevent accidental spinning while the ball is stationary */
+ if (v_len(v1) < 0.01f && u1)
+ {
+ up->w[0] = 0.0f;
+ up->w[1] = 0.0f;
+ up->w[2] = 0.0f;
+ }
+
+ if (v_len(v2) < 0.01f && u2)
+ {
+ u2p->w[0] = 0.0f;
+ u2p->w[1] = 0.0f;
+ u2p->w[2] = 0.0f;
+ }
+
+ /*
+ * Return the length of the relative velocity parallel
+ * to the line of impact
+ */
+ return fabsf(v_len(u));
+}
+
+/*
* Compute the new angular velocity and orientation of a ball pendulum.
* A gives the accelleration of the ball. G gives the gravity vector.
*/
/*
* Compute the positions of all balls after DT seconds have passed.
*/
-static void sol_ball_step(struct s_file *fp, float dt)
+static void sol_ball_step(struct s_file *fp, int arb, float dt)
{
int i;
- for (i = 0; i < fp->uc; i++)
+ if (arb)
{
- struct s_ball *up = fp->uv + i;
+ for (i = 0; i < fp->yc; i++)
+ {
+ struct s_ball *yp = fp->yv + i;
- v_mad(up->p, up->p, up->v, dt);
+ if (!yp->m)
+ continue;
- sol_rotate(up->e, up->w, dt);
+ v_mad(yp->p, yp->p, yp->v, dt);
+
+ sol_rotate(yp->e, yp->w, dt);
+ }
+ }
+
+ else
+ {
+ for (i = 0; i < fp->yc; i++)
+ {
+ struct s_ball *yp = fp->yv + i;
+
+ if (!yp->m)
+ continue;
+
+ v_mad(yp->p, yp->p, yp->v, dt);
+
+ sol_rotate(yp->e, yp->w, dt);
+ }
+
+ for (i = 0; i < fp->uc; i++)
+ {
+ struct s_ball *up = fp->uv + i;
+
+ v_mad(up->p, up->p, up->v, dt);
+
+ sol_rotate(up->e, up->w, dt);
+ }
}
}
return v_vert(T, o, vp->p, w, up->p, up->v, up->r);
}
+/*---------------------------------------------------------------------------*/
+
+static float sol_test_ball(float dt,
+ float T[3],
+ const struct s_ball *up,
+ const struct s_ball *u2p,
+ const float o[3],
+ const float w[3])
+{
+ return v_ball(T, o, u2p->p, w, up->p, up->v, up->r, u2p->r);
+}
+
static float sol_test_edge(float dt,
float T[3],
const struct s_ball *up,
const struct s_file *fp,
const struct s_lump *lp,
const float o[3],
- const float w[3])
+ const float w[3],
+ const int ui)
{
float U[3] = {0.0f, 0.0f, 0.0f}; /* init value only to avoid gcc warnings */
float u, t = dt;
- int i;
+ int i;
/* Short circuit a non-solid lump. */
if (lp->fl & L_DETAIL) return t;
+ /* Test all balls */
+
+ if (up->r > 0.0f)
+ {
+ for (i = 0; i < fp->yc; i++)
+ {
+ struct s_ball *yp = fp->yv + i;
+
+ if ((u = sol_test_ball(t, U, up, yp, o, yp->v)) < t)
+ {
+ ball_collision_flag = i + fp->yv - fp->uv;
+ t = u;
+ }
+ }
+
+ for (i = 1; config_get_d(CONFIG_BALL_COLLISIONS) && i < fp->uc; i++)
+ {
+ struct s_ball *u2p = fp->uv + i;
+
+ if(i == ui)
+ continue;
+
+ if (u2p->P && up->P &&
+ (u = sol_test_ball(t, U, up, u2p, o, u2p->v)) < t)
+ {
+ ball_collision_flag = i;
+ t = u;
+ }
+ }
+ }
+
/* Test all verts */
if (up->r > 0.0f)
const struct s_file *fp,
const struct s_node *np,
const float o[3],
- const float w[3])
+ const float w[3],
+ const int ui)
{
float U[3], u, t = dt;
int i;
{
const struct s_lump *lp = fp->lv + np->l0 + i;
- if ((u = sol_test_lump(t, U, up, fp, lp, o, w)) < t)
+ if ((u = sol_test_lump(t, U, up, fp, lp, o, w, ui)) < t)
{
v_cpy(T, U);
t = u;
{
const struct s_node *nq = fp->nv + np->ni;
- if ((u = sol_test_node(t, U, up, fp, nq, o, w)) < t)
+ if ((u = sol_test_node(t, U, up, fp, nq, o, w, ui)) < t)
{
v_cpy(T, U);
t = u;
{
const struct s_node *nq = fp->nv + np->nj;
- if ((u = sol_test_node(t, U, up, fp, nq, o, w)) < t)
+ if ((u = sol_test_node(t, U, up, fp, nq, o, w, ui)) < t)
{
v_cpy(T, U);
t = u;
float T[3], float V[3],
const struct s_ball *up,
const struct s_file *fp,
- const struct s_body *bp)
+ const struct s_body *bp,
+ const int ui)
{
float U[3], O[3], W[3], u, t = dt;
sol_body_p(O, fp, bp);
sol_body_v(W, fp, bp);
- if ((u = sol_test_node(t, U, up, fp, np, O, W)) < t)
+ if ((u = sol_test_node(t, U, up, fp, np, O, W, ui)) < t)
{
v_cpy(T, U);
v_cpy(V, W);
static float sol_test_file(float dt,
float T[3], float V[3],
const struct s_ball *up,
- const struct s_file *fp)
+ const struct s_file *fp,
+ const int ui)
{
float U[3], W[3], u, t = dt;
int i;
{
const struct s_body *bp = fp->bv + i;
- if ((u = sol_test_body(t, U, W, up, fp, bp)) < t)
+ if ((u = sol_test_body(t, U, W, up, fp, bp, ui)) < t)
{
v_cpy(T, U);
v_cpy(V, W);
float sol_step(struct s_file *fp, const float *g, float dt, int ui, int *m)
{
- float P[3], V[3], v[3], r[3], a[3], d, e, nt, b = 0.0f, tt = dt;
- int c = 16;
+ float b = 0.0f;
+ int i, c = 16;
- if (ui < fp->uc)
+ /*
+ * The user ball loop
+ */
+ for (i = 0; (config_get_d(CONFIG_BALL_COLLISIONS) && i < fp->uc) ||
+ (!config_get_d(CONFIG_BALL_COLLISIONS) && i < 4 + 1); i++)
{
- struct s_ball *up = fp->uv + ui;
+ float P[3], V[3], v[3], r[3], a[3], d, e, nt = 0.0f, tt = dt;
- /* If the ball is in contact with a surface, apply friction. */
+ if (i < fp->uc)
+ {
+ struct s_ball *up = fp->uv + i;
- v_cpy(a, up->v);
- v_cpy(v, up->v);
- v_cpy(up->v, g);
+ /* If the ball is in contact with a surface, apply friction. */
- if (m && sol_test_file(tt, P, V, up, fp) < 0.0005f)
- {
- v_cpy(up->v, v);
- v_sub(r, P, up->p);
+ v_cpy(a, up->v);
+ v_cpy(v, up->v);
+ v_cpy(up->v, g);
- if ((d = v_dot(r, g) / (v_len(r) * v_len(g))) > 0.999f)
+ if (m && sol_test_file(tt, P, V, up, fp, i) < 0.0005f)
{
- if ((e = (v_len(up->v) - dt)) > 0.0f)
+ v_cpy(up->v, v);
+ v_sub(r, P, up->p);
+
+ if ((d = v_dot(r, g) / (v_len(r) * v_len(g))) > 0.999f)
{
- /* Scale the linear velocity. */
+ if ((e = (v_len(up->v) - dt)) > 0.0f)
+ {
+ /* Scale the linear velocity. */
- v_nrm(up->v, up->v);
- v_scl(up->v, up->v, e);
+ v_nrm(up->v, up->v);
+ v_scl(up->v, up->v, e);
- /* Scale the angular velocity. */
+ /* Scale the angular velocity. */
- v_sub(v, V, up->v);
- v_crs(up->w, v, r);
- v_scl(up->w, up->w, -1.0f / (up->r * up->r));
- }
- else
- {
- /* Friction has brought the ball to a stop. */
+ v_sub(v, V, up->v);
+ v_crs(up->w, v, r);
+ v_scl(up->w, up->w, -1.0f / (up->r * up->r));
+ }
+ else
+ {
+ /* Friction has brought the ball to a stop. */
- up->v[0] = 0.0f;
- up->v[1] = 0.0f;
- up->v[2] = 0.0f;
+ up->v[0] = 0.0f;
+ up->v[1] = 0.0f;
+ up->v[2] = 0.0f;
- (*m)++;
+ if(i == ui)
+ (*m)++;
+ }
}
+ else v_mad(up->v, v, g, tt);
}
else v_mad(up->v, v, g, tt);
+
+ /* Test for collision. */
+
+ while (c && tt && tt > (nt = sol_test_file(tt, P, V, up, fp, i)))
+ {
+ sol_body_step(fp, nt);
+ sol_swch_step(fp, nt);
+ sol_ball_step(fp, 0, nt);
+
+ tt -= nt;
+
+ if (b < ((ball_collision_flag)
+ ? (d = sol_bounce_sphere(fp, up,
+ fp->uv + ball_collision_flag, nt))
+ : (d = sol_bounce(up, P, V, nt))))
+ b = d;
+
+ ball_collision_flag = 0;
+
+ c--;
+ }
+
+ if (i == ui || !c)
+ {
+ if (!c)
+ nt += tt;
+ sol_body_step(fp, nt);
+ sol_swch_step(fp, nt);
+ sol_ball_step(fp, 0, nt);
+ }
+
+ /* Apply the ball's accelleration to the pendulum. */
+
+ v_sub(a, up->v, a);
+
+ sol_pendulum(up, a, g, dt);
}
- else v_mad(up->v, v, g, tt);
+ }
- /* Test for collision. */
+ /*
+ * The arbitrary balls loop
+ */
+ for (i = 0; i < fp->yc && c > 0; i++)
+ {
+ float P[3], V[3], v[3], r[3], a[3], d, e, nt = 0.0f, tt = dt;
- while (c > 0 && tt > 0 && tt > (nt = sol_test_file(tt, P, V, up, fp)))
+ if (i < fp->yc)
{
- sol_body_step(fp, nt);
- sol_swch_step(fp, nt);
- sol_ball_step(fp, nt);
+ struct s_ball *yp = fp->yv + i;
- tt -= nt;
+ if ((!yp->m) || (!config_get_d(CONFIG_BALL_COLLISIONS) && yp->c))
+ continue;
- if (b < (d = sol_bounce(up, P, V, nt)))
- b = d;
+ /* If the ball is in contact with a surface, apply friction. */
- c--;
- }
+ v_cpy(a, yp->v);
+ v_cpy(v, yp->v);
+ v_cpy(yp->v, g);
+
+ if (m && sol_test_file(tt, P, V, yp, fp, i) < 0.0005f)
+ {
+ v_cpy(yp->v, v);
+ v_sub(r, P, yp->p);
+
+ if ((d = v_dot(r, g) / (v_len(r) * v_len(g))) > 0.999f)
+ {
+ if ((e = (v_len(yp->v) - dt)) > 0.0f)
+ {
+ /* Scale the linear velocity. */
+
+ v_nrm(yp->v, yp->v);
+ v_scl(yp->v, yp->v, e);
+
+ /* Scale the angular velocity. */
+
+ v_sub(v, V, yp->v);
+ v_crs(yp->w, v, r);
+ v_scl(yp->w, yp->w, -1.0f / (yp->r * yp->r));
+ }
+ else
+ {
+ /* Friction has brought the ball to a stop. */
+
+ yp->v[0] = 0.0f;
+ yp->v[1] = 0.0f;
+ yp->v[2] = 0.0f;
+ }
+ }
+ else v_mad(yp->v, v, g, tt);
+ }
+ else v_mad(yp->v, v, g, tt);
+
+ /* Test for collision. */
+
+ while (c && tt && tt > (nt = sol_test_file(tt, P, V, yp, fp, i)))
+ {
+ sol_ball_step(fp, 1, nt);
+
+ tt -= nt;
+
+ if (b < ((ball_collision_flag)
+ ? (d = sol_bounce_sphere(fp, yp,
+ fp->uv + ball_collision_flag, nt))
+ : (d = sol_bounce(yp, P, V, nt))))
+ b = d;
+
+ ball_collision_flag = 0;
+
+ c--;
+ }
- sol_body_step(fp, tt);
- sol_swch_step(fp, tt);
- sol_ball_step(fp, tt);
+ if (!c)
+ {
+ if (!c && ui == 0)
+ nt += tt;
+ sol_body_step(fp, nt);
+ sol_swch_step(fp, nt);
+ sol_ball_step(fp, 1, nt);
+ }
- /* Apply the ball's accelleration to the pendulum. */
+ /* Apply the ball's accelleration to the pendulum. */
- v_sub(a, up->v, a);
+ v_sub(a, yp->v, a);
- sol_pendulum(up, a, g, dt);
+ sol_pendulum(yp, a, g, dt);
+ }
}
+
return b;
}
struct s_item *sol_item_test(struct s_file *fp, float *p, float item_r)
{
- const float *ball_p = fp->uv->p;
- const float ball_r = fp->uv->r;
-
- int hi;
+ int hi, yi;
for (hi = 0; hi < fp->hc; hi++)
{
- float r[3];
+ {
+ const float *ball_p = fp->uv->p;
+ const float ball_r = fp->uv->r;
- r[0] = ball_p[0] - fp->hv[hi].p[0];
- r[1] = ball_p[1] - fp->hv[hi].p[1];
- r[2] = ball_p[2] - fp->hv[hi].p[2];
+ float r[3];
- if (fp->hv[hi].t != ITEM_NONE && v_len(r) < ball_r + item_r)
+ r[0] = ball_p[0] - fp->hv[hi].p[0];
+ r[1] = ball_p[1] - fp->hv[hi].p[1];
+ r[2] = ball_p[2] - fp->hv[hi].p[2];
+
+ if (fp->hv[hi].t != ITEM_NONE && v_len(r) < ball_r + item_r)
+ {
+ p[0] = fp->hv[hi].p[0];
+ p[1] = fp->hv[hi].p[1];
+ p[2] = fp->hv[hi].p[2];
+
+ return &fp->hv[hi];
+ }
+ }
+
+ for (yi = 0; yi < fp->yc; yi++)
{
- p[0] = fp->hv[hi].p[0];
- p[1] = fp->hv[hi].p[1];
- p[2] = fp->hv[hi].p[2];
+ const float *ball_p = fp->yv[yi].p;
+ const float ball_r = fp->yv[yi].r;
+
+ float r[3];
- return &fp->hv[hi];
+ r[0] = ball_p[0] - fp->hv[hi].p[0];
+ r[1] = ball_p[1] - fp->hv[hi].p[1];
+ r[2] = ball_p[2] - fp->hv[hi].p[2];
+
+ if (fp->hv[hi].t != ITEM_NONE && v_len(r) < ball_r + item_r)
+ {
+ p[0] = fp->hv[hi].p[0];
+ p[1] = fp->hv[hi].p[1];
+ p[2] = fp->hv[hi].p[2];
+
+ return &fp->hv[hi];
+ }
}
}
return NULL;
}
-struct s_goal *sol_goal_test(struct s_file *fp, float *p, int ui)
+int sol_goal_test(struct s_file *fp, float *p, int ui)
{
- const float *ball_p = fp->uv[ui].p;
- const float ball_r = fp->uv[ui].r;
- int zi;
-
- for (zi = 0; zi < fp->zc; zi++)
+ if (config_get_d(CONFIG_BALL_COLLISIONS) && ui)
{
- float r[3];
+ const float *ball_p = fp->uv[ui].p;
+ const float ball_r = fp->uv[ui].r;
+ float z[3] = {0.0f, 0.0f, 0.0f};
+ int zi, i;
- r[0] = ball_p[0] - fp->zv[zi].p[0];
- r[1] = ball_p[2] - fp->zv[zi].p[2];
- r[2] = 0;
+ for (i = 1; i < fp->uc; i++)
+ {
+ if(fp->uv[i].p[1] < -199.9f)
+ v_cpy(fp->uv[i].v, z);
+ if (v_len(fp->uv[i].v) > 0.0f)
+ return 0;
+ else
+ v_cpy(fp->uv[i].v, z);
+ }
- if (v_len(r) < fp->zv[zi].r * 1.1 - ball_r &&
- ball_p[1] > fp->zv[zi].p[1] &&
- ball_p[1] < fp->zv[zi].p[1] + GOAL_HEIGHT / 2)
+ for (i = 0; i < fp->yc; i++)
{
- p[0] = fp->zv[zi].p[0];
- p[1] = fp->zv[zi].p[1];
- p[2] = fp->zv[zi].p[2];
+ if(fp->yv[i].p[1] < -199.9f)
+ v_cpy(fp->yv[i].v, z);
+ if (v_len(fp->yv[i].v) > 0.0f)
+ return 0;
+ else
+ v_cpy(fp->yv[i].v, z);
+ }
- return &fp->zv[zi];
+ for (zi = 0; zi < fp->zc; zi++)
+ {
+ float r[3];
+
+ r[0] = ball_p[0] - fp->zv[zi].p[0];
+ r[1] = ball_p[2] - fp->zv[zi].p[2];
+ r[2] = 0;
+
+ if (v_len(r) < fp->zv[zi].r * 1.1 - ball_r &&
+ ball_p[1] > fp->zv[zi].p[1] &&
+ ball_p[1] < fp->zv[zi].p[1] + GOAL_HEIGHT / 2)
+ {
+ p[0] = fp->zv[zi].p[0];
+ p[1] = -200.0f;
+ p[2] = fp->zv[zi].p[2];
+
+ return 2;
+ }
}
+ return 1;
+ }
+
+ else
+ {
+ const float *ball_p = fp->uv[ui].p;
+ const float ball_r = fp->uv[ui].r;
+ int zi;
+
+ for (zi = 0; zi < fp->zc; zi++)
+ {
+ float r[3];
+
+ r[0] = ball_p[0] - fp->zv[zi].p[0];
+ r[1] = ball_p[2] - fp->zv[zi].p[2];
+ r[2] = 0;
+
+ if (v_len(r) < fp->zv[zi].r * 1.1 - ball_r &&
+ ball_p[1] > fp->zv[zi].p[1] &&
+ ball_p[1] < fp->zv[zi].p[1] + GOAL_HEIGHT / 2)
+ {
+ p[0] = fp->zv[zi].p[0];
+ p[1] = -200.0f;
+ p[2] = fp->zv[zi].p[2];
+
+ return 1;
+ }
+ }
+ return 0;
}
- return NULL;
}
/*
* a visible switch is activated, return 0 otherwise (no switch is
* activated or only invisible switches).
*/
-int sol_swch_test(struct s_file *fp, int ui)
+int sol_swch_test(struct s_file *fp)
{
- const float *ball_p = fp->uv[ui].p;
- const float ball_r = fp->uv[ui].r;
- int xi;
- int res = 0;
+ int xi, i, res = 0;
for (xi = 0; xi < fp->xc; xi++)
{
struct s_swch *xp = fp->xv + xi;
- if (xp->t0 == 0 || xp->f == xp->f0)
+ for (i = 0; i < fp->uc; i++)
{
- float l;
- float r[3];
+ float l, r[3];
- r[0] = ball_p[0] - xp->p[0];
- r[1] = ball_p[2] - xp->p[2];
- r[2] = 0;
-
- l = v_len(r) - xp->r;
+ const float *ball_p = fp->uv[i].p;
+ const float ball_r = fp->uv[i].r;
- if (l < ball_r &&
- ball_p[1] > xp->p[1] &&
- ball_p[1] < xp->p[1] + SWCH_HEIGHT / 2)
+ if (xp->t0 == 0 || xp->f == xp->f0)
{
- if (!xp->e && l < - ball_r)
+ r[0] = ball_p[0] - xp->p[0];
+ r[1] = ball_p[2] - xp->p[2];
+ r[2] = 0;
+
+ l = v_len(r) - xp->r;
+
+ if (l < ball_r &&
+ ball_p[1] > xp->p[1] &&
+ ball_p[1] < xp->p[1] + SWCH_HEIGHT / 2)
{
- int pi = xp->pi;
- int pj = xp->pi;
+ if (!xp->e && l < -ball_r)
+ {
+ int pi = xp->pi;
+ int pj = xp->pi;
- /* The ball enters. */
+ xp->b = i * 2;
- if (xp->t0 == 0)
- xp->e = 1;
+ /* The ball enters. */
- /* Toggle the state, update the path. */
+ if (xp->t0 == 0)
+ xp->e = 1;
- xp->f = xp->f ? 0 : 1;
+ /* Toggle the state, update the path. */
- do /* Tortoise and hare cycle traverser. */
- {
- fp->pv[pi].f = xp->f;
- fp->pv[pj].f = xp->f;
+ xp->f = xp->f ? 0 : 1;
- pi = fp->pv[pi].pi;
- pj = fp->pv[pj].pi;
- pj = fp->pv[pj].pi;
- }
- while (pi != pj);
+ do /* Tortoise and hare cycle traverser. */
+ {
+ fp->pv[pi].f = xp->f;
+ fp->pv[pj].f = xp->f;
+
+ pi = fp->pv[pi].pi;
+ pj = fp->pv[pj].pi;
+ pj = fp->pv[pj].pi;
+ }
+ while (pi != pj);
- /* It toggled to non-default state, start the timer. */
+ /* It toggled to non-default state, start the timer. */
- if (xp->f != xp->f0)
- xp->t = xp->t0;
+ if (xp->f != xp->f0)
+ xp->t = xp->t0;
- /* If visible, set the result. */
+ /* If visible, set the result. */
- if (!xp->i)
- res = 1;
+ if (!xp->i)
+ res = 1;
+ }
}
+
+ /* The ball exits. */
+
+ else if (xp->e && xp->b == i * 2)
+ xp->e = 0;
}
+ }
+
+ for (i = 0; i < fp->yc; i++)
+ {
+ float l, r[3];
+
+ const float *ball_p = fp->yv[i].p;
+ const float ball_r = fp->yv[i].r;
+
+ if (xp->t0 == 0 || xp->f == xp->f0)
+ {
+ r[0] = ball_p[0] - xp->p[0];
+ r[1] = ball_p[2] - xp->p[2];
+ r[2] = 0;
+
+ l = v_len(r) - xp->r;
+
+ if (l < ball_r &&
+ ball_p[1] > xp->p[1] &&
+ ball_p[1] < xp->p[1] + SWCH_HEIGHT / 2)
+ {
+ if (!xp->e && l < -ball_r)
+ {
+ int pi = xp->pi;
+ int pj = xp->pi;
+
+ xp->b = i * 2 + 1;
+
+ /* The ball enters. */
+
+ if (xp->t0 == 0)
+ xp->e = 1;
+
+ /* Toggle the state, update the path. */
+
+ xp->f = xp->f ? 0 : 1;
+
+ do /* Tortoise and hare cycle traverser. */
+ {
+ fp->pv[pi].f = xp->f;
+ fp->pv[pj].f = xp->f;
+
+ pi = fp->pv[pi].pi;
+ pj = fp->pv[pj].pi;
+ pj = fp->pv[pj].pi;
+ }
+ while (pi != pj);
- /* The ball exits. */
+ /* It toggled to non-default state, start the timer. */
- else if (xp->e)
- xp->e = 0;
+ if (xp->f != xp->f0)
+ xp->t = xp->t0;
+
+ /* If visible, set the result. */
+
+ if (!xp->i)
+ res = 1;
+ }
+ }
+
+ /* The ball exits. */
+
+ else if (xp->e && xp->b == i * 2 + 1)
+ xp->e = 0;
+ }
}
}
+
return res;
}
*
* The Xs are as documented by struct s_file:
*
- * f File (struct s_file)
- * m Material (struct s_mtrl)
- * v Vertex (struct s_vert)
- * e Edge (struct s_edge)
- * s Side (struct s_side)
- * t Texture coord (struct s_texc)
- * g Geometry (struct s_geom)
- * l Lump (struct s_lump)
- * n Node (struct s_node)
- * p Path (struct s_path)
- * b Body (struct s_body)
- * h Item (struct s_item)
- * z Goal (struct s_goal)
- * j Jump (struct s_jump)
- * x Switch (struct s_swch)
- * r Billboard (struct s_bill)
- * u User (struct s_ball)
- * w Viewpoint (struct s_view)
- * d Dictionary (struct s_dict)
- * i Index (int)
- * a Text (char)
+ * f File (struct s_file)
+ * m Material (struct s_mtrl)
+ * v Vertex (struct s_vert)
+ * e Edge (struct s_edge)
+ * s Side (struct s_side)
+ * t Texture coord (struct s_texc)
+ * g Geometry (struct s_geom)
+ * l Lump (struct s_lump)
+ * n Node (struct s_node)
+ * p Path (struct s_path)
+ * b Body (struct s_body)
+ * h Item (struct s_item)
+ * z Goal (struct s_goal)
+ * j Jump (struct s_jump)
+ * x Switch (struct s_swch)
+ * r Billboard (struct s_bill)
+ * u User (struct s_ball)
+ * y Arbitrary ball (struct s_ball)
+ * w Viewpoint (struct s_view)
+ * d Dictionary (struct s_dict)
+ * i Index (int)
+ * a Text (char)
+
*
* The Ys are as follows:
*
* Those members that do not conform to this convention are explicitly
* documented with a comment.
*
- * These prefixes are still available: c k o q y.
+ * These prefixes are still available: c k o q.
*/
/*---------------------------------------------------------------------------*/
int f; /* current state */
int i; /* is invisible? */
int e; /* is a ball inside it? */
+ int b; /* which ball? */
};
struct s_bill
struct s_ball
{
- float e[3][3]; /* basis of orientation */
- float p[3]; /* position vector */
- float v[3]; /* velocity vector */
- float w[3]; /* angular velocity vector */
- float E[3][3]; /* basis of pendulum */
- float W[3]; /* angular pendulum velocity */
- float r; /* radius */
+ float e[3][3]; /* basis of orientation */
+ float p[3]; /* position vector */
+ float v[3]; /* velocity vector */
+ float w[3]; /* angular velocity vector */
+ float E[3][3]; /* basis of pendulum */
+ float W[3]; /* angular pendulum velocity */
+ float r; /* radius */
+ int P; /* ball in play state */
+ int m; /* is ball mobile? */
+ int n; /* return the ball on fall-out? */
+ int c; /* is ball only existant with *
+ * ball_collisions? */
+ float O[3]; /* original ball *
+ * location (arbitrary balls) */
};
struct s_view
struct s_file
{
- int ac;
- int mc;
- int vc;
- int ec;
- int sc;
- int tc;
- int gc;
- int lc;
- int nc;
- int pc;
- int bc;
- int hc;
- int zc;
- int jc;
- int xc;
- int rc;
- int uc;
- int wc;
- int dc;
- int ic;
+ int ac;
+ int mc;
+ int vc;
+ int ec;
+ int sc;
+ int tc;
+ int gc;
+ int lc;
+ int nc;
+ int pc;
+ int bc;
+ int hc;
+ int zc;
+ int jc;
+ int xc;
+ int rc;
+ int uc;
+ int wc;
+ int dc;
+ int ic;
+ int yc;
char *av;
struct s_mtrl *mv;
struct s_swch *xv;
struct s_bill *rv;
struct s_ball *uv;
+ struct s_ball *yv;
struct s_view *wv;
struct s_dict *dv;
int *iv;
float sol_step(struct s_file *, const float *, float, int, int *);
int sol_jump_test(struct s_file *, float *, int);
-int sol_swch_test(struct s_file *, int);
+int sol_swch_test(struct s_file *);
+int sol_goal_test(struct s_file *, float *, int);
-struct s_goal *sol_goal_test(struct s_file *, float *, int);
struct s_item *sol_item_test(struct s_file *, float *, float);
/*---------------------------------------------------------------------------*/