Blame src/world.c

3f9996
3f9996
#include <glib.h></glib.h>
3f9996
#include <gtk gtk.h=""></gtk>
3f9996
#include <gdk gdk.h=""></gdk>
3f9996
3f9996
#include "private.h"
3f9996
3f9996
#include "world.h"
3f9996
3f9996
3f9996
static GtkWidget *window;
3f9996
1015c5
static GtkWidget *dialogMessage;
1015c5
static GtkWidget *dialogMessageLabel;
1015c5
static GtkWidget *dialogAsk;
1015c5
static GtkWidget *dialogAskLabel;
1015c5
static GtkWidget *dialogAskEntry;
1015c5
static GtkWidget *dialogAskMultiline;
1015c5
static GtkWidget *dialogAskMultilineLabel;
1015c5
static GtkWidget *dialogAskMultilineEntry;
1015c5
3f9996
static int started;
3f9996
static int stopped;
1015c5
static int inDialog;
3f9996
static unsigned long long frameCount;
3f9996
static gint64 startTime;
3f9996
static gint64 currentTime;
1015c5
static gint64 dialogTime;
3f9996
3f9996
static Callback initCallback;
3f9996
static Callback drawCallback;
1015c5
static Callback deinitCallback;
3f9996
650f35
static cairo_surface_t *cairoSurface;
981405
static int width = 400;
981405
static int height = 400;
8eb855
static int resizable;
8eb855
static char title[1000];
8eb855
static int titleSize = (int)(sizeof(title)/sizeof(*title));
546e3a
static double frameRate = 24;
3f9996
3f9996
static int cameraEnabled;
3f9996
static double cameraX;
3f9996
static double cameraY;
3f9996
static double cameraZoom = 1;
3f9996
1015c5
static HeliArray keyEvents[6];
1015c5
static int keyEventsCount = (int)(sizeof(keyEvents)/sizeof(*keyEvents));
3f9996
3f9996
static int mouseMovedInFrame;
3f9996
static double _mouseX;
3f9996
static double _mouseY;
3f9996
3f9996
static char* buttonNames[] = {
3f9996
	"left",
3f9996
	"middle",
3f9996
	"right" };
3f9996
static int buttonsCount = (int)(sizeof(buttonNames)/sizeof(*buttonNames));
3f9996
3f9996
1015c5
int keyEventGetCount(KeyEvent mode)
1015c5
	{ return (int)mode >= 0 && (int)mode <= keyEventsCount ? keyEvents[mode].count : 0; }
1015c5
const char *keyEventGet(KeyEvent mode, int i)
1015c5
	{ return (int)mode >= 0 && (int)mode <= keyEventsCount ? heliArrayGetValue(&keyEvents[mode], i) : NULL; }
1015c5
1015c5
static int keyEventCheck(KeyEvent mode, const char *code) {
1015c5
	int count = keyEventGetCount(mode);
1015c5
	for(int i = 0; i < count; ++i)
1015c5
		if (strcmp(keyEventGet(mode, i), code) == 0)
1015c5
			return TRUE;
1015c5
	return FALSE;
1015c5
}
1015c5
1015c5
static void keyEventAdd(KeyEvent mode, const char *code) {
1015c5
	if ((int)mode >= 0 && mode <= (int)keyEventsCount && !keyEventCheck(mode, code))
1015c5
		heliArrayInsert(&keyEvents[mode], -1, heliStringCopy(code), &free);
1015c5
}
1015c5
1015c5
static void keyEventRemove(KeyEvent mode, const char *code) {
1015c5
	if ((int)mode >= 0 && mode <= (int)keyEventsCount)
1015c5
		for(int i = keyEvents[mode].count-1; i >= 0; --i)
1015c5
			if (strcmp(keyEvents[mode].items[i].value, code) == 0)
1015c5
				heliArrayRemove(&keyEvents[mode], i);
1015c5
}
3f9996
1015c5
	
8535a3
int keyDown(const char *code)
1015c5
	{ return keyEventCheck(KEYEVENT_KEY_DOWN, code); }
07b70f
int keyWentDown(const char *code)
1015c5
	{ return keyEventCheck(KEYEVENT_KEY_WENTDOWN, code); }
