/*
	Engine Starter example
	Written by Sandy Barbour - 28/09/2005
	
	This examples shows how to start the engines.
	It also shows some neat widgetry
*/

#include "XPLMPlugin.h"
#include "XPLMUtilities.h"
#include "XPLMProcessing.h"
#include "XPLMDataAccess.h"
#include "XPLMMenus.h"
#include "XPWidgets.h"
#include "XPStandardWidgets.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#if IBM
#include <windows.h>
#endif

// Limit to 8 engines, any more and you are on your own :-)
#define MAX_NUMBER_ENGINES 8

// Enums for each engine
typedef struct _MESSAGE_STRUCT
{
	int MessageEnum;
} MESSAGE_STRUCT;

MESSAGE_STRUCT JoystickCommands[MAX_NUMBER_ENGINES] =
{
	{xplm_joy_start_0},
	{xplm_joy_start_1},
	{xplm_joy_start_2},
	{xplm_joy_start_3},
	{xplm_joy_start_4},
	{xplm_joy_start_5},
	{xplm_joy_start_6},
	{xplm_joy_start_7}
};

// These will hold the XPLMDataRef values	
static XPLMDataRef gIgnitersDataRef = NULL;
static XPLMDataRef gStarterDurationDataRef = NULL;
static XPLMDataRef gNumberOfEnginesDataRef = NULL;

// Descriptions for the Input Text widget
static char DataRefDesc[MAX_NUMBER_ENGINES][20] = {"1", "2", "3", "4", "5", "6", "7", "8"};

static float EngineStarterLoopCB(float elapsedMe, float elapsedSim, int counter, void * refcon);;

static int MenuItem1;
// Widgets
static XPWidgetID	EngineStarterWidget = NULL, EngineStarterWindow = NULL;
static XPWidgetID	Description[MAX_NUMBER_ENGINES] = {NULL};
static XPWidgetID	IgnitersState[MAX_NUMBER_ENGINES] = {NULL};
static XPWidgetID	StartButton[MAX_NUMBER_ENGINES] = {NULL};
static XPWidgetID	XPlaneStarterDurationEdit[MAX_NUMBER_ENGINES] = {NULL};
static XPWidgetID	StarterDurationText = NULL;
static XPWidgetID	StarterDurationEdit = NULL;
static XPWidgetID	ReleaseDelayText = NULL;
static XPWidgetID	ReleaseDelayEdit = NULL;

// Globals for engine starting.
static int Igniters[MAX_NUMBER_ENGINES] = {0};
static int ElapsedTime[MAX_NUMBER_ENGINES] = {0};
static int EngineStarting[MAX_NUMBER_ENGINES] = {0};

// Intitial window size
static int x = 300, y = 500, w = 390, h = 290;

// Callback prototypes
static void EngineStarterMenuHandler(void *, void *);

static void CreateEngineStarterWidget(int x1, int y1, int w, int h);

static int EngineStarterHandler(
						XPWidgetMessage			inMessage,
						XPWidgetID				inWidget,
						intptr_t				inParam1,
						intptr_t				inParam2);

static void ResizeWidgets(int NumberOfEngines);

// Standard Plugin callbacks
PLUGIN_API int XPluginStart(
						char *		outName,
						char *		outSig,
						char *		outDesc)
{
	XPLMMenuID	Id;
	int			Item;

	strcpy(outName, "EngineStarter");
	strcpy(outSig, "xpsdk.examples.EngineStarter");
	strcpy(outDesc, "A plug-in that handles data Input/Output.");

	// Create our menu
	Item = XPLMAppendMenuItem(XPLMFindPluginsMenu(), "Engine Starter", NULL, 1);
	Id = XPLMCreateMenu("Engine Starter", XPLMFindPluginsMenu(), Item, EngineStarterMenuHandler, NULL);
	XPLMAppendMenuItem(Id, "Start Engines", (void *)"EngineStarter", 1);

	// Flag to tell us if the widget is being displayed.
	MenuItem1 = 0;


	// Get our dataref handles here
	gIgnitersDataRef = XPLMFindDataRef("sim/cockpit/engine/igniters_on");
	gStarterDurationDataRef = XPLMFindDataRef("sim/cockpit/engine/starter_duration");
	gNumberOfEnginesDataRef = XPLMFindDataRef("sim/aircraft/engine/acf_num_engines");


	// Register our FL callback with initial callback time period of 1 second.
	XPLMRegisterFlightLoopCallback(EngineStarterLoopCB, 1.0, NULL);

	return 1;
}

PLUGIN_API void	XPluginStop(void)
{
	/* Unregister the callback */
	XPLMUnregisterFlightLoopCallback(EngineStarterLoopCB, NULL);

	if (MenuItem1 == 1)
	{
		XPDestroyWidget(EngineStarterWidget, 1);
		MenuItem1 = 0;
	}
}

