/* xpenguins_core.c - provides the core functionality of xpenguins
 * -copyright-
#-# 
#-# Copyright (C) 1999-2001  Robin Hogan, 2021-2026 Willem Vermin
#-# 
#-# This program 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.
#-# 
#-# You should have received a copy of the GNU General Public License
#-# along with this program; if not, write to the Free Software
#-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#-# 
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "main.h"
#include "xpenguins.h"
#include "debug.h"
#include "utils.h"

const char *NumToType[] =
{
   "PENGUIN_WALKER",     /* Walking along the tops of windows */
   "PENGUIN_FALLER",     /* Falling from the top of the screen */
   "PENGUIN_TUMBLER",    /* After falling off a window */
   "PENGUIN_FLOATER",    /* Well, flying really */
   "PENGUIN_CLIMBER",    /* Climbing up the sides of windows or the screen */
   "PENGUIN_EXIT",       /* Exit sequence */
   "PENGUIN_EXPLOSION",  /* Simple explosion with NO BLOOD! */
   "PENGUIN_RUNNER",     /* Just like walker but usually faster */
   "PENGUIN_SPLATTED",   /* Sometimes when falling onto a hard surface */
   "PENGUIN_SQUASHED",   /* When caught under windows */
   "PENGUIN_ZAPPED",     /* Zapped by mouse pointer (not yet used) */
   "PENGUIN_ANGEL",      /* After some nasty death, floating upwards */
   "PENGUIN_ACTION0",    /* Reading, sleeping, jumping, whatever */
   "PENGUIN_ACTION1",    /* The ACTIONs must have consecutive numbers */
   "PENGUIN_ACTION2", 
   "PENGUIN_ACTION3",
   "PENGUIN_ACTION4",
   "PENGUIN_ACTION5"
};

/* Random integer between 0 and maxint-1 */
//#define RandInt(maxint) ((int) ((maxint)*((float) rand()/(RAND_MAX+1.0))))

/* Global variables */
static Toon penguin[PENGUIN_MAX];
ToonData **penguin_data       = NULL;
int  *penguin_numbers         = NULL;
int  penguin_number           = 0;
int  penguin_ngenera          = 0;
int  xpenguins_active         = 0;
int  xpenguins_blood          = 1; /* 0 = suitable for children */
int  xpenguins_angels         = 1; /* 0 = no angels */

static int  xpenguins_specify_number = 0;

Toon *getpenguin(int i)
{
   return &penguin[i];
}

/* Start a new penguin from the top of the screen */
static void xpenguins_init_penguin(Toon *p)
{
   ToonData *data = penguin_data[p->genus] + PENGUIN_FALLER;
   p->direction = RandInt(2);
   P("init ...\n");
   ToonSetType(p, PENGUIN_FALLER, p->direction, TOON_UNASSOCIATED);
   P("init genus: %d type: %d tv: %f\n",p->genus, t->type,p->terminal_velocity);
   ToonSetPosition(p, RandInt(ToonDisplayWidth()
	    - data->width),
	 1 - data->height);
   ToonSetAssociation(p, TOON_UNASSOCIATED);
   ToonSetVelocity(p, ((p->direction)*2-1), data->speed);
   p->terminating = 0;
}

/* Turn a penguin into a climber */
static void xpenguins_make_climber(Toon *p)
{
   if (p->direction) {
      ToonSetType(p, PENGUIN_CLIMBER, p->direction,
	    TOON_DOWNRIGHT);
   }
   else {
      ToonSetType(p, PENGUIN_CLIMBER, p->direction,
	    TOON_DOWNLEFT);
   }
   ToonSetAssociation(p, p->direction);
   ToonSetVelocity(p, 0, -penguin_data[p->genus][PENGUIN_CLIMBER].speed);
}

/* Turn a penguin into a walker. To ensure that a climber turning into
 * a walker does not lose its footing, set shiftforward to 1
 * (otherwise 0) */