07b70f
int keyWentUp(const char *code)
1015c5
	{ return keyEventCheck(KEYEVENT_KEY_WENTUP, code); }
3f9996
3f9996
int mouseDidMove()
3f9996
	{ return mouseMovedInFrame; }
8535a3
int mouseDown(const char *code)
1015c5
	{ return keyEventCheck(KEYEVENT_MOUSE_DOWN, code); }
8535a3
int mouseWentDown(const char *code)
1015c5
	{ return keyEventCheck(KEYEVENT_MOUSE_WENTDOWN, code); }
8535a3
int mouseWentUp(const char *code)
1015c5
	{ return keyEventCheck(KEYEVENT_MOUSE_WENTUP, code); }
3f9996
double mouseX()
3f9996
	{ return _mouseX; }
3f9996
double mouseY()
3f9996
	{ return _mouseY; }
3f9996
3f9996
int mousePressedOver(Sprite sprite)
1015c5
	{ return keyEventGetCount(KEYEVENT_MOUSE_DOWN) && mouseIsOver(sprite); }
3f9996
981405
static void resize(int w, int h) {
981405
	width  = w > 200 ? w : 100;
981405
	height = h > 200 ? h : 200;
8eb855
	if (started && !stopped && window) {
981405
		gtk_window_set_default_size(GTK_WINDOW(window), width, height);
981405
		gtk_widget_set_size_request(window, width, height);
981405
	}
981405
}
981405
	
3f9996
int worldGetWidth()
3f9996
	{ return width; }
981405
void worldSetWidth(int w)
981405
	{ resize(w, height); }
3f9996
07b70f
int worldGetHeight()
3f9996
	{ return height; }
981405
void worldSetHeight(int h)
981405
	{ resize(width, h); }
3f9996
8eb855
int worldGetResizable()
8eb855
	{ return resizable; }
8eb855
void worldSetResizable(int r) {
8eb855
	if (resizable == r) return;
8eb855
	resizable = r ? TRUE : FALSE;
8eb855
	if (started && !stopped && window)
8eb855
		gtk_window_set_resizable(GTK_WINDOW(window), resizable);
8eb855
}
8eb855
8eb855
const char* worldGetTitle()
8eb855
	{ return title; }
8eb855
void worldSetTitle(const char *t) {
8eb855
	int changed = FALSE;
8eb855
	for(int i = 0; i < titleSize-1; ++i) {
8eb855
		if (title[i] != t[i]) changed = TRUE;
8eb855
		title[i] = t[i];
8eb855
		if (!t[i]) break;
8eb855
	}
8eb855
	if (changed && started && !stopped && window)
8eb855
		gtk_window_set_title(GTK_WINDOW(window), title);
8eb855
}
8eb855
3f9996
double worldGetFrameRate()
3f9996
	{ return frameRate; }
e034e4
void worldSetFrameRate(double fps) {
e034e4
	if (!(fps > 1)) fps = 1;
e034e4
	if (!(fps < 100)) fps = 100;
e034e4
	frameRate = fps;
3f9996
}
3f9996
981405
double worldGetTimeStep()
981405
	{ return 1/frameRate; }
981405
3f9996
int worldGetFrameCount()
3f9996
	{ return (int)frameCount; }
3f9996
double worldGetSeconds()
1c7488
	{ return started ? (currentTime - startTime)*HELI_PRECISION : 0.0; }
3f9996
3f9996
void worldSetInit(Callback init)
3f9996
	{ initCallback = init; }
3f9996
void worldSetDraw(Callback draw)
3f9996
	{ drawCallback = draw; }
1015c5
void worldSetDeinit(Callback deinit)
1015c5
	{ deinitCallback = deinit; }
3f9996
void worldStop()
3f9996
	{ if (started) stopped = TRUE; }
3f9996
3f9996
3f9996
void cameraOn()
3f9996
	{ cameraEnabled = TRUE; }
3f9996
void cameraOff()
3f9996
	{ cameraEnabled = FALSE; }
3f9996
int cameraIsActive()
3f9996
	{ return cameraEnabled; }
3f9996
3f9996
double cameraMouseX()
3f9996
	{ return _mouseX/cameraZoom + cameraX; }
3f9996
double cameraMouseY()
3f9996
	{ return _mouseY/cameraZoom + cameraY; }