PLUGIN_API int XPluginEnable(void)
{
	return 1;
}

PLUGIN_API void XPluginDisable(void)
{
}

PLUGIN_API void XPluginReceiveMessage(XPLMPluginID inFrom, int inMsg, void * inParam)
{
    if (inFrom == XPLM_PLUGIN_XPLANE)
    {
		switch(inMsg)
		{
			case XPLM_MSG_PLANE_LOADED:
				// Need to resize widgets if number of engines change.
				// This messge is received on XPlane start so that is covered.
				int NumberOfEngines = XPLMGetDatai(gNumberOfEnginesDataRef);
				ResizeWidgets(NumberOfEngines);
				break;
		}
	}
}


float EngineStarterLoopCB(float elapsedMe, float elapsedSim, int counter, void * refcon)
{
	int Item;
	int count;
	char Buffer[255];
	float StarterDurationArray[MAX_NUMBER_ENGINES];

	if (MenuItem1 == 0) // Don't process if widget not visible
		return 1;

	// Only deal with the actual engines that we have
	int NumberOfEngines = XPLMGetDatai(gNumberOfEnginesDataRef);

	// Get our Igniters states for each engine
	count = XPLMGetDatavi(gIgnitersDataRef, Igniters, 0, MAX_NUMBER_ENGINES);

	// Get Xplane starter duration for each engine
	count = XPLMGetDatavf(gStarterDurationDataRef, StarterDurationArray, 0, MAX_NUMBER_ENGINES);

	// Uncheck all Igniters check boxes, need this if user load an aircraft
	// with less engines than the current aircraft
	for (Item=0; Item<MAX_NUMBER_ENGINES; Item++)
		XPSetWidgetProperty(IgnitersState[Item], xpProperty_ButtonState, 0);

	// Process each actual engine
	for (Item=0; Item<NumberOfEngines; Item++)
	{
		if (Igniters[Item] == 1)
			XPSetWidgetProperty(IgnitersState[Item], xpProperty_ButtonState, 1);
		else
			XPSetWidgetProperty(IgnitersState[Item], xpProperty_ButtonState, 0);

		sprintf(Buffer, "%f", StarterDurationArray[Item]);
		XPSetWidgetDescriptor(XPlaneStarterDurationEdit[Item], Buffer);
	}

	// I have used extra braces for clarity.
	// Store elapsed start time for each engine
	for (Item=0; Item<NumberOfEngines; Item++)
	{
		if (EngineStarting[Item] == 1)
			ElapsedTime[Item]++;
	}

	// I have used extra braces for clarity.
	// If the time for that engine has elapsed then send release
	XPGetWidgetDescriptor(ReleaseDelayEdit, Buffer, sizeof(Buffer));
	int ReleaseDelay = atoi(Buffer);

	for (Item=0; Item<NumberOfEngines; Item++)
	{
		if (EngineStarting[Item] == 1)
		{
			if (ElapsedTime[Item] > ReleaseDelay)
			{
				XPLMCommandButtonRelease(JoystickCommands[Item].MessageEnum);
				XPSetWidgetProperty(StartButton[Item], xpProperty_Enabled, 1);
			}
		}
	}

	// This means call us ever 1 second.
	return (float) 1.0;
}

void EngineStarterMenuHandler(void * mRef, void * iRef)
{

	// If menu selected create our widget dialog
	if (!strcmp((char *) iRef, "EngineStarter"))
	{
		if (MenuItem1 == 0)
		{
			// Take care of resize based on actual number of engines
			int NumberOfEngines = XPLMGetDatai(gNumberOfEnginesDataRef);
			CreateEngineStarterWidget(x, y, w, h);
			ResizeWidgets(NumberOfEngines);
			MenuItem1 = 1;
		}
		else
			if(!XPIsWidgetVisible(EngineStarterWidget))
				XPShowWidget(EngineStarterWidget);
	}
}						