static void xpenguins_make_walker(Toon *p, int shiftforward)
{
   P("xpenguins_make_walker %d\n",p->direction);
   int newtype = PENGUIN_WALKER;
   int gravity = TOON_DOWN;

   if (shiftforward) {
      if (p->direction) {
	 gravity = TOON_DOWNRIGHT;
      }
      else {
	 gravity = TOON_DOWNLEFT;
      }
   }

   if (penguin_data[p->genus][PENGUIN_RUNNER].exists && !RandInt(4)) {
      newtype = PENGUIN_RUNNER;
      /* Sometimes runners are larger than walkers - check for immediate
	 squash */
      if (ToonCheckBlocked(p, newtype, gravity)) {
	 newtype = PENGUIN_WALKER;
      }
   }

   ToonSetType(p, newtype, p->direction, gravity);
   ToonSetAssociation(p, TOON_DOWN);
   ToonSetVelocity(p, penguin_data[p->genus][newtype].speed
	 * ((2 * p->direction) - 1), 0);
}

/* Turn a penguin into a faller */
static void xpenguins_make_faller(Toon *p)
{
   ToonSetVelocity(p, ((p->direction)*2 - 1),
	 penguin_data[p->genus][PENGUIN_FALLER].speed);
   ToonSetType(p, PENGUIN_FALLER, p->direction, TOON_UP);
   ToonSetAssociation(p, TOON_UNASSOCIATED);
}

/* Connect to X server and upload data */
char *xpenguins_start(char *display_name)
{
   if (!penguin_data) {
      return _("No toon data installed");
   }
   if (!xpenguins_active) {
      int i, index, imod = 1;
      unsigned long configure_mask = TOON_SIDEBOTTOMBLOCK;

      /* reset random-number generator */
      srand(time((time_t *) NULL));  // superfluous
      if (!ToonOpenDisplay(display_name)) {
	 return toon_error_message;
      }


      if (xpenguins_verbose && *toon_message) {
	 fprintf(stderr, "%s\n", toon_message);
      }

      /* Set up various preferences: Edge of screen is solid,
       * and if a signal is caught then exit the main event loop */
      ToonConfigure(configure_mask);

      /* Set the distance the window can move (up, down, left, right)
       * and penguin can still cling on */
      ToonSetMaximumRelocate(16,16,16,16);

      /* Send the pixmaps to the X server - penguin_data should have been 
       * defined in penguins/def.h */
      P("ToonInstallData %#lx\n",toon_root);
      ToonInstallData(penguin_data, penguin_ngenera, PENGUIN_NTYPES);

      if (!xpenguins_specify_number)
	 penguin_number = default_penguin_number();
      /* Set the genus of each penguin, whether it is to be activated or not */
      for (index = 0; index < penguin_ngenera && index < PENGUIN_MAX; ++index) {
	 penguin[index].genus = index;
      }
      while (index < PENGUIN_MAX) {
	 for (i = 0; i < penguin_ngenera; ++i) {
	    int j;
	    for (j = 0; j < penguin_numbers[i]-imod && index < PENGUIN_MAX; ++j) {
	       penguin[index++].genus = i;
	    }
	 }
	 imod = 0;
      }
      /* Initialise penguins */
      for (i = 0; i < penguin_number; i++) 
      {
	 penguin[i].pref_direction = -1;
	 penguin[i].pref_climb = 0;
	 penguin[i].hold = 0;
	 xpenguins_init_penguin(penguin+i);
	 penguin[i].x_map = 0;
	 penguin[i].y_map = 0;
	 penguin[i].width_map = 1; /* So that the screen isn't completely */
	 penguin[i].height_map = 1; /*    cleared at the start */
	 penguin[i].mapped = 0;
      }
      /* Find out where the windows are - should be done 
       * just before beginning the event loop */
      ToonLocateWindows();
   }
   xpenguins_active = 1;
   return NULL;
}

int default_penguin_number()
{
   int i,n=0;
   for (i = 0; i < penguin_ngenera; ++i) 
      n += penguin_numbers[i];
   P("penguin_number: %d\n",n);
   return n;
}