3f9996
3f9996
double cameraGetX()
3f9996
	{ return cameraX; }
3f9996
void cameraSetX(double x)
3f9996
	{ cameraX = x; }
3f9996
3f9996
double cameraGetY()
3f9996
	{ return cameraY; }
3f9996
void cameraSetY(double y)
3f9996
	{ cameraY = y; }
3f9996
3f9996
double cameraGetZoom()
3f9996
	{ return cameraZoom; }
3f9996
void cameraSetZoom(double x)
1c7488
	{ cameraZoom = x > HELI_PRECISION ? x : HELI_PRECISION; }
3f9996
1015c5
1015c5
static void beginDialog() {
1015c5
	inDialog = TRUE;
1015c5
	dialogTime = g_get_monotonic_time() - startTime;
1015c5
}
1015c5
1015c5
static void endDialog() {
1015c5
	for(int i = 0; i < 100; ++i) gtk_main_iteration();
1015c5
	currentTime = g_get_monotonic_time();
1015c5
	startTime = currentTime - dialogTime;
1015c5
	inDialog = FALSE;
1015c5
}
1015c5
1015c5
1015c5
void messageBox(const char *message) {
1015c5
	if (!started || stopped || !window)
1015c5
		return;
1015c5
	beginDialog();
1015c5
	gtk_label_set_label(GTK_LABEL(dialogMessageLabel), message);
1015c5
	gtk_dialog_run(GTK_DIALOG(dialogMessage));
1015c5
	gtk_widget_hide(dialogMessage);
1015c5
	endDialog();
1015c5
}
1015c5
1015c5
void askTextEx(const char *question, char *answer, int maxAnswerSize, int multiline, int password) {
1015c5
	if (!started || stopped || !window)
1015c5
		return;
1015c5
	beginDialog();
1015c5
	if (multiline) {
1015c5
		gtk_label_set_label(GTK_LABEL(dialogAskMultilineLabel), question);
1015c5
		GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(dialogAskMultilineEntry));
1015c5
		gtk_text_buffer_set_text(buffer, answer, -1);
1015c5
		gtk_dialog_run(GTK_DIALOG(dialogAskMultiline));
1015c5
		gtk_widget_hide(dialogAskMultiline);
1015c5
1015c5
		GtkTextIter start, end;
1015c5
		gtk_text_buffer_get_bounds(buffer, &start, &end);
1015c5
		const char *a = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1015c5
		for(int i = 0; i < maxAnswerSize; ++i)
1015c5
			{ answer[i] = a[i]; if (!a[i]) break; }
1015c5
	} else {
1015c5
		gtk_label_set_label(GTK_LABEL(dialogAskLabel), question);
1015c5
		gtk_entry_set_text(GTK_ENTRY(dialogAskEntry), answer);
1015c5
		gtk_entry_set_input_purpose(GTK_ENTRY(dialogAskEntry), password ? GTK_INPUT_PURPOSE_PASSWORD : GTK_INPUT_PURPOSE_FREE_FORM);
1015c5
		gtk_entry_set_visibility(GTK_ENTRY(dialogAskEntry), !password);
1015c5
		gtk_dialog_run(GTK_DIALOG(dialogAsk));
1015c5
		gtk_widget_hide(dialogAsk);
1015c5
		const char *a = gtk_entry_get_text(GTK_ENTRY(dialogAskEntry));
1015c5
		for(int i = 0; i < maxAnswerSize; ++i)
1015c5
			{ answer[i] = a[i]; if (!a[i]) break; }
1015c5
	}
1015c5
	endDialog();
1015c5
}
1015c5
1015c5
void askText(const char *question, char *answer, int maxAnswerSize)
1015c5
	{ askTextEx(question, answer, maxAnswerSize, FALSE, FALSE); }
1015c5
1015c5
	