// This will create our widget dialog.
// I have made all child widgets relative to the input paramter.
// This makes it easy to position the dialog
// Any position or size changes need to be done in the "ResizeWidgets" function as well.
// This could be made more manageable if required
void CreateEngineStarterWidget(int x, int y, int w, int h)
{
	int Item;

	int x2 = x + w;
	int y2 = y - h;
	
	// Create the Main Widget window
	EngineStarterWidget = XPCreateWidget(x, y, x2, y2,
					1,	// Visible
					"Engine Starter Example by Sandy Barbour",	// desc
					1,		// root
					NULL,	// no container
					xpWidgetClass_MainWindow);

	// Add Close Box decorations to the Main Widget
	XPSetWidgetProperty(EngineStarterWidget, xpProperty_MainWindowHasCloseBoxes, 1);

	// Create the Sub Widget window
	EngineStarterWindow = XPCreateWidget(x+10, y-30, x2-10, y2+10,
					1,	// Visible
					"",	// desc
					0,		// root
					EngineStarterWidget,
					xpWidgetClass_SubWindow);

	// Set the style to sub window
	XPSetWidgetProperty(EngineStarterWindow, xpProperty_SubWindowType, xpSubWindowStyle_SubWindow);

	// For each engine
	for (Item=0; Item<MAX_NUMBER_ENGINES; Item++)
	{
		// Create a text widget
		Description[Item] = XPCreateWidget(x+20, y-(40 + (Item*30)), x+82, y-(62 + (Item*30)),
							1,	// Visible
							DataRefDesc[Item],// desc
							0,		// root
							EngineStarterWidget,
							xpWidgetClass_Caption);

		// Create a check box for the Igniters
		IgnitersState[Item] = XPCreateWidget(x+45, y-(40 + (Item*30)), x+67, y-(62 + (Item*30)),
							1, "", 0, EngineStarterWidget,
							xpWidgetClass_Button);

		// Set it to be check box
		XPSetWidgetProperty(IgnitersState[Item], xpProperty_ButtonType, xpRadioButton);
		XPSetWidgetProperty(IgnitersState[Item], xpProperty_ButtonBehavior, xpButtonBehaviorCheckBox);
		XPSetWidgetProperty(IgnitersState[Item], xpProperty_ButtonState, 1);

		// Create a button widget for the Start Engine
		StartButton[Item] = XPCreateWidget(x+80, y-(40 + (Item*30)), x+130, y-(62 + (Item*30)),
							1, " Start", 0, EngineStarterWidget,
							xpWidgetClass_Button);

		// Set it to be normal push button
		XPSetWidgetProperty(StartButton[Item], xpProperty_ButtonType, xpPushButton);

		// Create an edit widget for the xplane starter duration
		XPlaneStarterDurationEdit[Item] = XPCreateWidget(x+140, y-(40 + (Item*30)), x+200, y-(62 + (Item*30)),
							1, "", 0, EngineStarterWidget,
							xpWidgetClass_TextField);

	}

	// Create a text widget for the starter duration
	StarterDurationText = XPCreateWidget(x+210, y-40, x+310, y-62,
							1,	// Visible
							"Starter Duration",// desc
							0,		// root
							EngineStarterWidget,
							xpWidgetClass_Caption);

	// Create an edit widget for the starter duration
	StarterDurationEdit = XPCreateWidget(x+315, y-40, x+355, y-62,
						1, "10", 0, EngineStarterWidget,
						xpWidgetClass_TextField);

	// Create a text widget for the release delay
	ReleaseDelayText = XPCreateWidget(x+210, y-70, x+310, y-92,
							1,	// Visible
							"Release Delay",// desc
							0,		// root
							EngineStarterWidget,
							xpWidgetClass_Caption);

	// Set it to be text entry
	XPSetWidgetProperty(StarterDurationEdit, xpProperty_TextFieldType, xpTextEntryField);

	// Create an edit widget for the release delay
	ReleaseDelayEdit = XPCreateWidget(x+315, y-70, x+355, y-92,
						1, "10", 0, EngineStarterWidget,
						xpWidgetClass_TextField);

	// Set it to be text entry
	XPSetWidgetProperty(ReleaseDelayEdit, xpProperty_TextFieldType, xpTextEntryField);

	// Register our widget handler
	XPAddWidgetCallback(EngineStarterWidget, EngineStarterHandler);
}