void xpenguins_ignorepopups(int yn)
{
   if (yn) {
      ToonConfigure(TOON_NOSOLIDPOPUPS);
   }
   else {
      ToonConfigure(TOON_SOLIDPOPUPS);
   }
   if (xpenguins_active) {
      ToonCalculateAssociations(penguin, penguin_number);
      ToonLocateWindows();
      ToonRelocateAssociated(penguin, penguin_number);
   }
}

void xpenguins_set_number(int n)
{
   int i;
   P("xpenguins_set_number: %d %d\n",n, penguin_number);
   if (xpenguins_active) 
   {
      if (n > penguin_number) {
	 int i;
	 if (n > PENGUIN_MAX) {
	    n = PENGUIN_MAX;
	 }
	 for (i = penguin_number; i < n; i++) {
	    xpenguins_init_penguin(penguin+i);			
	    penguin[i].active = 1;
	 }
	 penguin_number = n;
      }
      else if (n < penguin_number) 
      {
	 if (n < 0) {
	    n = 0;
	 }

	 for (i = n; i < penguin_number; i++) 
	 {
	    ToonData *gdata = penguin_data[penguin[i].genus];
	    if (penguin[i].active) 
	    {
	       if (xpenguins_blood && gdata[PENGUIN_EXIT].exists) 
	       {
		  ToonSetType(penguin+i, PENGUIN_EXIT,
			penguin[i].direction, TOON_DOWN);
	       }
	       else if (gdata[PENGUIN_EXPLOSION].exists) 
	       {
		  ToonSetType(penguin+i, PENGUIN_EXPLOSION,
			penguin[i].direction, TOON_HERE);
	       }
	       else {
		  penguin[i].active = 0;
	       }
	    }
	    penguin[i].terminating = 1;
	 }

	 P("set_number\n");
	 //ToonErase(penguin, penguin_number);
	 //ToonDraw(penguin, penguin_number);
	 //ToonFlush();
      }
   }
   penguin_number = n;
   xpenguins_specify_number = 1;
}

/* Don't advance penguins, but check if they have been uncovered by 
 * moving windows and need to be redrawn. */
/* not used */
// used during examining and debugging:
float termv(int i)
{
   Toon *p = &penguin[i];
   P("i: %d g: %d t: %d tv: %f u: %f v: %f\n",i,p->genus, p->type, p->terminal_velocity, p->u, p->v);
   return p->terminal_velocity;
}
#define TERMV penguin[i].terminal_velocity
//#define TERMV termv(i)
//
/* Returns the number of penguins that are active or not terminating */
/* i.e. when 0 is returned, we can end the program */