650f35
static gboolean idle(gpointer data G_GNUC_UNUSED) {
3f9996
	if (stopped) {
650f35
		if (window) {
650f35
			gtk_window_close(GTK_WINDOW(window));
650f35
			window = NULL;
650f35
		}
3f9996
		return FALSE;
3f9996
	}
650f35
	gtk_widget_queue_draw(window);
3f9996
	return TRUE;
3f9996
}
3f9996
3f9996
static gboolean draw(GtkWidget *widget G_GNUC_UNUSED, cairo_t *cr, gpointer data G_GNUC_UNUSED) {
650f35
	int doFrame = FALSE;
d1f083
	
650f35
	if ( cairoSurface
650f35
	  && cairo_image_surface_get_width(cairoSurface) != width
650f35
	  && cairo_image_surface_get_height(cairoSurface) != height )
650f35
		{ cairo_surface_destroy(cairoSurface); cairoSurface = NULL; }
650f35
	if (!cairoSurface) {
650f35
		cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
650f35
		doFrame = TRUE;
650f35
	}
650f35
650f35
	if (frameCount == 0) {
650f35
		currentTime = startTime = g_get_monotonic_time();
650f35
		doFrame = TRUE;
650f35
	}
650f35
981405
	double dt = 1/frameRate;
650f35
	gint64 timeStep = (gint64)round(dt*1e6);
650f35
	gint64 newTime = g_get_monotonic_time();
650f35
	if (currentTime < newTime)
650f35
		doFrame = TRUE;
650f35
	
1015c5
	if (inDialog)
1015c5
		doFrame = FALSE;
1015c5
	
650f35
	if (doFrame) {
650f35
		if (frameCount == 0) heliDoTests();
650f35
650f35
		heliCairo = cairo_create(cairoSurface);
07b70f
		heliDrawingPrepareFrame();
3f9996
		if (drawCallback) drawCallback();
650f35
		cairo_surface_flush(cairoSurface);
650f35
		cairo_destroy(heliCairo);
650f35
		heliCairo = NULL;
650f35
		
981405
		currentTime += timeStep;
981405
		++frameCount;
981405
		heliSpriteUpdate(dt);
09c823
		heliSoundUpdate();
650f35
		
1015c5
		heliArrayClear(&keyEvents[KEYEVENT_KEY_WENTDOWN]);
1015c5
		heliArrayClear(&keyEvents[KEYEVENT_KEY_WENTUP]);
1015c5
		heliArrayClear(&keyEvents[KEYEVENT_MOUSE_WENTDOWN]);
1015c5
		heliArrayClear(&keyEvents[KEYEVENT_MOUSE_WENTUP]);
650f35
		
650f35
		if (stopped) g_idle_add_full(G_PRIORITY_DEFAULT, idle, NULL, NULL);
3f9996
	}
650f35
	
650f35
	gint64 delta = newTime - currentTime - 2000000;
650f35
	if (delta > 0) {
650f35
		startTime += delta;
650f35
		currentTime += delta;
650f35
	}
650f35
	
650f35
	heliDrawingClearFrame(cr);
650f35
	cairo_save(cr);
650f35
	cairo_set_source_surface(cr, cairoSurface, 0, 0);
650f35
	cairo_paint(cr);
650f35
	cairo_restore(cr);
650f35
	
e034e4
	if (!stopped) {
e034e4
		gtk_widget_queue_draw(window);
e034e4
		delta = currentTime - g_get_monotonic_time();
e034e4
		if (delta > 100) g_usleep(delta - 100);
e034e4
	}
650f35
	
3f9996
	return TRUE;
3f9996
}
3f9996
3f9996
static gboolean handleEvent(GtkWidget *widget G_GNUC_UNUSED, GdkEvent *event, gpointer data G_GNUC_UNUSED) {
3f9996
	gchar *keyname;
3f9996
	int button;
3f9996
	
3f9996
	switch(event->type) {
3f9996
	case GDK_KEY_PRESS:
3f9996
		keyname = heliStringCopy( gdk_keyval_name(event->key.keyval) );
3f9996
		heliLowercase(keyname);
1015c5
		keyEventAdd(KEYEVENT_KEY_DOWN, keyname);
1015c5
		keyEventAdd(KEYEVENT_KEY_WENTDOWN, keyname);
3f9996
		free(keyname);
3f9996
		return TRUE;
3f9996
	case GDK_KEY_RELEASE:
3f9996
		keyname = heliStringCopy( gdk_keyval_name(event->key.keyval) );
3f9996
		heliLowercase(keyname);
1015c5
		keyEventRemove(KEYEVENT_KEY_DOWN, keyname);
1015c5
		keyEventAdd(KEYEVENT_KEY_WENTUP, keyname);
3f9996
		free(keyname);
3f9996
		return TRUE;
3f9996
	case GDK_BUTTON_PRESS:
3f9996
		button = event->button.button;
09c823
		if (button >= 1 && button <= buttonsCount) {
1015c5
			keyEventAdd(KEYEVENT_MOUSE_DOWN, buttonNames[button - 1]);
1015c5
			keyEventAdd(KEYEVENT_MOUSE_WENTDOWN, buttonNames[button - 1]);
3f9996
		}
3f9996
		_mouseX = event->button.x;
3f9996
		_mouseY = event->button.y;
3f9996
		return TRUE;
3f9996
	case GDK_BUTTON_RELEASE:
3f9996
		button = event->button.button;
09c823
		if (button >= 1 && button <= buttonsCount) {
1015c5
			keyEventRemove(KEYEVENT_MOUSE_DOWN, buttonNames[button - 1]);
1015c5
			keyEventAdd(KEYEVENT_MOUSE_WENTUP, buttonNames[button - 1]);
3f9996
		}
3f9996
		_mouseX = event->button.x;
3f9996
		_mouseY = event->button.y;
3f9996
		return TRUE;
3f9996
	case GDK_MOTION_NOTIFY:
3f9996
		_mouseX = event->motion.x;
3f9996
		_mouseY = event->motion.y;
3f9996
		return TRUE;
1015c5
	case GDK_FOCUS_CHANGE:
1015c5
		if (!event->focus_change.in) {
1015c5
			int count = keyEventGetCount(KEYEVENT_KEY_DOWN);
1015c5
			for(int i = 0; i < count; ++i)
1015c5
				keyEventAdd(KEYEVENT_KEY_WENTUP, keyEventGet(KEYEVENT_KEY_DOWN, i));
1015c5
			heliArrayClear(&keyEvents[KEYEVENT_KEY_DOWN]);
1015c5
		}
1015c5
		break;
650f35
	case GDK_DELETE:
650f35
		stopped = TRUE;
650f35
		window = NULL;
650f35
		break;
3f9996
	default:
3f9996
		break;
3f9996
	}
3f9996
	return FALSE;
3f9996
}
3f9996
3f9996
static void activate(GtkApplication* app, gpointer data G_GNUC_UNUSED) {
3f9996
	window = gtk_application_window_new(app);
3f9996
	g_signal_connect(window, "draw", G_CALLBACK(draw), NULL);
3f9996
	g_signal_connect(window, "event", G_CALLBACK(handleEvent), NULL);
1015c5
	gtk_window_set_resizable(GTK_WINDOW(window), resizable);
981405
	gtk_window_set_default_size(GTK_WINDOW(window), width, height);
8eb855
	gtk_window_set_title(GTK_WINDOW(window), title);
3f9996
	gtk_widget_add_events( window,
3f9996
						   GDK_POINTER_MOTION_MASK
3f9996
						 | GDK_BUTTON_PRESS_MASK
3f9996
						 | GDK_BUTTON_RELEASE_MASK
3f9996
						 | GDK_KEY_PRESS_MASK
1015c5
						 | GDK_KEY_RELEASE_MASK
1015c5
						 | GDK_FOCUS_CHANGE_MASK );
3f9996
	gtk_widget_set_size_request(window, width, height);
3f9996
	gtk_widget_show(window);
981405
1015c5
	dialogMessage = gtk_dialog_new_with_buttons(
1015c5
		"", GTK_WINDOW(window),
1015c5
		GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
1015c5
		"_OK", GTK_RESPONSE_OK, NULL);
1015c5
	gtk_dialog_set_default_response(GTK_DIALOG(dialogMessage), GTK_RESPONSE_OK);
1015c5
	dialogMessageLabel = gtk_label_new("");
1015c5
	gtk_widget_show(dialogMessageLabel);
1015c5
	gtk_container_add(
1015c5
		GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialogMessage))),
