All pastes #946402 Raw Edit

Anonymous

public text v1 · immutable
·published 2008-03-17 18:03 UTC
rendered paste body
Index: share/config.c
===================================================================
--- share/config.c	(.../trunk)	(revision 1922)
+++ share/config.c	(.../branches/putt-collisions)	(revision 1922)
@@ -115,7 +115,6 @@
     config_set_s(CONFIG_PLAYER,               DEFAULT_PLAYER);
     config_set_s(CONFIG_BALL,                 DEFAULT_BALL);
     config_set_s(CONFIG_WIIMOTE_ADDR,         DEFAULT_WIIMOTE_ADDR);
-    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_KEY_FORWARD,          DEFAULT_KEY_FORWARD);
@@ -127,6 +126,9 @@
     config_set_d(CONFIG_KEY_SCORE_NEXT,       DEFAULT_KEY_SCORE_NEXT);
     config_set_d(CONFIG_SCREENSHOT,           DEFAULT_SCREENSHOT);
     config_set_d(CONFIG_LOCK_GOALS,           DEFAULT_LOCK_GOALS);
+    config_set_s(CONFIG_BALL_GAMMA,           DEFAULT_BALL_GAMMA);
+    config_set_d(CONFIG_BALL_COLLISIONS,      DEFAULT_BALL_COLLISIONS);
+    config_set_d(CONFIG_CHEAT,                DEFAULT_CHEAT);
 }
 
 void config_load(void)
@@ -256,16 +258,22 @@
                 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, "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, "screenshot") == 0)
                     config_set_d(CONFIG_SCREENSHOT, atoi(val));
                 else if (strcmp(key, "lock_goals") == 0)
                     config_set_d(CONFIG_LOCK_GOALS, atoi(val));
+
+                else if (strcmp(key, "gamma") == 0)
+                    config_set_s(CONFIG_BALL_GAMMA, val);
+                else if (strcmp(key, "ball_collisions") == 0)
+                    config_set_d(CONFIG_BALL_COLLISIONS, atoi(val));
+
+                else if (strcmp(key, "cheat")      == 0)
+                    config_set_d(CONFIG_CHEAT,   atoi(val));
             }
 
         fclose(fp);
@@ -388,17 +396,24 @@
                 SDL_GetKeyName((SDLKey) option_d[CONFIG_KEY_SCORE_NEXT]));
 
         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, "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, "screenshot           %d\n", option_d[CONFIG_SCREENSHOT]);
         fprintf(fp, "lock_goals           %d\n", option_d[CONFIG_LOCK_GOALS]);
 
+        if (strlen(option_s[CONFIG_BALL_GAMMA]) > 0)
+            fprintf(fp, "gamma                %s\n",
+                    option_s[CONFIG_BALL_GAMMA]);
+
+        fprintf(fp, "ball_collisions      %d\n",
+                option_d[CONFIG_BALL_COLLISIONS]);
+
         if (config_cheat())
             fprintf(fp, "cheat                %d\n", option_d[CONFIG_CHEAT]);
 
Index: share/config.h
===================================================================
--- share/config.h	(.../trunk)	(revision 1922)
+++ share/config.h	(.../branches/putt-collisions)	(revision 1922)
@@ -86,11 +86,12 @@
     CONFIG_KEY_PAUSE,
     CONFIG_KEY_RESTART,
     CONFIG_KEY_SCORE_NEXT,
-    CONFIG_CHEAT,
     CONFIG_STATS,
     CONFIG_UNIFORM,
     CONFIG_SCREENSHOT,
     CONFIG_LOCK_GOALS,
+    CONFIG_BALL_COLLISIONS,
+    CONFIG_CHEAT,
 
     CONFIG_OPTION_D_COUNT
 };
@@ -99,6 +100,7 @@
     CONFIG_PLAYER,
     CONFIG_BALL,
     CONFIG_WIIMOTE_ADDR,
+    CONFIG_BALL_GAMMA,
 
     CONFIG_OPTION_S_COUNT
 };
@@ -115,7 +117,7 @@
 #define DEFAULT_REFLECTION           1
 #define DEFAULT_MULTISAMPLE          0
 #define DEFAULT_MIPMAP               0
-#define DEFAULT_ANISO                0
+#define DEFAULT_ANISO                1
 #define DEFAULT_BACKGROUND           1
 #define DEFAULT_SHADOW               1
 #define DEFAULT_AUDIO_BUFF           AUDIO_BUFF_HI
@@ -156,7 +158,6 @@
 #define DEFAULT_ROTATE_FAST          200
 #define DEFAULT_PLAYER               ""
 #define DEFAULT_BALL                 "ball/basic-ball/basic-ball"
-#define DEFAULT_CHEAT                0
 #define DEFAULT_KEY_FORWARD          SDLK_UP
 #define DEFAULT_KEY_BACKWARD         SDLK_DOWN
 #define DEFAULT_KEY_LEFT             SDLK_LEFT
@@ -167,7 +168,10 @@
 #define DEFAULT_STATS                0
 #define DEFAULT_UNIFORM              0
 #define DEFAULT_SCREENSHOT           0
+#define DEFAULT_BALL_GAMMA           "0.78"
+#define DEFAULT_BALL_COLLISIONS      0
 #define DEFAULT_LOCK_GOALS           0
+#define DEFAULT_CHEAT                0
 
 /*---------------------------------------------------------------------------*/
 
Index: share/mapc.c
===================================================================
--- share/mapc.c	(.../trunk)	(revision 1922)
+++ share/mapc.c	(.../branches/putt-collisions)	(revision 1922)
@@ -61,6 +61,7 @@
 #define MAXX    1024
 #define MAXR    2048
 #define MAXU    1024
+#define MAXY    1024
 #define MAXW    1024
 #define MAXD    1024
 #define MAXA    16384
@@ -153,6 +154,11 @@
     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");
@@ -186,6 +192,7 @@
     fp->xc = 0;
     fp->rc = 0;
     fp->uc = 0;
+    fp->yc = 0;
     fp->wc = 0;
     fp->dc = 0;
     fp->ac = 0;
@@ -207,6 +214,7 @@
     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));
@@ -1204,6 +1212,9 @@
     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++)
     {
@@ -1220,11 +1231,65 @@
             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)
@@ -1254,6 +1319,7 @@
     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);
@@ -2313,10 +2379,10 @@
            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[])
@@ -2370,4 +2436,3 @@
 
     return 0;
 }
-
Index: share/solid.c
===================================================================
--- share/solid.c	(.../trunk)	(revision 1922)
+++ share/solid.c	(.../branches/putt-collisions)	(revision 1922)
@@ -24,15 +24,20 @@
 #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 amplitude = 0.f;
+
+/*---------------------------------------------------------------------------*/
+
 static float erp(float t)
 {
     return 3.0f * t * t - 2.0f * t * t * t;
@@ -236,7 +241,12 @@
 {
     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;
     bp->e[0][2] = bp->E[0][2] = 0.0f;
@@ -292,6 +302,7 @@
     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);
 
@@ -329,6 +340,8 @@
         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)
@@ -356,6 +369,7 @@
     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);
 
@@ -393,10 +407,11 @@
     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)
     {
@@ -584,6 +599,9 @@
 {
     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)
@@ -625,6 +643,7 @@
     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);
 
@@ -647,6 +666,7 @@
     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);
 }
@@ -686,6 +706,7 @@
     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);
