/* gcompris - paratrooper.c
*
* Copyright (C) 2000 Bruno Coudoin
*
* 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 3 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, see .
*/
#include "gcompris/gcompris.h"
#define SOUNDLISTFILE PACKAGE
static int gamewon;
static gboolean board_paused = TRUE;
static GList *item_list = NULL;
static GList *item2del_list = NULL;
static GcomprisBoard *gcomprisBoard = NULL;
static gint dummy_id = 0;
static gint drop_tux_id = 0;
static GnomeCanvasItem *boatitem = NULL;
static gint boat_x, boat_y, boat_landarea_y, boat_length;
static GnomeCanvasItem *planeitem = NULL;
static gint plane_x, plane_y;
static gint planespeed_x, planespeed_y;
static double windspeed;
typedef struct {
gint number;
GnomeCanvasItem *rootitem;
} CloudItem;
typedef enum
{
TUX_INPLANE = 1 << 0,
TUX_DROPPING = 1 << 1,
TUX_FLYING = 1 << 2,
TUX_LANDED = 1 << 3,
TUX_CRASHED = 1 << 4
} ParaStatus;
typedef struct {
ParaStatus status;
double speed;
double drift;
gboolean speed_override;
double x;
double y;
GnomeCanvasItem *rootitem;
GnomeCanvasItem *paratrooper;
GnomeCanvasItem *parachute;
GnomeCanvasItem *instruct;
} ParatrooperItem;
static ParatrooperItem paratrooperItem;
/* default gnome pixmap directory in which this game tales the icon */
static char *pixmapsdir = "gcompris/misc/";
static void start_board (GcomprisBoard *agcomprisBoard);
static void pause_board (gboolean pause);
static void end_board (void);
static gboolean is_our_board (GcomprisBoard *gcomprisBoard);
static void set_level (guint level);
static gint key_press(guint keyval, gchar *commit_str, gchar *preedit_str);
static GnomeCanvasItem *paratrooper_create_cloud(GnomeCanvasGroup *parent);
static gint paratrooper_drop_clouds (GtkWidget *widget, gpointer data);
static gint paratrooper_move_items (GtkWidget *widget, gpointer data);
static gint paratrooper_move_tux (GtkWidget *widget, gpointer data);
static void paratrooper_destroy_item(CloudItem *clouditem);
static void paratrooper_destroy_items(void);
static void paratrooper_destroy_all_items(void);
static void paratrooper_next_level(void);
static gint item_event(GnomeCanvasItem *item, GdkEvent *event, void *data);
static void next_state(void);
static double speed = 0.0;
static double imageZoom = 0.0;
/* Description of this plugin */
static BoardPlugin menu_bp =
{
NULL,
NULL,
"Parachutist",
"Direct the parachutist to help him or her land safely",
"Bruno Coudoin ",
NULL,
NULL,
NULL,
NULL,
start_board,
pause_board,
end_board,
is_our_board,
key_press,
NULL,
set_level,
NULL,
NULL,
NULL,
NULL
};
/*
* Main entry point mandatory for each Gcompris's game
* ---------------------------------------------------
*
*/
GET_BPLUGIN_INFO(paratrooper)
/*
* in : boolean TRUE = PAUSE : FALSE = CONTINUE
*
*/
static void pause_board (gboolean pause)
{
if(gcomprisBoard==NULL)
return;
if(pause)
{
if (dummy_id) {
gtk_timeout_remove (dummy_id);
dummy_id = 0;
}
if (drop_tux_id) {
gtk_timeout_remove (drop_tux_id);
drop_tux_id = 0;
}
}
else
{
if(gamewon == TRUE) /* the game is won */
{
gcomprisBoard->level++;
if(gcomprisBoard->level>gcomprisBoard->maxlevel) { // the current board is finished : bail out
gc_bonus_end_display(GC_BOARD_FINISHED_RANDOM);
return;
}
printf("paratrooper pause start next level\n");
}
// Unpause code
if(!dummy_id) {
dummy_id = gtk_timeout_add (1000, (GtkFunction) paratrooper_move_items, NULL);
}
if(paratrooperItem.status!=TUX_INPLANE && paratrooperItem.status!=TUX_LANDED) {
drop_tux_id = gtk_timeout_add (1000, (GtkFunction) paratrooper_move_tux, NULL);
}
if(gamewon == TRUE) /* the game is won */
paratrooper_next_level();
}
board_paused = pause;
}
/*
*/
static void start_board (GcomprisBoard *agcomprisBoard)
{
if(agcomprisBoard!=NULL)
{
gcomprisBoard=agcomprisBoard;
/* disable im_context */
gcomprisBoard->disable_im_context = TRUE;
gc_set_background(gnome_canvas_root(gcomprisBoard->canvas), "opt/scenery3_background.png");
/* set initial values for this level */
gcomprisBoard->level = 1;
gcomprisBoard->maxlevel = 6;
gc_bar_set(GC_BAR_LEVEL);
/* Init of paratrooper struct */
paratrooperItem.rootitem = NULL;
paratrooper_next_level();
pause_board(FALSE);
}
}
static void
end_board ()
{
if(gcomprisBoard!=NULL)
{
pause_board(TRUE);
paratrooper_destroy_all_items();
gcomprisBoard->level = 1; // Restart this game to zero
}
gcomprisBoard = NULL;
}
static void
set_level (guint level)
{
if(gcomprisBoard!=NULL)
{
gcomprisBoard->level=level;
paratrooper_next_level();
}
}
static gint key_press(guint keyval, gchar *commit_str, gchar *preedit_str)
{
if(board_paused || !gcomprisBoard)
return FALSE;
/* Add some filter for control and shift key */
switch (keyval)
{
/* Avoid all this keys to be interpreted by this game */
case GDK_Shift_L:
case GDK_Shift_R:
case GDK_Control_L:
case GDK_Control_R:
case GDK_Caps_Lock:
case GDK_Shift_Lock:
case GDK_Meta_L:
case GDK_Meta_R:
case GDK_Alt_L:
case GDK_Alt_R:
case GDK_Super_L:
case GDK_Super_R:
case GDK_Hyper_L:
case GDK_Hyper_R:
case GDK_Num_Lock:
return FALSE;
case GDK_KP_Enter:
case GDK_Return:
return TRUE;
case GDK_Right:
break;
case GDK_Left:
break;
case GDK_Up:
paratrooperItem.speed_override = 1;
if(paratrooperItem.status == TUX_FLYING && paratrooperItem.speed >= 3)
paratrooperItem.speed--;
return TRUE;
case GDK_Down:
paratrooperItem.speed_override = 1;
if(paratrooperItem.status == TUX_FLYING && paratrooperItem.speed <= 6)
paratrooperItem.speed++;
return TRUE;
}
next_state();
return TRUE;
}
static gboolean
is_our_board (GcomprisBoard *gcomprisBoard)
{
if (gcomprisBoard)
{
if(g_strcasecmp(gcomprisBoard->type, "paratrooper")==0)
{
/* Set the plugin entry */
gcomprisBoard->plugin=&menu_bp;
return TRUE;
}
}
return FALSE;
}
/*-------------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------------*/
/* set initial values for the next level */
static void paratrooper_next_level()
{
GdkPixbuf *pixmap = NULL;
char *str = NULL;
gamewon = FALSE;
gc_bar_set_level(gcomprisBoard);
paratrooper_destroy_all_items();
/* Try the next level */
speed=100+(30/(gcomprisBoard->level));
gcomprisBoard->number_of_sublevel=0;
/* Make the images tend to 0.5 ratio */
imageZoom=0.4+(0.2 * (2 - (gcomprisBoard->level-1) % 3));
/* Setup and Display the plane */
planespeed_y = 0;
planespeed_x = 4 + gcomprisBoard->level;
str = g_strdup_printf("%s%s", pixmapsdir, "tuxplane.png");
pixmap = gc_pixmap_load(str);
plane_x = 0;
plane_y = 40;
planeitem = gnome_canvas_item_new (gnome_canvas_root(gcomprisBoard->canvas),
gnome_canvas_pixbuf_get_type (),
"pixbuf", pixmap,
"x", (double) plane_x,
"y", (double) plane_y,
"width", (double) gdk_pixbuf_get_width(pixmap)*imageZoom,
"height", (double) gdk_pixbuf_get_height(pixmap)*imageZoom,
"width_set", TRUE,
"height_set", TRUE,
NULL);
gtk_signal_connect(GTK_OBJECT(planeitem), "event",
(GtkSignalFunc) item_event,
NULL);
gtk_signal_connect(GTK_OBJECT(planeitem), "event",
(GtkSignalFunc) gc_item_focus_event,
NULL);
gdk_pixbuf_unref(pixmap);
windspeed = (3 + rand() % (100 * gcomprisBoard->level) / 100);
if(rand()%2==0)
windspeed *= -1;
if (gcomprisBoard->level >= 4)
windspeed *= 2;
/* Drop a cloud */
gtk_timeout_add (200,
(GtkFunction) paratrooper_drop_clouds, NULL);
/* Display the target */
g_free(str);
str = g_strdup_printf("%s%s", pixmapsdir, "fishingboat.png");
pixmap = gc_pixmap_load(str);
boat_x = 350;
boat_y = gcomprisBoard->height-100;
boat_landarea_y = boat_y+20;
boat_length = gdk_pixbuf_get_width(pixmap)*imageZoom;
boatitem = gnome_canvas_item_new (gnome_canvas_root(gcomprisBoard->canvas),
gnome_canvas_pixbuf_get_type (),
"pixbuf", pixmap,
"x", (double) boat_x,
"y", (double) boat_y,
"width", (double) gdk_pixbuf_get_width(pixmap)*imageZoom,
"height", (double) gdk_pixbuf_get_height(pixmap),
"width_set", TRUE,
"height_set", TRUE,
NULL);
gdk_pixbuf_unref(pixmap);
/* Prepare the parachute */
if (drop_tux_id) {
gtk_timeout_remove (drop_tux_id);
drop_tux_id = 0;
}
paratrooperItem.status = TUX_INPLANE;
paratrooperItem.x = 0;
paratrooperItem.y = 60;
paratrooperItem.speed = 3;
paratrooperItem.rootitem = \
gnome_canvas_item_new (gnome_canvas_root(gcomprisBoard->canvas),
gnome_canvas_group_get_type (),
"x", (double)paratrooperItem.x,
"y", (double)paratrooperItem.y,
NULL);
g_free(str);
str = g_strdup_printf("%s%s", pixmapsdir, "minitux.png");
pixmap = gc_pixmap_load(str);
paratrooperItem.paratrooper = gnome_canvas_item_new (GNOME_CANVAS_GROUP(paratrooperItem.rootitem),
gnome_canvas_pixbuf_get_type (),
"pixbuf", pixmap,
"x", (double) -gdk_pixbuf_get_width(pixmap)/2,
"y", (double) -gdk_pixbuf_get_height(pixmap)/2,
"width", (double) gdk_pixbuf_get_width(pixmap),
"height", (double) gdk_pixbuf_get_height(pixmap),
NULL);
gnome_canvas_item_hide(paratrooperItem.paratrooper);
gdk_pixbuf_unref(pixmap);
gtk_signal_connect(GTK_OBJECT(paratrooperItem.paratrooper), "event",
(GtkSignalFunc) item_event,
NULL);
gtk_signal_connect(GTK_OBJECT(paratrooperItem.paratrooper), "event",
(GtkSignalFunc) gc_item_focus_event,
NULL);
g_free(str);
str = g_strdup_printf("%s%s", pixmapsdir, "parachute.png");
pixmap = gc_pixmap_load(str);
paratrooperItem.parachute = gnome_canvas_item_new (GNOME_CANVAS_GROUP(paratrooperItem.rootitem),
gnome_canvas_pixbuf_get_type (),
"pixbuf", pixmap,
"x", (double) -gdk_pixbuf_get_width(pixmap)/2,
"y", (double) -(gdk_pixbuf_get_height(pixmap)/2)-60,
"width", (double) gdk_pixbuf_get_width(pixmap),
"height", (double) gdk_pixbuf_get_height(pixmap),
NULL);
gnome_canvas_item_hide(paratrooperItem.parachute);
gdk_pixbuf_unref(pixmap);
g_free (str);
paratrooperItem.instruct = gnome_canvas_item_new (gnome_canvas_root (gcomprisBoard->canvas),
gnome_canvas_text_get_type (),
"text", _("Control fall speed with up and down arrow keys."),
"font", gc_skin_font_board_medium,
"fill_color_rgba", gc_skin_color_title,
"anchor", GTK_ANCHOR_CENTER,
"x", (double) gcomprisBoard->width / 2.0,
"y", (double) 130,
NULL);
gnome_canvas_item_hide (paratrooperItem.instruct);
}
/* Move the plane */
static void paratrooper_move_plane(GnomeCanvasItem *item)
{
if(plane_x>gcomprisBoard->width && planespeed_x>0)
{
double x1, y1, x2, y2;
gnome_canvas_item_get_bounds (item,
&x1,
&y1,
&x2,
&y2);
gnome_canvas_item_move(item, (double)-gcomprisBoard->width-(x2-x1), (double)planespeed_y);
plane_x = plane_x - gcomprisBoard->width - (x2-x1);
if(paratrooperItem.status!=TUX_INPLANE)
gnome_canvas_item_hide(item);
}
gnome_canvas_item_move(item, (double)planespeed_x, (double)planespeed_y);
plane_x+=planespeed_x;
plane_y+=planespeed_y;
}
static void paratrooper_move_cloud(CloudItem *clouditem)
{
double x1, y1, x2, y2;
GnomeCanvasItem *item = clouditem->rootitem;
gnome_canvas_item_move(item, windspeed, 0.0);
gnome_canvas_item_get_bounds (item,
&x1,
&y1,
&x2,
&y2);
/* Manage the wrapping for the cloud */
if(windspeed<0 && x2<0)
{
gnome_canvas_item_move(item, gcomprisBoard->width, 0.0);
}
else if(windspeed>0 && x1>gcomprisBoard->width)
{
gnome_canvas_item_move(item, -gcomprisBoard->width, 0.0);
}
}
static void paratrooper_destroy_item(CloudItem *clouditem)
{
GnomeCanvasItem *item = clouditem->rootitem;
item_list = g_list_remove (item_list, clouditem);
item2del_list = g_list_remove (item2del_list, clouditem);
gtk_object_destroy (GTK_OBJECT(item));
g_free(clouditem);
}
/* Destroy items that falls out of the canvas */
static void paratrooper_destroy_items()
{
CloudItem *clouditem;
while(g_list_length(item2del_list)>0)
{
clouditem = g_list_nth_data(item2del_list, 0);
paratrooper_destroy_item(clouditem);
}
}
/* Destroy all the items */
static void paratrooper_destroy_all_items()
{
CloudItem *clouditem;
while(g_list_length(item_list)>0)
{
clouditem = g_list_nth_data(item_list, 0);
paratrooper_destroy_item(clouditem);
}
if(planeitem)
{
gtk_object_destroy (GTK_OBJECT(planeitem));
planeitem = NULL;
}
if(boatitem)
{
gtk_object_destroy (GTK_OBJECT(boatitem));
boatitem = NULL;
}
if(paratrooperItem.rootitem)
{
gtk_object_destroy (GTK_OBJECT(paratrooperItem.rootitem));
paratrooperItem.rootitem = NULL;
}
if (paratrooperItem.instruct) {
gtk_object_destroy (GTK_OBJECT (paratrooperItem.instruct));
paratrooperItem.instruct = NULL;
}
}
/*
* This does the moves of the game items on the play canvas
*
*/
static gint paratrooper_move_items (GtkWidget *widget, gpointer data)
{
g_list_foreach (item_list, (GFunc) paratrooper_move_cloud, NULL);
/* Destroy items that falls out of the canvas */
paratrooper_destroy_items();
/* move the plane */
paratrooper_move_plane(planeitem);
dummy_id = gtk_timeout_add (speed,
(GtkFunction) paratrooper_move_items, NULL);
return(FALSE);
}
/*
* This does the moves of the game's paratropper
*
*/
static gint paratrooper_move_tux (GtkWidget *widget, gpointer data)
{
double offset;
/* Manage the wrapping */
if(paratrooperItem.x<0) {
paratrooperItem.x+=gcomprisBoard->width;
gnome_canvas_item_move(paratrooperItem.rootitem, gcomprisBoard->width, 0);
}
if(paratrooperItem.x>gcomprisBoard->width) {
paratrooperItem.x-=gcomprisBoard->width;
gnome_canvas_item_move(paratrooperItem.rootitem, -gcomprisBoard->width, 0);
}
offset = (windspeed / 2 + 15 * paratrooperItem.drift) / 16;
paratrooperItem.drift = offset;
if (paratrooperItem.status == TUX_DROPPING && gcomprisBoard->level >= 4)
paratrooperItem.speed *= 1.05;
if (paratrooperItem.status == TUX_FLYING && paratrooperItem.speed > 3 &&
!paratrooperItem.speed_override)
paratrooperItem.speed /= 1.2;
gnome_canvas_item_move(paratrooperItem.rootitem, offset, paratrooperItem.speed);
paratrooperItem.y += paratrooperItem.speed;
paratrooperItem.x += offset;
/* Check we reached the target */
if(paratrooperItem.y>boat_landarea_y)
{
if(paratrooperItem.x>boat_x && paratrooperItem.xheight-20)
drop_tux_id = gtk_timeout_add (150,
(GtkFunction) paratrooper_move_tux, NULL);
else
{
gnome_canvas_item_hide(paratrooperItem.parachute);
paratrooperItem.status = TUX_CRASHED;
next_state();
}
}
}
else
{
drop_tux_id = gtk_timeout_add (150,
(GtkFunction) paratrooper_move_tux, NULL);
}
return(FALSE);
}
static GnomeCanvasItem *paratrooper_create_cloud(GnomeCanvasGroup *parent)
{
GdkPixbuf *pixmap = NULL;
GnomeCanvasItem *itemgroup;
char *str = NULL;
int x;
CloudItem *clouditem;
str = g_strdup_printf("%s%s", pixmapsdir, "cloud.png");
pixmap = gc_pixmap_load(str);
if(windspeed>0)
x = 0;
else
x = gcomprisBoard->width;
itemgroup = \
gnome_canvas_item_new (parent,
gnome_canvas_group_get_type (),
"x", (double) x,
"y", (double)(rand()%(gcomprisBoard->height-200-
(guint)(gdk_pixbuf_get_height(pixmap)*
imageZoom))),
NULL);
gnome_canvas_item_new (GNOME_CANVAS_GROUP(itemgroup),
gnome_canvas_pixbuf_get_type (),
"pixbuf", pixmap,
"x", (double) -gdk_pixbuf_get_width(pixmap)*imageZoom/2,
"y", (double) -gdk_pixbuf_get_height(pixmap)*imageZoom/2,
"width", (double) gdk_pixbuf_get_width(pixmap)*imageZoom,
"height", (double) gdk_pixbuf_get_height(pixmap)*imageZoom,
"width_set", TRUE,
"height_set", TRUE,
NULL);
gdk_pixbuf_unref(pixmap);
/* The plane is always on top */
gnome_canvas_item_raise_to_top(planeitem);
clouditem = g_malloc(sizeof(CloudItem));
clouditem->rootitem = itemgroup;
item_list = g_list_append (item_list, clouditem);
g_free (str);
return (itemgroup);
}
static void paratrooper_add_new_item()
{
paratrooper_create_cloud(gnome_canvas_root(gcomprisBoard->canvas));
}
/*
* This is called on a low frequency and is used to drop new items
*
*/
static gint paratrooper_drop_clouds (GtkWidget *widget, gpointer data)
{
paratrooper_add_new_item();
return (FALSE);
}
/*
* This is the state machine of the paratrooper
*/
void next_state()
{
switch(paratrooperItem.status)
{
case TUX_INPLANE:
gc_sound_play_ogg ("sounds/tuxok.wav", NULL);
gnome_canvas_item_move(paratrooperItem.rootitem, plane_x+100, 0);
paratrooperItem.x += plane_x+100;
gnome_canvas_item_show(paratrooperItem.paratrooper);
paratrooperItem.status = TUX_DROPPING;
paratrooperItem.drift = planespeed_x;
drop_tux_id = gtk_timeout_add (10, (GtkFunction) paratrooper_move_tux, NULL);
break;
case TUX_DROPPING:
gc_sound_play_ogg ("sounds/eraser2.wav", NULL);
gnome_canvas_item_lower_to_bottom(paratrooperItem.parachute);
gnome_canvas_item_show(paratrooperItem.parachute);
paratrooperItem.status = TUX_FLYING;
paratrooperItem.speed_override = 0;
if (gcomprisBoard->level >= 2) {
gnome_canvas_item_raise_to_top (paratrooperItem.instruct);
gnome_canvas_item_show (paratrooperItem.instruct);
}
break;
case TUX_LANDED:
gc_sound_play_ogg ("sounds/tuxok.wav", NULL);
gnome_canvas_item_hide (paratrooperItem.instruct);
gamewon = TRUE;
gc_bonus_display(gamewon, GC_BONUS_TUX);
break;
case TUX_CRASHED:
/* Restart */
gc_sound_play_ogg ("sounds/bubble.wav", NULL);
gnome_canvas_item_hide (paratrooperItem.instruct);
gnome_canvas_item_move(paratrooperItem.rootitem, -paratrooperItem.x, -paratrooperItem.y+60);
paratrooperItem.status = TUX_INPLANE;
paratrooperItem.x = 0;
paratrooperItem.y = 60;
paratrooperItem.speed = 3;
gnome_canvas_item_hide(paratrooperItem.paratrooper);
gnome_canvas_item_show(planeitem);
break;
default:
break;
}
}
static gint
item_event(GnomeCanvasItem *item, GdkEvent *event, void *data)
{
if(!gcomprisBoard)
return FALSE;
switch (event->type)
{
case GDK_BUTTON_PRESS:
next_state();
break;
default:
break;
}
return FALSE;
}