int xpenguins_frame()
{
   int status, i, direction;
   int last_active = -1;

   if (!xpenguins_active) {
      return 0;
   }

   /* check if windows have moved, and flush the display */
   if ( ToonWindowsMoved() ) 
   {
      /* if so, check for squashed toons */
      ToonCalculateAssociations(penguin, penguin_number);
      ToonLocateWindows();
      ToonRelocateAssociated(penguin, penguin_number);
   }

   P("penguin_number %d\n",penguin_number);
   /* Loop through all the toons */
   for (i = 0; i < penguin_number; i++) {
      int type = penguin[i].type;
      ToonData *gdata = penguin_data[penguin[i].genus];
      if (!penguin[i].active) {
	 if (!penguin[i].terminating) {
	    xpenguins_init_penguin(penguin+i);
	    last_active = i;
	 }
      }
      else if (toon_button_x >= 0
	    && type != PENGUIN_EXPLOSION && type != PENGUIN_ZAPPED
	    && type != PENGUIN_SQUASHED  && type != PENGUIN_ANGEL
	    && type != PENGUIN_SPLATTED  && type != PENGUIN_EXIT
	    && !penguin[i].terminating
	    && toon_button_x > penguin[i].x_map
	    && toon_button_y > penguin[i].y_map
	    && toon_button_x < (penguin[i].x_map + penguin[i].width_map)
	    && toon_button_y < (penguin[i].y_map + penguin[i].height_map)) 
      {
	 /* Toon has been hit by a button press */
	 if (xpenguins_blood && gdata[PENGUIN_ZAPPED].exists) {
	    ToonSetType(penguin+i, PENGUIN_ZAPPED,
		  penguin[i].direction, TOON_DOWN);
	 }
	 else if (gdata[PENGUIN_EXPLOSION].exists) {
	    ToonSetType(penguin+i, PENGUIN_EXPLOSION,
		  penguin[i].direction, TOON_HERE);
	 }
	 else {
	    penguin[i].active = 0;
	 }
	 ToonSetAssociation(penguin+i, TOON_UNASSOCIATED);
	 last_active = i;
      }
      else 
      {
	 ToonData *data = gdata + type;
	 long int conf  = data->conf;

	 last_active = i;
	 if ( !((conf & TOON_NOBLOCK) | (conf & TOON_INVULNERABLE))
	       && ToonBlocked(penguin+i, TOON_HERE)) {
	    if (xpenguins_blood && gdata[PENGUIN_SQUASHED].exists) {
	       ToonSetType(penguin+i, PENGUIN_SQUASHED,
		     penguin[i].direction, TOON_HERE);
	    }
	    else if (gdata[PENGUIN_EXPLOSION].exists) {
	       ToonSetType(penguin+i, PENGUIN_EXPLOSION,
		     penguin[i].direction, TOON_HERE);
	    }
	    else {
	       penguin[i].active = 0;
	    }
	    ToonSetVelocity(penguin+i, 0, 0);
	    ToonSetAssociation(penguin+i, TOON_UNASSOCIATED);
	 }
	 else 
	 {
	    status=ToonAdvance(penguin+i,TOON_MOVE);
	    switch (type) {
	       case PENGUIN_FALLER:
		  if (status != TOON_OK) {
		     if (ToonBlocked(penguin+i,TOON_DOWN)) {
			if (penguin[i].pref_direction > -1)
			   penguin[i].direction = penguin[i].pref_direction;
			else
			   penguin[i].direction = RandInt(2);
			xpenguins_make_walker(penguin+i, 0);
			penguin[i].pref_direction = -1;
		     }
		     else {
			if (!gdata[PENGUIN_CLIMBER].exists
			      || RandInt(2)) {
			   ToonSetVelocity(penguin+i, -penguin[i].u,
				 gdata[PENGUIN_FALLER].speed);
			}
			else {
			   penguin[i].direction = (penguin[i].u > 0);
			   xpenguins_make_climber(penguin+i);
			}
		     }
		  }
		  else if (penguin[i].v < TERMV) {
		     penguin[i].v += data->acceleration;
		  }
		  break;

	       case PENGUIN_TUMBLER:
		  P("status: %d\n",status);
		  if (status != TOON_OK) 
		  {
		     if (xpenguins_blood && data[PENGUIN_SPLATTED].exists
			   && penguin[i].v >= TERMV
			   && !RandInt(3)) 
		     {
			ToonSetType(penguin+i, PENGUIN_SPLATTED, TOON_LEFT, TOON_DOWN);
			ToonSetAssociation(penguin+i, TOON_DOWN);
			ToonSetVelocity(penguin+i, 0, 0);
		     }
		     else 
		     {
			if (penguin[i].pref_direction > -1)
			   penguin[i].direction = penguin[i].pref_direction;
			else
			   penguin[i].direction = RandInt(2);
			xpenguins_make_walker(penguin+i, 0);
			penguin[i].pref_direction = -1;
		     }
		  }
		  else if (penguin[i].v < TERMV) 
		  {
		     penguin[i].v += data->acceleration;
		  }
		  break;

	       case PENGUIN_WALKER:
	       case PENGUIN_RUNNER:
		  if (status != TOON_OK) {
		     if (status == TOON_BLOCKED) {
			/* Try to step up... */
			float u = penguin[i].u;
			if (!ToonOffsetBlocked(penguin+i, u, -PENGUIN_JUMP)) 
			{
			   ToonMove(penguin+i, u, -PENGUIN_JUMP);
			   ToonSetVelocity(penguin+i, 0, PENGUIN_JUMP - 1);
			   ToonAdvance(penguin+i, TOON_MOVE);
			   ToonSetVelocity(penguin+i, u, 0);
			   /* Don't forget to accelerate! */
			   if (fabsf(u) < TERMV) {
			      if (penguin[i].direction) {
				 penguin[i].u += data->acceleration;
			      }
			      else {
				 penguin[i].u -= data->acceleration;
			      }
			   }
			}
			else {
			   /* Blocked! We can turn round, fly or climb... */
			   int n = RandInt(8) * (1 - penguin[i].pref_climb);
			   if (n < 2) 
			   {
			      int floater_exists = gdata[PENGUIN_FLOATER].exists;
			      int climber_exists = gdata[PENGUIN_CLIMBER].exists;
			      if ((n == 0 || !floater_exists) && climber_exists) 
			      {
				 xpenguins_make_climber(penguin+i);
				 break;
			      }
			      else if (floater_exists) {
				 /* Make floater */
				 int newdir = !penguin[i].direction;
				 ToonSetType(penguin+i, PENGUIN_FLOATER,
				       newdir, TOON_DOWN);
				 ToonSetAssociation(penguin+i, TOON_UNASSOCIATED);
				 ToonSetVelocity(penguin+i, 
				       (RandInt(5)+1) * (newdir*2-1),
				       -gdata[PENGUIN_FLOATER].speed);
				 break;
			      }
			   }
			   else {
			      /* Change direction *after* creating toon to make sure
				 that a runner doesn't get instantly squashed... */
			      xpenguins_make_walker(penguin+i, 0);
			      penguin[i].direction = (!penguin[i].direction);
			      penguin[i].u = -penguin[i].u;
			   }
			}
		     }
		  }
		  else if (!ToonBlocked(penguin+i, TOON_DOWN)) {
		     /* Try to step down... */
		     float u = penguin[i].u; /* Save velocity */
		     ToonSetVelocity(penguin+i, 0, PENGUIN_JUMP);
		     status=ToonAdvance(penguin+i, TOON_MOVE);
		     if (status == TOON_OK) {
			penguin[i].pref_direction = penguin[i].direction;
			if (gdata[PENGUIN_TUMBLER].exists) 
			{
			   ToonSetType(penguin+i, PENGUIN_TUMBLER,
				 penguin[i].direction, TOON_DOWN);
			   ToonSetAssociation(penguin+i, TOON_UNASSOCIATED);
			   ToonSetVelocity(penguin+i, 0, gdata[PENGUIN_TUMBLER].speed);
			}
			else {
			   xpenguins_make_faller(penguin+i);
			   penguin[i].u = 0;
			}
			penguin[i].pref_climb = 0;
		     }
		     else {
			ToonSetVelocity(penguin+i, u, 0);
		     }
		  }
		  else if (gdata[PENGUIN_ACTION0].exists && !RandInt(100)) {
		     int action = 1;
		     /* find out how many actions have been defined */
		     while (gdata[PENGUIN_ACTION0+action].exists
			   && ++action < PENGUIN_NACTIONS);
		     if (action) {
			action = RandInt(action);
		     }
		     else {
			action = 0;
		     }
		     /* If we have enough space, start the action: */
		     if (!ToonCheckBlocked(penguin+i, PENGUIN_ACTION0 + action, TOON_DOWN)) {
			ToonSetType(penguin+i, PENGUIN_ACTION0 + action,
			      penguin[i].direction, TOON_DOWN);
			ToonSetVelocity(penguin+i, gdata[PENGUIN_ACTION0+action].speed
			      * ((2*penguin[i].direction)-1), 0);
		     }
		  }
		  else if (fabsf(penguin[i].u) < TERMV) {
		     if (penguin[i].direction) {
			penguin[i].u += data->acceleration;
		     }
		     else {
			penguin[i].u -= data->acceleration;
		     }
		  }
		  break;

	       case PENGUIN_ACTION0:
	       case PENGUIN_ACTION1:
	       case PENGUIN_ACTION2:
	       case PENGUIN_ACTION3:
	       case PENGUIN_ACTION4:
	       case PENGUIN_ACTION5:
		  if (status != TOON_OK) {
		     /* Try to drift up... */
		     float u = penguin[i].u;
		     if (!ToonOffsetBlocked(penguin+i, u, -PENGUIN_JUMP)) {
			ToonMove(penguin+i, u, -PENGUIN_JUMP);
			ToonSetVelocity(penguin+i, 0, PENGUIN_JUMP - 1);
			ToonAdvance(penguin+i, TOON_MOVE);
			ToonSetVelocity(penguin+i, u, 0);
		     }
		     else {
			/* Blocked! Turn back into a walker: */
			xpenguins_make_walker(penguin+i, 0);
		     }
		  }
		  else if (!ToonBlocked(penguin+i, TOON_DOWN)) {
		     /* Try to drift down... */
		     ToonSetVelocity(penguin+i, 0, PENGUIN_JUMP);
		     status=ToonAdvance(penguin+i, TOON_MOVE);
		     if (status == TOON_OK) 
		     {
			if (gdata[PENGUIN_TUMBLER].exists) {
			   ToonSetType(penguin+i, PENGUIN_TUMBLER,
				 penguin[i].direction, TOON_DOWN);
			   ToonSetAssociation(penguin+i, TOON_UNASSOCIATED);
			   ToonSetVelocity(penguin+i, 0,
				 gdata[PENGUIN_TUMBLER].speed);
			}
			else {
			   xpenguins_make_faller(penguin+i);
			}
			penguin[i].pref_climb = 0;
		     }
		     else {
			ToonSetVelocity(penguin+i, data->speed
			      * ((2*penguin[i].direction)-1), 0);
		     }
		  }
		  else if (penguin[i].frame == 0) {
		     int loop = data->loop;
		     if (!loop) {
			loop = -10;
		     }
		     if (loop < 0) {
			if (!RandInt(-loop)) {
			   xpenguins_make_walker(penguin+i, 0);
			}
		     }
		     else if (penguin[i].cycle >= loop) {
			xpenguins_make_walker(penguin+i, 0);
		     }
		  }
		  break;

	       case PENGUIN_CLIMBER:
		  direction = penguin[i].direction;
		  if (penguin[i].y < 0) {
		     penguin[i].direction = (!direction);
		     xpenguins_make_faller(penguin+i);
		     penguin[i].pref_climb = 0;
		  }
		  else if (status == TOON_BLOCKED) {
		     /* Try to step out... */
		     float v = penguin[i].v;
		     int xoffset = (1-direction*2) * PENGUIN_JUMP;
		     if (!ToonOffsetBlocked(penguin+i, xoffset, v)) {
			ToonMove(penguin+i, xoffset, v);
			ToonSetVelocity(penguin+i, -xoffset-(1-direction*2), 0);
			ToonAdvance(penguin+i, TOON_MOVE);
			ToonSetVelocity(penguin+i, 0, v);
		     }
		     else {
			penguin[i].direction = (!direction);
			xpenguins_make_faller(penguin+i);
			penguin[i].pref_climb = 0;
		     }
		  }
		  else if (!ToonBlocked(penguin+i, direction)) 
		  {
		     if (ToonOffsetBlocked(penguin+i, ((2*direction)-1)
			      * PENGUIN_JUMP, 0)) {
			ToonSetVelocity(penguin+i, ((2*direction)-1)
			      * (PENGUIN_JUMP - 1), 0);
			ToonAdvance(penguin+i, TOON_MOVE);
			ToonSetVelocity(penguin+i, 0, -data->speed);
		     }
		     else {
			xpenguins_make_walker(penguin+i, 1);
			ToonSetPosition(penguin+i, penguin[i].x + (2*direction)-1,
			      penguin[i].y);
			penguin[i].pref_direction = direction;
			penguin[i].pref_climb = 1;
		     }
		  }
		  else if (penguin[i].v > -TERMV) {
		     penguin[i].v -= data->acceleration;
		  }
		  break;

	       case PENGUIN_FLOATER:
		  if (penguin[i].y < 0) 
		  {
		     penguin[i].direction = (penguin[i].u > 0);
		     xpenguins_make_faller(penguin+i);
		  }
		  else if (status != TOON_OK) 
		  {
		     if (ToonBlocked(penguin+i, TOON_UP)) 
		     {
			penguin[i].direction = (penguin[i].u>0);
			xpenguins_make_faller(penguin+i);
		     }
		     else 
		     {
			penguin[i].direction = !penguin[i].direction;
			ToonSetVelocity(penguin+i,-penguin[i].u,
			      -data->speed);
		     }
		  }
		  break;
	       case PENGUIN_EXPLOSION:
		  if (xpenguins_angels && !penguin[i].terminating
			&& gdata[PENGUIN_ANGEL].exists) {
		     ToonSetType(penguin+i, PENGUIN_ANGEL,
			   penguin[i].direction, TOON_HERE);
		     ToonSetVelocity(penguin+i, RandInt(5) -2,
			   -gdata[PENGUIN_ANGEL].speed);
		     ToonSetAssociation(penguin+i, TOON_UNASSOCIATED);
		  }
		  /* FALLTHRU */
	       case PENGUIN_ANGEL:
		  if (penguin[i].y < -data->height) {
		     penguin[i].active = 0;
		  }
		  if (status != TOON_OK) {
		     penguin[i].u = -penguin[i].u;
		  } 
	    }
	 }
      }
   }

   //xpenguins_draw();
   penguin_number = last_active + 1;

   /* Clear any button press information */
   toon_button_x = toon_button_y = -1;

   return penguin_number;
}