// Included this as it shows how to maintain a dynamic widget
// Any position or size changes need to be done in the "CreateEngineStarterWidget" function as well.
// This could be made more manageable if required
void ResizeWidgets(int NumberOfEngines)
{
	int Item;

	// Adjust main Widget height
	switch (NumberOfEngines)
	{
		case 1:
		case 2:
			h = 110;
			break;
		case 4:
			h = 170;
			break;
		case 8:
			h = 290;
			break;
	}
	int x2 = x + w;
	int y2 = y - h;

	// Only set or enable widgets that are actually needed
	for (Item=0; Item<MAX_NUMBER_ENGINES; Item++)
	{
		XPSetWidgetProperty(IgnitersState[Item], xpProperty_ButtonState, 0);
		XPSetWidgetDescriptor(Description[Item], "");
		XPHideWidget(Description[Item]);
		XPHideWidget(StartButton[Item]);
		XPHideWidget(IgnitersState[Item]);
		XPHideWidget(XPlaneStarterDurationEdit[Item]);
	}

	// Resize the main widget and window
	XPSetWidgetGeometry(EngineStarterWidget, x, y, x2, y2);
	XPSetWidgetGeometry(EngineStarterWindow, x+10, y-30, x2-10, y2+10);

	
	// For each engine widget do a resize
	// Then make sure each widget is shown.
	for (Item=0; Item<NumberOfEngines; Item++)
	{
		XPSetWidgetGeometry(Description[Item], x+20, y-(40 + (Item*30)), x+82, y-(62 + (Item*30)));
		XPSetWidgetGeometry(IgnitersState[Item], x+45, y-(40 + (Item*30)), x+67, y-(62 + (Item*30)));
		XPSetWidgetGeometry(StartButton[Item], x+80, y-(40 + (Item*30)), x+130, y-(62 + (Item*30)));
		XPSetWidgetGeometry(XPlaneStarterDurationEdit[Item], x+140, y-(40 + (Item*30)), x+200, y-(62 + (Item*30)));
		XPSetWidgetDescriptor(Description[Item], DataRefDesc[Item]);
		XPShowWidget(Description[Item]);
		XPShowWidget(StartButton[Item]);
		XPShowWidget(IgnitersState[Item]);
		XPShowWidget(XPlaneStarterDurationEdit[Item]);
	}

	// No take care of the rest
	XPSetWidgetGeometry(StarterDurationText, x+210, y-40, x+310, y-62);
	XPSetWidgetGeometry(StarterDurationEdit, x+315, y-40, x+355, y-62);
	XPSetWidgetGeometry(ReleaseDelayText, x+210, y-70, x+310, y-92);
	XPSetWidgetGeometry(ReleaseDelayEdit, x+315, y-70, x+355, y-92);
}

// This is the handler for our widget
// It can be used to process button presses etc.
// In this example we are only interested when the close box is pressed
int	EngineStarterHandler(
						XPWidgetMessage			inMessage,
						XPWidgetID				inWidget,
						intptr_t				inParam1,
						intptr_t				inParam2)
{
	char Buffer[255];
	int Item, State;
	float StarterDuration, StarterDurationArray[MAX_NUMBER_ENGINES];

	if (inMessage == xpMessage_CloseButtonPushed)
	{
		if (MenuItem1 == 1)
		{
			XPHideWidget(EngineStarterWidget);
		}
		return 1;
	}

	// Handle any button pushes
	if (inMessage == xpMsg_PushButtonPressed)
	{
		// This is redundant in V8.
		// It will work in V7 but my other method can be used
		// which reduces the code size.
		XPGetWidgetDescriptor(StarterDurationEdit, Buffer, sizeof(Buffer));
		StarterDuration = (float)atof(Buffer);

		// Set all starter durations in XPlane
		for (Item=0; Item<MAX_NUMBER_ENGINES; Item++)
			StarterDurationArray[Item] = StarterDuration;

		XPLMSetDatavf(gStarterDurationDataRef, StarterDurationArray, 0, MAX_NUMBER_ENGINES);

		for (Item=0; Item<MAX_NUMBER_ENGINES; Item++)
		{
			// Handle if the Start Button is pushed
			if (inParam1 == (intptr_t)StartButton[Item])
			{
				// Do the actual joystick button press
				XPLMCommandButtonPress(JoystickCommands[Item].MessageEnum);
				// Set flag for the engine selected
				EngineStarting[Item] = 1;
				// Reset the timer for that engine
				ElapsedTime[Item] = 0;
				// Disable the start button for that engine
				XPSetWidgetProperty(StartButton[Item], xpProperty_Enabled, 0);
			}
		}
		return 1;
	}

	// Handle any check box selections
	if (inMessage == xpMsg_ButtonStateChanged)
	{
		// Get the Igniters check box for each engine.
		for (Item=0; Item<MAX_NUMBER_ENGINES; Item++)
		{
			State = (int) XPGetWidgetProperty(IgnitersState[Item], xpProperty_ButtonState, 0);
			Igniters[Item] = State;
		}
		// This will switch the igniter on/off depeding on State.
		XPLMSetDatavi(gIgnitersDataRef, Igniters, 0, MAX_NUMBER_ENGINES);
		return 1;
	}

	return 0;
}

One comment on “EngineStarter

  1. Hello,
    I wonder, if and how an engine can be modelled more accurate with additional data and procedures (e.g. the very known REPs). My goal would be an exact simulation of an DB-601 engine (used in a ME-109) inside X-Plane. I may have access to the respective data of the mentioned engine (e.g. Measurement protocols etc.).
    Thanks for some information about that.

Leave a Reply

Your email address will not be published. Required fields are marked *

Please do not report bugs in the blog comments.
Only bugs reported via the X-Plane Bug Reporter are tracked.