1015c5
		dialogMessageLabel );
1015c5
	
1015c5
	dialogAsk = gtk_dialog_new_with_buttons(
1015c5
		"", GTK_WINDOW(window),
1015c5
		GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
1015c5
		"_OK", GTK_RESPONSE_OK, NULL);
1015c5
	gtk_dialog_set_default_response(GTK_DIALOG(dialogAsk), GTK_RESPONSE_OK);
1015c5
	GtkWidget *askBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
1015c5
	dialogAskLabel = gtk_label_new("");
1015c5
	dialogAskEntry = gtk_entry_new();
1015c5
	gtk_entry_set_activates_default(GTK_ENTRY(dialogAskEntry), TRUE);
1015c5
	gtk_container_add(GTK_CONTAINER(askBox), dialogAskLabel);
1015c5
	gtk_container_add(GTK_CONTAINER(askBox), dialogAskEntry);
1015c5
	gtk_widget_show_all(askBox);
1015c5
	gtk_container_add(
1015c5
		GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialogAsk))),
1015c5
		askBox );
1015c5
1015c5
	dialogAskMultiline = gtk_dialog_new_with_buttons(
1015c5
		"", GTK_WINDOW(window),
1015c5
		GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
1015c5
		"_OK", GTK_RESPONSE_OK, NULL);