void xpenguins_draw()
{
   /* First erase them all, then draw them all
    * - greatly reduces flickering */
   // this seems critical: when flushing here, penguins are moving Ok,
   // else we have severe flicker
#ifdef DOUBLE_BUFFER
   if (UseXdbe)
   {
      XdbeSwapInfo swapInfo;
      swapInfo.swap_window = toon_root1; 
      swapInfo.swap_action = BMETHOD;
      XdbeSwapBuffers(toon_display, &swapInfo, 1);
      // see toon_draw.c:
      if(toon_trans)
	 XSetWindowBackground(toon_display,toon_root1,0);
   }
   else
   {
      ToonFlush(); 
      ToonErase(penguin, penguin_number);
   }
#else
   ToonFlush(); 
   ToonErase(penguin, penguin_number);
#endif
   //ToonFlush(); // introduces severe flicker
   ToonDraw(penguin, penguin_number);
   ToonFlush(); // does no harm, but seems not to be necessary
}


void ToonDataPrint(ToonData *p)
{
   printf("conf                %#lx\n",p->conf);
   printf("filename            %s\n",p->filename);
   printf("nframes             %d\n",p->nframes);
   printf("ndirections         %d\n",p->ndirections);
   printf("width               %d\n",p->width);
   printf("height              %d\n",p->height);
   printf("acceleration        %f\n",p->acceleration);
   printf("speed               %f\n",p->speed);
   printf("terminal_velocity   %f\n",p->terminal_velocity);
   printf("loop                %d\n",p->loop);
   printf("exists              %d\n",p->exists);

}

void ToonPrint()
{
   int i;
   for (i=0; i<penguin_number; i++)
   {
      int genus = penguin[i].genus;
      int type = penguin[i].type;
      float tv = penguin[i].terminal_velocity;
      printf("tvv: %d genus: %d type %d tv: %f\n",i,genus, type, tv);
   }
}

void CheckPenguinsY()
{
   int i;
   for (i=0; i<penguin_number; i++)
   {
      int height = penguin_data[penguin[i].genus][penguin[i].type].height;
      if (penguin[i].y + height > toon_display_height)
      {
	 penguin[i].y = toon_display_height - height-1;
      }
   }

}