@@ -914,11 +935,132 @@
     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_ball(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.
  */
@@ -1039,6 +1181,18 @@
 {
     int i;
 
+    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;
@@ -1061,6 +1215,27 @@
     return v_vert(T, o, vp->p, w, up->p, up->v, up->r);
 }
 
+/*---------------------------------------------------------------------------*/
+
+static float sol_test_sphere_inter(const struct s_ball  *up,
+                                   const struct s_ball  *u2p)
+{
+    float P[3], V[3];
+    float t = LARGE;
+
+    v_sub(P, up->p, u2p->p);
+    v_sub(V, up->v, u2p->v);
+
+    if (v_dot(P, V) < 0.0f)
+    {
+        t = v_sol(P, V, up->r + u2p->r);
+    }
+
+    return (t > LARGE) ? (LARGE) : (t);
+}
+
+/*---------------------------------------------------------------------------*/
+
 static float sol_test_edge(float dt,
                            float T[3],
                            const struct s_ball *up,
@@ -1181,7 +1356,7 @@
 {
     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. */
 
@@ -1302,9 +1477,70 @@
         v_cpy(V, W);
         t = u;
     }
+
     return t;
 }
 
+static float sol_test_balls(float dt, const struct s_file *fp)
+{
+    float t = 0.0f;
+    int   i, j;
+
+    for (i = 0; i < fp->yc; i++)
+    {
+        struct s_ball *yp = fp->yv + i;
+
+        for (j = i + 1; j < fp->yc; j++)
+        {
+            struct s_ball *y2p = fp->yv + j;
+
+            if (!(yp->r > 0.0f || y2p->r > 0.0f))
+                continue;
+
+            if (sol_test_sphere_inter(yp, y2p) < dt)
+            {
+                t = sol_bounce_ball(yp, y2p, t);
+            }
+        }
+    }
+
+    for (i = 0; i < fp->uc; i++)
+    {
+        struct s_ball *up = fp->uv + i;
+
+        if (i > 0 && !up->P)
+            continue;
+
+        for (j = 0; j < fp->yc; j++)
+        {
+            struct s_ball *y2p = fp->yv + j;
+
+            if (!(up->r > 0.0f || y2p->r > 0.0f))
+                continue;
+
+            if (sol_test_sphere_inter(up, y2p) < dt)
+            {
+                t = sol_bounce_ball(up, y2p, t);
+            }
+        }
+
+        for (j = i + 1; config_get_d(CONFIG_BALL_COLLISIONS) && j < fp->uc; j++)
+        {
+            struct s_ball *u2p = fp->uv + j;
+
+            if (!(up->r > 0.0f || u2p->r > 0.0f))
+                continue;
+
+            if (u2p->P && up->P && sol_test_sphere_inter(up, u2p) < dt)
+            {
+                t = sol_bounce_ball(up, u2p, t);
+            }
+        }
+    }
+
+    return t;
+}
+
 static float sol_test_file(float dt,
                            float T[3], float V[3],
                            const struct s_ball *up,
@@ -1324,6 +1560,12 @@
             t = u;
         }
     }
+
+    if ((u = sol_test_balls(t, fp)) > 0.0f)
+        amplitude = u;
+    else
+        amplitude = 0.f;
+
     return t;
 }
 
@@ -1339,80 +1581,177 @@
 
 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, nt = 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, 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) < 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 (tt && tt > (nt = sol_test_file(tt, P, V, up, fp)) && c > 0)
+            {
+                sol_body_step(fp, nt);
+                sol_swch_step(fp, nt);
+                sol_ball_step(fp, nt);
+
+                tt -= nt;
+
+                if (b < ((d = sol_bounce(up, P, V, nt))))
+                    b = d;
+
+                c--;
+            }
+
+            if (b < amplitude)
+                b = amplitude;
+
+            /* 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; i++)
+    {
+        float P[3], V[3], v[3], r[3], a[3], d, e, 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);
 
-        sol_body_step(fp, tt);
-        sol_swch_step(fp, tt);
-        sol_ball_step(fp, tt);
+            if (m && sol_test_file(tt, P, V, yp, fp) < 0.0005f)
+            {
+                v_cpy(yp->v, v);
+                v_sub(r, P, yp->p);
 
-        /* Apply the ball's accelleration to the pendulum. */
+                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_sub(a, up->v, a);
+                        v_nrm(yp->v, yp->v);
+                        v_scl(yp->v, yp->v, e);
 
-        sol_pendulum(up, a, g, dt);
+                        /* 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 (tt && tt > (nt = sol_test_file(tt, P, V, yp, fp)) && c > 0)
+            {
+                sol_body_step(fp, nt);
+                sol_swch_step(fp, nt);
+                sol_ball_step(fp, nt);
+
+                tt -= nt;
+
+                if (b < ((d = sol_bounce(yp, P, V, nt))))
+                    b = d;
+
+                c--;
+            }
+
+            if (b < amplitude)
+                b = amplitude;
+
+            /* Apply the ball's accelleration to the pendulum. */
+
+            v_sub(a, yp->v, a);
+
+            sol_pendulum(yp, a, g, dt);
+        }
     }
+
+    if (!c && !ui)
+        nt += dt;
+
+    sol_body_step(fp, nt);
+    sol_swch_step(fp, nt);
+    sol_ball_step(fp, nt);
+
     return b;
 }
 
@@ -1420,13 +1759,13 @@
 
 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, yi;
 
-    int hi;
-
     for (hi = 0; hi < fp->hc; hi++)
     {
+        const float *ball_p = fp->uv->p;
+        const float  ball_r = fp->uv->r;
+
         float r[3];
 
         r[0] = ball_p[0] - fp->hv[hi].p[0];
@@ -1441,36 +1780,110 @@
 
             return &fp->hv[hi];
         }
+
+        for (yi = 0; yi < fp->yc; yi++)
+        {
+            const float *ball_p = fp->yv[yi].p;
+            const float  ball_r = fp->yv[yi].r;
+
+            float r[3];
+
+            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;
     }
-    return NULL;
+
+    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;
+    }
 }
 
 /*
@@ -1519,75 +1932,145 @@
  * 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;
+            const float *ball_p  = fp->uv[i].p;
+            const float  ball_r  = fp->uv[i].r;
 
-            l = v_len(r) - xp->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;
 
-            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)
+                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;
 
-                    /* It toggled to non-default state, start the timer. */
+                            pi = fp->pv[pi].pi;
+                            pj = fp->pv[pj].pi;
+                            pj = fp->pv[pj].pi;
+                        }
+                        while (pi != pj);
 
-                    if (xp->f != xp->f0)
-                        xp->t  = xp->t0;
+                        /* It toggled to non-default state, start the timer. */
 
-                    /* If visible, set the result. */
+                        if (xp->f != xp->f0)
+                            xp->t  = xp->t0;
 
-                    if (!xp->i)
-                        res = 1;
+                        /* If visible, set the result. */
+
+                        if (!xp->i)
+                            res = 1;
+                    }
                 }
+
+                /* The ball exits. */
+
+                else if (xp->e && xp->b == i * 2)
+                    xp->e = 0;
             }
+        }
 
-            /* The ball exits. */
+        for (i = 0; i < fp->yc; i++)
+        {
+            float l, r[3];
 
-            else if (xp->e)
-                xp->e = 0;
+            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);
+
+                        /* It toggled to non-default state, start the timer. */
+
+                        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;
 }
 
@@ -1616,4 +2099,3 @@
 }
 
 /*---------------------------------------------------------------------------*/
-
Index: share/solid.h
===================================================================
--- share/solid.h	(.../trunk)	(revision 1922)
+++ share/solid.h	(.../branches/putt-collisions)	(revision 1922)
@@ -32,27 +32,29 @@
  *
  * 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:
  *
@@ -72,7 +74,7 @@
  * 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.
  */
 
 /*---------------------------------------------------------------------------*/
@@ -223,6 +225,7 @@
     int   f;                                   /* current state              */
     int   i;                                   /* is invisible?              */
     int   e;                                   /* is a ball inside it?       */
+    int   b;                                   /* which ball?                */
 };
 
 struct s_bill
@@ -258,6 +261,11 @@
     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 on fall-out flag    */
+    int   c;                                   /* exists only when BCis set? */
+    float O[3];                                /* original position          */
 };
 
 struct s_view
@@ -274,26 +282,27 @@
 
 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;
@@ -312,6 +321,7 @@
     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;
@@ -331,9 +341,9 @@
 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);
 
 /*---------------------------------------------------------------------------*/
Index: doc/AUTHORS
===================================================================
--- doc/AUTHORS	(.../trunk)	(revision 1922)
+++ doc/AUTHORS	(.../branches/putt-collisions)	(revision 1922)
@@ -17,6 +17,7 @@
     Mehdi Yousfi-Monod  (mym)
     Michael Middleton   (slippifishi)
     Florian Priester