1015c5
	gtk_dialog_set_default_response(GTK_DIALOG(dialogAskMultiline), GTK_RESPONSE_OK);
1015c5
	GtkWidget *askMultilineBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
1015c5
	dialogAskMultilineLabel = gtk_label_new("");
1015c5
	dialogAskMultilineEntry = gtk_text_view_new();
1015c5
	GtkWidget *askMultilineScrollBox = gtk_scrolled_window_new(NULL, NULL);
1015c5
	gtk_container_add(GTK_CONTAINER(askMultilineScrollBox), dialogAskMultilineEntry);
1015c5
	gtk_container_add(GTK_CONTAINER(askMultilineBox), dialogAskMultilineLabel);
5b96a1
	gtk_container_add_with_properties(
5b96a1
		GTK_CONTAINER(askMultilineBox), askMultilineScrollBox,
5b96a1
		"expand", TRUE, "fill", TRUE, NULL );
1015c5
	gtk_widget_show_all(askMultilineBox);
5b96a1
	gtk_container_add_with_properties(
1015c5
		GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialogAskMultiline))),
5b96a1
		askMultilineBox,
5b96a1
		"expand", TRUE, "fill", TRUE, NULL );
1015c5
650f35
	currentTime = startTime = 0;
8eb855
	heliInitialized = TRUE;
3f9996
	if (initCallback) initCallback();
650f35
	g_idle_add(idle, NULL);
3f9996
}
3f9996
8eb855
void worldRun() {
3f9996
	if (started) return;
3f9996
	started = TRUE;
3f9996
	stopped = FALSE;
3f9996
3f9996
	frameCount = 0;
3f9996
	srand(time(NULL));
3f9996
3f9996
	GtkApplication *application = gtk_application_new(NULL, 0);
3f9996
	g_signal_connect(application, "activate", G_CALLBACK(activate), NULL);
3f9996
	g_application_run(G_APPLICATION(application), 0, NULL);
3f9996
	g_object_unref(application);
1015c5
	window = NULL;
1015c5
	dialogMessage = NULL;
1015c5
	dialogMessageLabel = NULL;
1015c5
	dialogAsk = NULL;
1015c5
	dialogAskEntry = NULL;
1015c5
	dialogAskLabel = NULL;
1015c5
	dialogAskMultiline = NULL;
1015c5
	dialogAskMultilineEntry = NULL;
1015c5
	dialogAskMultilineLabel = NULL;
1015c5
	
1015c5
	if (deinitCallback) deinitCallback();
3f9996
	
8eb855
	heliArrayClear(&heliObjectsSet);
07b70f
	heliSpriteFinish();
07b70f
	heliDrawingFinish();
07b70f
	heliAnimationFinish();
09c823
	heliSoundFinish();
8eb855
	heliArrayDestroy(&heliObjectsSet);
8eb855
	
8eb855
	heliInitialized = FALSE;
650f35
	
650f35
	if (cairoSurface) cairo_surface_destroy(cairoSurface);
650f35
	cairoSurface = NULL;
650f35
	
1015c5
	for(int i = 0; i < keyEventsCount; ++i)
1015c5
		heliArrayDestroy(&keyEvents[i]);
3f9996
	
3f9996
	started = FALSE;
3f9996
}