/* gcompris - paratrooper.c
*
* Copyright (C) 2000, 2008 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 GcomprisBoard *gcomprisBoard = NULL;
static gint drop_tux_id = 0;
static GooCanvasItem *boardRootItem = NULL;
static gint boat_x, boat_y, boat_landarea_y, boat_width;
static gint plane_x, plane_y;
static gint planespeed_x;
static gdouble windspeed;
static GooCanvasItem *planeitem;
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;
GooCanvasItem *rootitem;
GooCanvasItem *paratrooper;
GooCanvasItem *instruct;
} ParatrooperItem;
static ParatrooperItem paratrooperItem;
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 void paratrooper_create_cloud(GooCanvasItem *parent);
static gboolean paratrooper_drop_clouds (gpointer data);
static gboolean paratrooper_move_tux (gpointer data);
static void paratrooper_destroy_all_items(void);
static void paratrooper_next_level(void);
static gboolean item_event (GooCanvasItem *item,
GooCanvasItem *target,
GdkEventButton *event,
gpointer 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 (drop_tux_id) {
g_source_remove (drop_tux_id);
drop_tux_id = 0;
}
}
else
{
if(gamewon == TRUE) /* the game is won */
{
gcomprisBoard->level++;
if(gcomprisBoard->level>gcomprisBoard->maxlevel)
gcomprisBoard->level = gcomprisBoard->maxlevel;
}
// Unpause code
if(paratrooperItem.status!=TUX_INPLANE && paratrooperItem.status!=TUX_LANDED) {
drop_tux_id = g_timeout_add (1000, paratrooper_move_tux, NULL);
}
if(gamewon == TRUE) /* the game is won */
paratrooper_next_level();
}
}
/*
*/
static void start_board (GcomprisBoard *agcomprisBoard)
{
if(agcomprisBoard!=NULL)
{
gcomprisBoard=agcomprisBoard;
/* disable im_context */
gcomprisBoard->disable_im_context = TRUE;
gc_set_background(goo_canvas_get_root_item(gcomprisBoard->canvas),
"paratrooper/background.svgz");
/* set initial values for this level */
gcomprisBoard->level = 1;
gcomprisBoard->maxlevel = 6;
gc_bar_set(GC_BAR_LEVEL);
gc_bar_location(BOARDWIDTH-200, -1, 0.7);
/* 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(!gcomprisBoard)
return FALSE;
/* Add some filter for control and shift key */
switch (keyval)
{
/* Avoid all this keys to be interpreted by this game */
case 0:
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_ascii_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()
{
RsvgHandle *svg_handle;
RsvgDimensionData rsvg_dimension;
GooCanvasItem *item;
GooCanvasItem *planeroot;
GooCanvasBounds bounds;
gamewon = FALSE;
gc_bar_set_level(gcomprisBoard);
paratrooper_destroy_all_items();
boardRootItem = \
goo_canvas_group_new (goo_canvas_get_root_item(gcomprisBoard->canvas),
NULL);
/* Try the next level */
speed=10+(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 */
svg_handle = gc_rsvg_load("paratrooper/tuxplane.svgz");
rsvg_handle_get_dimensions (svg_handle, &rsvg_dimension);
planeroot = \
goo_canvas_group_new (boardRootItem,
NULL);
planespeed_x = 4 + gcomprisBoard->level;
plane_x = -rsvg_dimension.width;
plane_y = 10;
planeitem = goo_canvas_svg_new (planeroot,
svg_handle,
NULL);
goo_canvas_item_set_simple_transform(planeitem,
plane_x,
plane_y,
imageZoom,
0);
goo_canvas_item_animate(planeroot,
BOARDWIDTH + rsvg_dimension.width,
plane_y,
1,
0,
FALSE,
BOARDWIDTH * speed,
gc_timing (speed * 1.5, 4),
GOO_CANVAS_ANIMATE_RESTART);
g_signal_connect(planeitem, "button-press-event",
(GCallback) item_event,
NULL);
gc_item_focus_init(planeitem, NULL);
g_object_unref(svg_handle);
windspeed = (3 + rand() % (100 * gcomprisBoard->level) / 100);
if(rand()%2==0)
windspeed *= -1;
if (gcomprisBoard->level >= 4)
windspeed *= 2;
/* Drop a cloud */
g_timeout_add (200,
paratrooper_drop_clouds, NULL);
/* Display the target */
svg_handle = gc_rsvg_load("paratrooper/fishingboat.svgz");
rsvg_handle_get_dimensions (svg_handle, &rsvg_dimension);
boat_x = (BOARDWIDTH - rsvg_dimension.width) / 2;
boat_y = BOARDHEIGHT - 100;
boat_landarea_y = boat_y + 20;
item = \
goo_canvas_svg_new (boardRootItem,
svg_handle,
NULL);
goo_canvas_item_translate(item,
-rsvg_dimension.width/2,
boat_y - 50);
goo_canvas_item_get_bounds(item, &bounds);
boat_width = bounds.x2 - bounds.x1;
goo_canvas_item_animate(item,
boat_x,
boat_y,
1,
0,
TRUE,
BOARDWIDTH/2 * 30,
gc_timing (40 * 2, 4),
GOO_CANVAS_ANIMATE_FREEZE);
g_object_unref(svg_handle);
/* Prepare the parachute */
if (drop_tux_id) {
g_source_remove (drop_tux_id);
drop_tux_id = 0;
}
paratrooperItem.status = TUX_INPLANE;
paratrooperItem.speed = 3;
paratrooperItem.rootitem = \
goo_canvas_group_new (boardRootItem,
NULL);
paratrooperItem.paratrooper = \
goo_canvas_svg_new (paratrooperItem.rootitem,
NULL,
NULL);
g_object_set (paratrooperItem.paratrooper, "visibility",
GOO_CANVAS_ITEM_INVISIBLE, NULL);
g_signal_connect(paratrooperItem.paratrooper, "button-press-event",
(GCallback) item_event,
NULL);
paratrooperItem.instruct = \
goo_canvas_text_new (boardRootItem,
_("Control fall speed with up and down arrow keys."),
(double) BOARDWIDTH / 2.0,
(double) 130,
-1,
GTK_ANCHOR_CENTER,
"font", gc_skin_font_board_medium,
"fill_color_rgba", gc_skin_color_title,
NULL);
g_object_set (paratrooperItem.instruct, "visibility", GOO_CANVAS_ITEM_INVISIBLE, NULL);
}
/* Destroy all the items */
static void paratrooper_destroy_all_items()
{
if(boardRootItem)
goo_canvas_item_remove(boardRootItem);
boardRootItem = NULL;
}
/*
* This does the moves of the game's paratropper
*
*/
static gboolean
paratrooper_move_tux (gpointer data)
{
double offset;
GooCanvasBounds bounds;
guint center;
goo_canvas_item_get_bounds(paratrooperItem.paratrooper, &bounds);
center = bounds.x1 + (bounds.x2 - bounds.x1) / 2;
/* Manage the wrapping */
if(center < 0) {
goo_canvas_item_translate(paratrooperItem.rootitem,
BOARDWIDTH,
0);
}
if(center > BOARDWIDTH) {
goo_canvas_item_translate(paratrooperItem.rootitem,
-BOARDWIDTH,
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;
goo_canvas_item_translate(paratrooperItem.rootitem,
offset, paratrooperItem.speed);
/* Check we reached the target */
if(bounds.y2 > boat_landarea_y)
{
if( center > boat_x &&
center < boat_x + boat_width &&
paratrooperItem.status == TUX_FLYING)
{
paratrooperItem.status = TUX_LANDED;
next_state();
}
else
{
if(bounds.y2 < BOARDHEIGHT - 20)
drop_tux_id = g_timeout_add (150,
paratrooper_move_tux,
NULL);
else
{
paratrooperItem.status = TUX_CRASHED;
next_state();
}
}
}
else
{
drop_tux_id = g_timeout_add (150,
paratrooper_move_tux, NULL);
}
return(FALSE);
}
static void
paratrooper_create_cloud(GooCanvasItem *parent)
{
RsvgHandle *svg_handle = NULL;
GooCanvasItem *item;
GooCanvasItem *root;
int x, y;
int x_end;
RsvgDimensionData rsvg_dimension;
svg_handle = gc_rsvg_load("paratrooper/cloud.svgz");
rsvg_handle_get_dimensions (svg_handle, &rsvg_dimension);
if(windspeed>0)
{
x = 0;
x_end = BOARDWIDTH;
}
else
{
x = BOARDWIDTH - rsvg_dimension.width * imageZoom;
x_end = 0;
}
y = 60;
root = \
goo_canvas_group_new (parent,
NULL);
item = goo_canvas_svg_new (root,
svg_handle,
NULL);
goo_canvas_item_scale(item,
imageZoom, imageZoom);
goo_canvas_item_translate(root, x, y);
goo_canvas_item_animate(root,
x_end,
y,
1,
0,
TRUE,
BOARDWIDTH * (80 / ABS(windspeed)),
gc_timing (40, 4),
GOO_CANVAS_ANIMATE_RESTART);
g_object_unref(svg_handle);
/* The plane is always on top */
goo_canvas_item_raise(planeitem, NULL);
}
/*
* This is called on a low frequency and is used to drop new items
*
*/
static gboolean paratrooper_drop_clouds (gpointer data)
{
paratrooper_create_cloud(boardRootItem);
return (FALSE);
}
/*
* This is the state machine of the paratrooper
*/
void next_state()
{
GooCanvasBounds bounds;
switch(paratrooperItem.status)
{
case TUX_INPLANE:
{
RsvgHandle *svg_handle;
gc_sound_play_ogg ("sounds/tuxok.wav", NULL);
svg_handle = gc_rsvg_load("paratrooper/minitux.svgz");
g_object_set (paratrooperItem.paratrooper,
"svg-handle", svg_handle,
NULL);
g_object_unref(svg_handle);
gc_item_focus_init(paratrooperItem.paratrooper, NULL);
goo_canvas_item_get_bounds(planeitem, &bounds);
g_object_set (paratrooperItem.paratrooper, "visibility",
GOO_CANVAS_ITEM_VISIBLE, NULL);
paratrooperItem.status = TUX_DROPPING;
paratrooperItem.drift = planespeed_x;
goo_canvas_item_translate(paratrooperItem.rootitem,
(bounds.x1 > 0 ? bounds.x1 : 0),
bounds.y2);
drop_tux_id = \
g_timeout_add (gc_timing (10, 4),
paratrooper_move_tux, NULL);
gc_item_focus_remove(planeitem, NULL);
}
break;
case TUX_DROPPING:
{
RsvgHandle *svg_handle;
gc_sound_play_ogg ("sounds/eraser2.wav", NULL);
svg_handle = gc_rsvg_load("paratrooper/parachute.svgz");
g_object_set (paratrooperItem.paratrooper,
"svg-handle", svg_handle,
NULL);
gc_item_focus_remove(paratrooperItem.paratrooper, NULL);
g_object_unref(svg_handle);
paratrooperItem.status = TUX_FLYING;
paratrooperItem.speed_override = 0;
if (gcomprisBoard->level >= 2) {
goo_canvas_item_raise (paratrooperItem.instruct, NULL);
g_object_set (paratrooperItem.instruct, "visibility", GOO_CANVAS_ITEM_VISIBLE, NULL);
}
}
break;
case TUX_LANDED:
gc_sound_play_ogg ("sounds/tuxok.wav", NULL);
g_object_set (paratrooperItem.instruct, "visibility", GOO_CANVAS_ITEM_INVISIBLE, NULL);
gamewon = TRUE;
gc_bonus_display(gamewon, GC_BONUS_TUX);
break;
case TUX_CRASHED:
/* Restart */
gc_sound_play_ogg ("sounds/bubble.wav", NULL);
g_object_set (paratrooperItem.instruct, "visibility", GOO_CANVAS_ITEM_INVISIBLE, NULL);
goo_canvas_item_set_transform(paratrooperItem.rootitem, NULL);
paratrooperItem.status = TUX_INPLANE;
paratrooperItem.speed = 3;
g_object_set (paratrooperItem.paratrooper, "visibility", GOO_CANVAS_ITEM_INVISIBLE, NULL);
g_object_set (planeitem, "visibility", GOO_CANVAS_ITEM_VISIBLE, NULL);
gc_item_focus_init(planeitem, NULL);
break;
default:
break;
}
}
static gboolean
item_event (GooCanvasItem *item,
GooCanvasItem *target,
GdkEventButton *event,
gpointer data)
{
if(!gcomprisBoard)
return FALSE;
next_state();
return FALSE;
}