+    Byron James Johnson (Krabby Krap)
 
 * TRANSLATORS
 
@@ -63,6 +64,7 @@
       - 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
@@ -82,6 +84,8 @@
       - 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
Index: doc/MANUAL
===================================================================
--- doc/MANUAL	(.../trunk)	(revision 1922)
+++ doc/MANUAL	(.../branches/putt-collisions)	(revision 1922)
@@ -222,6 +222,20 @@
 
         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>
Index: data/map-ckk/17.map
===================================================================
--- data/map-ckk/17.map	(.../trunk)	(revision 1922)
+++ data/map-ckk/17.map	(.../branches/putt-collisions)	(revision 1922)
@@ -3950,3 +3950,39 @@
 ( -112 -800 464 ) ( -128 -800 464 ) ( -128 -928 464 ) mtrl/turf-grey 128 128 0 0.5 0.5 0 0 0
 }
 }
+// entity 96
+{
+"classname" "info_notnull"
+"origin" "-48 6848 96"
+"radius" "0.0625"
+"collisions" "1"
+"return" "1"
+"mobile" "1"
+}
+// entity 97
+{
+"classname" "info_notnull"
+"origin" "48 6848 96"
+"radius" "0.0625"
+"collisions" "1"
+"return" "1"
+"mobile" "1"
+}
+// entity 98
+{
+"classname" "info_notnull"
+"origin" "32 6848 96"
+"radius" "0.0625"
+"collisions" "1"
+"return" "1"
+"mobile" "1"
+}
+// entity 99
+{
+"classname" "info_notnull"
+"origin" "-32 6848 96"
+"radius" "0.0625"
+"collisions" "1"
+"return" "1"
+"mobile" "1"
+}
Index: data/set-misc.txt
===================================================================
--- data/set-misc.txt	(.../trunk)	(revision 1922)
+++ data/set-misc.txt	(.../branches/putt-collisions)	(revision 1922)
@@ -17,3 +17,4 @@
 map-misc/hurdles.sol
 map-misc/canals.sol
 map-misc/groweasy.sol
+map-misc/arbitrary_demo.sol
Index: data/shot-misc/arbitrary_demo.jpg
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: data/shot-misc/arbitrary_demo.jpg
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Index: data/ball/eyeball.png
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: data/map-misc/arbitrary_demo.map
===================================================================
--- data/map-misc/arbitrary_demo.map	(.../trunk)	(revision 0)
+++ data/map-misc/arbitrary_demo.map	(.../branches/putt-collisions)	(revision 1922)
@@ -0,0 +1,582 @@
+
+// entity 0
+{
+"classname" "worldspawn"
+"levelname" "billiard"
+"version" "1.0"
+"author" "Byron James Johnson (Krabby Krap)"
+"shot" "shot-misc/arbitrary_demo.jpg"
+"back" "map-back/clouds.sol"
+"grad" "back/land.png"
+"song" "bgm/track2.ogg"
+"time" "12000"
+"goal" "2"
+"time_hs" "7500 8000"
+"coin_hs" "16 15"
+"goal_hs" "600 700"
+"message" "Use the other balls to your advantage\Be careful, the universe effects all balls!"
+// brush 0
+{
+( 280 -192 64 ) ( 272 -192 64 ) ( 280 -64 64 ) mtrl/edge-green 0 0 0 0.5 0.5 0 0 0
+( 336 144 64 ) ( 336 144 56 ) ( 336 -208 56 ) mtrl/turf-grey -0 0 0 0.5 0.5 0 0 0
+( 336 -16 48 ) ( 336 -16 40 ) ( 320 0 40 ) mtrl/invisible 0 96 0 0.5 0.5 0 0 0
+( 272 -112 56 ) ( 272 -240 56 ) ( 280 -112 56 ) mtrl/turf-grey 0 0 0 0.5 0.5 0 0 0
+( 320 -56 40 ) ( 320 -56 48 ) ( 320 -184 40 ) mtrl/invisible 0 96 0 0.5 0.5 0 0 0
+( 336 144 40 ) ( 336 144 48 ) ( 320 128 48 ) mtrl/invisible 0 96 0 0.5 0.5 0 0 0
+}
+// brush 1
+{
+( 320 88 64 ) ( 320 80 64 ) ( 192 88 64 ) mtrl/edge-green 0 0 0 0.5 0.5 0 0 0
+( -16 144 64 ) ( -16 144 56 ) ( 336 144 56 ) mtrl/turf-grey 0 0 0 0.5 0.5 0 0 0
+( 336 144 48 ) ( 336 144 40 ) ( 320 128 40 ) mtrl/invisible 0 96 0 0.5 0.5 0 0 0
+( 192 80 56 ) ( 320 80 56 ) ( 192 88 56 ) mtrl/turf-grey 0 0 0 0.5 0.5 0 0 0
+( -136 128 56 ) ( -136 128 64 ) ( -8 128 56 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( -336 144 56 ) ( -336 144 64 ) ( -320 128 64 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+}
+// brush 2
+{
+( 0 40 64 ) ( 0 48 64 ) ( 128 40 64 ) mtrl/edge-green 0 0 0 0.5 0.5 0 0 0
+( 336 -16 64 ) ( 336 -16 56 ) ( -16 -16 56 ) mtrl/turf-grey 0 0 0 0.5 0.5 0 0 0
+( -336 -16 64 ) ( -336 -16 56 ) ( -320 0 56 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( 80 48 56 ) ( -48 48 56 ) ( 80 40 56 ) mtrl/turf-grey 0 0 0 0.5 0.5 0 0 0
+( 136 0 56 ) ( 136 0 64 ) ( 8 0 56 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( 336 -16 40 ) ( 336 -16 48 ) ( 320 0 48 ) mtrl/invisible 0 96 0 0.5 0.5 0 0 0
+}
+// brush 3
+{
+( -280 320 64 ) ( -272 320 64 ) ( -280 192 64 ) mtrl/edge-green 128 0 0 0.5 0.5 0 0 0
+( -336 -16 64 ) ( -336 -16 56 ) ( -336 336 56 ) mtrl/turf-grey 0 0 0 0.5 0.5 0 0 0
+( -336 144 64 ) ( -336 144 56 ) ( -320 128 56 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( -272 240 56 ) ( -272 368 56 ) ( -280 240 56 ) mtrl/turf-grey 128 0 0 0.5 0.5 0 0 0
+( -320 184 56 ) ( -320 184 64 ) ( -320 312 56 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( -336 -16 56 ) ( -336 -16 64 ) ( -320 0 64 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+}
+// brush 4
+{
+( 320 320 64 ) ( 320 192 64 ) ( 192 320 64 ) mtrl/arrow-green-light 128 128 90 0.5 0.5 134217728 0 0
+( 320 320 64 ) ( 192 320 64 ) ( 320 320 48 ) mtrl/invisible 128 128 90 0.5 0.5 134217728 0 0
+( 320 320 64 ) ( 320 320 48 ) ( 320 192 64 ) mtrl/invisible 128 128 90 0.5 0.5 134217728 0 0
+( 191 192 63 ) ( 319 192 63 ) ( 191 320 63 ) mtrl/invisible 128 128 90 0.5 0.5 134217728 0 0
+( 192 192 48 ) ( 192 192 64 ) ( 320 192 48 ) mtrl/invisible 128 128 90 0.5 0.5 134217728 0 0
+( 192 192 48 ) ( 192 320 48 ) ( 192 192 64 ) mtrl/invisible 128 128 90 0.5 0.5 134217728 0 0
+}
+// brush 5
+{
+( 320 128 64 ) ( 320 0 64 ) ( 192 128 64 ) mtrl/switch 128 192 0 1 1 0 0 0
+( 320 160 64 ) ( 192 160 64 ) ( 320 160 48 ) mtrl/invisible 128 192 0 1 1 0 0 0
+( 352 128 64 ) ( 352 128 48 ) ( 352 0 64 ) mtrl/invisible 128 192 0 1 1 0 0 0
+( 191 0 63 ) ( 319 0 63 ) ( 191 128 63 ) mtrl/invisible 128 192 0 1 1 0 0 0
+( 176 -32 48 ) ( 176 -32 64 ) ( 304 -32 48 ) mtrl/invisible 128 192 0 1 1 0 0 0
+( 160 -16 48 ) ( 160 112 48 ) ( 160 -16 64 ) mtrl/invisible 128 192 0 1 1 0 0 0
+}
+// brush 6
+{
+( 320 512 64 ) ( 320 384 64 ) ( 192 512 64 ) mtrl/teleporter 128 0 0 0.5 0.5 134217728 0 0
+( 320 512 64 ) ( 192 512 64 ) ( 320 512 48 ) mtrl/invisible -0 0 0 0.5 0.5 134217728 0 0
+( 320 512 64 ) ( 320 512 48 ) ( 320 384 64 ) mtrl/invisible -0 0 0 0.5 0.5 134217728 0 0
+( 191 384 63 ) ( 319 384 63 ) ( 191 512 63 ) mtrl/invisible -0 0 0 0.5 0.5 134217728 0 0
+( 192 384 48 ) ( 192 384 64 ) ( 320 384 48 ) mtrl/invisible -0 0 0 0.5 0.5 134217728 0 0
+( 192 384 48 ) ( 192 512 48 ) ( 192 384 64 ) mtrl/invisible -0 0 0 0.5 0.5 134217728 0 0
+}
+// brush 7
+{
+( -192 320 64 ) ( -192 192 64 ) ( -320 320 64 ) mtrl/teleporter 128 128 0 0.25 0.25 134217728 0 0
+( -160 288 64 ) ( -288 288 64 ) ( -160 288 48 ) mtrl/invisible 128 128 0 0.25 0.25 134217728 0 0
+( -224 352 64 ) ( -224 352 48 ) ( -224 224 64 ) mtrl/invisible 128 128 0 0.25 0.25 134217728 0 0
+( -321 192 63 ) ( -193 192 63 ) ( -321 320 63 ) mtrl/invisible 128 128 0 0.25 0.25 134217728 0 0
+( -352 224 48 ) ( -352 224 64 ) ( -224 224 48 ) mtrl/invisible 128 128 0 0.25 0.25 134217728 0 0
+( -288 176 48 ) ( -288 304 48 ) ( -288 176 64 ) mtrl/invisible 128 128 0 0.25 0.25 134217728 0 0
+}
+// brush 8
+{
+( 320 264 64 ) ( 320 256 64 ) ( 192 264 64 ) mtrl/turf-green-small 0 0 0 0.5 0.5 0 0 0
+( 264 320 64 ) ( 136 320 64 ) ( 264 320 56 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( 320 264 64 ) ( 320 264 56 ) ( 320 256 64 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( 192 256 56 ) ( 320 256 56 ) ( 192 264 56 ) mtrl/wood 0 0 0 0.5 0.5 0 0 0
+( 192 192 56 ) ( 192 192 64 ) ( 320 192 56 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( -320 320 56 ) ( -320 328 56 ) ( -320 320 64 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+}
+// brush 9
+{
+( 320 72 64 ) ( 320 64 64 ) ( 192 72 64 ) mtrl/turf-green-small 0 0 0 0.5 0.5 0 0 0
+( 264 128 64 ) ( 136 128 64 ) ( 264 128 56 ) mtrl/turf-green-small 0 0 0 0.5 0.5 0 0 0
+( 320 72 64 ) ( 320 72 56 ) ( 320 64 64 ) mtrl/turf-green-small 0 0 0 0.5 0.5 0 0 0
+( 192 64 56 ) ( 320 64 56 ) ( 192 72 56 ) mtrl/turf-grey 0 0 0 0.5 0.5 0 0 0
+( 192 0 56 ) ( 192 0 64 ) ( 320 0 56 ) mtrl/turf-green-small 0 0 0 0.5 0.5 0 0 0
+( -320 128 56 ) ( -320 136 56 ) ( -320 128 64 ) mtrl/turf-green-small 0 0 0 0.5 0.5 0 0 0
+}
+// brush 10
+{
+( -80 288 576 ) ( -64 288 576 ) ( -80 160 576 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+( -80 320 416 ) ( -80 192 416 ) ( -80 320 400 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+( -80 336 416 ) ( -80 336 400 ) ( -64 336 416 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+( -64 192 400 ) ( -64 320 400 ) ( -80 192 400 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+( -64 192 400 ) ( -64 192 416 ) ( -64 320 400 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+( -64 176 400 ) ( -80 176 400 ) ( -64 176 416 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+}
+// brush 11
+{
+( -32 176 576 ) ( -32 192 576 ) ( 96 176 576 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+( -64 176 416 ) ( 64 176 416 ) ( -64 176 400 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+( -64 176 416 ) ( -64 176 400 ) ( -64 192 416 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( 64 192 400 ) ( -64 192 400 ) ( 64 176 400 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+( 64 192 400 ) ( 64 192 416 ) ( -64 192 400 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+( 64 192 400 ) ( 64 176 400 ) ( 64 192 416 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+}
+// brush 12
+{
+( 80 224 576 ) ( 64 224 576 ) ( 80 352 576 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+( 80 192 416 ) ( 80 320 416 ) ( 80 192 400 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+( 80 176 416 ) ( 80 176 400 ) ( 64 176 416 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+( 64 320 400 ) ( 64 192 400 ) ( 80 320 400 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+( 64 320 400 ) ( 64 320 416 ) ( 64 192 400 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+( 64 336 400 ) ( 80 336 400 ) ( 64 336 416 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+}
+// brush 13
+{
+( 32 336 576 ) ( 32 320 576 ) ( -96 336 576 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+( 64 336 416 ) ( -64 336 416 ) ( 64 336 400 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+( 64 336 416 ) ( 64 336 400 ) ( 64 320 416 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( -64 320 400 ) ( 64 320 400 ) ( -64 336 400 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+( -64 320 400 ) ( -64 320 416 ) ( 64 320 400 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+( -64 320 400 ) ( -64 336 400 ) ( -64 320 416 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+}
+// brush 14
+{
+( 64 320 576 ) ( 64 192 576 ) ( -64 320 576 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+( 64 320 576 ) ( -64 320 576 ) ( 64 320 400 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( 64 320 576 ) ( 64 320 400 ) ( 64 192 576 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( -80 192 560 ) ( 48 192 560 ) ( -80 320 560 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+( -64 192 400 ) ( -64 192 576 ) ( 64 192 400 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( -64 192 400 ) ( -64 320 400 ) ( -64 192 576 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+}
+// brush 15
+{
+( 320 456 64 ) ( 320 448 64 ) ( 192 456 64 ) mtrl/turf-green-small 0 0 0 0.5 0.5 0 0 0
+( 264 512 64 ) ( 136 512 64 ) ( 264 512 56 ) mtrl/turf-green-small 0 0 0 0.5 0.5 0 0 0
+( 320 456 64 ) ( 320 456 56 ) ( 320 448 64 ) mtrl/turf-green-small 0 0 0 0.5 0.5 0 0 0
+( 192 448 56 ) ( 320 448 56 ) ( 192 456 56 ) mtrl/turf-grey 0 0 0 0.5 0.5 0 0 0
+( 192 384 56 ) ( 192 384 64 ) ( 320 384 56 ) mtrl/turf-green-small 0 0 0 0.5 0.5 0 0 0
+( -320 512 56 ) ( -320 520 56 ) ( -320 512 64 ) mtrl/turf-green-small 0 0 0 0.5 0.5 0 0 0
+}
+// brush 16
+{
+( 280 192 64 ) ( 272 192 64 ) ( 280 320 64 ) mtrl/edge-green 0 0 0 0.5 0.5 0 0 0
+( 336 528 64 ) ( 336 528 56 ) ( 336 176 56 ) mtrl/turf-grey -0 0 0 0.5 0.5 0 0 0
+( 336 368 48 ) ( 336 368 40 ) ( 320 384 40 ) mtrl/invisible 0 96 0 0.5 0.5 0 0 0
+( 272 272 56 ) ( 272 144 56 ) ( 280 272 56 ) mtrl/turf-grey 0 0 0 0.5 0.5 0 0 0
+( 320 328 40 ) ( 320 328 48 ) ( 320 200 40 ) mtrl/invisible 0 96 0 0.5 0.5 0 0 0
+( 336 528 40 ) ( 336 528 48 ) ( 320 512 48 ) mtrl/invisible 0 96 0 0.5 0.5 0 0 0
+}
+// brush 17
+{
+( 0 424 64 ) ( 0 432 64 ) ( 128 424 64 ) mtrl/edge-green 0 0 0 0.5 0.5 0 0 0
+( 336 368 64 ) ( 336 368 56 ) ( -16 368 56 ) mtrl/turf-grey 0 0 0 0.5 0.5 0 0 0
+( -336 368 64 ) ( -336 368 56 ) ( -320 384 56 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( 80 432 56 ) ( -48 432 56 ) ( 80 424 56 ) mtrl/turf-grey 0 0 0 0.5 0.5 0 0 0
+( 136 384 56 ) ( 136 384 64 ) ( 8 384 56 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( 336 368 40 ) ( 336 368 48 ) ( 320 384 48 ) mtrl/invisible 0 96 0 0.5 0.5 0 0 0
+}
+// brush 18
+{
+( -280 704 64 ) ( -272 704 64 ) ( -280 576 64 ) mtrl/edge-green 128 0 0 0.5 0.5 0 0 0
+( -336 368 64 ) ( -336 368 56 ) ( -336 720 56 ) mtrl/turf-grey 0 0 0 0.5 0.5 0 0 0
+( -336 528 64 ) ( -336 528 56 ) ( -320 512 56 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( -272 624 56 ) ( -272 752 56 ) ( -280 624 56 ) mtrl/turf-grey 128 0 0 0.5 0.5 0 0 0
+( -320 568 56 ) ( -320 568 64 ) ( -320 696 56 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( -336 368 56 ) ( -336 368 64 ) ( -320 384 64 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+}
+// brush 19
+{
+( 320 472 64 ) ( 320 464 64 ) ( 192 472 64 ) mtrl/edge-green 0 0 0 0.5 0.5 0 0 0
+( -16 528 64 ) ( -16 528 56 ) ( 336 528 56 ) mtrl/turf-grey 0 0 0 0.5 0.5 0 0 0
+( 336 528 48 ) ( 336 528 40 ) ( 320 512 40 ) mtrl/invisible 0 96 0 0.5 0.5 0 0 0
+( 192 464 56 ) ( 320 464 56 ) ( 192 472 56 ) mtrl/turf-grey 0 0 0 0.5 0.5 0 0 0
+( -136 512 56 ) ( -136 512 64 ) ( -8 512 56 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( -336 528 56 ) ( -336 528 64 ) ( -320 512 64 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+}
+// brush 20
+{
+( -192 512 64 ) ( -192 384 64 ) ( -320 512 64 ) mtrl/switch 128 0 0 0.5 0.5 134217728 0 0
+( -256 512 64 ) ( -384 512 64 ) ( -256 512 48 ) mtrl/invisible 128 0 0 0.5 0.5 134217728 0 0
+( -192 480 64 ) ( -192 480 48 ) ( -192 352 64 ) mtrl/invisible 128 0 0 0.5 0.5 134217728 0 0
+( -321 384 63 ) ( -193 384 63 ) ( -321 512 63 ) mtrl/invisible 128 0 0 0.5 0.5 134217728 0 0
+( -320 384 48 ) ( -320 384 64 ) ( -192 384 48 ) mtrl/invisible 128 0 0 0.5 0.5 134217728 0 0
+( -320 384 48 ) ( -320 512 48 ) ( -320 384 64 ) mtrl/invisible 128 0 0 0.5 0.5 134217728 0 0
+}
+// brush 21
+{
+( 1072 208 160 ) ( 1072 48 160 ) ( 1024 208 160 ) mtrl/chrome-faceted 0 0 0 0.5 0.5 0 0 0
+( 1072 192 160 ) ( 1024 192 160 ) ( 1072 192 144 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( 1168 176 152 ) ( 1168 176 160 ) ( 1152 192 160 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( 1016 48 152 ) ( 1064 48 152 ) ( 1016 208 152 ) mtrl/chrome-faceted 0 0 0 0.5 0.5 0 0 0
+( 1168 176 160 ) ( 1168 176 152 ) ( 640 176 160 ) mtrl/chrome-faceted 0 0 0 0.5 0.5 0 0 0
+( 640 160 144 ) ( 640 320 144 ) ( 640 160 160 ) mtrl/chrome-faceted 0 0 0 0.5 0.5 0 0 0
+}
+// brush 22
+{
+( 1088 336 160 ) ( 1088 176 160 ) ( 1040 336 160 ) mtrl/chrome-faceted 0 0 0 0.5 0.5 0 0 0
+( 1168 336 152 ) ( 1168 336 160 ) ( 1152 320 160 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( 1168 176 152 ) ( 1168 176 160 ) ( 1168 336 160 ) mtrl/chrome-faceted 0 0 0 0.5 0.5 0 0 0
+( 1032 176 152 ) ( 1080 176 152 ) ( 1032 336 152 ) mtrl/chrome-faceted 0 0 0 0.5 0.5 0 0 0
+( 1168 176 160 ) ( 1168 176 152 ) ( 1152 192 160 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( 1152 160 144 ) ( 1152 320 144 ) ( 1152 160 160 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+}
+// brush 23
+{
+( 1072 352 160 ) ( 1072 192 160 ) ( 1024 352 160 ) mtrl/chrome-faceted 0 0 0 0.5 0.5 0 0 0
+( 1168 336 152 ) ( 1168 336 160 ) ( 640 336 160 ) mtrl/chrome-faceted 0 0 0 0.5 0.5 0 0 0
+( 1168 336 160 ) ( 1168 336 152 ) ( 1152 320 152 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( 1016 192 152 ) ( 1064 192 152 ) ( 1016 352 152 ) mtrl/chrome-faceted 0 0 0 0.5 0.5 0 0 0
+( 1024 320 144 ) ( 1024 320 160 ) ( 1072 320 144 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( 640 304 144 ) ( 640 464 144 ) ( 640 304 160 ) mtrl/chrome-faceted 0 0 0 0.5 0.5 0 0 0
+}
+// brush 24
+{
+( 1072 336 160 ) ( 1072 176 160 ) ( 1024 336 160 ) mtrl/turf-red 0 0 0 0.5 0.5 0 0 0
+( 1072 320 160 ) ( 1024 320 160 ) ( 1072 320 144 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( 1152 352 160 ) ( 1152 352 144 ) ( 1152 192 160 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( 1016 176 152 ) ( 1064 176 152 ) ( 1016 336 152 ) mtrl/turf-grey 0 0 0 0.5 0.5 0 0 0
+( 1024 192 144 ) ( 1024 192 160 ) ( 1072 192 144 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( 640 176 144 ) ( 640 336 144 ) ( 640 176 160 ) mtrl/turf-grey 0 0 0 0.5 0.5 0 0 0
+}
+// brush 25
+{
+( 1344 320 0 ) ( 1344 192 0 ) ( 1216 320 0 ) mtrl/goal 0 128 0 0.5 0.5 134217728 0 0
+( 1472 320 8 ) ( 1344 320 8 ) ( 1472 320 -8 ) mtrl/invisible 0 128 0 0.5 0.5 134217728 0 0
+( 1408 448 8 ) ( 1408 448 -8 ) ( 1408 320 8 ) mtrl/invisible 0 128 0 0.5 0.5 134217728 0 0
+( 1211 192 -1 ) ( 1339 192 -1 ) ( 1211 320 -1 ) mtrl/invisible 0 128 0 0.5 0.5 134217728 0 0
+( 1152 192 -8 ) ( 1152 192 8 ) ( 1280 192 -8 ) mtrl/invisible 0 128 0 0.5 0.5 134217728 0 0
+( 1280 128 -8 ) ( 1280 256 -8 ) ( 1280 128 8 ) mtrl/invisible 0 128 0 0.5 0.5 134217728 0 0
+}
+// brush 26
+{
+( 1344 320 0 ) ( 1344 192 0 ) ( 1216 320 0 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+( 1408 384 8 ) ( 1280 384 8 ) ( 1408 384 -8 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+( 1472 384 8 ) ( 1472 384 -8 ) ( 1472 256 8 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+( 1216 192 -16 ) ( 1344 192 -16 ) ( 1216 320 -16 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+( 1216 128 -8 ) ( 1216 128 8 ) ( 1344 128 -8 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+( 1216 192 -8 ) ( 1216 320 -8 ) ( 1216 192 8 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+}
+}
+// entity 1
+{
+"classname" "info_player_intermission"
+"target" "t1"
+"origin" "601 -444 624"
+}
+// entity 2
+{
+"classname" "target_position"
+"targetname" "t1"
+"origin" "-38 560 -40"
+}
+// entity 3
+{
+"classname" "info_player_start"
+"origin" "256 256 88"
+}
+// entity 4
+{
+"classname" "info_notnull"
+"origin" "144 256 88"
+"radius" "0.125"
+"collisions" "0"
+"mobile" "1"
+"return" "1"
+}
+// entity 5
+{
+"classname" "light"
+"origin" "-328 256 96"
+"light" "10"
+}
+// entity 6
+{
+"classname" "light"
+"origin" "0 328 96"
+"light" "5"
+}
+// entity 7
+{
+"classname" "light"
+"origin" "0 184 96"
+"light" "5"
+}
+// entity 8
+{
+"classname" "light"
+"origin" "-128 64 80"
+"light" "1"
+}
+// entity 9
+{
+"classname" "light"
+"origin" "80 256 80"
+"light" "1"
+}
+// entity 10
+{
+"classname" "light"
+"origin" "40 256 80"
+"light" "1"
+}
+// entity 11
+{
+"classname" "light"
+"origin" "64 64 80"
+"light" "1"
+}
+// entity 12
+{
+"classname" "light"
+"origin" "-32 64 80"
+"light" "5"
+}
+// entity 13
+{
+"classname" "func_train"
+"target" "p1_0"
+// brush 0
+{
+( 64 320 416 ) ( 64 192 416 ) ( -64 320 416 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+( 64 320 416 ) ( -64 320 416 ) ( 64 320 400 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( 64 320 416 ) ( 64 320 400 ) ( 64 192 416 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+( -64 192 400 ) ( 64 192 400 ) ( -64 320 400 ) mtrl/glass 0 0 0 0.5 0.5 0 0 0
+( -64 192 400 ) ( -64 192 416 ) ( 64 192 400 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( -64 192 400 ) ( -64 320 400 ) ( -64 192 416 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+}
+}
+// entity 14
+{
+"classname" "path_corner"
+"origin" "0 256 480"
+"targetname" "p1_0"
+"target" "p1_1"
+"speed" "3"
+"state" "0"
+}
+// entity 15
+{
+"classname" "path_corner"
+"origin" "-128 256 480"
+"targetname" "p1_1"
+}
+// entity 16
+{
+"classname" "info_notnull"
+"origin" "0 256 440"
+"mobile" "1"
+"collisions" "0"
+"return" "1"
+"radius" "0.375"
+}
+// entity 17
+{
+"classname" "light"
+"origin" "-32 448 80"
+"light" "1"
+}
+// entity 18
+{
+"classname" "light"
+"origin" "64 448 80"
+"light" "1"
+}
+// entity 19
+{
+"classname" "light"
+"origin" "-80 448 96"
+"light" "10"
+}
+// entity 20
+{
+"classname" "info_camp"
+"origin" "-256 448 64"
+"target" "p1_0"
+"timer" "9999"
+}
+// entity 21
+{
+"classname" "info_camp"
+"origin" "256 64 64"
+"target" "p2_0"
+"timer" "9999"
+"radius" "1.0"
+}
+// entity 22
+{
+"classname" "info_camp"
+"origin" "256 64 64"
+"target" "p3_0"
+"timer" "9999"
+"invisible" "1"
+"radius" "1.0"
+}
+// entity 23
+{
+"classname" "target_position"
+"origin" "-256 256 64"
+"targetname" "t2"
+}
+// entity 24
+{
+"classname" "target_position"
+"origin" "256 448 64"
+"targetname" "t3"
+}
+// entity 25
+{
+"classname" "target_teleporter"
+"origin" "-256 256 64"
+"target" "t3"
+"radius" "0.25"
+}
+// entity 26
+{
+"classname" "target_teleporter"
+"origin" "256 448 64"
+"target" "t2"
+}
+// entity 27
+{
+"classname" "func_train"
+"target" "p3_0"
+// brush 0
+{
+( 320 280 80 ) ( 320 272 80 ) ( 192 280 80 ) mtrl/wood 0 0 0 0.5 0.5 0 0 0
+( -16 336 64 ) ( -16 336 56 ) ( 336 336 56 ) mtrl/wood 0 0 0 0.5 0.5 0 0 0
+( 336 336 64 ) ( 336 336 56 ) ( 320 320 56 ) mtrl/wood 0 0 0 0.5 0.5 0 0 0
+( 192 272 56 ) ( 320 272 56 ) ( 192 280 56 ) mtrl/wood 0 0 0 0.5 0.5 0 0 0
+( 184 320 56 ) ( 184 320 64 ) ( 312 320 56 ) mtrl/wood 0 0 0 0.5 0.5 0 0 0
+( -336 336 56 ) ( -336 336 64 ) ( -320 320 64 ) mtrl/wood 0 0 0 0.5 0.5 0 0 0
+}
+// brush 1
+{
+( 280 0 80 ) ( 272 0 80 ) ( 280 128 80 ) mtrl/wood 0 0 0 0.5 0.5 0 0 0
+( 336 336 64 ) ( 336 336 56 ) ( 336 -16 56 ) mtrl/wood 0 0 0 0.5 0.5 0 0 0
+( 336 176 64 ) ( 336 176 56 ) ( 320 192 56 ) mtrl/wood 0 0 0 0.5 0.5 0 0 0
+( 272 80 56 ) ( 272 -48 56 ) ( 280 80 56 ) mtrl/wood 0 0 0 0.5 0.5 0 0 0
+( 320 136 56 ) ( 320 136 64 ) ( 320 8 56 ) mtrl/wood 0 0 0 0.5 0.5 0 0 0
+( 336 336 56 ) ( 336 336 64 ) ( 320 320 64 ) mtrl/wood 0 0 0 0.5 0.5 0 0 0
+}
+// brush 2
+{
+( 0 232 80 ) ( 0 240 80 ) ( 128 232 80 ) mtrl/wood 0 0 0 0.5 0.5 0 0 0
+( 336 176 64 ) ( 336 176 56 ) ( -16 176 56 ) mtrl/wood 0 0 0 0.5 0.5 0 0 0
+( -336 176 64 ) ( -336 176 56 ) ( -320 192 56 ) mtrl/wood 0 0 0 0.5 0.5 0 0 0
+( 80 240 56 ) ( -48 240 56 ) ( 80 232 56 ) mtrl/wood 0 0 0 0.5 0.5 0 0 0
+( -184 192 56 ) ( -184 192 64 ) ( -312 192 56 ) mtrl/wood 0 0 0 0.5 0.5 0 0 0
+( 336 176 56 ) ( 336 176 64 ) ( 320 192 64 ) mtrl/wood 0 0 0 0.5 0.5 0 0 0
+}
+// brush 3
+{
+( -280 512 80 ) ( -272 512 80 ) ( -280 384 80 ) mtrl/wood 0 0 0 0.5 0.5 0 0 0
+( -336 176 64 ) ( -336 176 56 ) ( -336 528 56 ) mtrl/wood 0 0 0 0.5 0.5 0 0 0
+( -336 336 64 ) ( -336 336 56 ) ( -320 320 56 ) mtrl/wood 0 0 0 0.5 0.5 0 0 0
+( -272 432 56 ) ( -272 560 56 ) ( -280 432 56 ) mtrl/wood 0 0 0 0.5 0.5 0 0 0
+( -320 376 56 ) ( -320 376 64 ) ( -320 504 56 ) mtrl/wood 0 0 0 0.5 0.5 0 0 0
+( -336 176 56 ) ( -336 176 64 ) ( -320 192 64 ) mtrl/wood 0 0 0 0.5 0.5 0 0 0
+}
+}
+// entity 28
+{
+"classname" "path_corner"
+"origin" "0 256 192"
+"targetname" "p3_0"
+"target" "p3_1"
+"speed" "3"
+"state" "0"
+}
+// entity 29
+{
+"classname" "path_corner"
+"origin" "0 256 192"
+"targetname" "p3_1"
+"target" "p3_2"
+"speed" "0.75"
+}
+// entity 30
+{
+"classname" "path_corner"
+"origin" "0 256 184"
+"targetname" "p3_2"
+}
+// entity 31
+{
+"classname" "light"
+"origin" "704 288 176"
+"light" "5"
+}
+// entity 32
+{
+"classname" "light"
+"origin" "704 224 176"
+"light" "5"
+}
+// entity 33
+{
+"classname" "info_notnull"
+"origin" "832 256 184"
+"collisions" "0"
+"return" "1"
+"mobile" "0"
+}
+// entity 34
+{
+"classname" "info_player_deathmatch"
+"origin" "1344 256 24"
+}
+// entity 35
+{
+"classname" "func_train"
+"target" "p2_0"
+// brush 0
+{
+( 640 352 240 ) ( 640 192 240 ) ( 336 192 160 ) mtrl/chrome-faceted 0 0 0 0.5 0.5 0 0 0
+( 400 336 160 ) ( 336 336 160 ) ( 400 336 144 ) mtrl/chrome-faceted 0 0 0 0.5 0.5 0 0 0
+( 640 464 232 ) ( 640 304 232 ) ( 640 304 240 ) mtrl/chrome-faceted 0 0 0 0.5 0.5 0 0 0
+( 640 192 232 ) ( 640 352 232 ) ( 336 192 152 ) mtrl/chrome-faceted 0 0 0 0.5 0.5 0 0 0
+( 336 320 144 ) ( 336 320 160 ) ( 400 320 144 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( 336 192 144 ) ( 336 352 144 ) ( 336 192 160 ) mtrl/chrome-faceted 0 0 0 0.5 0.5 0 0 0
+}
+// brush 1
+{
+( 640 208 240 ) ( 640 48 240 ) ( 336 48 160 ) mtrl/chrome-faceted 0 0 0 0.5 0.5 0 0 0
+( 400 192 160 ) ( 336 192 160 ) ( 400 192 144 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( 640 320 232 ) ( 640 160 232 ) ( 640 160 240 ) mtrl/chrome-faceted 0 0 0 0.5 0.5 0 0 0
+( 640 48 232 ) ( 640 208 232 ) ( 336 48 152 ) mtrl/chrome-faceted 0 0 0 0.5 0.5 0 0 0
+( 336 176 144 ) ( 336 176 160 ) ( 400 176 144 ) mtrl/chrome-faceted 0 0 0 0.5 0.5 0 0 0
+( 336 48 144 ) ( 336 208 144 ) ( 336 48 160 ) mtrl/chrome-faceted 0 0 0 0.5 0.5 0 0 0
+}
+// brush 2
+{
+( 640 336 240 ) ( 640 176 240 ) ( 336 176 160 ) mtrl/turf-red 0 0 0 0.5 0.5 0 0 0
+( 400 320 160 ) ( 336 320 160 ) ( 400 320 144 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( 640 336 232 ) ( 640 176 232 ) ( 640 176 240 ) mtrl/turf-grey 0 0 0 0.5 0.5 0 0 0
+( 640 176 232 ) ( 640 336 232 ) ( 336 176 152 ) mtrl/turf-grey 0 0 0 0.5 0.5 0 0 0
+( 336 192 144 ) ( 336 192 160 ) ( 400 192 144 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0
+( 336 176 144 ) ( 336 336 144 ) ( 336 176 160 ) mtrl/turf-grey 0 0 0 0.5 0.5 0 0 0
+}
+}
+// entity 36
+{
+"classname" "path_corner"
+"origin" "1024 512 192"
+"targetname" "p2_0"
+"target" "p2_1"
+"state" "0"
+"speed" "3"
+}
+// entity 37
+{
+"classname" "path_corner"
+"origin" "1024 512 112"
+"targetname" "p2_1"
+}
+// entity 38
+{
+"classname" "light"
+"origin" "-128 448 80"
+"light" "1"
+}
Index: ball/game.c
===================================================================
--- ball/game.c	(.../trunk)	(revision 1922)
+++ ball/game.c	(.../branches/putt-collisions)	(revision 1922)
@@ -54,11 +54,12 @@
 static float view_k;
 
 static int   coins  = 0;                /* Collected coins                   */
-static int   goal_e = 0;                /* Goal enabled flag                 */
+static int   goal_e = 0;                /* Goal coins remaining (0 = open)   */
 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             */
@@ -78,7 +79,7 @@
  * r:
  *     -32767 = -VIEWR_BOUND
  *     +32767 = +VIEWR_BOUND
- *     
+ *
  */
 
 struct input
@@ -156,7 +157,7 @@
         put_short(fout, &input_current.z);
         put_short(fout, &input_current.r);
         put_short(fout, &input_current.c);
-        
+
         return 1;
     }
     return 0;
@@ -340,6 +341,7 @@
 
     jump_e = 1;
     jump_b = 0;
+    jump_y = 0;
 
     goal_e = e ? 1    : 0;
     goal_k = e ? 1.0f : 0.0f;
@@ -398,14 +400,60 @@
 
 /*---------------------------------------------------------------------------*/
 
+void game_check_balls(struct s_file *fp)
+{
+    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]);
 
@@ -510,7 +558,7 @@
                     glTranslatef(fp->zv[zi].p[0],
                                  fp->zv[zi].p[1],
                                  fp->zv[zi].p[2]);
-                    
+
                     part_draw_goal(M, fp->zv[zi].r, goal_k, t);
                 }
                 glPopMatrix();
@@ -795,8 +843,12 @@
 {
     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)
     {
         config_push_persp(fov, 0.1f, FAR_DIST);
@@ -994,12 +1046,13 @@
 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)))
@@ -1016,30 +1069,56 @@
 
         /* Discard item. */
 
+        /* 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_e && (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;
@@ -1090,9 +1169,19 @@
 
             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;
@@ -1103,6 +1192,8 @@
 
             float b = sol_step(fp, h, dt, 0, NULL);
 
+            game_check_balls(fp);
+
             /* Mix the sound of a ball bounce. */
 
             if (b > 0.5f)
Index: putt/game.c
===================================================================
--- putt/game.c	(.../trunk)	(revision 1922)
+++ putt/game.c	(.../branches/putt-collisions)	(revision 1922)
@@ -46,6 +46,7 @@
 
 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                  */
 
@@ -82,6 +83,7 @@
 {
     jump_e = 1;
     jump_b = 0;
+    jump_u = 0;
 
     view_init();
     sol_load_gl(&file, config_data(s), config_get_d(CONFIG_TEXTURES),
@@ -95,6 +97,124 @@
 
 /*---------------------------------------------------------------------------*/
 
+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];
@@ -158,19 +278,50 @@
 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 ball_M[16];
+        float pend_M[16];
+
+        if (!config_get_d(CONFIG_BALL_COLLISIONS) && fp->yv[yi].c)
+            continue;
+
+        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);
+
+            glEnable(GL_COLOR_MATERIAL);
+            glColor4fv(color[5]);
+
+            ball_draw(ball_M, pend_M, bill_M, t);
+            glDisable(GL_COLOR_MATERIAL);
+        }
+        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];
@@ -189,6 +340,7 @@
 
                 glEnable(GL_COLOR_MATERIAL);
                 glColor4fv(color[ui]);
+
                 ball_draw(ball_M, pend_M, bill_M, t);
                 glDisable(GL_COLOR_MATERIAL);
             }
@@ -280,15 +432,21 @@
     static const float s[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
     static const float e[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
     static const float h[1] = { 0.0f };
-    
+
     const float light_p[4] = { 8.f, 32.f, 8.f, 1.f };
 
     const struct s_file *fp = &file;
 
     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);
     glPushMatrix();
@@ -325,6 +483,23 @@
 
         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();
@@ -442,49 +617,133 @@
     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))
     {
-        jump_b  = 1;
-        jump_e  = 0;
-        jump_dt = 0.f;
+        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);
+                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;
+        }
     }
-    if (jump_e == 0 && jump_b == 0 &&  sol_jump_test(fp, jump_p, ball) == 0)
-        jump_e = 1;
+    else
+    {
+        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);
+        }
+        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 (sol_goal_test(fp, p, ball))
-            return GAME_GOAL;
+        if (config_get_d(CONFIG_BALL_COLLISIONS))
+        {
+            switch (sol_goal_test(fp, p, ball))
+            {
+                case 2:  /* All balls stopped & Player's ball stopped in hole */
+                    t = 0.0f;
+                    return GAME_GOAL;
+                    break;
+                case 1:  /* All balls stopped                                 */
+                    t = 0.0f;
+                    return GAME_STOP;
+                    break;
+                case 0:  /* At least one ball is still in motion              */
+                    return GAME_NONE;
+                    break;
+                default: /* Should never reach this */
+                    break;
+            }
+        }
+
         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
@@ -517,18 +776,49 @@
 
     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
     {
@@ -542,8 +832,13 @@
 
         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)
@@ -568,10 +863,20 @@
      * 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;
 }
 
@@ -705,4 +1010,3 @@
 }
 
 /*---------------------------------------------------------------------------*/
-
Index: putt/st_all.c
===================================================================
--- putt/st_all.c	(.../trunk)	(revision 1922)
+++ putt/st_all.c	(.../branches/putt-collisions)	(revision 1922)
@@ -239,7 +239,7 @@
 
 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));
@@ -873,6 +873,8 @@
     if (paused)
         paused = 0;
 
+    game_set_played(1);
+
     return 0;
 }
 
@@ -891,7 +893,7 @@
 
 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;
 
@@ -1002,7 +1004,7 @@
     if (paused)
         paused = 0;
     else
-        hole_goal();
+        hole_goal(0);
 
     hud_init();
 
@@ -1089,7 +1091,7 @@
 
 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);
@@ -1216,6 +1218,8 @@
     if (paused)
         paused = 0;
 
+    game_set_played(0);
+
     return score_card(_("Scores"), gui_yel, gui_red);
 }
 
Index: putt/game.h
===================================================================
--- putt/game.h	(.../trunk)	(revision 1922)
+++ putt/game.h	(.../branches/putt-collisions)	(revision 1922)
@@ -43,6 +43,8 @@
 
 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);
Index: putt/main.c
===================================================================
--- putt/main.c	(.../trunk)	(revision 1922)
+++ putt/main.c	(.../branches/putt-collisions)	(revision 1922)
@@ -48,6 +48,7 @@
 
     return 1;
 }
+
 /*---------------------------------------------------------------------------*/
 
 static void toggle_wire(void)
@@ -294,4 +295,3 @@
 }
 
 /*---------------------------------------------------------------------------*/
-
Index: putt/hole.c
===================================================================
--- putt/hole.c	(.../trunk)	(revision 1922)
+++ putt/hole.c	(.../branches/putt-collisions)	(revision 1922)
@@ -134,7 +134,7 @@
 
     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);
@@ -152,7 +152,7 @@
 
     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);
@@ -167,11 +167,10 @@
     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);
@@ -267,31 +266,63 @@
     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][player] == 1)
-        audio_play(AUD_ONE, 1.0f);
+        if (score_v[hole][playerid] == 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 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);
+    }
+
     else
-        audio_play(AUD_SUCCESS, 1.0f);
+    {
+        score_v[hole][player]++;
 
-    stat_v[player] = 1;
-    done++;
+        if (score_v[hole][player] == 1)
+            audio_play(AUD_ONE, 1.0f);
 
-    if (done == party)
-        audio_music_fade_out(2.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++;
+
+        if (done == party)
+            audio_music_fade_out(2.0f);
+    }
 }
 
 void hole_stop(void)
Index: putt/st_balt.c
===================================================================
--- putt/st_balt.c	(.../trunk)	(revision 0)
+++ putt/st_balt.c	(.../branches/putt-collisions)	(revision 1922)
@@ -0,0 +1,196 @@
+/*
+ * 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
+};

Property changes on: putt/st_balt.c
___________________________________________________________________
Name: svn:executable
   + *

Index: putt/st_conf.c
===================================================================
--- putt/st_conf.c	(.../trunk)	(revision 1922)
+++ putt/st_conf.c	(.../branches/putt-collisions)	(revision 1922)
@@ -25,6 +25,7 @@
 #include "st_conf.h"
 #include "st_all.h"
 #include "st_resol.h"
+#include "st_balt.h"
 
 /*---------------------------------------------------------------------------*/
 
@@ -38,7 +39,10 @@
     CONF_SHDON,
     CONF_SHDOF,
     CONF_BACK,
-    CONF_RESOL
+    CONF_RESOL,
+    CONF_BALT,
+    CONF_BCLON,
+    CONF_BCLOF
 };
 
 static int music_id[11];
@@ -112,6 +116,22 @@
         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)
         {
@@ -151,18 +171,39 @@
     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);
@@ -184,7 +225,7 @@
         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);
         }
@@ -194,30 +235,57 @@
         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, _("Textures"), GUI_SML, GUI_ALL, 0, 0);
+            gui_label(jd, _("Ball Collisions"), 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));
+            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_label(jd, _("Geometry"), GUI_SML, GUI_ALL, 0, 0);
+        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);
         }
 
         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, _("Low"),        GUI_SML, CONF_GEOLO, (g == 0));
+            gui_state(kd, _("High"),       GUI_SML, CONF_GEOHI, (g == 1));
 
-            gui_label(jd, _("Shadow"), 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_label(jd, _("Shadow"),     GUI_SML, GUI_ALL, 0, 0);
+        }
+
         gui_space(id);
 
         if ((jd = gui_harray(id)) &&
Index: putt/hole.h
===================================================================
--- putt/hole.h	(.../trunk)	(revision 1922)
+++ putt/hole.h	(.../branches/putt-collisions)	(revision 1922)
@@ -28,7 +28,7 @@
 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);
 
Index: putt/st_balt.h
===================================================================
--- putt/st_balt.h	(.../trunk)	(revision 0)
+++ putt/st_balt.h	(.../branches/putt-collisions)	(revision 1922)
@@ -0,0 +1,8 @@
+#ifndef ST_BALT_H
+#define ST_BALT_H
+
+#include "state.h"
+
+extern struct state st_balt;
+
+#endif

Property changes on: putt/st_balt.h
___________________________________________________________________
Name: svn:executable
   + *

Index: Makefile
===================================================================
--- Makefile	(.../trunk)	(revision 1922)
+++ Makefile	(.../branches/putt-collisions)	(revision 1922)
@@ -125,6 +125,8 @@
 	share/solid.o       \
 	share/binary.o      \
 	share/base_config.o \
+	share/config.o      \
+	share/sync.o        \
 	share/mapc.o
 BALL_OBJS := \
 	share/lang.o        \
@@ -200,6 +202,7 @@
 	putt/course.o       \
 	putt/st_all.o       \
 	putt/st_conf.o      \
+	putt/st_balt.o      \
 	putt/main.o
 
 BALL_DEPS := $(BALL_OBJS:.o=.d)
@@ -229,7 +232,7 @@
 	$(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.