#define ORBITER_MODULE
#define GRAVCONST		6.6742e-11	//Gravitational constant
#define USERSIZE		8			//hashsize for 256 users
#define BUFFERSIZE		4096		//aprox. 80x40 characters incl. linebreaks
#define LINESIZE		4096
#define TABPAGES		5
#define LOGLEVELS		10
#define THREADS			6			//Status, TCP-RCVR, UDP-XMTR, UDP-RCVR, SYNC, GC
#define STATUSREFRESH	500			//500 ms status refresh time
#define SYNCREFRESH		59000		//59 s sync refresh time (excluding SYNCICON)
#define SYNCICON		1000		//1 s sync control icon sleep time
#define CLKAV			30			//30 entries for history average
#define RETRIES			3			//3 retries on UDP receiving errors
#define MATCHES			13			//13 possible matches
#define IRCMATCH		"\nping :"	//0..ping match for internal auto ping reply feature
#define ALTIRCMATCH		"\nPING :"	//0..alternative ping match for internal auto ping reply feature
#define REQUESTGLOBAL	"\nREQST "	//1..request for information (followed by global ID)
#define GLOBALINFO		"\nGINFO "	//2..information for global ID
#define RADIOTALK		"\nRADIO:"	//3..radio transmission
#define CHECKREPLY		"\nCHECK "	//4..checking server environment
#define TARGETIP		"\nTarget"	//5..IP Target reply
#define REMOTEIP		"\nRemote"	//6..IP Remote reply
#define ACTUALIP		"\nActual"  //7..IP Actual reply
#define LISTREPLY		"\nLIST  "	//8..listing user entries
#define JOINREPLY		"\nJOIN  "	//9..user joined server
#define ASSIGNREPLY		"\nASSIGN"	//10..assigned session to user
#define LEAVEREPLY		"\nLEAVE "	//11..user left server
#define ACCEPTREPLY		"\nACCPT "	//12..accepted offered object
#define IRCREPLY		"\npong :"	//auto ping reply
#define LOOPBACK_IP		"127.0.0.1" //ANYIP
#define DEFAULT_SYSTEM	"Sol"		//Default solar system
#define SCN_FORMAT		"Scenarios\\%c%s%c.scn"	//Default scenario path
#define CRASH_REPORT	"OMPClient.crash"		//crash report path
#define LIGHTSPEED		299792458.0		//c=299.792,458km/s within vacuum
#define MAXIDLE			50			//50 idle steps at hash search
#define NEIGHBOURIDLE	5000		//neighbour information may be idle for 5s before deleted
#define	GLOBALIDLE		5000		//global vessel may be idle for 5s before deleted
#define	DELETEPINGS		10			//amount of vessel delete pings to be sent
#define _WIN32_WINNT	0x0400
#define IDC_STARTORBI	0x03E8		//Orbiter's Start button = 1000dec
#define IDC_SELECTTREE	0x0442		//Orbiter's Scenario selector = 1088dec
#define IDC_PAUSED		0x0445		//Orbiter's Pausing checkbox = 1090dec
#define TABPAGESKIP		15			//How many windows next from Start buttom to tab page
#define ERROR_APPLICATION	0x20000000 //Bit 29 for application errors
#define GENERIC_CHAR	'$'			//Character used to ensure first entry in Orbiter's Scenario selector
#define VERSIONLABELPREFIX	"OMP Client "
#define MINSERVERVERSION	"0.8"
#define MAXANIMPERLINE	16
#define OMPSTATELABEL	"--OMP-STATE--"
#define QUICKHEADER		"BEGIN_DESC\n  This is the generic multiplayer session scenario. Your scenario will be generated according to current multiplayer server settings.\n  All scenario selecting and starting controls are disabled by the OMPClient plugin. To reactivate the controls, please select the OMPClient plugin in the \"Modules\" tab page and click \"Deactivate selected\".\nEND_DESC\nBEGIN_ENVIRONMENT\n  System %s\n  Date MJD %s\nEND_ENVIRONMENT\nBEGIN_SHIPS\nAnchor:OMPDefault\nSTATUS Landed Earth\nPOS 0 0\nHEADING 0\nEND\nEND_SHIPS\n"
#define S_STOPPED		0
#define S_STARTED		1
#define S_SYSTEM		2
#define S_VESSELS		3
#define S_LOCATIONS		4
#define S_IP			5
#define S_VERSION		6
#define S_LISTJOINS		11
#define S_ASSIGN		12
#define S_JOIN			13
#define S_CHOOSE		14
#define S_CHOOSEDIALOG	15
#define S_LOCDIALOG		16
#define S_TIME			17
#define S_READLINES		18
#define S_CLICKSTART	19
#define S_WAITSTART		20
#define S_RUNNING		21
#define S_CLICKSTOP		22
#define S_LEAVEDIALOG	23
#define S_WAITSTOP		24
#define S_GETGBODIES	25

#include <vcclr.h>
#include "Launcher.h"

#pragma unmanaged

#include "orbitersdk.h"

#include "windows.h"
#define SO_EXCLUSIVEADDRUSE ((int)(~SO_REUSEADDR)) /* disallow local address reuse */
#include "resource.h"
#include <stdio.h>
#include <commctrl.h>

#include "HashTable.h"
#include "HashData.h"
#include "VirtualDockingTunnel.h"
#include <map>
#include <vector>
#include "OMPModule.h"
#include "KeyboardFilter.h"

// ==============================================================
// Global variables
// ==============================================================

HINSTANCE g_hInst;  // module instance handle
DWORD g_dwCmd;      // custom function identifier
HWND hMain;
OMPModule *module;

bool launchpad=true;
bool fullscreen=true;
bool hide=false;
bool lostfocus=false;
bool statusupdate=false;
int state=S_STOPPED;
bool selfCreate=false;
bool selfDelete=false;

CRITICAL_SECTION shelldisplay;
CRITICAL_SECTION logdisplay;
CRITICAL_SECTION image;
CRITICAL_SECTION hashing;
CRITICAL_SECTION orbiter;
CRITICAL_SECTION messages;
CRITICAL_SECTION ptp;
HANDLE sleeper;
HANDLE tcpconnect;
HANDLE udpsendconnect;
HANDLE udprecvconnect;
HANDLE startgc;
HANDLE udpsenddisconnect;
HANDLE udprecvdisconnect;
HANDLE stoppedgc;
HANDLE startadd;
HANDLE stoppedadd;
HANDLE startmove;
HANDLE stoppedmove;
WSADATA wsaData;

//TabPage elements
HWND hTabPage[TABPAGES];
int currentpage[TABPAGES];
char *tabnames[TABPAGES];
int currenttabs=1;
char *tabnumbers=NULL;
char *versionlabel=NULL;

struct payload
{
	char *nick, *passwd;
	unsigned long ip_addr;
	int receiverport, sendsocket, sendtoport;
	double timestamp;
	//Hashtables
	CHashTable *GlobalsByID;
	CHashTable *GlobalsByHandle;
	CHashTable *LocalsByID;	
	unsigned short int LocalID;
	CHashTable *Neighbours;
	struct MessageLoad *MessageQueue;
	struct KillLoad *KillList;
};

struct plane
{
	VECTOR3 tp1, tp2, tp3;
} touchdown;

struct IDload
{
	unsigned short int	ID;
	unsigned short int	refcount;
	int					MasterID;
	IDload				*GlobalID;
	OBJHANDLE			object;
	VESSEL				*vessel;
	char				size;	
	char				linkage;	
	double				tsSent;	
	char				*NavInfo;
	char				*DockInfo;
	union
	{
		//for local vessels
		struct
		{
			unsigned short int	MasterPreset;
			unsigned char		justAdded;
			int					idle;			
			double				lastJump;
		};
		//for remote vessels
		struct
		{
			THRUSTER_HANDLE		thruster;
			PROPELLANT_HANDLE	fuel;
			double				lastInfoPing;			
		};		
	};
};

struct NeighbourLoad
{
	IDload				*Source;
	unsigned short int	port;
	unsigned long int	ip;
	double				tsSent;
};

struct Sthreaddata
{
	int com, thisport;
	int sin_size;// = sizeof(struct sockaddr);
	struct sockaddr_in remote_addr;	
	HANDLE hThread;
	DWORD ThreadID;
	bool activation;
	payload *user;
	CRITICAL_SECTION mutex;
};

struct Sthreaddata *threaddata[THREADS], *xthreaddata, *rthreaddata;

union UDPpacket
{
	struct
	{		
		char			flags;
		char			size;
	unsigned short int	reference;
		unsigned int	ID;
		double			MJD;
		double			tsSent;
		VECTOR3			pos;
		VECTOR3			vel;
		VECTOR3			acc;
		VECTOR3			arot;
		VECTOR3			vrot;
	}		content;
	struct
	{
		char			flags;
		char			Global4Local;
	unsigned short int	reserve2;
	unsigned short int	GlobalID;
	unsigned short int	LocalID;	
		union
		{
			//for server KA
			double		MJD;
			//for neighbour KA
			struct
			{
		signed char		main;
		signed char		hover;
		signed char		pitch;
		signed char		yaw;
		signed char		bank;
		signed char		sideward;
		signed char		upward;
		signed char		forward;	
			};
		};
		double			tsSent;
	}		contentKA;
	struct
	{
		char			flags;
		char			reserve3;
	unsigned short int	id;
	unsigned short int	id2;
	unsigned short int	id3;	
		double			tsSent;
	}		contentPG;
	struct
	{
		char			flags;
		char			reserve1;
	unsigned short int	Source;
	unsigned short int	reserve4;
	unsigned short int	port;
	unsigned long int	ip;
		double			MJD;				
		double			tsSent;
	}		contentGI;	
	char	buffer[	144];	
};
#define UDPSIZESI	144
#define UDPSIZEKA	24
#define UDPSIZEPG	16
#define UDPSIZEGI	32

struct global_image
{
	double MJD;
	double last_MJD_update;
	double last_skew;
	double e, e1, integral; //PID store
};

struct MessageLoad
{
	double				timestamp;
	double				distance;
	double				frequency;
	OBJHANDLE			object;
	char				*message;
	struct MessageLoad	*next;
};

struct KillLoad
{
	OBJHANDLE			object;
	struct KillLoad		*next;
};

struct
{
	IDload				*id;
	char				*cls;
	char				*name;
} AddLoad;

struct
{
	IDload				*id;
	char				*cls;
	unsigned short		local;
} MoveLoad;

struct
{
	union
	{
		struct
		{
	unsigned short int	id;
	unsigned short int	id2;
	unsigned short int	id3;
	unsigned short int	id4;
		};
		double			T1;
	};
	double				T2;
	double				T3;
	int					divider;
} PTPLoad;

struct cntrlinfo
{
	LONG left;
	LONG right;
	LONG top;
	LONG bottom;
	LONG width;
	LONG heigth;
};

struct vesselinfo
{
		char	*pattern;
		char    *casedName;
		bool	ignore;
};

struct windowinfo
{
	//Orbiter elements
	HWND		idcstartorbi,
				idcselecttree,
				idcpaused,
				idcorbiter,
				idcrender;
	//Dynamic layout information
	cntrlinfo	idciam,
				idcinput,				
				idclose,
				idupdown,
				idctab,
				frame,
				idclist,
				idclisttext;
	//Dynamic layout helpers
	RECT		oldwnd,
				screen;	
	//Main window handles
	HWND		idc_input,
				idc_iam,
				idc_buffer,				
				idc_ip,
				id_close,				
				id_updown,
				idc_tab[TABPAGES],
				hscreen,
				idc_list,
				idc_listtext;
	//Main window picture elements
	HANDLE		idiclose,
				idiup,
				ididown;
	//Status window handles
	HWND		latmx;
	HWND		mjdmx;
	HWND		synmx;
	HWND		latmn;
	HWND		mjdmn;
	HWND		synmn;
	HWND		latav;
	HWND		mjdav;
	HWND		synav;
	HWND		background[2];
	HWND		solarsystem[2];
	HWND		clocksymbol[2][2];
	HWND		clockpulse[2];
	HWND		systempulse[3];	
	//Network window handles
	HWND		srvip;
	HWND		srvtcp;
	HWND		clnudp;
	HWND		username;
	HWND		password;
	HWND		shcon;
	HWND		analyse;
	HWND		chkcustom;
	HWND		chkhairpinning;
	HWND		chkcustomstun;
	//Timing window handles	
	HWND		wrnlat;
	HWND		wrnmjd;
	HWND		wrnsyn;
	HWND		alalat;
	HWND		alamjd;
	HWND		alasyn;
	HWND		sntplog;
	//Shell window handles
	HWND		shbuffer;
	HWND		shprefix;
	HWND		shpfxesc;
	HWND		shsysesc;
	HWND		shsfxasc;
	HWND		autohide[3];
	HWND		suffix[5];
	//Logging window handles
	HWND		lgbuffer;
	HWND		loglevel[LOGLEVELS];
	HWND		lglog;
	HHOOK		hhook;
	HHOOK		hhook2;
	HHOOK		hhook3;
}
winfo;

struct datainfo
{
	//Status	
	int latmx;
	int mjdmx;
	int synmx;
	int latmn;
	int mjdmn;
	int synmn;
	int latav;
	int mjdav;
	int synav;
	bool solarsystem;
	int clocker;
	int oldclock;
	bool clockpulse;
	bool clockpulsed;
	int pulser;
	int oldpuls;
	char *synmnmsg;
	char *synmxmsg;
	char *synavmsg;
	char *mjdmnmsg;
	char *mjdmxmsg;
	char *mjdavmsg;
	//Network;
	unsigned long srvip;
	char *srvipchar;
	int srvudp;
	int srvtcp;
	int clnudp;
	char *username;
	char *password;
	int clientconfig;
	bool shcon;
	bool smcon;
	bool smconFE;
	//Timing
	int curdel;
	int curblk;
	int idler;
	int curlat;
	char *curmjd;
	int cursyn;
	int cursynicon;
	int wrnlat;
	int wrnmjd;
	int wrnsyn;
	int alalat;
	int alamjd;
	int alasyn;	
	char *sntplog;
	//Shell
	int shbuffer;
	int buffersize;
	char *shprefix;
	char *shpfxesc;
	char *shsysesc;
	char *shsfxasc;
	int autohide;
	int suffix;
	char *rcvbuf;
	bool rcvbufchange;
	char lastchar;
	//Logging
	int lgbuffer;
	int lgbuffersize;
	bool loglevel[LOGLEVELS];
	char *lglog;
	bool lgbufchange;

	//Command interpreter states
	bool radiosim;
	bool clockcontroller;
	bool localecho;
	bool ircpong;
	bool debugStep;
	double Kp;
	double Ki;
	double Kd;
	double tolerance;
	double radiofreq;
	int curgc;
	int maxidle;
	int neighidle;
	int globalidle;
	int deletepings;

	//Additional settings
	char *scenario;
	char *SolarSystem;
	int Step;
	int StepEdge;
	CHashTable *vessellist, *locationlist;
	std::map<int, char *> gbodies;
	std::map<unsigned int, std::map<std::pair<unsigned long int, unsigned short int>, int>> removedToNeighbours;
	std::map<unsigned int, int> removedToServer;
	std::map<unsigned int, double> deletedVessels;
	int gbodiescount;
	char *vesselname;
	char *vesselclass;
	char *vessellocation;
	char vesselID;
	int ptpDivider;
	bool ntp;
	double snapshotMJD;
	std::map<OBJHANDLE, VESSELSTATUS2> snapshot;
	bool container;
	bool quickLaunch;
	bool quickLeave;
	bool closeDialogOnceStarted;
	char statusPingKey;
	OBJHANDLE statusPinger;
	KeyboardFilter *filter;
	
	//Missile toy
	char missileLaunchKey;
	char missileTargetKey;
	char missileModifiers;
	VECTOR3 missileOffset;
	std::map<VESSEL *, int> missiles;
	NOTEHANDLE missileDisplay;
	OBJHANDLE missileTarget;
	double missileDisplayRatio;
	double missileDisplaySize;
	double missileDisplayX;
	double missileDisplayY;
	unsigned int missileTargetRotate;

	bool loaderLock;
}
dinfo;

struct global_image	gimage;
struct payload		clientData;

// ==============================================================
// Local prototypes
// ==============================================================

void OpenDlgClbk (void *context);
void CloseDlg (void);
void OpenDlgClbk2 (void *context);
void logging(int level, char *text);
void tlogging(int level, char *text);
BOOL CALLBACK MsgProc (HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK MsgProc2 (HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK StatusProc (HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK NetworkProc (HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK TimeProc (HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK ShellProc (HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK LogProc (HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK ScanForOrbiter(HWND, LPARAM);
void WINAPI statustimer(DWORD sd);
void WINAPI receiverthread(DWORD sd);
void WINAPI synccontrol(DWORD sd);
void WINAPI garbagecollector(DWORD sd);
void statusdisplay();
void WINAPI hostrecvthread(DWORD sd);
void WINAPI hostsendthread(DWORD sd);
double get_global_timestamp();
double get_clock_acceleration(double T, double mjd, double stamp, double acceleration);
void MJDtoSystemTime(double MJD, LPSYSTEMTIME lpSystemTime);
int keyhandler(void *null, char *keystate);
void keyfilter(void *null, WPARAM &wparam, LPARAM &lparam);
void TakeSnapshots(double mjd);

#pragma managed

using namespace Orbiter::Multiplayer::Client;
using namespace System;
using namespace System::IO;
using namespace System::Text;
using namespace System::Reflection;
using namespace System::Runtime::InteropServices;

Wrapper wrapper(gcnew Launcher());

namespace Orbiter
{
	namespace Multiplayer
	{
		namespace Client
		{
			Launcher::Launcher()
			{				
				Assembly^ assm=Assembly::LoadFrom(
					Path::Combine(
						Path::Combine(
							Path::GetDirectoryName(
								Assembly::GetExecutingAssembly()->Location
							),
							"DotNET"
						),
						"Orbiter.Multiplayer.Client.dll"
					)
				);
				if (assm!=nullptr)
				{
					for each (Type^ t in assm->GetTypes())
					{
						if (t->Name->EndsWith("ManagedPart"))
						{
							managed=assm->CreateInstance(t->FullName);
							start=t->GetMethod("Start");
							stop=t->GetMethod("Stop");
							update=t->GetMethod("Update");
							propagate=t->GetMethod("Propagate");
							generateSNTPLog=t->GetMethod("GenerateSNTPLog");
							add=t->GetMethod("Add");
							up=t->GetMethod("Up");
							down=t->GetMethod("Down");
							getNewWindow=t->GetMethod("GetNewWindow");
							saveConfig=t->GetMethod("SaveConfig");
							log=t->GetMethod("Log");
							analyse=t->GetMethod("Analyse");
							ptpSample=t->GetMethod("PTPSample");
							now=t->GetProperty("Now");
							nowLocal=t->GetProperty("NowLocal");
							synchronizationAverage=t->GetProperty("SynchronizationAverage");
							synchronizationMaximum=t->GetProperty("SynchronizationMaximum");
							synchronizationMinimum=t->GetProperty("SynchronizationMinimum");
							clockVisibility=t->GetProperty("ClockVisibility");
							clockPulseVisibility=t->GetProperty("ClockPulseVisibility");
							sampleWaitTime=t->GetProperty("SampleWaitTime");
							synchronizationWaitTime=t->GetProperty("SynchronizationWaitTime");
							logger=t->GetProperty("Logger");
							mjdAverage=t->GetProperty("MjdAverage");
							mjdMaximum=t->GetProperty("MjdMaximum");
							mjdMinimum=t->GetProperty("MjdMinimum");
							systemVisibility=t->GetProperty("SystemVisibility");
							pulse=t->GetProperty("Pulse");
							name=t->GetProperty("Name");
							password=t->GetProperty("Password");
							ip=t->GetProperty("IP");
							tcp=t->GetProperty("TCP");
							udp=t->GetProperty("UDP");
							method=t->GetProperty("Method");
							scenario=t->GetProperty("Scenario");
							compareVersions=t->GetMethod("CompareVersions");
							container=t->GetProperty("Container");
							quickLaunch=t->GetProperty("QuickLaunch");
							quickLeave=t->GetProperty("QuickLeave");

							//Get version string
							String^ s=(String^)t->GetMethod("Version")->Invoke(managed, nullptr);
							IntPtr buf=Marshal::StringToHGlobalAnsi(s);
							int len=strlen((char *)(void *)buf);
							versionlabel = new char[len+21]; //Enough room for prefix
							sprintf(versionlabel, VERSIONLABELPREFIX "%s", (char *)(void *)buf);
							Marshal::FreeHGlobal(buf);
							SetWindowText(hMain, versionlabel);
							break;
						}
					}
				}				
			}

			void Launcher::Start()
			{
				synchronizationAverage->SetValue(managed, (IntPtr)&dinfo.synav, nullptr);
				synchronizationMaximum->SetValue(managed, (IntPtr)&dinfo.synmx, nullptr);
				synchronizationMinimum->SetValue(managed, (IntPtr)&dinfo.synmn, nullptr);
				clockVisibility->SetValue(managed, (IntPtr)&dinfo.clocker, nullptr);
				clockPulseVisibility->SetValue(managed, (IntPtr)&dinfo.clockpulse, nullptr);
				sampleWaitTime->SetValue(managed, (IntPtr)&dinfo.cursynicon, nullptr);
				synchronizationWaitTime->SetValue(managed, (IntPtr)&dinfo.cursyn, nullptr);
				logger->SetValue(managed, (IntPtr)tlogging, nullptr);
				mjdAverage->SetValue(managed, (IntPtr)&dinfo.mjdav, nullptr);
				mjdMaximum->SetValue(managed, (IntPtr)&dinfo.mjdmx, nullptr);
				mjdMinimum->SetValue(managed, (IntPtr)&dinfo.mjdmn, nullptr);
				systemVisibility->SetValue(managed, (IntPtr)&dinfo.shcon, nullptr);
				pulse->SetValue(managed, (IntPtr)&dinfo.pulser, nullptr);
				start->Invoke(managed, nullptr);

				String^ s;
				IntPtr buf;
				int i;

				//Get scenario string
				s=(String^)scenario->GetValue(managed, nullptr);
				buf=Marshal::StringToHGlobalAnsi(s);
				sprintf(dinfo.scenario, SCN_FORMAT, GENERIC_CHAR, (char *)(void *)buf, GENERIC_CHAR);
				Marshal::FreeHGlobal(buf);

				//Get name string
				s=(String^)name->GetValue(managed, nullptr);
				buf=Marshal::StringToHGlobalAnsi(s);
				strcpy(dinfo.username, (char *)(void *)buf);
				Marshal::FreeHGlobal(buf);
				SetWindowText(winfo.username, dinfo.username);

				//Get password string
				s=(String^)password->GetValue(managed, nullptr);
				buf=Marshal::StringToHGlobalAnsi(s);
				strcpy(dinfo.password, (char *)(void *)buf);
				Marshal::FreeHGlobal(buf);
				SetWindowText(winfo.password, dinfo.password);

				//Get IP string
				s=(String^)ip->GetValue(managed, nullptr);
				buf=Marshal::StringToHGlobalAnsi(s);
				strcpy(dinfo.srvipchar, (char *)(void *)buf);
				dinfo.srvip=inet_addr(dinfo.srvipchar);
				Marshal::FreeHGlobal(buf);
				SetWindowText(winfo.srvip, dinfo.srvipchar);

				//Get quick switches
				dinfo.container=(bool)container->GetValue(managed, nullptr);
				dinfo.quickLaunch=(bool)quickLaunch->GetValue(managed, nullptr);
				dinfo.quickLeave=(bool)quickLeave->GetValue(managed, nullptr);

				//Get ports and method
				char line[LINESIZE];
				dinfo.srvtcp=(int)tcp->GetValue(managed, nullptr);				
				SetWindowText(winfo.srvtcp, itoa(dinfo.srvtcp, line, 10));
				dinfo.clnudp=(int)udp->GetValue(managed, nullptr);
				SetWindowText(winfo.clnudp, itoa(dinfo.clnudp, line, 10));
				dinfo.clientconfig=(int)method->GetValue(managed, nullptr);
				SendMessage(winfo.chkcustom,(UINT) BM_SETCHECK,(WPARAM) dinfo.clientconfig!=0?BST_CHECKED:BST_UNCHECKED,(LPARAM) 0);
				SendMessage(winfo.chkhairpinning,(UINT) BM_SETCHECK,(WPARAM) dinfo.clientconfig==1?BST_CHECKED:BST_UNCHECKED,(LPARAM) 0);
				SendMessage(winfo.chkcustomstun,(UINT) BM_SETCHECK,(WPARAM) dinfo.clientconfig==-1?BST_CHECKED:BST_UNCHECKED,(LPARAM) 0);

				//Enable .NET-launch deferred controls
				EnableWindow(winfo.clnudp, dinfo.clientconfig!=0);
				EnableWindow(winfo.chkhairpinning, dinfo.clientconfig!=0);
				EnableWindow(winfo.chkcustomstun, dinfo.clientconfig!=0);
				EnableWindow(winfo.shcon, true);
				EnableWindow(winfo.analyse, true);
				if (hMain!=NULL)
				{
					if (dinfo.quickLaunch || dinfo.container)
					{
						state=S_STARTED;						
						dinfo.shcon=true;						
					}
					else ShowWindow(hMain, SW_RESTORE);
				}
			}
			void Launcher::Stop(){stop->Invoke(managed, nullptr);}
			void Launcher::Update(){update->Invoke(managed, nullptr);}
			void Launcher::Propagate(){propagate->Invoke(managed, nullptr);}
			void Launcher::Add(char *message)
			{
				String^ s=Marshal::PtrToStringAnsi((IntPtr)message);
				add->Invoke(managed, gcnew array<String^>{s});
			}
			void Launcher::Up()
			{
				String^ s=(String^)up->Invoke(managed, nullptr);
				IntPtr buf=Marshal::StringToHGlobalAnsi(s);
				int len=strlen((char *)(void *)buf);
				SetWindowText(winfo.idc_input, (char *)(void *)buf);
				SendMessage(winfo.idc_input,(UINT) EM_SETSEL,(WPARAM) len,(LPARAM) len);
				Marshal::FreeHGlobal(buf);
			}
			void Launcher::Down()
			{
				String^ s=(String^)down->Invoke(managed, nullptr);
				IntPtr buf=Marshal::StringToHGlobalAnsi(s);
				int len=strlen((char *)(void *)buf);
				SetWindowText(winfo.idc_input, (char *)(void *)buf);
				SendMessage(winfo.idc_input,(UINT) EM_SETSEL,(WPARAM) len,(LPARAM) len);
				Marshal::FreeHGlobal(buf);
			}
			double Launcher::Now::get(void){return (double)now->GetValue(managed, nullptr);}
			double Launcher::NowLocal::get(void){return (double)nowLocal->GetValue(managed, nullptr);}
			void Launcher::GenerateSNTPLog()
			{
				StringBuilder^ s=(StringBuilder^)generateSNTPLog->Invoke(managed, nullptr);
				IntPtr buf=Marshal::StringToHGlobalAnsi(s->ToString());
				SetWindowText(winfo.sntplog, (char *)(void *)buf);
				Marshal::FreeHGlobal(buf);				
			}
			bool Launcher::CheckSNTPServer()
			{
				StringBuilder^ s=(StringBuilder^)generateSNTPLog->Invoke(managed, nullptr);
				IntPtr buf=Marshal::StringToHGlobalAnsi(s->ToString());
				bool result=*((char *)(void *)buf)!='\0';
				Marshal::FreeHGlobal(buf);
				return result;
			}
			System::IntPtr Launcher::GetNewWindow(){return (System::IntPtr)getNewWindow->Invoke(managed, nullptr);}
			void Launcher::SaveConfig()
			{
				name->SetValue(managed, Marshal::PtrToStringAnsi((System::IntPtr)dinfo.username, strlen(dinfo.username)), nullptr);
				password->SetValue(managed, Marshal::PtrToStringAnsi((System::IntPtr)dinfo.password, strlen(dinfo.password)), nullptr);
				ip->SetValue(managed, Marshal::PtrToStringAnsi((System::IntPtr)dinfo.srvipchar, strlen(dinfo.srvipchar)), nullptr);
				tcp->SetValue(managed, dinfo.srvtcp, nullptr);
				udp->SetValue(managed, dinfo.clnudp, nullptr);
				method->SetValue(managed, dinfo.clientconfig, nullptr);
				container->SetValue(managed, dinfo.container, nullptr);
				quickLaunch->SetValue(managed, dinfo.quickLaunch, nullptr);
				quickLeave->SetValue(managed, dinfo.quickLeave, nullptr);
				saveConfig->Invoke(managed, nullptr);
			}
			void Launcher::Log(int level, char *message)
			{
				String^ s=Marshal::PtrToStringAnsi((IntPtr)message);
				log->Invoke(managed, gcnew array<Object^>{level, s});
			}
			void Launcher::Analyse(){analyse->Invoke(managed, nullptr);}
			void Launcher::PTPSample(double t1, double t2, double t3, double t4)
			{
				ptpSample->Invoke(managed, gcnew array<Object^>{t1,t2,t3,t4});
			}
			int Launcher::CompareVersions(char *v1, char *v2)
			{
				String^ s1=Marshal::PtrToStringAnsi((IntPtr)v1);
				String^ s2=Marshal::PtrToStringAnsi((IntPtr)v2);
				return (int)compareVersions->Invoke(managed, gcnew array<String^>{s1,s2});
			}

			Wrapper::Wrapper(Launcher ^launcher){this->launcher=launcher;}
			void Wrapper::Start(){launcher->Start();}
			void Wrapper::Stop(){launcher->Stop();}
			void Wrapper::Update(){launcher->Update();}
			void Wrapper::Propagate(){launcher->Propagate();}
			void Wrapper::Add(char * message){launcher->Add(message);}
			void Wrapper::Up(){launcher->Up();}
			void Wrapper::Down(){launcher->Down();}
			double Wrapper::Now(bool compensated)
			{
				if (compensated) return launcher->Now;
				return launcher->NowLocal;
			}
			void Wrapper::GenerateSNTPLog(){launcher->GenerateSNTPLog();}
			bool Wrapper::CheckSNTPServer(){return launcher->CheckSNTPServer();}
			HWND Wrapper::GetNewWindow(){return (HWND)(void *)launcher->GetNewWindow();}
			void Wrapper::SaveConfig(){launcher->SaveConfig();}
			void Wrapper::Log(int level, char *message){launcher->Log(level, message);}
			void Wrapper::Analyse(){launcher->Analyse();}
			void Wrapper::PTPSample(double t1, double t2, double t3, double t4){launcher->PTPSample(t1,t2,t3,t4);}
			int Wrapper::CompareVersions(char *v1, char *v2){return launcher->CompareVersions(v1, v2);}
		}
	}
}

#pragma unmanaged

// ==============================================================
// API interface
// ==============================================================

// ==============================================================
// This function is called when Orbiter starts or when the module
// is activated.

#define OCT5_1582		(2299160L)		// "really" 15-Oct-1582
#define OCT14_1582		(2299169L)		// "really"  4-Oct-1582
#define JAN1_1			(1721423L)

#define YEAR			(365)
#define FOUR_YEARS		(1461)
#define CENTURY 		(36524L)
#define FOUR_CENTURIES	(146097L)

static int	DaysSoFar[][13] =
			{
			{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
			{0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}
			};
static char *DayOfWeek[7] =
			{ "Sun", "Mon", "Tue", "Wed", "Thu", "Fry", "Sat" };
static char *MonthOfYear[12] =
			{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

void MJDtoSystemTime(double MJD, LPSYSTEMTIME lpSystemTime)
{
	//Day starts at midnight
	MJD+=2400000;

	//Split days from fraction
	long julian=(long) MJD;
	double fraction=MJD-julian;

	//Calculate date
	long	z,y;
	short 	m,d;
	int 	lp;

	z = julian+1;
	if (z >= OCT5_1582)
	{
		z -= JAN1_1;
		z  = z + (z/CENTURY)  - (z/FOUR_CENTURIES) -2;
		z += JAN1_1;
	}
	z = z - ((z-YEAR) / FOUR_YEARS);		// Remove leap years before current year
	y = z / YEAR;
	d = (short) (z - (y * YEAR));
	y = y - 4712;							// our base year in 4713BC
	if (y < 1) y--;
	lp = !(y & 3);							// lp = 1 if this is a leap year.
	if (d==0)
	{
		y--;
		d = (short) (YEAR + lp);
	}
	m  = (short) (d/30);					// guess at month
	while (DaysSoFar[lp][m] >=d) m--;		// correct guess
	lpSystemTime->wDay = (unsigned short) (d - DaysSoFar[lp][m]);
	lpSystemTime->wMonth = (unsigned short) m;
	lpSystemTime->wYear = (unsigned short) y;	

	//Calculate time
	lpSystemTime->wHour=(unsigned short)(fraction*=24);
	fraction-=lpSystemTime->wHour;
	lpSystemTime->wMinute=(unsigned short)(fraction*=60);
	fraction-=lpSystemTime->wMinute;
	lpSystemTime->wSecond=(unsigned short)(fraction*=60);
	fraction-=lpSystemTime->wSecond;
	lpSystemTime->wMilliseconds=(unsigned short)(fraction*=1000);
	
	//Calculate day of week
	lpSystemTime->wDayOfWeek=(unsigned short)( (julian+2) % 7 );	
}

double SystemTimeToMJD(LPSYSTEMTIME lpSystemTime)
{
	int		a;
	int		work_year=lpSystemTime->wYear;
	long	j;
	int 	lp;
	double	julian;

	if (work_year < 0) work_year++;				// correct for negative year  (-1 = 1BC = year 0)
	lp = !(work_year & 3);						// lp = 1 if this is a leap year.
	j =	((work_year-1) / 4)					+	// ALL leap years
		DaysSoFar[lp][lpSystemTime->wMonth]	+	// days in this year
		lpSystemTime->wDay					+	// day in this month
		(work_year * 365L)					+	// days in years
		 JAN1_1								+	// first january in year 1
		 -366;									// adjustments
	// deal with Gregorian calendar
	if (j >= OCT14_1582)
	{
		a = (int)(work_year/100);
		j = j+ 2 - a + a/4;						// skip days which didn't exist.
	}
	julian=j-2400000;							// convert to modified julian days
	return julian							+	// return julian days + time fraction
		((((double)lpSystemTime->wMilliseconds/1000	+
		   (double)lpSystemTime->wSecond)/60		+
		  (double)lpSystemTime->wMinute)/60			+
		 (double)lpSystemTime->wHour)/24;	
}

void InvertMatrix(MATRIX3 &temprot, MATRIX3 &rotation)
{
	//Calculate determinant
	double temp=temprot.m11*(temprot.m22*temprot.m33-temprot.m23*temprot.m32)-
				temprot.m12*(temprot.m21*temprot.m33-temprot.m23*temprot.m31)+
				temprot.m13*(temprot.m21*temprot.m32-temprot.m22*temprot.m31);
	if (temp>0e0)
	{
		temp=1/temp;
		//Calculate inverse matrix of rotation
		rotation.m11=(temprot.m22*temprot.m33-temprot.m23*temprot.m32)*temp;
		rotation.m12=(temprot.m13*temprot.m32-temprot.m12*temprot.m33)*temp;
		rotation.m13=(temprot.m12*temprot.m23-temprot.m13*temprot.m22)*temp;
		rotation.m21=(temprot.m23*temprot.m31-temprot.m21*temprot.m33)*temp;
		rotation.m22=(temprot.m11*temprot.m33-temprot.m13*temprot.m31)*temp;
		rotation.m23=(temprot.m13*temprot.m21-temprot.m11*temprot.m23)*temp;
		rotation.m31=(temprot.m21*temprot.m32-temprot.m22*temprot.m31)*temp;
		rotation.m32=(temprot.m12*temprot.m31-temprot.m11*temprot.m32)*temp;
		rotation.m33=(temprot.m11*temprot.m22-temprot.m12*temprot.m21)*temp;
	}
	else
	{
		//Return same matrix if zero determinant
		rotation.m11=temprot.m11;
		rotation.m12=temprot.m12;
		rotation.m13=temprot.m13;
		rotation.m21=temprot.m21;
		rotation.m22=temprot.m22;
		rotation.m23=temprot.m23;
		rotation.m31=temprot.m31;
		rotation.m32=temprot.m32;
		rotation.m33=temprot.m33;
	}
}

void RotateVector(VECTOR3 &input, MATRIX3 &rotation, VECTOR3 &direction)
{
	direction.x=input.x*rotation.m11+
				input.y*rotation.m12+
				input.z*rotation.m13;
	direction.y=input.x*rotation.m21+
				input.y*rotation.m22+
				input.z*rotation.m23;
	direction.z=input.x*rotation.m31+
				input.y*rotation.m32+
				input.z*rotation.m33;
}

void gRK4(VECTOR3 &p, double GBodyMass, VECTOR3 &output)
{
	double temp=length(p);
	if (!(temp>0e0)) temp=FLT_MIN;
	output=p*GRAVCONST*GBodyMass/temp/temp/temp;
}

void fvRK4(double step, VECTOR3 &a, VECTOR3 &acc, VECTOR3 &vrot, VECTOR3 &output)
{
	//Currently rotation is not taken into account!!!
	output=a-acc;
}

void velRK4(double step, VECTOR3 &p, VECTOR3 &v, VECTOR3 &a, VECTOR3 &vrot, double GBodyMass, VECTOR3 &output)
{
	VECTOR3 k1,k2,k3,k4;

	gRK4(p, GBodyMass, output);
	fvRK4(0,		a, output, vrot, k1);
	fvRK4(step/2,	a, output, vrot, k2);
	fvRK4(step/2,	a, output, vrot, k3);
	fvRK4(step,		a, output, vrot, k4);
	step/=6;
	output=v+(k1+k2*2+k3*2+k4)*step;
}

void posRK4(double step, VECTOR3 &p, VECTOR3 &v, VECTOR3 &a, VECTOR3 &vrot,
			double GBodyMass, VECTOR3 &outputp, VECTOR3 &outputv)
{
	VECTOR3 k1,k2,k3,k4;	

	velRK4(0,		p,		v, a, vrot, GBodyMass, k1);
	outputp=p+k1*(step/2);
	velRK4(step/2,	outputp,v, a, vrot, GBodyMass, k2);
	outputp=p+k2*(step/2);
	velRK4(step/2,	outputp,v, a, vrot, GBodyMass, k3);
	outputp=p+k3*step;
	velRK4(step,	outputp,v, a, vrot, GBodyMass, k4);
	outputv=(k1+k2*2+k3*2+k4)/6;
	outputp=p+outputv*step;
	outputv=k4;
}

// Returns local internet address
in_addr localIP()
{
	char buf[256];
	hostent* remotehost;
	in_addr result;
	
	gethostname(buf, 256);
	remotehost=gethostbyname(buf);
	if (remotehost==NULL)
	{
		result.S_un.S_addr=inet_addr(LOOPBACK_IP);
		return(result);
	}
	else return(*(struct in_addr*) remotehost->h_addr);
}

BOOL CALLBACK ScanForOrbiter(HWND hWnd, LPARAM lparam)
{
	DWORD hinstance;	
	long *conf_long=(long *)lparam;
	GetWindowThreadProcessId(hWnd, &hinstance);
	if (((HINSTANCE)hinstance)==((HINSTANCE)conf_long[0]))
		if (IsWindowVisible(hWnd))
		{
			SetLastError(ERROR_APPLICATION);
			conf_long[0]=(long)hWnd;
			return FALSE;
		}	
	return TRUE;
}

void SaveConfig()
{
	if (dinfo.loaderLock) return;
	wrapper.SaveConfig();
}

void DeleteVesselList()
{
	struct vesselinfo *entry4;
	struct animationinfo *entry5;
	struct animationmap *entry6;
	if (dinfo.vessellist!=0)
	{
		while (!dinfo.vessellist->empty)
		{
			entry4=(vesselinfo *) dinfo.vessellist->clear(false);
			delete [] entry4->pattern;
			delete [] entry4->casedName;
			delete entry4;
			entry4=0;
		}
		delete dinfo.vessellist;
		dinfo.vessellist=0;
	}
}

void Init(HINSTANCE hDLL)
{
	g_hInst = hDLL; // remember the instance handle
	int i, j, yes;
	char logger[LINESIZE], line[LINESIZE], conf_file[LINESIZE], conf_string[LINESIZE];
	FILE *file;
	long conf_long, conf_long2, conf_long3;
	struct SNTPServer *servbuf;
//	TVITEM tvitem;

	dinfo.shbuffer=BUFFERSIZE;
	dinfo.lgbuffer=BUFFERSIZE;
	dinfo.buffersize=dinfo.shbuffer;
	dinfo.lgbuffersize=dinfo.lgbuffer;
	dinfo.rcvbuf=new char[dinfo.buffersize];	
	dinfo.rcvbuf[0]=0x00;	
	dinfo.rcvbuf[dinfo.buffersize-1]=0x00;
	dinfo.lglog=new char[dinfo.lgbuffersize];
	sprintf(dinfo.lglog, "Log started.\r\n");
	dinfo.lglog[dinfo.lgbuffersize-1]=0x00;
	dinfo.lastchar=0x00;
	
	g_dwCmd = oapiRegisterCustomCmd ("OMP interface",
		"Orbiter Multiplayer Project control interface.\r\nChat and client control interface.",
		OpenDlgClbk, NULL);
	
	//***********************************
	// INITIALISATION
	//***********************************

	PTPLoad.T1=PTPLoad.T2=PTPLoad.T3=-1;
	PTPLoad.divider=0;
	dinfo.ptpDivider=3;

	//Touchdown points
	touchdown.tp1.x=0.03;
	touchdown.tp1.y=0;
	touchdown.tp1.z=0;
	touchdown.tp2.x=-0.03;
	touchdown.tp2.y=0;
	touchdown.tp2.z=0.03;
	touchdown.tp3.x=-0.03;
	touchdown.tp3.y=0;
	touchdown.tp3.z=-0.03;

	//Startup mutex
	InitializeCriticalSection(&logdisplay);			
	InitializeCriticalSection(&shelldisplay);
	InitializeCriticalSection(&image);	
	InitializeCriticalSection(&hashing);
	InitializeCriticalSection(&orbiter);
	InitializeCriticalSection(&messages);
	InitializeCriticalSection(&ptp);

	//Reset logging
	for(i=0; i<3; i++) dinfo.loglevel[i]=true;
	for(; i<LOGLEVELS; i++) dinfo.loglevel[i]=false;

	gimage.last_skew=0;

	//Default network settings
	dinfo.srvipchar=new char[LINESIZE];
	dinfo.srvipchar[0]=0x00;
	dinfo.srvip=0;	
	dinfo.srvtcp=0;	
	dinfo.clnudp=0;
	dinfo.username=new char[LINESIZE];
	dinfo.username[0]=0x00;
	dinfo.password=new char[LINESIZE];
	dinfo.password[0]=0x00;

	//Default additionals
	dinfo.scenario=new char[LINESIZE];
	dinfo.scenario[0]=0x00;
	dinfo.SolarSystem=new char[i=strlen(DEFAULT_SYSTEM)+1];
	strncpy (dinfo.SolarSystem, DEFAULT_SYSTEM, i);
	dinfo.container=false;
	dinfo.quickLaunch=false;
	dinfo.quickLeave=true;
	
	//Find Orbiter's main windows
	conf_long=(long)GetCurrentProcessId();
	if (EnumWindows(ScanForOrbiter, (LPARAM)&conf_long))
	{
		sprintf (logger, "Unable to locate Orbiter window!");
		logging(-1, logger);
		exit (-1);
	}
	if ((conf_long2=GetLastError())!=ERROR_APPLICATION)
	{
		sprintf (logger, "Unable to locate Orbiter window (Error %X)!", conf_long2);
		logging(-1, logger);
		exit (-1);
	}
	sprintf (logger, "Located Orbiter window handle: %X", conf_long);
	logging(0, logger);

	//Get Orbiter's Start button
	winfo.idcorbiter=(HWND)conf_long;
	winfo.idcstartorbi=GetDlgItem(winfo.idcorbiter, IDC_STARTORBI);
	if (winfo.idcstartorbi==NULL)
	{
		sprintf (logger, "Unable to locate Orbiter's start button (Error %X)!", GetLastError());
		logging(-1, logger);
		exit (-1);
	}	

	//Get the first tab page's window handle
	conf_long=(long)winfo.idcstartorbi;
	for(i=0;i<TABPAGESKIP;i++) conf_long=(long)GetNextWindow((HWND)conf_long, GW_HWNDNEXT);
	if (conf_long==NULL)
	{
		sprintf (logger, "Unable to locate Orbiter's first tab page (Error %X)!", GetLastError());
		logging(-1, logger);
		exit (-1);
	}
	sprintf (logger, "Located Orbiter's first tab page: %X", conf_long);
	logging(0, logger);

	//Get Orbiter's selection tree
	winfo.idcselecttree=GetDlgItem((HWND)conf_long, IDC_SELECTTREE);
	if (winfo.idcselecttree==NULL)
	{
		sprintf (logger, "Unable to locate Orbiter's selection tree (Error %X)!", GetLastError());
		logging(-1, logger);
		exit (-1);
	}
	if (!TreeView_SelectItem(winfo.idcselecttree, TreeView_GetRoot(winfo.idcselecttree)))
	{
		sprintf (logger, "Unable to select Orbiter's first scenario entry(Error %X)!", GetLastError());
		logging(-1, logger);
		exit (-1);
	}
	
	//Get Orbiter's Paused checkbox
	winfo.idcpaused=GetDlgItem((HWND)conf_long, IDC_PAUSED);
	if (winfo.idcpaused==NULL)
	{
		sprintf (logger, "Unable to locate Orbiter's pausing check box (Error %X)!", GetLastError());
		logging(-1, logger);
		exit (-1);
	}
	SendMessage(winfo.idcpaused, BM_SETCHECK, BST_UNCHECKED, 0);
	EnableWindow(winfo.idcpaused, FALSE);
	ShowWindow(winfo.idcstartorbi, SW_HIDE);
	EnableWindow(winfo.idcselecttree, FALSE);
	winfo.shcon = 0;

	//Reset status
	dinfo.latmx=0;
	dinfo.mjdmx=0;
	dinfo.synmx=0;
	dinfo.latmn=0;
	dinfo.mjdmn=0;
	dinfo.synmn=0;
	dinfo.latav=0;
	dinfo.mjdav=0;
	dinfo.synav=0;
	dinfo.solarsystem=false;
	dinfo.clocker=dinfo.oldclock=0;
	dinfo.clockpulse=false;
	dinfo.clockpulsed=false;
	dinfo.pulser=dinfo.oldpuls=0;
	dinfo.synavmsg=new char[LINESIZE];
	dinfo.synmxmsg=new char[LINESIZE];
	dinfo.synmnmsg=new char[LINESIZE];
	dinfo.mjdavmsg=new char[LINESIZE];
	dinfo.mjdmxmsg=new char[LINESIZE];
	dinfo.mjdmnmsg=new char[LINESIZE];
	gimage.e=gimage.e1=gimage.integral=0;

	//Reset network
	dinfo.shcon=false;
	dinfo.smcon=false;
	dinfo.smconFE=false;

	//Reset timing
	dinfo.curdel=50;
	dinfo.idler=10;
	dinfo.curblk=200;
	dinfo.cursyn=SYNCREFRESH;
	dinfo.cursynicon=SYNCICON;
	dinfo.curlat=132;
	dinfo.curmjd=new char[LINESIZE];
	sprintf(dinfo.curmjd, "");	
	dinfo.wrnlat=300;
	dinfo.wrnmjd=5000;
	dinfo.wrnsyn=50;
	dinfo.alalat=500;
	dinfo.alamjd=10000;
	dinfo.alasyn=100;
	dinfo.sntplog=new char[BUFFERSIZE];
	sprintf(dinfo.sntplog, "Press \"Status\" for a report.");

	//Reset shell	
	dinfo.shprefix=new char[LINESIZE];
	sprintf(dinfo.shprefix, "t:");
	dinfo.shpfxesc=new char[2];
	sprintf(dinfo.shpfxesc, "\\");
	dinfo.shsysesc=new char[2];
	sprintf(dinfo.shsysesc, "%%");
	dinfo.shsfxasc=new char[LINESIZE];	
	sprintf(dinfo.shsfxasc, "\x02\r\n"); //Pascal-Type String (First character=length)	
	dinfo.autohide=1; //0:none/1:status/2:full
	dinfo.suffix=3;	//0:none/1:CR/2:LF/3:CRLF/4:Extended HEX-String	

	//Reset command interpreter states
	dinfo.radiosim=false;
	dinfo.radiofreq=2205e6;
	dinfo.clockcontroller=true;
	dinfo.localecho=false;
	dinfo.ircpong=true;
	dinfo.Kp=1;
	dinfo.Ki=0.5;
	dinfo.Kd=0;
	dinfo.tolerance=0.5;
	dinfo.curgc=500;
	dinfo.maxidle=MAXIDLE;
	dinfo.neighidle=NEIGHBOURIDLE;
	dinfo.globalidle=GLOBALIDLE;
	dinfo.deletepings=DELETEPINGS;

	//Additional things
	dinfo.vesselclass=0;
	dinfo.vesselname=0;
	dinfo.vessellocation=0;
	dinfo.statusPinger=NULL;
	dinfo.statusPingKey=OAPI_KEY_S;
	dinfo.missileLaunchKey=OAPI_KEY_SPACE;
	dinfo.missileTargetKey=OAPI_KEY_C;
	dinfo.missileModifiers=0x14; //Left Control + Alt
	dinfo.missileOffset=_V(0,-3,0);
	dinfo.missileDisplay=NULL;
	dinfo.missileTarget=NULL;
	dinfo.missileDisplaySize=1.0;
	dinfo.missileDisplayX=0.017;
	dinfo.missileDisplayY=0.018;
	dinfo.missileTargetRotate=0;

	//Hashtables
	clientData.GlobalsByHandle=0;
	clientData.GlobalsByID=0;
	clientData.LocalsByID=0;
	clientData.LocalID=0;
	clientData.MessageQueue=0;
	clientData.KillList=0;
	dinfo.vessellist=new CHashTable(0); 
	dinfo.locationlist=new CHashTable(0); 

	//Client data
	clientData.nick=new char[LINESIZE];
	clientData.passwd=new char[LINESIZE];

	//Starting up WinSocket
	if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0)
	{
		logging(-1,"WinSocket Startup failed!");
		exit(-1);
	}
	else logging(0,"WinSocket started...");
	//Create synchronization event for dialog threads
	sleeper=CreateEvent(0, TRUE, FALSE, 0);	
	tcpconnect=CreateEvent(0, TRUE, FALSE, 0);	
	udpsendconnect=CreateEvent(0, TRUE, FALSE, 0);	
	udprecvconnect=CreateEvent(0, TRUE, FALSE, 0);	
	startgc=CreateEvent(0, TRUE, FALSE, 0);	
	udpsenddisconnect=CreateEvent(0, TRUE, FALSE, 0);	
	udprecvdisconnect=CreateEvent(0, TRUE, FALSE, 0);	
	stoppedgc=CreateEvent(0, TRUE, FALSE, 0);	
	startadd=CreateEvent(0, FALSE, FALSE, 0);	
	stoppedadd=CreateEvent(0, FALSE, FALSE, 0);	
	startmove=CreateEvent(0, FALSE, FALSE, 0);
	stoppedmove=CreateEvent(0, FALSE, FALSE, 0);
	//Create thread structures
	for(i=0;i<THREADS;i++) threaddata[i]=new struct Sthreaddata;
	threaddata[1]->sin_size=sizeof(struct sockaddr);
	threaddata[3]->sin_size=sizeof(struct sockaddr);
	threaddata[4]->sin_size=sizeof(struct sockaddr);
	//Starting dialog thread
	threaddata[0]->activation=true;
	threaddata[0]->hThread = CreateThread(
											0,
											0,
											(LPTHREAD_START_ROUTINE) statustimer,
											(void *)threaddata[0],
											0,
											&threaddata[0]->ThreadID);
    if(threaddata[0]->hThread == INVALID_HANDLE_VALUE)
	{
		sprintf (line,"Main: Can't create dialog thread, Error code = %i",GetLastError());
		logging(-1,line);
		exit(-1);
	}			
	//Starting listener thread
	xthreaddata=threaddata[1];
	threaddata[1]->activation=true;
	threaddata[1]->user=&clientData;
	threaddata[1]->thisport=dinfo.srvtcp;
	threaddata[1]->hThread = CreateThread(
											0,
											0,
											(LPTHREAD_START_ROUTINE) receiverthread,
											(void *)threaddata[1],
											0,
											&threaddata[1]->ThreadID);
	if(threaddata[1]->hThread == INVALID_HANDLE_VALUE)
	{
		sprintf (line,"Main: Can't create listener thread, Error code = %i",GetLastError());
		logging(-1,line);
		exit(-1);
	}
	InitializeCriticalSection(&(threaddata[1]->mutex));
//	else
//		SetThreadPriority(threaddata[1]->hThread, THREAD_PRIORITY_TIME_CRITICAL);
	//Starting Sync thread
	threaddata[2]->activation=true;
	threaddata[2]->thisport=dinfo.srvtcp;
	threaddata[2]->hThread = CreateThread(
											0,
											0,
											(LPTHREAD_START_ROUTINE) synccontrol,
											(void *)threaddata[2],
											0,
											&threaddata[2]->ThreadID);
	if(threaddata[2]->hThread == INVALID_HANDLE_VALUE)
	{
		sprintf (line,"Main: Can't create sync control thread, Error code = %i",GetLastError());
		logging(-1,line);
		exit(-1);
	}		
//	else
//		SetThreadPriority(threaddata[2]->hThread, THREAD_PRIORITY_TIME_CRITICAL);

	//Starting UDP XMIT thread
	threaddata[3]->activation=false;
	threaddata[3]->user=&clientData;
	threaddata[3]->hThread = CreateThread(
											0,
											0,
											(LPTHREAD_START_ROUTINE) hostsendthread,
											(void *)threaddata[3],
											0,
											&threaddata[3]->ThreadID);
	if(threaddata[3]->hThread == INVALID_HANDLE_VALUE)
	{
		sprintf (line,"Main: Can't create UDP transmitter thread, Error code = %i",GetLastError());
		logging(-1,line);
		exit(-1);
	}		
//	else
//		SetThreadPriority(threaddata[3]->hThread, THREAD_PRIORITY_TIME_CRITICAL);

	//Starting UDP RCVR thread
	rthreaddata=threaddata[4];
	threaddata[4]->activation=false;
	threaddata[4]->user=&clientData;
	threaddata[4]->hThread = CreateThread(
											0,
											0,
											(LPTHREAD_START_ROUTINE) hostrecvthread,
											(void *)threaddata[4],
											0,
											&threaddata[4]->ThreadID);
	if(threaddata[4]->hThread == INVALID_HANDLE_VALUE)
	{
		sprintf (line,"Main: Can't create UDP receiver thread, Error code = %i",GetLastError());
		logging(-1,line);
		exit(-1);
	}		

	//Starting garbage collector
	threaddata[5]->activation=false;
	threaddata[5]->user=&clientData;
	threaddata[5]->hThread = CreateThread(
											0,
											0,
											(LPTHREAD_START_ROUTINE) garbagecollector,
											(void *)threaddata[5],
											0,
											&threaddata[5]->ThreadID);
	if(threaddata[5]->hThread == INVALID_HANDLE_VALUE)
	{
		sprintf (line,"Main: Can't create garbage collector thread, Error code = %i",GetLastError());
		logging(-1,line);
		exit(-1);
	}

	OpenDlgClbk(NULL);
}

// ==============================================================
// This function is called when Orbiter shuts down or when the
// module is deactivated

void Exit(HINSTANCE hDLL)
{
	UnhookWindowsHookEx(winfo.hhook3);
	SetParent(winfo.shcon, hMain);
	CloseDlg();
	oapiUnregisterCustomCmd (g_dwCmd);
	//Removing dialog threads
	for(int i=0;i<THREADS;i++)
	{
		threaddata[i]->activation=false;
		//Wake the appropriate thread up
		switch(i)
		{
		case 1:
			SetEvent(tcpconnect);
			break;
		case 3:
			SetEvent(udpsendconnect);
			break;
		case 4:
			SetEvent(udprecvconnect);
			break;
		case 5:
			SetEvent(startgc);
			break;
		}
		//Wake up all sleepers
		SetEvent(sleeper);
		WaitForSingleObject(threaddata[i]->hThread, INFINITE);
		TerminateThread(threaddata[i]->hThread, 0);
		CloseHandle(threaddata[i]->hThread);
		if (i==1) DeleteCriticalSection(&(threaddata[i]->mutex));
		delete threaddata[i];
		threaddata[i]=0;
	}
	
	//Removing mutex handles	
	DeleteCriticalSection(&image);
	DeleteCriticalSection(&hashing);
	DeleteCriticalSection(&orbiter);
	DeleteCriticalSection(&messages);
	DeleteCriticalSection(&ptp);
	DeleteCriticalSection(&shelldisplay);
	DeleteCriticalSection(&logdisplay);
	//Remove sync-events
	CloseHandle(sleeper);
	CloseHandle(tcpconnect);	
	CloseHandle(udpsendconnect);	
	CloseHandle(udprecvconnect);	
	CloseHandle(startgc);	
	CloseHandle(udpsenddisconnect);	
	CloseHandle(udprecvdisconnect);	
	CloseHandle(stoppedgc);	
	CloseHandle(startadd);
	CloseHandle(stoppedadd);
	CloseHandle(startmove);
	CloseHandle(stoppedmove);
	
	SaveConfig();
	//Shutting down WinSocket
	WSACleanup();
	//Clean up memory
	delete [] dinfo.srvipchar;
	delete [] dinfo.username;
	delete [] dinfo.password;
	delete [] dinfo.curmjd;
	delete [] dinfo.sntplog;
	delete [] dinfo.shprefix;
	delete [] dinfo.shpfxesc;
	delete [] dinfo.shsysesc;
	delete [] dinfo.shsfxasc;
	delete [] dinfo.lglog;
	delete [] dinfo.rcvbuf;
	delete [] dinfo.synavmsg;
	delete [] dinfo.synmnmsg;
	delete [] dinfo.synmxmsg;
	delete [] dinfo.mjdavmsg;
	delete [] dinfo.mjdmnmsg;
	delete [] dinfo.mjdmxmsg;
	delete [] clientData.nick;
	delete [] clientData.passwd;
	delete [] dinfo.scenario;
	delete [] dinfo.vesselname;

	dinfo.srvipchar=0;
	dinfo.username=0;
	dinfo.password=0;
	dinfo.curmjd=0;
	dinfo.sntplog=0;
	dinfo.shprefix=0;
	dinfo.shpfxesc=0;
	dinfo.shsysesc=0;
	dinfo.shsfxasc=0;
	dinfo.lglog=0;
	dinfo.rcvbuf=0;
	dinfo.synavmsg=0;
	dinfo.synmnmsg=0;
	dinfo.synmxmsg=0;
	dinfo.mjdavmsg=0;
	dinfo.mjdmnmsg=0;
	dinfo.mjdmxmsg=0;
	clientData.nick=0;
	clientData.passwd=0;
	dinfo.scenario=0;
	dinfo.vesselname=0;

	//Removing hash tables
	struct IDload *entry;
	struct NeighbourLoad *entry1;
	if (clientData.LocalsByID!=0)
	{
		while (!clientData.LocalsByID->empty)
		{
			entry=(struct IDload *) clientData.LocalsByID->clear(false);
			delete [] entry->NavInfo;
			entry->NavInfo=0;
			delete [] entry->DockInfo;
			entry->DockInfo=0;
			delete entry;
			entry=0;
		}
		delete clientData.LocalsByID;
		clientData.LocalsByID=0;
	}
	if (clientData.GlobalsByHandle!=0)
	{
		while (!clientData.GlobalsByHandle->empty)
		{
			entry=(struct IDload *) clientData.GlobalsByHandle->clear(false);
			delete [] entry->NavInfo;
			entry->NavInfo=0;
			delete [] entry->DockInfo;
			entry->DockInfo=0;
			delete entry;
			entry=0;
		}
		delete clientData.GlobalsByHandle;
		clientData.GlobalsByHandle=0;
	}
	if (clientData.GlobalsByID!=0)
	{		
		while (!clientData.GlobalsByID->empty)
		{
			entry=(struct IDload *) clientData.GlobalsByID->clear(false);
			delete [] entry->NavInfo;
			entry->NavInfo=0;
			delete [] entry->DockInfo;
			entry->DockInfo=0;
			delete entry;
			entry=0;
		}
		delete clientData.GlobalsByID;
		clientData.GlobalsByID=0;
	}	
	if (clientData.Neighbours!=0)
	{
		while (!clientData.Neighbours->empty)
		{
			entry1=(struct NeighbourLoad *) clientData.Neighbours->clear(false);
			delete entry1;
			entry1=0;
		}
		delete clientData.Neighbours;
		clientData.Neighbours=0;
	}	
	if (dinfo.locationlist->get(dinfo.vessellocation)!=0) dinfo.vessellocation=0;
	if (dinfo.locationlist!=0)
	{
		while (!dinfo.locationlist->empty)
			delete [] (char *) dinfo.locationlist->clear(false);					
		delete dinfo.locationlist;
		dinfo.locationlist=0;
	}
	delete [] dinfo.vessellocation;
	dinfo.vessellocation=0;
	struct MessageLoad *entry2;
	while (clientData.MessageQueue!=0)
	{
		entry2=clientData.MessageQueue;
		delete [] entry2->message;
		entry2->message=0;
		clientData.MessageQueue=entry2->next;
		delete entry2;
		entry2=0;
	}	
	struct KillLoad *entry3;
	while (clientData.KillList!=0)
	{
		entry3=clientData.KillList;
		clientData.KillList=entry3->next;
		delete entry3;
		entry3=0;
	}	
	DeleteVesselList();
	dinfo.removedToNeighbours.clear();
	dinfo.removedToServer.clear();
	dinfo.deletedVessels.clear();

	delete [] versionlabel;

	ShowWindow(winfo.idcstartorbi, SW_SHOW);
	EnableWindow(winfo.idcstartorbi, TRUE);	
	EnableWindow(winfo.idcselecttree, TRUE);
	EnableWindow(winfo.idcpaused, TRUE);
	TreeView_SelectItem(winfo.idcselecttree, TreeView_GetNextSibling(winfo.idcselecttree, TreeView_GetRoot(winfo.idcselecttree)));
}

DLLCLBK void InitModule(HINSTANCE hDLL)
{
	dinfo.loaderLock=true;
	oapiRegisterModule(new OMPModule(hDLL));
	dinfo.loaderLock=false;
}

LRESULT CALLBACK intercept(int nCode, WPARAM wParam, LPARAM lParam)
{
	if (nCode < 0)  // do not process message 
        return CallNextHookEx(winfo.hhook, nCode, wParam, lParam);

	switch (nCode) 
    { 
        case HCBT_DESTROYWND:
			if ((HWND)wParam==winfo.idcrender)
			{				
				dinfo.smcon=false;
				return TRUE;
			}
			break; 
        default:
            break; 
    } 
 
    return CallNextHookEx(winfo.hhook, nCode, wParam, lParam);
}

LRESULT CALLBACK dialogstarter(int nCode, WPARAM wParam, LPARAM lParam)
{
	if (nCode < 0)  // do not process message 
        return CallNextHookEx(winfo.hhook2, nCode, wParam, lParam);

	launchpad=true;
	fullscreen=true;
	OpenDlgClbk(NULL);
	UnhookWindowsHookEx(winfo.hhook2);
 
    return CallNextHookEx(winfo.hhook2, nCode, wParam, lParam);
}

LRESULT CALLBACK startorbiter(int nCode, WPARAM wParam, LPARAM lParam)
{
	if (nCode < 0)  // do not process message 
        return CallNextHookEx(winfo.hhook3, nCode, wParam, lParam);

	switch (nCode) 
    { 
        case HC_ACTION:
			PCWPSTRUCT message=(PCWPSTRUCT)lParam;
			switch(message->message)
			{
			case WM_COMMAND:
				switch (LOWORD (message->wParam))
				{
				case IDC_SHCON:
					if (!dinfo.shcon)
					{
						SaveConfig();
						state=S_STARTED;						
						dinfo.shcon=true;
					}
					else
					{
						if (state<S_ASSIGN)
						{
							ShowWindow(winfo.idc_list, SW_HIDE);
							ShowWindow(winfo.idc_listtext, SW_HIDE);
							ShowWindow(winfo.idc_input, SW_SHOW);
							ShowWindow(winfo.idc_iam, SW_SHOW);							
							dinfo.shcon=false;
						}
						else state=S_CLICKSTOP;
					}
					
					if (dinfo.smcon) dinfo.smcon=false;
					break;
				}
				break;
			case WM_ENTERSIZEMOVE:
			case WM_SIZING:
			case WM_EXITSIZEMOVE:
				RECT orgframe;
				POINT origin;
				GetWindowRect(winfo.idcstartorbi, &orgframe);
				origin.x=orgframe.left;
				origin.y=orgframe.top;
				ScreenToClient(GetParent(winfo.shcon), &origin);
				SetWindowPos(winfo.shcon, 0, origin.x, origin.y, orgframe.right-orgframe.left, orgframe.bottom-orgframe.top, SWP_SHOWWINDOW);
				break;
			}
			break;         
    } 
 
    return CallNextHookEx(winfo.hhook3, nCode, wParam, lParam);
}

#pragma managed

DLLCLBK void opcOpenRenderViewport (HWND hWnd, DWORD w, DWORD h, BOOL fs)
{
	int extensionsResult=OrbiterExtensions::Init((VESSEL *)g_hInst, ORBITEREXTENSIONSHOOK_RECORDEVENT);
	if (extensionsResult<0) sprintf(oapiDebugString(), "OrbiterExtensions threw error %d on hooking recorder event!", extensionsResult);
	UnhookWindowsHookEx(winfo.hhook3);
	SetParent(winfo.shcon, hMain);
	winfo.idcrender=hWnd;
	winfo.hhook=SetWindowsHookEx(WH_CBT, intercept, NULL, GetWindowThreadProcessId(winfo.idcrender, NULL));
	CloseDlg();
	dinfo.filter=new KeyboardFilter(NULL, &keyhandler, &keyfilter);
	launchpad=false;
	fullscreen=h==0;//mode==oapi::Module::RENDER_FULLSCREEN;
	dinfo.missiles.clear();
	dinfo.missileDisplay=oapiCreateAnnotation(true, 1, _V(1,0,0));
	oapiAnnotationSetSize(dinfo.missileDisplay, dinfo.missileDisplaySize);
	dinfo.missileDisplayRatio=(double)h/(double)w;

	//Build up a first snapshot
	TakeSnapshots(oapiGetSimMJD());

	OpenDlgClbk(NULL);

	dinfo.closeDialogOnceStarted=dinfo.quickLaunch || dinfo.container;

	EnterCriticalSection(&orbiter);
}

void CloseRenderViewport ()
{
	LeaveCriticalSection(&orbiter);
	delete dinfo.filter;
	dinfo.filter=NULL;
	CloseDlg();
	winfo.hhook2=SetWindowsHookEx(WH_FOREGROUNDIDLE, dialogstarter, NULL, GetWindowThreadProcessId(winfo.idcorbiter, NULL));
	oapiDelAnnotation(dinfo.missileDisplay);
	OrbiterExtensions::Exit((VESSEL *)g_hInst, ORBITEREXTENSIONSHOOK_RECORDEVENT);
}

void PreStep ( double SimT, double SimDT, double mjd)
{	
	char line[LINESIZE];
	struct IDload *entry;
	VESSEL2 *vessel;
	int i;

	double acc=oapiGetTimeAcceleration();
	double newacc=get_clock_acceleration(SimDT, mjd, get_global_timestamp(), acc);
	if (acc!=newacc) oapiSetTimeAcceleration(newacc);

	TakeSnapshots(mjd);
		
	LeaveCriticalSection(&orbiter);
	
	//Visualization terms
	if (dinfo.rcvbufchange)
	{		
		EnterCriticalSection(&shelldisplay);
			SetWindowText (winfo.idc_iam, dinfo.rcvbuf);
			SendMessage(winfo.idc_iam,(UINT) EM_LINESCROLL,(WPARAM) 0,(LPARAM) dinfo.buffersize);
			if (dinfo.lastchar==0x0A || dinfo.lastchar==0x0D) //one line up, if last character is line feed
				SendMessage(winfo.idc_iam,(UINT) EM_SCROLL,(WPARAM) SB_LINEUP,(LPARAM) 0);			
			dinfo.rcvbufchange=false;
		LeaveCriticalSection(&shelldisplay);
	}
	if (!hide || !dinfo.smcon)
	{
		if (dinfo.lgbufchange)
		{
			EnterCriticalSection(&logdisplay);
				SetWindowText (winfo.lglog, dinfo.lglog);
				SendMessage(winfo.lglog,(UINT) EM_LINESCROLL,(WPARAM) 0,(LPARAM) dinfo.lgbuffersize);
			LeaveCriticalSection(&logdisplay);
			dinfo.lgbufchange=false;
		}
		if (statusupdate || !dinfo.smcon)
		{
			statusdisplay();
			if (dinfo.closeDialogOnceStarted)
			{
				CloseDlg();
				dinfo.closeDialogOnceStarted=false;
			}
		}
	}
	if (statusupdate) statusupdate=false;
	SleepEx(10, true);
	EnterCriticalSection(&orbiter);

	//Add vessel if command is pending
	if (WaitForSingleObject(startadd, 0)==WAIT_OBJECT_0)
	{
		VECTOR3 location, direction;
		VESSELSTATUS2 targetstate;
		location=_V(0,0,0);
		//Vessel class and name known yet - recreate it
		entry=AddLoad.id;
		targetstate=dinfo.snapshot[entry->object]; //Since this is a recreate, a previous vessel state should already be there in the snapshot
		entry->vessel->GetThrusterDir(entry->thruster, direction);
		double temp=entry->vessel->GetThrusterLevel(entry->thruster)/entry->vessel->GetMass();
		//Put the old vessel into the kill list
		//IT ISNT a hooked vessel, so NO hook NOR queue removal
		KillLoad *kill=new KillLoad;
		kill->next=clientData.KillList;
		kill->object=entry->object;
		clientData.KillList=kill;
		//Create the new vessel representation
		selfCreate=true;
		entry->object=oapiCreateVesselEx(AddLoad.name, AddLoad.cls, &targetstate);
		entry->vessel=oapiGetVesselInterface(entry->object);
		selfCreate=false;
		entry->vessel->SetTouchdownPoints(touchdown.tp1, touchdown.tp2, touchdown.tp3);
		for(int matchi=entry->vessel->GetThrusterCount()-1;matchi>=0;matchi--)
			entry->vessel->SetThrusterMax0(entry->vessel->GetThrusterHandleByIndex(matchi), 0);
		//Generate fake fuel resource and fake thruster for acceleration
		entry->fuel=entry->vessel->CreatePropellantResource(0.001); //1g fake fuel
		entry->thruster=entry->vessel->CreateThruster(
			location, direction, 1E9,
			entry->fuel, 1E15); //ISP: full thrust for 1g for 1000s
		temp*=entry->vessel->GetMass();
		entry->vessel->SetThrusterLevel(entry->thruster, temp);																	
		strcpy(line, entry->vessel->GetClassNameA());
		struct vesselinfo *entry3=(struct vesselinfo *)dinfo.vessellist->get(_strlwr(line));
		if (entry3!=0)
		{
			//Do something with class info here
		}
		entry->tsSent=get_global_timestamp();
		sprintf(line, "%p", entry->object);
		clientData.GlobalsByHandle->put(line, entry);		
		SetEvent(stoppedadd);
	}

	if (WaitForSingleObject(startmove, 0)==WAIT_OBJECT_0)
	{
		VESSELSTATUS2 targetstate=dinfo.snapshot[MoveLoad.id->object]; //Since this is a move, a previous vessel state should already be there in the snapshot
		//Put the old vessel into the kill list
		KillLoad *kill=new KillLoad;
		kill->next=clientData.KillList;
		kill->object=MoveLoad.id->object;
		clientData.KillList=kill;
		//Create the new vessel representation
		clientData.LocalID=MoveLoad.local;
		oapiCreateVesselEx(MoveLoad.id->vessel->GetName(), dinfo.container?"OMPDefault":MoveLoad.cls, &targetstate);
		SetEvent(stoppedmove);
	}
	
	//Delete vessels according to kill list
	while (clientData.KillList!=0)
	{
		//Kill one vessel from the list
		struct KillLoad *entry=clientData.KillList;
		oapiDeleteVessel(entry->object);
		clientData.KillList=entry->next;
		delete entry;		
		entry=0;
	}
	OBJHANDLE focobj=oapiGetFocusObject();
	if (oapiIsVessel(focobj))
	{
		VESSEL *focus=oapiGetVesselInterface(focobj);
		if (focus!=NULL)
		{
			VECTOR3 vector;
			if (focus->GetThrustVector(vector))
			{
				sprintf(line,"%p",focus->GetHandle());
				if (clientData.GlobalsByHandle!=0)
				{
					entry=(struct IDload *)clientData.GlobalsByHandle->get(line);
					if (entry!=0) if (entry->GlobalID!=NULL) entry->idle=0;
				}
			}
			if (focus->GroundContact() && dinfo.missiles[focus]!=3) dinfo.missiles[focus]=3; //TODO: make missile count a server-set constant
			if (dinfo.missileTarget)
			{
				if (!oapiIsVessel(dinfo.missileTarget)) dinfo.missileTarget=NULL;
				else
				{
					VECTOR3 g,l,n;				
					oapiGetGlobalPos(dinfo.missileTarget, &g);
					focus->Global2Local(g, l);
					//Quick frustum culling
					if (l.z<0) dinfo.missileTarget=NULL;
					else
					{
						double d=length(l);
						if (d>5e4) dinfo.missileTarget=NULL; //TODO: make missile targeting range a server-set constant
						else
						{
							//Slow frustum culling
							normalise(n=l);
							double phi=acos(n.z);
							oapiAnnotationSetColour(dinfo.missileDisplay, phi>12*RAD?_V(0,1,0):_V(1,0,0)); //TODO: make missile targeting angle a server-set constant							
							if (oapiCameraInternal())
							{
								//Calculate on-screen position
								oapiCameraGlobalPos(&l);
								normalise(g-=l);
								oapiCameraGlobalDir(&n);
								double r=0;
								phi=acos(dotp(g, n)); //Angle between view direction and target
								if (phi<PI/2) //We don't display targets at our back
								{
									double r=tan(phi)/tan(oapiCameraAperture())/2;	//Radius of polar coordinates around camera center in height units
									normalise(l=crossp(g, n));						//Normal of plane between target vector and cam direction										
									focus->GlobalRot(_V(0,1,0), g);
									normalise(g=crossp(g, n));						//Normal of plane between up vector and cam direction
									phi=dotp(g,l);									//Cosine of polar coordinates around camera center
									g=crossp(g,l);
									double si=length(g)*dinfo.missileDisplayRatio;	//Sine of polar coordinates around camera center
									if (length(g+n)<1) si=-si;						//Invert sine if opposing cam direction
									oapiAnnotationSetPos(dinfo.missileDisplay, 0.5-si*r-dinfo.missileDisplayX, 0.5-phi*r-dinfo.missileDisplayY, 1, 1);
									sprintf(line, "[ %d ]\n%lf\n%s", dinfo.missiles[focus], d, oapiGetVesselInterface(dinfo.missileTarget)->GetName());
									oapiAnnotationSetText(dinfo.missileDisplay, line);
								}
								else oapiAnnotationSetText(dinfo.missileDisplay, "");																											
							}
							else oapiAnnotationSetText(dinfo.missileDisplay, "");
						}
					}
				}
			}
			if (!dinfo.missileTarget) oapiAnnotationSetText(dinfo.missileDisplay, "");
		}
	}	
}

void TakeSnapshots(double mjd)
{
	dinfo.snapshotMJD=mjd;
	OBJHANDLE handle;
	for(int i=0;(handle=oapiGetVesselByIndex(i))!=NULL;i++)
	{
		VESSELSTATUS2 *state=&(dinfo.snapshot[handle]);
		state->version=2;
		oapiGetVesselInterface(handle)->GetStatusEx((void*) state);
	}
}

void PostStep ( double SimT, double SimDT, double mjd)
{
}

void AddVessel(OBJHANDLE obj)
{
	if (selfCreate) return;
	char line[LINESIZE];
	bool own=(DWORD)orbiter.OwningThread==GetCurrentThreadId();
	if (own) LeaveCriticalSection(&orbiter);
	EnterCriticalSection(&hashing);
	VESSEL *vessel=oapiGetVesselInterface(obj);
	strcpy(line, vessel->GetClassNameA());
	vesselinfo *info=(vesselinfo*)dinfo.vessellist->get(_strlwr(line));
	if (info!=NULL)
		if (info->ignore && !dinfo.container)
		{
			LeaveCriticalSection(&hashing);
			if (own) EnterCriticalSection(&orbiter);
			return;
		}
	IDload *id=new struct IDload;
	id->object=obj;
	id->vessel=vessel;
	OrbiterExtensions::RecordVesselEvents(id->vessel, 100);
	long vesselsize=(long)id->vessel->GetSize();
	id->size=0;
	id->MasterID=0;
	id->GlobalID=0;
	id->linkage=1;
	id->refcount=0;
	id->lastJump=0;
	id->idle=0;
	id->justAdded=1;
	id->NavInfo=0;
	id->DockInfo=0;
	while (vesselsize>0)
	{
		id->size++;
		vesselsize=vesselsize >> 1;
	}
	do
	{
		id->ID=clientData.LocalID++;
		sprintf(line, "%d", id->ID);						
	}
	while (clientData.LocalsByID->get(line)!=0); //TODO: this could lead to deadlock if too much local vessels are used!
	clientData.LocalsByID->put(line, id);
	LeaveCriticalSection(&hashing);
	if (own) EnterCriticalSection(&orbiter);
}

void DeleteVessel(OBJHANDLE obj)
{
	bool own=(DWORD)orbiter.OwningThread==GetCurrentThreadId();
	if (!own) EnterCriticalSection(&orbiter);
	if (dinfo.snapshot.find(obj)!=dinfo.snapshot.end()) dinfo.snapshot.erase(obj);
	if (!own) LeaveCriticalSection(&orbiter);
	if (selfDelete) return;
	char line[LINESIZE];
	if (own) LeaveCriticalSection(&orbiter);
	EnterCriticalSection(&hashing);
	unsigned long k=clientData.LocalsByID->get_size();
	CHashData *data;
	IDload *id=0, *load=0;
	NeighbourLoad *nb=0;
	bool tracked=false;
	//No reverse hash table, do linear search in local table
	for(unsigned long i=0;i<k;i++)
	{
		data=clientData.LocalsByID->content(i);
		while (data!=0)
		{
			if (data->key==0) break;
			id=(IDload *)data->data;
			data=data->next;
			if (id->object==obj) break;
			id=0;
		}
		if (id!=0) break;
	}
	if (id!=0)
	{
		OrbiterExtensions::DeleteVesselEvents(id->vessel);
		sprintf(line, "%d", id->ID);
		clientData.LocalsByID->del(line);
		sprintf(line, "%p", id->object);
		clientData.GlobalsByHandle->del(line);
		dinfo.removedToServer[id->ID]=dinfo.deletepings;
		if (id->GlobalID!=0)
		{			
			sprintf(line, "%d", id->GlobalID->ID);
			load=(IDload *)clientData.GlobalsByID->del(line);

			//Generate neighbour delete pings
			k=clientData.Neighbours->get_size();
			for(unsigned long i=0;i<k;i++)
			{
				data=clientData.Neighbours->content(i);
				while (data!=0)
				{
					if (data->key==0) break;
					nb=(NeighbourLoad *)data->data;
					data=data->next;
					if (nb->Source!=load) continue;
					std::pair<unsigned long int, unsigned short int> ip;
					ip.first=nb->ip;
					ip.second=nb->port;
					dinfo.removedToNeighbours[nb->Source->ID][ip]=dinfo.deletepings;
				}
			}
		}
		delete id;
	}
	else
	{
		//No registered local vessel, check for global vessel
		sprintf(line, "%p", obj);
		load=(IDload *)clientData.GlobalsByHandle->del(line);
		if (load==0)
		{
			//No registered global vessel, check for unknown or tracked remote vessels
			k=clientData.GlobalsByID->get_size();
			for(unsigned long i=0;i<k;i++)
			{
				data=clientData.GlobalsByID->content(i);
				while (data!=0)
				{
					if (data->key==0) break;
					load=(IDload *)data->data;
					if (load->object==obj) break;
					data=data->next;
					load=0;
				}
				if (load!=0)
				{
					tracked = data->key[0]=='T';
					break;
				}
			}
		}
		if (load!=0 && load->object!=obj) load=0; //If there was a global object for that handle, but the Orbiter object is different, don't do anything else. This will happen on classic class/name negotiation, where you have an OMPDefault craft at first.	
		if (load!=0)
		{
			//There was a global object registered under that handle, so let's delete it
			sprintf(line, "%s%d", tracked?"T":"", load->ID);
			clientData.GlobalsByID->del(line);
		
			//Check if there are some patterns matching the killed vessel
			if (dinfo.vessellist!=0 && !tracked)
			{
				for(int i=0;true;i++)
				{
					OBJHANDLE obj2=oapiGetVesselByIndex(i);
					if (obj2==NULL) break;
					VESSEL *vessel=oapiGetVesselInterface(obj2);
					strcpy(line, vessel->GetClassNameA());
					struct vesselinfo *vi=(struct vesselinfo *)dinfo.vessellist->get(_strlwr(line));
					if (vi==0) continue;
					if (vi->pattern==0) continue;
					//Class with remove pattern found - check pattern match
					char *name=new char[strlen(vi->pattern)+strlen(load->vessel->GetName())+1];
					char *wildcard=strpbrk(vi->pattern, "*");
					if (wildcard!=0)
					{
						int j=0;
						for(char *p=vi->pattern+1;p<wildcard;p++) name[j++]=*p;
						name[j]=0;
						strcat(name, load->vessel->GetName());
						strcat(name, wildcard+1);
					}
					else strcpy(name, vi->pattern);
					if (strcmp(name, vessel->GetName())==0)
					{
						selfDelete=true;
						oapiDeleteVessel(obj2);
						selfDelete=false;
					}
					delete [] name;
				}
			}
		}
	}
	if (load!=0)
	{
		//Set deletion mark to avoid race conditioned re-adding of global vessel if not a tracker object
		if (!tracked)
		{
			dinfo.deletedVessels[load->ID]=get_global_timestamp();

			//Delete also the neighbour packets holding that object as reference
			k=clientData.Neighbours->get_size();
			for(unsigned long i=0;i<k;i++)
			{
				data=clientData.Neighbours->content(i);
				while (data!=0)
				{
					if (data->key!=0)
					{
						nb=(NeighbourLoad *)data->data;
						data=data->next;
						if (nb->Source!=load) continue;
						sprintf(line, "%d:%d:%d", nb->Source->ID, nb->ip, nb->port);
						clientData.Neighbours->del(line);
						delete nb;
					}
					else data=0;
				}
			}
		}

		delete [] load->NavInfo;
		delete [] load->DockInfo;
		delete load;
	}
	LeaveCriticalSection(&hashing);
	if (own) EnterCriticalSection(&orbiter);
}

#pragma unmanaged

void OpenDlgClbk (void *context)
{	
	if (fullscreen)
	{
		hMain = CreateDialog(g_hInst, launchpad?MAKEINTRESOURCE(IDD_MYDIALOGLPD):MAKEINTRESOURCE(IDD_MYDIALOGSIM), NULL, MsgProc);
		ShowWindow(hMain, SW_SHOW);
	}
	else hMain = oapiOpenDialogEx(g_hInst, IDD_MYDIALOGSIM, MsgProc);
}

void CloseDlg()
{	
	if (hMain!=NULL)
	{
		if (fullscreen) DestroyWindow(hMain);
		else oapiCloseDialog (hMain);
	}
	hMain=NULL;
	if (tabnumbers!=NULL)
	{
		delete [] tabnumbers;
		tabnumbers=0;
	}
	tabnumbers=NULL;
}

void addtobuffer(char *input)
{	
	EnterCriticalSection(&shelldisplay);		
		int i=strlen(dinfo.rcvbuf);		
		int j=strlen(input);
		if ((i=i+j-dinfo.buffersize+1)>0) strncpy(dinfo.rcvbuf, dinfo.rcvbuf+i, dinfo.buffersize-i);
		strncat(dinfo.rcvbuf, input, j);
		dinfo.lastchar=input[j-1];
		if (launchpad)
		{			
			SetWindowText (winfo.idc_iam, dinfo.rcvbuf);
			SendMessage(winfo.idc_iam,(UINT) EM_LINESCROLL,(WPARAM) 0,(LPARAM) dinfo.buffersize);
			if (dinfo.lastchar==0x0A || dinfo.lastchar==0x0D) //one line up, if last character is line feed
				SendMessage(winfo.idc_iam,(UINT) EM_SCROLL,(WPARAM) SB_LINEUP,(LPARAM) 0);
		}
		else
			dinfo.rcvbufchange=true;
	LeaveCriticalSection(&shelldisplay);
}

void log4net(int level, char *text, char *intline)
{
	char *label;
	switch (level)
	{	
	case 10:
	case 1:label="      ";break;
	case 20:
	case 2:label=" TCP: ";break;
	case 30:
	case 3:label=" UDP: ";break;
	case 40:
	case 4:label="SNTP: ";break;
	case 50:
	case 5:label="DEBG: ";break;
	case 60:
	case 6:label=" CLK: ";break;
	case 70:
	case 7:label="  GC: ";break;
	default:label="!";break;
	}
	sprintf(intline, "%s%s\r\n",label,text);
	wrapper.Log(level, text);
}

// Logging according to level:
// 0.. direct output for user interaction
// 1.. standard logging
// 2.. TCP logging
// 3.. UDP logging
// 4.. SNTP logging
// 5.. DEBuG logging
// 6.. CLocK logging
// 7.. GarbageCollector logging
// 8.. reserved
// 9.. reserved
void constructline(int level, char *text, char *intline)
{
	static char backbuffer[LINESIZE*10], *pbuf=backbuffer;

	if (level>0)
	{
		if (pbuf!=backbuffer)
		{
			pbuf=backbuffer;
			for(char *t=pbuf;*t!=0x00;t++)
				if (*t=='\n')
				{
					*t=0x00;
					wrapper.Log(10, pbuf);
					pbuf=t+1;
				}
			pbuf=backbuffer;
		}
		log4net(level, text, intline);
		return;
	}
	else
	{
		if (level<0)
		{
			//Clear back buffer and write this into crash report
			FILE *f=fopen(CRASH_REPORT, "w+");
			if (f!=NULL)
			{
				fprintf(f, backbuffer);
				fprintf(f, text);				
			}
			fclose(f);
			pbuf=backbuffer;
		}
		else
		{
			//Store this in back buffer
			for(char *t=text;*t!=0x00;) *pbuf++=*t++;
			*pbuf++='\n';
			*pbuf=0x00;
		}	
	}
	sprintf(intline, "%s\r\n",text);
}

void logging(int level, char *text)
{
	char intline[LINESIZE];
	int section=level<0?0:level;
	if (section>9) section/=10;

	if (dinfo.loglevel[section])
	{
		constructline(level, text, intline);		
		EnterCriticalSection(&logdisplay);
			int i=strlen(dinfo.lglog);
			int j=strlen(intline);
			if ((i=i+j-dinfo.lgbuffersize+1)>0) strncpy(dinfo.lglog, dinfo.lglog+i, dinfo.lgbuffersize-i);
			strncat(dinfo.lglog, intline, j);
			SetWindowText (winfo.lglog, dinfo.lglog);
			SendMessage(winfo.lglog,(UINT) EM_LINESCROLL,(WPARAM) 0,(LPARAM) dinfo.lgbuffersize);			
		LeaveCriticalSection(&logdisplay);
	}
}

// Threaded logging
void tlogging(int level, char *text)
{
	if (level==-2) //Special logging level for stun analysis
	{
		addtobuffer(text);
		addtobuffer("=====================\r\n");
		EnableWindow(winfo.analyse, true);
		return;
	}

	if (launchpad)
	{
		logging(level, text);
		return;
	}

	char intline[LINESIZE];
	int section=level<0?0:level;
	if (section>9) section/=10;

	if (dinfo.loglevel[section])
	{
		constructline(level, text, intline);		
		EnterCriticalSection(&logdisplay);		
			int i=strlen(dinfo.lglog);
			int j=strlen(intline);
			if ((i=i+j-dinfo.lgbuffersize+1)>0) strncpy(dinfo.lglog, dinfo.lglog+i, dinfo.lgbuffersize-i);
			strncat(dinfo.lglog, intline, j);
		LeaveCriticalSection(&logdisplay);
		dinfo.lgbufchange=true;
	}	
}

void prepareUDPThread()
{
	char line[LINESIZE];

	//Get Connection data
	GetWindowText(winfo.srvip, dinfo.srvipchar, LINESIZE);
	dinfo.srvip=inet_addr(dinfo.srvipchar);
	GetWindowText(winfo.clnudp, line, LINESIZE);
	dinfo.clnudp=atoi(line);
	SetWindowText(winfo.clnudp, itoa(dinfo.clnudp, line, 10));	

	//Set socket address for XMTR
	threaddata[3]->remote_addr.sin_family = AF_INET;				// host byte order
	threaddata[3]->thisport=dinfo.srvudp;
	threaddata[3]->remote_addr.sin_port = htons(dinfo.clnudp);		// short, network byte order
	threaddata[3]->remote_addr.sin_addr.s_addr = dinfo.srvip;	
	memset(&(threaddata[3]->remote_addr.sin_zero), 0, 8);			// zero the rest of the struct

	//Set socket address for RCVR
	threaddata[4]->remote_addr.sin_family = AF_INET;				// host byte order
	threaddata[4]->thisport=dinfo.clnudp;
	threaddata[4]->remote_addr.sin_port = htons(dinfo.clnudp);		// short, network byte order
	threaddata[4]->remote_addr.sin_addr.s_addr = INADDR_ANY;		// automatically fill with local IP
	memset(&(threaddata[4]->remote_addr.sin_zero), 0, 8);			// zero the rest of the struct

	//Set client data
	GetWindowText(winfo.username, clientData.nick, LINESIZE);
	GetWindowText(winfo.password, clientData.passwd, LINESIZE);
	clientData.receiverport=dinfo.clnudp;
	clientData.sendtoport=dinfo.srvudp;	
	
	if (threaddata[3]->remote_addr.sin_addr.s_addr == INADDR_NONE)
	{
		LPHOSTENT lphost;
		lphost = gethostbyname(dinfo.srvipchar);
		if (lphost != NULL)
		{
			threaddata[3]->remote_addr.sin_addr.s_addr = dinfo.srvip = ((LPIN_ADDR)lphost->h_addr)->s_addr;
			sprintf(line, "Resolved hostname %s to ip %s", dinfo.srvipchar, inet_ntoa(threaddata[3]->remote_addr.sin_addr));
			clientData.ip_addr = threaddata[3]->remote_addr.sin_addr.S_un.S_addr;
			logging(3,line);
		}
		else
		{
			SetWindowText(winfo.srvip, "0.0.0.0");
			sprintf(line, "Hostname %s unresolvable!", dinfo.srvipchar);
			logging(3,line);
		}
	}	
	else
		clientData.ip_addr = threaddata[3]->remote_addr.sin_addr.S_un.S_addr;

	threaddata[3]->remote_addr.sin_addr.s_addr = INADDR_ANY;
}

void prepareTCPThread()
{
	char line[LINESIZE];

	//Get Connection data
	GetWindowText(winfo.srvip, dinfo.srvipchar, LINESIZE);
	dinfo.srvip=inet_addr(dinfo.srvipchar);
	GetWindowText(winfo.srvtcp, line, LINESIZE);
	dinfo.srvtcp=atoi(line);
	SetWindowText(winfo.srvtcp, itoa(dinfo.srvtcp, line, 10));

	//Set socket address
	threaddata[1]->remote_addr.sin_family = AF_INET;				// host byte order
	threaddata[1]->remote_addr.sin_port = htons(dinfo.srvtcp);		// short, network byte order
	threaddata[1]->remote_addr.sin_addr.S_un.S_addr = dinfo.srvip;
	memset(&(threaddata[1]->remote_addr.sin_zero), 0, 8);			// zero the rest of the struct

	if (threaddata[1]->remote_addr.sin_addr.s_addr == INADDR_NONE)
	{
		LPHOSTENT lphost;
		lphost = gethostbyname(dinfo.srvipchar);
		if (lphost != NULL)
		{
			threaddata[1]->remote_addr.sin_addr.s_addr = dinfo.srvip = ((LPIN_ADDR)lphost->h_addr)->s_addr;
			sprintf(line, "Resolved hostname %s to ip %s", dinfo.srvipchar, inet_ntoa(threaddata[1]->remote_addr.sin_addr));
			logging(2,line);
		}
		else
		{
			SetWindowText(winfo.srvip, "0.0.0.0");
			sprintf(line, "Hostname %s unresolvable!", dinfo.srvipchar);
			logging(2,line);
		}
	}
}

void statusdisplay()
{
	char line[LINESIZE], logger[LINESIZE], conf_file[LINESIZE], conf_string[LINESIZE];
	unsigned int count, i, j;
	struct IDload *entry;
	struct NeighbourLoad *entry1;
	struct KillLoad *entry2;
	CHashData *entry3;
	unsigned short int globalsize, GlobalIDs;	
	unsigned long int vesselsize;
	FILE *file;
	struct vesselinfo *entry4;
	OBJHANDLE gbody;

	if (dinfo.shcon && !dinfo.solarsystem)
	{
		prepareTCPThread();
		ShowWindow(winfo.solarsystem[1], SW_SHOW);
		threaddata[1]->activation=true;
		SetEvent(tcpconnect);
		SetWindowText(winfo.shcon, "Disconnect Orbiter");					
		dinfo.solarsystem=true;		
	}
	if (!dinfo.shcon && dinfo.solarsystem)
	{
		ShowWindow(winfo.solarsystem[1], SW_HIDE);	
		threaddata[1]->activation=false;
		closesocket(threaddata[1]->com);
		SetWindowText(winfo.shcon, "Connect Orbiter");			
		dinfo.solarsystem=false;
		state=S_STOPPED;
	}
	if (state==S_WAITSTART && dinfo.pulser==0)
	{
		selfDelete=false; //Remove security
		state=S_RUNNING;
		dinfo.smcon=true;
		globalsize=(unsigned short int)(log((double)oapiGetGbodyCount()+HASHSIZE(USERSIZE))/log(2.0)+1.0);
		clientData.GlobalsByHandle=new CHashTable(globalsize);
		clientData.GlobalsByID=new CHashTable(globalsize);
		dinfo.gbodiescount=0;

		for(std::map<int, char*>::iterator iter=dinfo.gbodies.begin();iter!=dinfo.gbodies.end();iter++)			
		{
			gbody=oapiGetGbodyByName(iter->second+1); //Skip the first character from name, as it denotes type
			if (gbody!=0)
			{
				entry=new struct IDload;
				entry->object=gbody;
				entry->vessel=NULL;
				entry->MasterID=0;
				entry->GlobalID=0;
				entry->NavInfo=0;
				entry->DockInfo=0;
				entry->ID=(unsigned short)iter->first;
				entry->linkage=0;
				entry->refcount=0;
				entry->lastJump=0;
				entry->idle=0;
				entry->justAdded=0;
				sprintf(line, "%p", entry->object);
				clientData.GlobalsByHandle->put(line, entry);
				sprintf(line, "%d", entry->ID);
				clientData.GlobalsByID->put(line, entry);
				sprintf(logger, "GBody %d name: %s..", iter->first, iter->second+1);
				tlogging(1, logger);
				dinfo.gbodiescount++;
			}
			else
			{
				sprintf(logger, "GBody reference %s skipped - no name found..", iter->second+1);
				tlogging(1, logger);
			}			
			delete [] iter->second;
		}
		dinfo.gbodies.clear();

		//Create local vessel IDs
		count=oapiGetVesselCount();
		globalsize=(unsigned short int)(log((double)count)/log(2.0)+2.0);
		clientData.LocalsByID=new CHashTable(globalsize);
		clientData.LocalID=0;
		if (!dinfo.quickLaunch && !dinfo.container) for(i=0; i<count; i++)
		{
			bool ignore=false;
			strcpy(line, oapiGetVesselInterface(oapiGetVesselByIndex(i))->GetClassNameA());
			entry4=(vesselinfo*)dinfo.vessellist->get(_strlwr(line));
			if (entry4!=0) ignore=entry4->ignore;
			if (!ignore)
			{
				entry=new struct IDload;
				entry->object=oapiGetVesselByIndex(i);
				entry->vessel=oapiGetVesselInterface(entry->object);
				int res=OrbiterExtensions::RecordVesselEvents(entry->vessel, 100);
				vesselsize=(long)entry->vessel->GetSize();
				entry->size=0;
				entry->MasterID=0;
				entry->GlobalID=0;
				entry->linkage=1;
				entry->refcount=0;
				entry->lastJump=0;
				entry->idle=0;
				entry->justAdded=1;
				entry->NavInfo=0;
				entry->DockInfo=0;
				while (vesselsize>0)
				{
					entry->size++;
					vesselsize=vesselsize >> 1;
				}
				do
				{
					entry->ID=clientData.LocalID++;
					sprintf(line, "%d", entry->ID);						
				}
				while (clientData.LocalsByID->get(line)!=0);
				clientData.LocalsByID->put(line, entry);
			}
		}
		selfCreate = false; // Deactivate defense against vessel creation right on startup again.

		//Create neighbour list
		clientData.Neighbours=new CHashTable(USERSIZE);				

		prepareUDPThread();		
		threaddata[3]->activation=true;
		threaddata[4]->activation=true;
		threaddata[5]->activation=true;
		SetEvent(udpsendconnect);
		if (dinfo.clientconfig>0) SetEvent(udprecvconnect); //Set by senderthread if standard config
		SetEvent(startgc);
		SetWindowText(winfo.shcon, "Disconnect Orbiter");
		dinfo.pulser=1;			
		wrapper.Propagate();			
	}
	if (!dinfo.smcon && dinfo.smconFE)
	{
		selfDelete=true; //Secure against race on shutdown
		threaddata[3]->activation=false;
		closesocket(threaddata[3]->com);							
		WaitForSingleObject(udpsenddisconnect, 5000);
		ResetEvent(udpsenddisconnect);
		threaddata[4]->activation=false;
		closesocket(threaddata[4]->com);
		WaitForSingleObject(udprecvdisconnect, 5000);
		ResetEvent(udprecvdisconnect);
		threaddata[5]->activation=false;
		WaitForSingleObject(stoppedgc, 5000);
		ResetEvent(stoppedgc);
	EnterCriticalSection(&hashing);
		//Removing hash tables
		if (clientData.LocalsByID!=0)
		{
			while (!clientData.LocalsByID->empty)
			{
				entry=(struct IDload *) clientData.LocalsByID->clear(false);
				OrbiterExtensions::DeleteVesselEvents(entry->vessel);
				sprintf(line, "%p", entry->object);
				if (clientData.GlobalsByHandle->get(line)==0)
				{
				EnterCriticalSection(&orbiter);
					if (entry->vessel!=0)
					{
						delete [] entry->NavInfo;
						entry->NavInfo=0;
						delete [] entry->DockInfo;
						entry->DockInfo=0;
					}
				LeaveCriticalSection(&orbiter);
				}
				delete entry;
				entry=0;
			}
			delete clientData.LocalsByID;
			clientData.LocalsByID=0;
		}		
		if (clientData.GlobalsByHandle!=0)
		{
			while (!clientData.GlobalsByHandle->empty)
			{
				entry=(struct IDload *) clientData.GlobalsByHandle->clear(false);
			EnterCriticalSection(&orbiter);
				if (entry->vessel!=0)
				{
					delete [] entry->NavInfo;
					entry->NavInfo=0;
					delete [] entry->DockInfo;
					entry->DockInfo=0;
				}
			LeaveCriticalSection(&orbiter);				
			}
			delete clientData.GlobalsByHandle;		
			clientData.GlobalsByHandle=0;
		}				
		if (clientData.GlobalsByID!=0)
		{		
			while (!clientData.GlobalsByID->empty)
			{
				entry=(struct IDload *) clientData.GlobalsByID->clear(false);
				if (oapiIsVessel(entry->object) && entry->linkage==0)
				{
				EnterCriticalSection(&orbiter);
					oapiDeleteVessel(entry->object);
				LeaveCriticalSection(&orbiter);
					delete [] entry->NavInfo;
					entry->NavInfo=0;
					delete [] entry->DockInfo;
					entry->DockInfo=0;
				}								
				delete entry;
				entry=0;
			}
			delete clientData.GlobalsByID;
			clientData.GlobalsByID=0;
		}		
		if (clientData.Neighbours!=0)
		{
			while (!clientData.Neighbours->empty)
			{
				entry1=(struct NeighbourLoad *) clientData.Neighbours->clear(false);				
				delete entry1;
				entry1=0;
			}
			delete clientData.Neighbours;
			clientData.Neighbours=0;
		}
		while (clientData.KillList!=0)
		{
			entry2=clientData.KillList;
			clientData.KillList=entry2->next;
			delete entry2;
			entry2=0;
		}
		dinfo.removedToNeighbours.clear();
		dinfo.removedToServer.clear();
		dinfo.deletedVessels.clear();
	LeaveCriticalSection(&hashing);		
		dinfo.pulser=0;
		wrapper.Propagate();
		state=S_CLICKSTOP;
		UnhookWindowsHookEx(winfo.hhook);
		SetWindowText(winfo.shcon, "Connect Orbiter");		
		PostMessage(winfo.idcrender, WM_CLOSE, 0, 0);
	}
	if (!dinfo.shcon && dinfo.pulser==1)
	{
		dinfo.pulser=2;
		wrapper.Propagate();
	}
	if (dinfo.pulser!=dinfo.oldpuls)
	{
		switch (dinfo.pulser)
		{
		case 0:
			ShowWindow(winfo.systempulse[0], SW_HIDE);
			ShowWindow(winfo.systempulse[1], SW_HIDE);		
			ShowWindow(winfo.systempulse[2], SW_HIDE);		
			break;
		case 1:
			ShowWindow(winfo.systempulse[0], SW_SHOW);
			ShowWindow(winfo.systempulse[1], SW_HIDE);		
			ShowWindow(winfo.systempulse[2], SW_HIDE);			
			break;
		case 2:
			ShowWindow(winfo.systempulse[1], SW_SHOW);
			ShowWindow(winfo.systempulse[0], SW_HIDE);		
			ShowWindow(winfo.systempulse[2], SW_HIDE);					
			break;
		case 3:
			ShowWindow(winfo.systempulse[2], SW_SHOW);		
			ShowWindow(winfo.systempulse[1], SW_HIDE);		
			break;
		}
		dinfo.oldpuls=dinfo.pulser;
	}
	if (dinfo.clockpulse && !dinfo.clockpulsed)
	{
		ShowWindow(winfo.clockpulse[1], SW_SHOW);
		dinfo.clockpulsed=true;
	}
	if (!dinfo.clockpulse && dinfo.clockpulsed)
	{
		ShowWindow(winfo.clockpulse[1], SW_HIDE);
		dinfo.clockpulsed=false;
	}
	if (dinfo.clocker!=dinfo.oldclock)
	{
		switch (dinfo.clocker)
		{
		case 0:
			ShowWindow(winfo.clocksymbol[1][0], SW_HIDE);
			ShowWindow(winfo.clocksymbol[1][1], SW_HIDE);					
			break;
		case 1:
			ShowWindow(winfo.clocksymbol[1][0], SW_SHOW);
			ShowWindow(winfo.clocksymbol[1][1], SW_HIDE);				
			break;
		case 2:
			ShowWindow(winfo.clocksymbol[1][1], SW_SHOW);
			ShowWindow(winfo.clocksymbol[1][0], SW_HIDE);											
			break;		
		}
		dinfo.oldclock=dinfo.clocker;
	}
	SetWindowText(winfo.synmx, dinfo.synmxmsg);
	SetWindowText(winfo.synav, dinfo.synavmsg);
	SetWindowText(winfo.synmn, dinfo.synmnmsg);
	SetWindowText(winfo.mjdmx, dinfo.mjdmxmsg);
	SetWindowText(winfo.mjdav, dinfo.mjdavmsg);
	SetWindowText(winfo.mjdmn, dinfo.mjdmnmsg);
	dinfo.smconFE=dinfo.smcon;
}

// Threaded status display (in Simulation Mode)
void tstatusdisplay()
{
//	char line[LINESIZE];
	int i=0;
	if (launchpad)
	{		
		if (dinfo.shcon && !dinfo.solarsystem)
		{
			prepareTCPThread();
			ShowWindow(winfo.solarsystem[0], SW_SHOW);
			threaddata[1]->activation=true;			
			SetEvent(tcpconnect);
			SetWindowText(winfo.shcon, "Disconnect Orbiter");					
			dinfo.solarsystem=true;
			EnableWindow(winfo.srvip, false);
			EnableWindow(winfo.srvtcp, false);
			EnableWindow(winfo.clnudp, false);
			EnableWindow(winfo.username, false);
			EnableWindow(winfo.password, false);
			EnableWindow(winfo.chkcustom, false);
			EnableWindow(winfo.chkhairpinning, false);
			EnableWindow(winfo.chkcustomstun, false);
			EnableWindow(winfo.analyse, false);
		}
		else if (state!=S_STARTED)
		{
			ShowWindow(winfo.idcstartorbi, SW_HIDE);
		}
		if (!dinfo.shcon && dinfo.solarsystem)
		{
			EnableWindow(winfo.srvip, true);
			EnableWindow(winfo.srvtcp, true);
			if (dinfo.clientconfig!=0)
			{
				EnableWindow(winfo.clnudp, true);
				EnableWindow(winfo.chkhairpinning, true);
				EnableWindow(winfo.chkcustomstun, true);
			}
			EnableWindow(winfo.username, true);
			EnableWindow(winfo.password, true);
			EnableWindow(winfo.chkcustom, true);			
			EnableWindow(winfo.analyse, true);			
			ShowWindow(winfo.solarsystem[0], SW_HIDE);	
			threaddata[1]->activation=false;
			closesocket(threaddata[1]->com);							
			SetWindowText(winfo.shcon, "Connect Orbiter");			
			dinfo.solarsystem=false;
			if ((dinfo.quickLaunch || dinfo.container) && state==S_WAITSTOP) exit(0);
			state=S_STOPPED;
		}
		if (state==S_CLICKSTOP)
		{
			state=S_LEAVEDIALOG;
			if (!dinfo.quickLaunch && !dinfo.quickLeave && !dinfo.container)
			{
				SendMessage(winfo.idc_list, LB_RESETCONTENT, 0, 0);
				SetWindowText(winfo.idc_listtext, "Do you want to leave the OMP server? (select via double-click)");
				SendMessage(winfo.idc_list, LB_ADDSTRING, 0, (LPARAM) "YES - Vessels get deleted");
				SendMessage(winfo.idc_list, LB_ADDSTRING, 0, (LPARAM) " NO - Vessels persist");
				ShowWindow(winfo.idc_input, SW_HIDE);
				ShowWindow(winfo.idc_iam, SW_HIDE);
				ShowWindow(winfo.idc_list, SW_SHOW);
				ShowWindow(winfo.idc_listtext, SW_SHOW);
			}
			PostMessage(hMain, WM_NULL, 0, 0);
		}
		if (dinfo.clockpulse && !dinfo.clockpulsed)
		{
			ShowWindow(winfo.clockpulse[0], SW_SHOW);
			dinfo.clockpulsed=true;
		}
		if (!dinfo.clockpulse && dinfo.clockpulsed)
		{
			ShowWindow(winfo.clockpulse[0], SW_HIDE);
			dinfo.clockpulsed=false;
		}
		if (dinfo.clocker!=dinfo.oldclock)
		{
			switch (dinfo.clocker)
			{
			case 0:
				ShowWindow(winfo.clocksymbol[0][0], SW_HIDE);
				ShowWindow(winfo.clocksymbol[0][1], SW_HIDE);					
				break;
			case 1:
				ShowWindow(winfo.clocksymbol[0][0], SW_SHOW);
				ShowWindow(winfo.clocksymbol[0][1], SW_HIDE);				
				break;
			case 2:
				ShowWindow(winfo.clocksymbol[0][1], SW_SHOW);
				ShowWindow(winfo.clocksymbol[0][0], SW_HIDE);											
				break;		
			}
			dinfo.oldclock=dinfo.clocker;
		}
		SetWindowText(winfo.synmx, dinfo.synmxmsg);
		SetWindowText(winfo.synav, dinfo.synavmsg);
		SetWindowText(winfo.synmn, dinfo.synmnmsg);
		SetWindowText(winfo.mjdmx, dinfo.mjdmxmsg);
		SetWindowText(winfo.mjdav, dinfo.mjdavmsg);
		SetWindowText(winfo.mjdmn, dinfo.mjdmnmsg);
	}
	else statusupdate=true;
}

void printdim(char *text, RECT frame)
{
	char line[LINESIZE];
	sprintf(line, "%s\r\n   %+04d\r\n     |\r\n%+04d-|-%+04d\r\n     |\r\n    %+04d\r\n",
			text,
			frame.top,
			frame.left,
			frame.right,
			frame.bottom);	
	addtobuffer(line);	
}

void calcframe(HWND hDlg, int resource, RECT &wnd, cntrlinfo &frame, cntrlinfo &result, HWND &Hresult)
{
	RECT temp;
	//Gathering layout information of dynamic element IDC_IAM
	//Hresult..handle of text area
	//result ..dimension of frame around text area
	GetWindowRect(Hresult=GetDlgItem(hDlg, resource), &temp);
	result.width	=temp.right-temp.left;
	result.heigth	=temp.bottom-temp.top;
	result.left		=temp.left-wnd.left-frame.left;
	result.top		=temp.top-wnd.top-frame.top;
	result.right	=wnd.right-temp.right-frame.right;
	result.bottom	=wnd.bottom-temp.bottom-frame.bottom;	
}

#define POS_HIDE		-winfo.idctab.heigth-winfo.frame.top
#define POS_SHOW		0
#define POS_AUTOSBAR	-wnd.bottom+wnd.top+winfo.idcinput.height+winfo.frame.bottom
#define POS_AUTOTBAR	-wnd.bottom+wnd.top+winfo.frame.bottom

BOOL DockProc (HWND hDlg)
{
	RECT wnd;
	int height=winfo.frame.width*2;		
	GetWindowRect(hDlg, &wnd);		
	if (fullscreen)
	{
		//Ensure window minimum size in launchpad mode
		if (wnd.left!=winfo.oldwnd.left)
		{
				//Left moved
				if ((wnd.right-wnd.left)<height)
				{
					wnd.left=wnd.right-height;												
					SetWindowPos(hDlg, HWND_TOPMOST, wnd.left, wnd.top, wnd.right-wnd.left, wnd.bottom-wnd.top,0);	
				}
		}
		else
			if (wnd.right!=winfo.oldwnd.right)
			{
				//Right moved
				if ((wnd.right-wnd.left)<height)
				{
					wnd.right=wnd.left+height;	
					SetWindowPos(hDlg, HWND_TOPMOST, wnd.left, wnd.top, wnd.right-wnd.left, wnd.bottom-wnd.top,0);	
				}
			}		

		if (wnd.bottom!=winfo.oldwnd.bottom)
		{
				//Bottom moved
				if ((wnd.bottom-wnd.top)<winfo.frame.heigth)
				{
					wnd.bottom=wnd.top+winfo.frame.heigth;				
					SetWindowPos(hDlg, HWND_TOPMOST, wnd.left, wnd.top, wnd.right-wnd.left, wnd.bottom-wnd.top,0);	
				}
		}	
		else
			if (wnd.top!=winfo.oldwnd.top)
			{
				//Top moved
				if ((wnd.bottom-wnd.top)<winfo.frame.heigth)
				{
					wnd.top=wnd.bottom-winfo.frame.heigth;			
					SetWindowPos(hDlg, HWND_TOPMOST, wnd.left, wnd.top, wnd.right-wnd.left, wnd.bottom-wnd.top,0);	
				}
			}
	}
	else
	{
		//Keep window centered and on top in simulation mode
		if (wnd.left!=winfo.oldwnd.left)
		{
				//Left moved
				if ((wnd.right=winfo.screen.right/2-wnd.left)<winfo.frame.width)
				{
					wnd.left=winfo.screen.right/2-winfo.frame.width;
					wnd.right=winfo.frame.width+winfo.screen.right/2;
				}
				else
				{
					wnd.right+=winfo.screen.right/2;
				}
		}
		else
			if (wnd.right!=winfo.oldwnd.right)
			{
				//Right moved
				if ((wnd.left=wnd.right-winfo.screen.right/2)<winfo.frame.width)
				{
					wnd.left=winfo.screen.right/2-winfo.frame.width;
					wnd.right=winfo.frame.width+winfo.screen.right/2;
				}
				else
				{
					wnd.left=winfo.screen.right/2-wnd.left;
				}
			}		
		height=wnd.bottom-winfo.oldwnd.top;
		if (wnd.bottom!=winfo.oldwnd.bottom)
		{
				//Bottom moved
				if (height<winfo.frame.heigth)
				{
					height=winfo.frame.heigth;
				}				
		}	
		if (!lostfocus) SetWindowPos(hDlg, HWND_TOPMOST, wnd.left, POS_SHOW, wnd.right-wnd.left, height,0);
		else
		{
			if (!hide)
			{
				SetWindowPos(hDlg, HWND_TOPMOST, wnd.left, POS_SHOW, wnd.right-wnd.left, height,0);		
			}
			else
			{	
				SetWindowPos(hDlg, HWND_TOPMOST, winfo.oldwnd.left, winfo.oldwnd.top, winfo.oldwnd.right-winfo.oldwnd.left, winfo.oldwnd.bottom-winfo.oldwnd.top,0);		
			}
		}
	}
	return TRUE;
}

int SplitNumber(int levelsToSplit, char **numberStringPointer, int count)
{
	int i, highpos=0;
	char highest=0;	
	char *numberString=numberStringPointer[0];
	
	//Last level?
	if (levelsToSplit<=1) return count;
	//Search highest number
	for(i=0;i<count;i++)
		if (numberString[i]>=highest)
		{
			highest=numberString[i];
			highpos=i;
		}
	if (highest<2) return count;
	//Copy numbers till highest number
	char *intString=new char[count+1];
	for(i=0;i<highpos;i++) intString[i]=numberString[i];
	//Enter splitted numbers
	if (((count>>1)<<1)==count)
	{
		//Even level
		intString[highpos]=(i=highest>>1);	//Lower number on the left
		if ((i<<1)!=highest) i+=1;
		intString[highpos+1]=i;				//Higher number on the right
	}
	else
	{
		//Odd level
		intString[highpos+1]=(i=highest>>1);//Lower number on the right
		if ((i<<1)!=highest) i+=1;
		intString[highpos]=i;				//Higher number on the left
	}
	//Copy numbers from highest number
	for(i=highpos+1;i<count;i++) intString[i+1]=numberString[i];
	//Split next level
	delete [] numberString;
	numberString=0;
	numberStringPointer[0]=intString;	
	return SplitNumber(levelsToSplit-1, numberStringPointer, count+1);	
}

BOOL DynaLayoutProc (HWND hDlg)
{
	RECT wnd;
	TCITEM tabpage;	
	int tabpointer, i, j;

	//Process dynamic layout elements
	GetClientRect(hDlg, &wnd);
	SetWindowPos(winfo.idc_iam,
				HWND_TOP,
				winfo.idciam.left,
				winfo.idciam.top,
				wnd.right-winfo.idciam.left-winfo.idciam.right,
				wnd.bottom-winfo.idciam.top-winfo.idciam.bottom,
				0);		
	SetWindowPos(winfo.idc_list,
				HWND_TOP,
				winfo.idclist.left,
				winfo.idclist.top,
				wnd.right-winfo.idclist.left-winfo.idclist.right,
				wnd.bottom-winfo.idclist.top-winfo.idclist.bottom,
				0);		
	SetWindowPos(winfo.idc_listtext,
				HWND_TOP,
				winfo.idclisttext.left,
				winfo.idclisttext.top,
				wnd.right-winfo.idclisttext.left-winfo.idclisttext.right,
				winfo.idclisttext.heigth,
				0);	
	SetWindowPos(winfo.idc_input,
				HWND_TOP,
				winfo.idcinput.left,
				winfo.idcinput.top,
				wnd.right-winfo.idcinput.left-winfo.idcinput.right,
				winfo.idcinput.heigth,
				0);	
	SetWindowPos(winfo.id_close,
				HWND_TOP,
				wnd.right-winfo.idclose.right-winfo.idclose.width,
				winfo.idclose.top,
				winfo.idclose.width,
				winfo.idclose.heigth,
				0);
	SetWindowPos(winfo.id_updown,
				HWND_TOP,
				wnd.right-winfo.idupdown.right-winfo.idupdown.width,
				winfo.idupdown.top,
				winfo.idupdown.width,
				winfo.idupdown.heigth,
				0);
	//Process dynamic tab control splitting
	if (hide)
	{
		for(j=0;j<5;j++)
		{
			ShowWindow(winfo.idc_tab[j], SW_HIDE);
			SendMessage(winfo.idc_tab[j], TCM_DELETEALLITEMS, 0, 0);
		}
		currenttabs=0;
	}
	else
	{
		i=currenttabs;
		currenttabs=(int) wnd.right/winfo.idctab.width;		
		if (i!=currenttabs)
		{		
			//Split tabpages
			if (tabnumbers!=NULL)
			{
				delete [] tabnumbers;
				tabnumbers=0;
			}
			tabnumbers=new char[1];
			tabnumbers[0]=TABPAGES;
			currenttabs=SplitNumber(currenttabs, &tabnumbers, 1);
			//Change tab amount
			for(j=0;j<i;j++)
				SendMessage(winfo.idc_tab[j], TCM_DELETEALLITEMS, 0, 0);
			if (i<currenttabs)
				for(j=i;j<currenttabs;j++)
					ShowWindow(winfo.idc_tab[j], SW_SHOW);
			if (currenttabs<i)
				for(j=currenttabs;j<i;j++)
					ShowWindow(winfo.idc_tab[j], SW_HIDE);
			//Relabel tabpages
			tabpage.mask=TCIF_TEXT;
			tabpointer=0;
			for(i=0;i<currenttabs;i++)
			{
				currentpage[i]=tabpointer;
				for(j=0; j<tabnumbers[i]; j++)
				{
					tabpage.pszText=tabnames[tabpointer++];
					SendMessage(winfo.idc_tab[i], TCM_INSERTITEM, j, (LPARAM) &tabpage);
				}
			}					
		}
		j=(int)((wnd.right/currenttabs-winfo.idctab.width)/2);
		int k=(int)(wnd.right/currenttabs);
		for (i=0; i<currenttabs; i++)
			SetWindowPos(winfo.idc_tab[i],
					HWND_TOP,
					j+i*k,
					winfo.idctab.top,
					winfo.idctab.width,
					winfo.idctab.heigth,
					0);
	}
	InvalidateRect(hDlg, &wnd, TRUE);
	return TRUE;
}

BOOL TabPageAdjustProc()
{
	RECT wnd;
	for(int i=0; i<currenttabs; i++)
	{
		GetWindowRect(winfo.idc_tab[i], &wnd);		
		TabCtrl_AdjustRect(winfo.idc_tab[i], FALSE, &wnd);
		SetWindowPos(hTabPage[currentpage[i]],
			HWND_TOPMOST,
			wnd.left,
			wnd.top,
			wnd.right-wnd.left,
			wnd.bottom-wnd.top,
			0);
	}
	return TRUE;
}

int GetWindowTextReduced(HWND hWnd, LPSTR lpString, int nMaxCount, bool &entered)
{
	char line2[LINESIZE];
	int i=GetWindowTextA (hWnd, line2, nMaxCount);	
	entered=false;
	if (i<=0) return 0;
	
	int k=strlen(line2);
	int wt=0, wt2=0;

	for(;wt2<k;wt2++)
		if (line2[wt2]==0xa || line2[wt2]==0xd) entered=true;
		else lpString[wt++]=line2[wt2];
	lpString[wt++]=0xa;
	lpString[wt++]=0xd;
	lpString[wt]=0;

	return wt;
}

// ==============================================================
// Windows message handler for the dialog box

BOOL CALLBACK MsgProc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{		
	RECT wnd, orgframe;	
	bool fr;
	int i=0, j=0;
	byte k;
	TCITEM tabpage;
	LPNMHDR notifymessage;
	char line[LINESIZE];
	double stamp, temp;
	VECTOR3 pos;
	struct IDload *entry;
	VESSELSTATUS2 targetstate;
	CHashData *entry1;
	unsigned long int vesselsize;
	struct vesselinfo *entry4;
		
	targetstate.version=2;
	targetstate.flag=0;

	if (state==S_LEAVEDIALOG && (dinfo.quickLaunch || dinfo.quickLeave || dinfo.container))
	{
		state=S_WAITSTOP;
		ShowWindow(winfo.idc_iam, SW_SHOW);
		ShowWindow(winfo.idc_list, SW_HIDE);
		ShowWindow(winfo.idc_listtext, SW_HIDE);
		ShowWindow(winfo.idc_input, SW_SHOW);
		//send leave command
		sprintf(line, "lv \"%s\" \"%s\"", dinfo.username, dinfo.password);
		wrapper.Log(100, line);
		k=(int)dinfo.shsfxasc[0];
		i=strlen(line);
		//exchange input suffix with specified suffix
		for(j=1;j<=k;j++)
			//do not insert a NULL-character due to it's string terminating nature in C++
			//instead such, insert a 0x03-character (end of text)
			if ((line[i-1+j]=dinfo.shsfxasc[j])==0x00) line[i-1+j]=0x03;
		//add a string termination
		line[i-1+j]=0x00;
		EnterCriticalSection(&(threaddata[1]->mutex));		
		if (send(threaddata[1]->com, line, strlen(line), 0) == SOCKET_ERROR)
		{
		LeaveCriticalSection(&(threaddata[1]->mutex));
			sprintf (line, "Error sending to %s:%d..", dinfo.srvipchar,ntohs(threaddata[1]->remote_addr.sin_port));
			logging(20,line);								
		}
		else LeaveCriticalSection(&(threaddata[1]->mutex));						
	}

	switch (uMsg) {
	case WM_COMMAND:
		switch (LOWORD (wParam)) {

		case IDC_INPUT:
						
			if (HIWORD(wParam)==EN_CHANGE)
			{
				bool wte;
				i=GetWindowTextReduced (winfo.idc_input, line, LINESIZE-2-(k=(byte)dinfo.shsfxasc[0]), wte);
				if (wte)
				{					
					switch (state)
					{
					case S_CHOOSEDIALOG:												
						line[--i]=0x00;
						line[--i]=0x00;
						if (i==0)
						{
							sprintf(line, "%s", dinfo.username);
							i=strlen(line);
						}
						if (dinfo.vesselname!=0)
						{
							delete [] dinfo.vesselname;
							dinfo.vesselname=0;
						}
						dinfo.vesselname=new char[i+1];
						strcpy(dinfo.vesselname, line);
						entry1=dinfo.vessellist->content(0);
						if (entry1->key!=0)
						{
							j=SendMessage(winfo.idc_list, LB_GETCURSEL, 0, 0);
							struct vesselinfo *vi=(vesselinfo*)entry1->data;
							while(true)
							{
								vi=(vesselinfo*)entry1->data;
								if (vi->ignore) j++;
								if (j--==0 || entry1->next!=0) break;
								entry1=entry1->next;
							}
							dinfo.vesselclass=vi->casedName;
						}
						else dinfo.vesselclass=0;
						state=S_LOCDIALOG;
						SendMessage(winfo.idc_list, LB_RESETCONTENT, 0, 0);
						entry1=dinfo.locationlist->content(0);
						if (entry1->key!=0)
						{
							while (entry1!=0)
							{
								SendMessage(winfo.idc_list, LB_ADDSTRING, 0, (LPARAM) entry1->data);
								entry1=entry1->next;
							}
							SendMessage(winfo.idc_list, LB_SETCURSEL, (WPARAM) 0, 0);							
							SetWindowText(winfo.idc_listtext, "Choose a location by double-clicking on it:");
							ShowWindow(winfo.idc_iam, SW_HIDE);
							ShowWindow(winfo.idc_input, SW_HIDE);
							ShowWindow(winfo.idc_list, SW_SHOW);
							ShowWindow(winfo.idc_listtext, SW_SHOW);
						}
						else
						{
							ShowWindow(winfo.idc_list, SW_HIDE);
							ShowWindow(winfo.idc_listtext, SW_HIDE);
							ShowWindow(winfo.idc_input, SW_SHOW);
							ShowWindow(winfo.idc_iam, SW_SHOW);
							state=S_READLINES;
							i=sprintf(line, "chk f");
							wrapper.Log(100, line);
							//send answer
							k=(int)dinfo.shsfxasc[0];							
							//exchange input suffix with specified suffix
							for(j=1;j<=k;j++)
								//do not insert a NULL-character due to it's string terminating nature in C++
								//instead such, insert a 0x03-character (end of text)
								if ((line[i-1+j]=dinfo.shsfxasc[j])==0x00) line[i-1+j]=0x03;
							//add a string termination
							line[i-1+j]=0x00;
							EnterCriticalSection(&(threaddata[1]->mutex));							
							if (send(threaddata[1]->com, line, strlen(line), 0) == SOCKET_ERROR)
							{
							LeaveCriticalSection(&(threaddata[1]->mutex));
								sprintf (line, "Error sending to %s:%d..", dinfo.srvipchar,ntohs(threaddata[1]->remote_addr.sin_port));
								logging(20,line);								
							}
							else LeaveCriticalSection(&(threaddata[1]->mutex));

						}						
						break;
					default:					
						//********** Text input event occured **************
						//Check for escape characters
						if (line[0]==dinfo.shsysesc[0])
						{
							//****** Commando interpreter ******
							wrapper.Add(line);
							if (strncmp(line+1, "clock", 5)==0)
							{
								if (dinfo.clockcontroller)
								{
									dinfo.clockcontroller=false;
									addtobuffer("Clock control has been set to OFF.\r\n");
								}
								else
								{
									dinfo.clockcontroller=true;
									addtobuffer("Clock control has been set to ON.\r\n");
								}
							}							
							else if (strncmp(line+1, "echo", 4)==0)
							{
								if (dinfo.localecho)
								{
									dinfo.localecho=false;
									addtobuffer("Local echo has been set to OFF.\r\n");
								}
								else
								{
									dinfo.localecho=true;
									addtobuffer("Local echo has been set to ON.\r\n");
								}
							}
							else if (strncmp(line+1, "irc", 3)==0)
							{
								if (dinfo.ircpong)
								{
									dinfo.ircpong=false;
									addtobuffer("Automatic IRC ping reply has been set to OFF.\r\n");
								}
								else
								{
									dinfo.ircpong=true;
									addtobuffer("Automatic IRC ping reply has been set to ON.\r\n");
								}								
							}
							else if (strncmp(line+1, "state", 5)==0)
							{
								if (dinfo.clockcontroller)
									 addtobuffer("        Clock control is: ON.\r\n");
								else addtobuffer("        Clock control is: OFF.\r\n");								
								if (dinfo.localecho)
									 addtobuffer("           Local echo is: ON.\r\n");
								else addtobuffer("           Local echo is: OFF.\r\n");
								if (dinfo.ircpong)
									 addtobuffer("IRC ping reply (pong) is: ON.\r\n");
								else addtobuffer("IRC ping reply (pong) is: OFF.\r\n");
								if (dinfo.radiosim)
									 addtobuffer("  Radio delay simulation: ON.\r\n");
								else addtobuffer("  Radio delay simulation: OFF.\r\n");
								   sprintf(line, " Clock control parameter: Kp=%f Ki=%f Kd=%f Eps=%fs\r\n", dinfo.Kp, dinfo.Ki, dinfo.Kd, dinfo.tolerance);
								addtobuffer(line);
								   sprintf(line, "  Sync control parameter: Rint=%dms Tdel=%dms PTPdiv=%d\r\n", dinfo.cursyn, dinfo.cursynicon, dinfo.ptpDivider);
								addtobuffer(line);
								   sprintf(line, "        Sender parameter: Delay=%dms Block=%dms Idle=%d steps\r\n", dinfo.curdel, dinfo.curblk, dinfo.idler);
								addtobuffer(line);
								   sprintf(line, "            GC parameter: Delay=%dms\r\n", dinfo.curgc);
								addtobuffer(line);
								   sprintf(line, "   Radio delay frequency: Freq=%fMHz\r\n", dinfo.radiofreq/1e6);
								addtobuffer(line);
								   sprintf(line, "       Missile parameter: offset=%lf,%lf,%lf keys=%.2X,%.2X,%.2X display=%lf,%lf,%lf\r\n", dinfo.missileOffset.x,dinfo.missileOffset.y,dinfo.missileOffset.z,dinfo.missileLaunchKey,dinfo.missileTargetKey,dinfo.missileModifiers,dinfo.missileDisplaySize,dinfo.missileDisplayX,dinfo.missileDisplayY);
								addtobuffer(line);
								sprintf(line,"%p",oapiGetFocusObject());
								EnterCriticalSection(&hashing);
									if (clientData.GlobalsByHandle!=0)
									{
										entry=(struct IDload *)clientData.GlobalsByHandle->get(line);
										if (entry!=0)
										{										
											if (entry->MasterID==0)
								   sprintf(line, "Current vessel master ID: none\r\n");
											else
								   sprintf(line, "Current vessel master ID: %d\r\n", entry->MasterID);
										}
										else
								   sprintf(line, "Current vessel master ID: undetectable\r\n");
									}
									else
								   sprintf(line, "Current vessel master ID: undetectable\r\n");
								LeaveCriticalSection(&hashing);
								addtobuffer(line);
							}
							else if (strncmp(line+1, "mjd", 3)==0)
							{
								i=atoi(line+4);
								stamp=get_global_timestamp();
								EnterCriticalSection(&image);
									gimage.MJD=dinfo.snapshotMJD+(double)i/86400;
									gimage.last_MJD_update=stamp;
								LeaveCriticalSection(&image);
								sprintf(line, "Received global MJD set to be local off %d seconds.\r\n", i);
								addtobuffer(line);
							}
							else if (strncmp(line+1, "Kp", 2)==0)
							{
								dinfo.Kp=atof(line+3);
								sprintf(line, "Factor Kp set to %f.\r\n", dinfo.Kp);
								addtobuffer(line);
							}
							else if (strncmp(line+1, "Ki", 2)==0)
							{
								dinfo.Ki=atof(line+3);		
								sprintf(line, "Factor Ki set to %f.\r\n", dinfo.Ki);
								addtobuffer(line);
							}
							else if (strncmp(line+1, "Kd", 2)==0)
							{
								dinfo.Kd=atof(line+3);		
								sprintf(line, "Factor Kd set to %f.\r\n", dinfo.Kd);
								addtobuffer(line);
							}
							else if (strncmp(line+1, "Eps", 3)==0)
							{
								dinfo.tolerance=atof(line+4);		
								sprintf(line, "Tolerance epsilon set to %f.\r\n", dinfo.tolerance);
								addtobuffer(line);
							}
							else if (strncmp(line+1, "Delay", 5)==0)
							{
								dinfo.curdel=atoi(line+6);		
								sprintf(line, "State information sender interval set to %dms.\r\n", dinfo.curdel);
								addtobuffer(line);
							}
							else if (strncmp(line+1, "Block", 5)==0)
							{
								dinfo.curblk=atoi(line+6);		
								sprintf(line, "State information sender block interval set to %dms.\r\n", dinfo.curblk);
								addtobuffer(line);
							}
							else if (strncmp(line+1, "Idle", 4)==0)
							{
								dinfo.idler=atoi(line+5);		
								sprintf(line, "State information sender idle steps set to %d.\r\n", dinfo.idler);
								addtobuffer(line);
							}
							else if (strncmp(line+1, "Rint", 4)==0)
							{
								dinfo.cursyn=atoi(line+5);
								wrapper.Update();
								sprintf(line, "Resynchronization interval set to %dms.\r\n", dinfo.cursyn);
								addtobuffer(line);
							}
							else if (strncmp(line+1, "Tdel", 4)==0)
							{
								dinfo.cursynicon=atoi(line+5);
								wrapper.Update();
								sprintf(line, "SNTP sample delay set to %dms.\r\n", dinfo.cursynicon);
								addtobuffer(line);
							}
							else if (strncmp(line+1, "PTPdiv", 6)==0)
							{
								dinfo.ptpDivider=atoi(line+7);
								sprintf(line, "PTP divider set to %d keep-alives.\r\n", dinfo.ptpDivider);
								addtobuffer(line);
							}
							else if (strncmp(line+1, "Radio", 5)==0)
							{
								if (dinfo.radiosim)
								{
									dinfo.radiosim=false;
									addtobuffer("Simulation of radio propagation has been deactivated.\r\n");
								}
								else
								{
									dinfo.radiosim=true;
									addtobuffer("Simulation of radio propagation activated.\r\n");
								}
							}
							else if (strncmp(line+1, "Freq", 4)==0)
							{
								dinfo.radiofreq=atof(line+5);
								if (dinfo.radiofreq<0) dinfo.radiofreq=2205e6;
								sprintf(line, "Simulated radio broadcasts at %fMHz.\r\n", dinfo.radiofreq/1e6);
								addtobuffer(line);
							}
							else if (strncmp(line+1, "GCD", 3)==0)
							{
								dinfo.curgc=atoi(line+4);		
								sprintf(line, "Garbage collector delay set to %dms.\r\n", dinfo.curgc);
								addtobuffer(line);
							}
							else if (strncmp(line+1, "jump to", 7)==0)
							{
								j=atoi(line+8);
								//Jump to vessel coordinates + 1000m in opposite to GBody direction
								EnterCriticalSection(&hashing);
									if (clientData.GlobalsByID!=0)										
									{
										sprintf(line, "T%d", j);
										if ((entry=(struct IDload *)clientData.GlobalsByID->get(line))!=0)
										{
											EnterCriticalSection(&orbiter);
												targetstate=dinfo.snapshot[entry->object];												
												temp=length(targetstate.rpos);
												if (!(temp>0e0)) temp=FLT_MIN;
												temp=1000.0/temp;
												targetstate.rpos+=targetstate.rpos*temp;
												VESSEL *focus=NULL;
												OBJHANDLE focobj=oapiGetFocusObject();
												if (oapiIsVessel(focobj)) focus=oapiGetFocusInterface();
												if (focus!=NULL)
												{
													sprintf(line,"%p",focus->GetHandle());
													if (clientData.GlobalsByHandle!=0)
													{
														entry=(struct IDload *)clientData.GlobalsByHandle->get(line);
														if (entry!=0)
														{											
															entry->lastJump=get_global_timestamp();
															entry->vessel->DefSetStateEx(&targetstate);
															sprintf(line, "Jumped...\r\n");
														}
														else sprintf(line, "Unable to get focused global ID!\r\n");
													}
													else sprintf(line, "Unable to get global ID list!\r\n");
												}
												else sprintf(line, "Unable to get focused object!\r\n");
											LeaveCriticalSection(&orbiter);											
										}
										else sprintf(line, "Only tracked vessels are allowed!\r\n");
									}
									else sprintf(line, "Simulation not connected yet!\r\n");
									addtobuffer(line);
								LeaveCriticalSection(&hashing);
									i=sprintf(line, "trk off %d", j);
									wrapper.Log(100, line);
									//send answer
									k=(int)dinfo.shsfxasc[0];							
									//exchange input suffix with specified suffix
									for(j=1;j<=k;j++)
										//do not insert a NULL-character due to it's string terminating nature in C++
										//instead such, insert a 0x03-character (end of text)
										if ((line[i-1+j]=dinfo.shsfxasc[j])==0x00) line[i-1+j]=0x03;
									//add a string termination
									line[i-1+j]=0x00;
									EnterCriticalSection(&(threaddata[1]->mutex));									
									if (send(threaddata[1]->com, line, strlen(line), 0) == SOCKET_ERROR)
									{
									LeaveCriticalSection(&(threaddata[1]->mutex));
										sprintf (line, "Error sending to %s:%d..", dinfo.srvipchar,ntohs(threaddata[1]->remote_addr.sin_port));
										logging(20,line);
									}
									else LeaveCriticalSection(&(threaddata[1]->mutex));
							}
							else if (strncmp(line+1, "master", 6)==0)
							{								
								EnterCriticalSection(&hashing);
									VESSEL *focus=NULL;
									OBJHANDLE focobj=oapiGetFocusObject();
									if (oapiIsVessel(focobj)) focus=oapiGetFocusInterface();
									if (focus!=0)
									{
										char hndl[40];
										sprintf(hndl,"%p",focus->GetHandle());
										if (clientData.GlobalsByHandle!=0)
										{
											entry=(struct IDload *)clientData.GlobalsByHandle->get(hndl);
											if (entry!=0)
											{
												entry->MasterID=atoi(line+7);
												if (entry->MasterID==0)
													sprintf(line, "No master for current vessel.\r\n");
												else
													sprintf(line, "Current vessel master ID set to %d.\r\n", entry->MasterID);
											}
											else
												sprintf(line, "Unable to get focused global ID!\r\n");
										}
										else
											sprintf(line, "Unable to get global ID list!\r\n");
									}
									else
										sprintf(line, "Unable to get focused object!\r\n");
								LeaveCriticalSection(&hashing);								
								addtobuffer(line);
							}
							else if (strncmp(line+1, "version", 7)==0)
							{								
								sprintf(line, "%s\r\n", versionlabel);
								addtobuffer(line);
							}
							else if (strncmp(line+1, "missile", 7)==0)
							{
								 if (strncmp(line+8, " offset", 7)==0)
								 {
									 sscanf(line+15, "%lf %lf %lf", &dinfo.missileOffset.x,&dinfo.missileOffset.y,&dinfo.missileOffset.z);
									 sprintf(line, "Missile launch offset set to %lf,%lf,%lf\r\n", dinfo.missileOffset.x,dinfo.missileOffset.y,dinfo.missileOffset.z);
									 addtobuffer(line);
								 }
								 else
								 if (strncmp(line+8, " keys", 5)==0)
								 {
									 sscanf(line+13, "%X %X %X", &dinfo.missileLaunchKey,&dinfo.missileTargetKey,&dinfo.missileModifiers);
									 sprintf(line, "Missile key parameters set to %.2X,%.2X,%.2X\r\n", dinfo.missileLaunchKey,dinfo.missileTargetKey,dinfo.missileModifiers);
									 addtobuffer(line);
								 }
								 else
								 if (strncmp(line+8, " display", 8)==0)
								 {
									 sscanf(line+16, "%lf %lf %lf", &dinfo.missileDisplaySize,&dinfo.missileDisplayX,&dinfo.missileDisplayY);
									 sprintf(line, "Missile target display parameters set to %lf,%lf,%lf\r\n", dinfo.missileDisplaySize,dinfo.missileDisplayX,dinfo.missileDisplayY);
									 addtobuffer(line);
									 oapiAnnotationSetSize(dinfo.missileDisplay, dinfo.missileDisplaySize);
								 }
								 else
								 {
									 addtobuffer("missile subcommands:\r\n\r\n");
									 addtobuffer("offset <x> <y> <z>                 sets offset of missile launch point\r\n\r\n");
									 addtobuffer("keys <launch> <target> <modifiers> sets keys for missile launch and target as well as modifiers - all values are hexadecimal, consult OAPI key values, modifiers is bitmap of left Shift (LSB), right Shift, left Ctrl, right Ctrl, left Alt, right Alt (MSB)\r\n\r\n");
									 addtobuffer("display <size> <x> <y>             sets missile target display size and offset\r\n");
								 }
							}
							else if (strncmp(line+1, "help", 4)==0)
							{	
								addtobuffer("clock        toggles the clock controller on and off - this way you can turn off time compression mechanism if you are connected to a server - highly experimental!\r\n\r\n");
								addtobuffer("echo         toggles the local shell echo on and off - similar to the server side echo ; it is off by default\r\n\r\n");
								addtobuffer("irc          toggles the automatic irc reply (ping/pong mechanism) on and off - this is useful if you connect to a IRC server (some may remember...) ; it is off by default - OBSOLETE\r\n\r\n");
								addtobuffer("state        displays information about internal settings\r\n\r\n");
								addtobuffer("mjd <s>      simulates a server MJD with the given offset to the current client MJD - useful for tuning the PID algorithm\r\n\r\n");
								addtobuffer("Kp <factor>  sets the PID proportional factor\r\n\r\n");
								addtobuffer("Kd <factor>  sets the PID integral factor\r\n\r\n");
								addtobuffer("Ki <factor>  sets the PID differential factor\r\n\r\n");
								addtobuffer("Eps <s>      sets the PID tolerance epsilon - below this threshold, the clients MJD is considered synchronous\r\n\r\n");
								addtobuffer("Delay <ms>   sets the state information sender interval\r\n\r\n");
								addtobuffer("Block <ms>   sets the state information sender block interval\r\n\r\n");
								addtobuffer("Idle <steps> sets the state information sender idle steps\r\n\r\n");
								addtobuffer("Rint <ms>    sets the resynchronization interval - the time between each SNTP sequences for clock skew analysis\r\n\r\n");
								addtobuffer("Tdel <ms>    sets the SNTP sample delay - the time between each SNTP sample within a resynchronization sequence\r\n\r\n");
								addtobuffer("Radio        toggles radio transmission delay simulation on and off - this is a proof-of-concept feature and implements a client-side solution to the delay simulation problem ; it is off by default\r\n\r\n");
								addtobuffer("Freq <Hz>    sets the radio transceiver - if delay simulation is on, a message will be only displayed at the proper time, if it was sent at the currently selected frequency just the same as it is in real life\r\n\r\n");
								addtobuffer("GCD <ms>     sets the garbage collector sample time - this is NOT the timeout value for idle streams (fixed to 5 seconds for now), it’s just the interval of garbage collector checks\r\n\r\n");
								addtobuffer("jump to <ID> Jumps the focused vessel to the vessel specified by the given ID number. This must be a currently tracked vessel (see track commando). After the jump, tracking will be switched off automatically.\r\n\r\n");
								addtobuffer("master <ID>  Sets the relative state vector master for the focused vessel to the specified ID number. This is useful in orbital clusters, were many clients want to see each other in the correct position. All clients must agree to a single master and set this value appropriately to maximize the cluster experience. No harm will be done to the system if this is not used in clusters, just relative positions can be wired. If the specified master is not in range, normal transmission will be used.\r\n\r\n");
								addtobuffer("version      Displays the version of the client.\r\n\r\n");
								addtobuffer("missile      Displays the state of the missile toy.\r\n\r\n");
								addtobuffer("help         Displays this help information\r\n");
							}							
						}
						else
						{
							if (line[0]!=dinfo.shpfxesc[0])
							{
								if (dinfo.radiosim && !launchpad)
								{
									oapiGetGlobalPos(oapiGetFocusObject(), &pos);
									j=sprintf(line, "RADIO:%f %f %f %f %f ",
										get_global_timestamp(),
										dinfo.radiofreq,
										pos.x, pos.y, pos.z);
									i=GetWindowTextReduced (winfo.idc_input, line+j, LINESIZE-2-k-i, wte)+j;
								}
								else if (dinfo.shprefix[0]!=0x00)
								{
									
									{
										//Add prefix
										j=sprintf(line, "%s", dinfo.shprefix);							
										i=GetWindowTextReduced (winfo.idc_input, line+j, LINESIZE-2-k-i, wte)+j;
									}
								}
							}
							else
							{
								wrapper.Add(line);
								//Remove escape character, if not NULL
								if (dinfo.shpfxesc[0]!=0x00)
									i=sprintf(line, "%s", line+1);
							}
							wrapper.Log(101, line);
							//exchange input suffix with specified suffix
							for(j=1;j<=k;j++)
								//do not insert a NULL-character due to it's string terminating nature in C++
								//instead such, insert a 0x03-character (end of text)
								if ((line[i-3+j]=dinfo.shsfxasc[j])==0x00) line[i-3+j]=0x03;
							//add a string termination
							line[i-3+j]=0x00;
							if (dinfo.localecho) addtobuffer(line);
							EnterCriticalSection(&(threaddata[1]->mutex));							
							if (send(threaddata[1]->com, line, strlen(line), 0) == SOCKET_ERROR)
							{
							LeaveCriticalSection(&(threaddata[1]->mutex));
								sprintf (line, "Error sending to %s:%d..", dinfo.srvipchar,ntohs(threaddata[1]->remote_addr.sin_port));
								logging(20,line);								
							}
							else LeaveCriticalSection(&(threaddata[1]->mutex));
						}					
						break;
					}
					//delete input line
					SetWindowText(winfo.idc_input, "");
				}
			}
			return TRUE;

		case IDCLOSE:
			CloseDlg();
			DeleteObject(winfo.idiclose);
			DeleteObject(winfo.ididown);
			DeleteObject(winfo.idiup);
			return TRUE;

		case IDUPDOWN:
			GetWindowRect(hDlg, &wnd);
			if (!hide)
			{
				SendMessage(winfo.id_updown,(UINT) BM_SETIMAGE, (WPARAM) IMAGE_ICON, (LPARAM) winfo.ididown);
				for(i=0;i<currenttabs;i++) ShowWindow(hTabPage[currentpage[i]], SW_HIDE);				
				winfo.idciam.top+=POS_HIDE;
				winfo.idclist.top+=POS_HIDE;
				winfo.idclisttext.top+=POS_HIDE;
				winfo.idcinput.top+=POS_HIDE;
				winfo.idclose.top+=POS_HIDE;
				winfo.idupdown.top+=POS_HIDE;
				wnd.bottom+=POS_HIDE;
				winfo.oldwnd.bottom+=POS_HIDE;
				winfo.frame.heigth+=POS_HIDE;
			}
			else
			{
				winfo.idciam.top-=POS_HIDE;
				winfo.idclist.top-=POS_HIDE;
				winfo.idclisttext.top-=POS_HIDE;
				winfo.idcinput.top-=POS_HIDE;
				winfo.idclose.top-=POS_HIDE;
				winfo.idupdown.top-=POS_HIDE;
				wnd.bottom-=POS_HIDE;
				winfo.oldwnd.bottom-=POS_HIDE;
				winfo.frame.heigth-=POS_HIDE;
			}
			SetWindowPos(hDlg, HWND_TOPMOST, wnd.left, wnd.top, wnd.right-wnd.left, wnd.bottom-wnd.top, 0);
			hide=!hide;
			DynaLayoutProc(hDlg);
			TabPageAdjustProc();
			if (!hide)
			{
				SendMessage(winfo.id_updown,(UINT) BM_SETIMAGE, (WPARAM) IMAGE_ICON, (LPARAM) winfo.idiup);
				for(i=0;i<currenttabs;i++) ShowWindow(hTabPage[currentpage[i]], SW_SHOW);				
			}
			UpdateWindow(hDlg);
			dinfo.rcvbufchange=true;
			return TRUE;

		case IDC_LIST:
			if (HIWORD(wParam)==LBN_DBLCLK)
			{
				fr=false;
				switch (state)
				{
				case S_LEAVEDIALOG:
					state=S_WAITSTOP;					
					ShowWindow(winfo.idc_iam, SW_SHOW);
					if (SendMessage(winfo.idc_list, LB_GETCURSEL, 0, 0)==0)
					{
						//send leave command
						sprintf(line, "lv \"%s\" \"%s\"", dinfo.username, dinfo.password);						
					}
					else
					{
						//send exit command
						sprintf(line, "exit");
						addtobuffer("Logged out...\r\n");
						state=S_STOPPED;
					}
					ShowWindow(winfo.idc_list, SW_HIDE);
					ShowWindow(winfo.idc_listtext, SW_HIDE);
					ShowWindow(winfo.idc_input, SW_SHOW);					
					fr=true;
					break;
				case S_LOCDIALOG:
					state=S_READLINES;					
					ShowWindow(winfo.idc_iam, SW_SHOW);
					j=SendMessage(winfo.idc_list, LB_GETCURSEL, 0, 0);
					delete [] dinfo.vessellocation;
					dinfo.vessellocation=0;
					entry1=dinfo.locationlist->content(0);
					for(;j>0;j--) if (entry1->next!=0) entry1=entry1->next;
					dinfo.vessellocation=entry1->key;
					sprintf(line, "chk l %s", dinfo.vessellocation);
					ShowWindow(winfo.idc_list, SW_HIDE);
					ShowWindow(winfo.idc_listtext, SW_HIDE);
					ShowWindow(winfo.idc_input, SW_SHOW);					
					fr=true;
					break;
				}
				if (fr)
				{
					//send answer
					wrapper.Log(100, line);
					k=(int)dinfo.shsfxasc[0];
					i=strlen(line);
					//exchange input suffix with specified suffix
					for(j=1;j<=k;j++)
						//do not insert a NULL-character due to it's string terminating nature in C++
						//instead such, insert a 0x03-character (end of text)
						if ((line[i-1+j]=dinfo.shsfxasc[j])==0x00) line[i-1+j]=0x03;
					//add a string termination
					line[i-1+j]=0x00;
					EnterCriticalSection(&(threaddata[1]->mutex));					
					if (send(threaddata[1]->com, line, strlen(line), 0) == SOCKET_ERROR)
					{
					LeaveCriticalSection(&(threaddata[1]->mutex));
						sprintf (line, "Error sending to %s:%d..", dinfo.srvipchar,ntohs(threaddata[1]->remote_addr.sin_port));
						logging(20,line);								
					}
					else LeaveCriticalSection(&(threaddata[1]->mutex));
				}				
			}
			return TRUE;
		}		
		break;	
	case WM_SHOWWINDOW:		
		if (wParam==TRUE)
		{			
			//Move designed dialog to top and center of screen
			hide=false;
			GetWindowRect(winfo.hscreen=GetDesktopWindow(), &winfo.screen);
			GetWindowRect(hDlg, &wnd);
			SetWindowPos(hDlg, HWND_TOPMOST, (winfo.screen.right-wnd.right+wnd.left)/2, 0, wnd.right-wnd.left, wnd.bottom-wnd.top, 0);
			//Getting window frame dimension		
			//frame.. dimension of frame around client area		
			GetClientRect(hDlg, &wnd);
			SetWindowPos(GetDlgItem(hDlg, IDC_TEST),HWND_BOTTOM, 0,0,wnd.right,wnd.bottom,0);			
			GetWindowRect(hDlg, &wnd);
			GetWindowRect(GetDlgItem(hDlg, IDC_TEST), &orgframe);
			winfo.frame.top=orgframe.top-wnd.top;
			winfo.frame.left=orgframe.left-wnd.left;
			winfo.frame.right=wnd.right-orgframe.right;
			winfo.frame.bottom=wnd.bottom-orgframe.bottom;			
			//Gathering layout information of dynamic elements
			calcframe(hDlg, IDC_IAM,	 wnd, winfo.frame, winfo.idciam,	 winfo.idc_iam);
			calcframe(hDlg, IDC_INPUT,	 wnd, winfo.frame, winfo.idcinput,	 winfo.idc_input);
			calcframe(hDlg, IDCLOSE,	 wnd, winfo.frame, winfo.idclose,	 winfo.id_close);
			calcframe(hDlg, IDUPDOWN,	 wnd, winfo.frame, winfo.idupdown,	 winfo.id_updown);
			calcframe(hDlg, IDC_LIST,	 wnd, winfo.frame, winfo.idclist,	 winfo.idc_list);
			calcframe(hDlg, IDC_LISTTEXT,wnd, winfo.frame, winfo.idclisttext,winfo.idc_listtext);
			calcframe(hDlg, IDC_TAB,	 wnd, winfo.frame, winfo.idctab,	 winfo.idc_tab[0]);
			calcframe(hDlg, IDC_TAB2,	 wnd, winfo.frame, winfo.idctab,	 winfo.idc_tab[1]);
			calcframe(hDlg, IDC_TAB3,	 wnd, winfo.frame, winfo.idctab,	 winfo.idc_tab[2]);
			calcframe(hDlg, IDC_TAB4,	 wnd, winfo.frame, winfo.idctab,	 winfo.idc_tab[3]);
			calcframe(hDlg, IDC_TAB5,	 wnd, winfo.frame, winfo.idctab,	 winfo.idc_tab[4]);
			//Setting minimum frame dimensions (width=window_width/2)
			winfo.frame.heigth=winfo.frame.top+winfo.idcinput.top+winfo.idcinput.heigth+winfo.idcinput.bottom+winfo.frame.bottom;
			winfo.frame.width=(winfo.frame.left+winfo.idctab.width+winfo.frame.right)/2;
			//Setting icons		
			winfo.idiclose=LoadIcon(g_hInst, MAKEINTRESOURCE(IDI_CLOSE));
			winfo.idiup=LoadIcon(g_hInst, MAKEINTRESOURCE(IDI_UP));
			winfo.ididown=LoadIcon(g_hInst, MAKEINTRESOURCE(IDI_DOWN));		
			SendMessage(winfo.id_close, (UINT) BM_SETIMAGE, (WPARAM) IMAGE_ICON, (LPARAM) winfo.idiclose);
			if (launchpad)
			{
				EnableWindow(winfo.id_close, FALSE);
				ShowWindow(winfo.id_close, SW_HIDE);				
			}
			else EnableWindow(winfo.id_close, TRUE);
			SendMessage(winfo.id_updown,(UINT) BM_SETIMAGE, (WPARAM) IMAGE_ICON, (LPARAM) winfo.idiup);
			if (launchpad)
			{
				EnableWindow(winfo.id_updown, FALSE);
				ShowWindow(winfo.id_updown, SW_HIDE);
			}
			else EnableWindow(winfo.id_updown, TRUE);
			//Setting Tabpages
			tabpage.mask=TCIF_TEXT;
			tabpage.pszText=tabnames[0]="Status";
			SendMessage(winfo.idc_tab[0], TCM_INSERTITEM, 0, (LPARAM) &tabpage);
			tabpage.pszText=tabnames[1]="Network";
			SendMessage(winfo.idc_tab[0], TCM_INSERTITEM, 1, (LPARAM) &tabpage);
			tabpage.pszText=tabnames[2]="Timing";
			SendMessage(winfo.idc_tab[0], TCM_INSERTITEM, 2, (LPARAM) &tabpage);
			tabpage.pszText=tabnames[3]="Shell";
			SendMessage(winfo.idc_tab[0], TCM_INSERTITEM, 3, (LPARAM) &tabpage);
			tabpage.pszText=tabnames[4]="Logging";
			SendMessage(winfo.idc_tab[0], TCM_INSERTITEM, 4, (LPARAM) &tabpage);
			//Create dialog boxes
			hTabPage[0]=CreateDialog(g_hInst,MAKEINTRESOURCE(IDD_STATUS),	hDlg,(DLGPROC) StatusProc);
			hTabPage[1]=CreateDialog(g_hInst,MAKEINTRESOURCE(IDD_NETWORK),	hDlg,(DLGPROC) NetworkProc);
			hTabPage[2]=CreateDialog(g_hInst,MAKEINTRESOURCE(IDD_TIME),		hDlg,(DLGPROC) TimeProc);
			hTabPage[3]=CreateDialog(g_hInst,MAKEINTRESOURCE(IDD_SHELL),	hDlg,(DLGPROC) ShellProc);
			hTabPage[4]=CreateDialog(g_hInst,MAKEINTRESOURCE(IDD_LOG),		hDlg,(DLGPROC) LogProc);			
			//Set default tab
			currentpage[0]=0;
			currentpage[1]=0;
			currentpage[2]=0;
			currentpage[3]=0;
			currentpage[4]=0;
			currenttabs=1;
			TabPageAdjustProc();		
			for(i=1;i<TABPAGES;i++)
				ShowWindow(hTabPage[i], SW_HIDE);
			//Preset status tab elements		
			SetWindowText(winfo.latmx=GetDlgItem(hTabPage[0],IDC_LATMX), itoa(dinfo.latmx, line, 10));
			SetWindowText(winfo.mjdmx=GetDlgItem(hTabPage[0],IDC_MJDMX), itoa(dinfo.mjdmx, line, 10));
			SetWindowText(winfo.synmx=GetDlgItem(hTabPage[0],IDC_SYNMX), itoa(dinfo.synmx, line, 10));
			SetWindowText(winfo.latmn=GetDlgItem(hTabPage[0],IDC_LATMN), itoa(dinfo.latmn, line, 10));
			SetWindowText(winfo.mjdmn=GetDlgItem(hTabPage[0],IDC_MJDMN), itoa(dinfo.mjdmn, line, 10));
			SetWindowText(winfo.synmn=GetDlgItem(hTabPage[0],IDC_SYNMN), itoa(dinfo.synmn, line, 10));
			SetWindowText(winfo.latav=GetDlgItem(hTabPage[0],IDC_LATAV), itoa(dinfo.latav, line, 10));
			SetWindowText(winfo.mjdav=GetDlgItem(hTabPage[0],IDC_MJDAV), itoa(dinfo.mjdav, line, 10));
			SetWindowText(winfo.synav=GetDlgItem(hTabPage[0],IDC_SYNAV), itoa(dinfo.synav, line, 10));
			//Showing status animation elements			
			ShowWindow(winfo.background[0]=GetDlgItem(hTabPage[0],IDC_L),		launchpad?SW_SHOW:SW_HIDE);			
			ShowWindow(winfo.background[1]=GetDlgItem(hTabPage[0],IDC_S),		launchpad?SW_HIDE:SW_SHOW);
			ShowWindow(winfo.solarsystem[0]=GetDlgItem(hTabPage[0],IDC_LS1),	launchpad?dinfo.solarsystem?SW_SHOW:SW_HIDE:SW_HIDE);
			ShowWindow(winfo.solarsystem[1]=GetDlgItem(hTabPage[0],IDC_SS1),	!launchpad?dinfo.solarsystem?SW_SHOW:SW_HIDE:SW_HIDE);
			ShowWindow(winfo.clocksymbol[0][0]=GetDlgItem(hTabPage[0],IDC_LC),	launchpad?dinfo.clocker==1?SW_SHOW:SW_HIDE:SW_HIDE);
			ShowWindow(winfo.clocksymbol[1][0]=GetDlgItem(hTabPage[0],IDC_SC),	!launchpad?dinfo.clocker==1?SW_SHOW:SW_HIDE:SW_HIDE);
			ShowWindow(winfo.clocksymbol[0][1]=GetDlgItem(hTabPage[0],IDC_LFC),	launchpad?dinfo.clocker==2?SW_SHOW:SW_HIDE:SW_HIDE);
			ShowWindow(winfo.clocksymbol[1][1]=GetDlgItem(hTabPage[0],IDC_SFC),	!launchpad?dinfo.clocker==2?SW_SHOW:SW_HIDE:SW_HIDE);
			ShowWindow(winfo.clockpulse[0]=GetDlgItem(hTabPage[0],IDC_LP),		SW_HIDE);
			ShowWindow(winfo.clockpulse[1]=GetDlgItem(hTabPage[0],IDC_SP),		SW_HIDE);
			ShowWindow(winfo.systempulse[0]=GetDlgItem(hTabPage[0],IDC_P1),		SW_HIDE);
			ShowWindow(winfo.systempulse[1]=GetDlgItem(hTabPage[0],IDC_P2),		SW_HIDE);
			ShowWindow(winfo.systempulse[2]=GetDlgItem(hTabPage[0],IDC_P3),		SW_HIDE);
			//Preset network tab elements
			SetWindowText(winfo.srvip=GetDlgItem(hTabPage[1],IDC_SRVIP), dinfo.srvipchar);			
			SetWindowText(winfo.srvtcp=GetDlgItem(hTabPage[1],IDC_SRVTCP), itoa(dinfo.srvtcp, line, 10));
			winfo.analyse=GetDlgItem(hTabPage[1],IDC_ANALYSE);
			SendMessage(winfo.chkcustom=GetDlgItem(hTabPage[1],IDC_CUSTOM),(UINT) BM_SETCHECK,(WPARAM) dinfo.clientconfig!=0?BST_CHECKED:BST_UNCHECKED,(LPARAM) 0);
			SendMessage(winfo.chkhairpinning=GetDlgItem(hTabPage[1],IDC_HAIRPINNING),(UINT) BM_SETCHECK,(WPARAM) dinfo.clientconfig==1?BST_CHECKED:BST_UNCHECKED,(LPARAM) 0);
			SendMessage(winfo.chkcustomstun=GetDlgItem(hTabPage[1],IDC_CUSTOMSTUN),(UINT) BM_SETCHECK,(WPARAM) dinfo.clientconfig==-1?BST_CHECKED:BST_UNCHECKED,(LPARAM) 0);
			SetWindowText(winfo.clnudp=GetDlgItem(hTabPage[1],IDC_CLNUDP), itoa(dinfo.clnudp, line, 10));
			if (dinfo.clientconfig==0)
			{
				EnableWindow(winfo.clnudp, false);
				EnableWindow(winfo.chkhairpinning, false);
				EnableWindow(winfo.chkcustomstun, false);
			}
			SetWindowText(winfo.username=GetDlgItem(hTabPage[1],IDC_USERNAME), dinfo.username);				
			SetWindowText(winfo.password=GetDlgItem(hTabPage[1],IDC_PASSWORD), dinfo.password);
			if (winfo.shcon==0)
			{
				SetWindowText(winfo.shcon=GetDlgItem(hTabPage[1],IDC_SHCON), dinfo.shcon?"Disconnect Orbiter":"Connect Orbiter");
				//Disable buttons until .NET loaded successfully
				EnableWindow(winfo.shcon, false); 
				EnableWindow(winfo.analyse, false);
			}
			else SetWindowText(winfo.shcon=GetDlgItem(hTabPage[1],IDC_SHCON), dinfo.shcon?"Disconnect Orbiter":"Connect Orbiter");
			if (launchpad)
			{
				HFONT buttonfont=(HFONT)SendMessage(winfo.idcstartorbi, WM_GETFONT, 0, 0);
				SetParent(winfo.shcon, GetParent(winfo.idcstartorbi));
				SendMessage(winfo.shcon, WM_SETFONT, (WPARAM)buttonfont, 0);
				SetWindowPos(winfo.shcon, 0, 0, 0, 0, 0, SWP_HIDEWINDOW);
				GetWindowRect(winfo.shcon, &wnd);
				GetWindowRect(winfo.idcstartorbi, &orgframe);
				SetWindowPos(winfo.shcon, 0, orgframe.left-wnd.left, orgframe.top-wnd.top, orgframe.right-orgframe.left, orgframe.bottom-orgframe.top, SWP_SHOWWINDOW);
				winfo.hhook3=SetWindowsHookEx(WH_CALLWNDPROC, startorbiter, NULL, GetWindowThreadProcessId(winfo.idcorbiter, NULL));
			}
			else
			{
				ShowWindow(winfo.shcon, SW_HIDE);
			}
			if (dinfo.shcon)
			{
				EnableWindow(winfo.srvip, false);
				EnableWindow(winfo.srvtcp, false);
				EnableWindow(winfo.clnudp, false);
				EnableWindow(winfo.username, false);
				EnableWindow(winfo.password, false);
				EnableWindow(winfo.chkcustom, false);
				EnableWindow(winfo.chkhairpinning, false);
				EnableWindow(winfo.chkcustomstun, false);
				EnableWindow(winfo.analyse, false);
			}			
			//Preset timing tab elements
			SetWindowText(winfo.wrnlat=GetDlgItem(hTabPage[2],IDC_WRNLAT), itoa(dinfo.wrnlat, line, 10));		
			SetWindowText(winfo.wrnmjd=GetDlgItem(hTabPage[2],IDC_WRNMJD), itoa(dinfo.wrnmjd, line, 10));		
			SetWindowText(winfo.wrnsyn=GetDlgItem(hTabPage[2],IDC_WRNSYN), itoa(dinfo.wrnsyn, line, 10));		
			SetWindowText(winfo.alalat=GetDlgItem(hTabPage[2],IDC_ALALAT), itoa(dinfo.alalat, line, 10));		
			SetWindowText(winfo.alamjd=GetDlgItem(hTabPage[2],IDC_ALAMJD), itoa(dinfo.alamjd, line, 10));		
			SetWindowText(winfo.alasyn=GetDlgItem(hTabPage[2],IDC_ALASYN), itoa(dinfo.alasyn, line, 10));		
			SetWindowText(winfo.sntplog=GetDlgItem(hTabPage[2],IDC_SNTPLOG), dinfo.sntplog);	
			//Preset shell tab elements
			SetWindowText(winfo.shbuffer=GetDlgItem(hTabPage[3],IDC_SHBUF), itoa(dinfo.shbuffer, line, 10));		
			SetWindowText(winfo.shprefix=GetDlgItem(hTabPage[3],IDC_SHPREFIX), dinfo.shprefix);		
			SetWindowText(winfo.shpfxesc=GetDlgItem(hTabPage[3],IDC_SHPFXESC), dinfo.shpfxesc);		
			SetWindowText(winfo.shsysesc=GetDlgItem(hTabPage[3],IDC_SHSYSESC), dinfo.shsysesc);		
			sprintf(line, "");
			for(j=1;j<=dinfo.shsfxasc[0];j++) sprintf(line,"%s%0.2X", line, (k=(byte)dinfo.shsfxasc[j]));
			SetWindowText(winfo.shsfxasc=GetDlgItem(hTabPage[3],IDC_SHSFXASC), line);
			if (dinfo.suffix!=4) EnableWindow(winfo.shsfxasc, FALSE);		
			winfo.autohide[0]=GetDlgItem(hTabPage[3],IDC_AHNONE);
			winfo.autohide[1]=GetDlgItem(hTabPage[3],IDC_AHSLINE);
			winfo.autohide[2]=GetDlgItem(hTabPage[3],IDC_AHFULL);
			SendMessage(winfo.autohide[dinfo.autohide],(UINT) BM_SETCHECK,(WPARAM) BST_CHECKED,(LPARAM) 0);			
			winfo.suffix[0]=GetDlgItem(hTabPage[3],IDC_SFXNONE);
			winfo.suffix[1]=GetDlgItem(hTabPage[3],IDC_SFXCR);
			winfo.suffix[2]=GetDlgItem(hTabPage[3],IDC_SFXLF);
			winfo.suffix[3]=GetDlgItem(hTabPage[3],IDC_SFXCRLF);
			winfo.suffix[4]=GetDlgItem(hTabPage[3],IDC_SFXASC);
			SendMessage(winfo.suffix[dinfo.suffix],(UINT) BM_SETCHECK,(WPARAM) BST_CHECKED,(LPARAM) 0);		
			//Preset log tab elements
			SetWindowText(winfo.lgbuffer=GetDlgItem(hTabPage[4],IDC_LGBUFFER), itoa(dinfo.lgbuffer, line, 10));		
			//SetWindowText(winfo.lglog=GetDlgItem(hTabPage[4],IDC_LGLOG), dinfo.lglog);
			winfo.lglog=GetDlgItem(hTabPage[4],IDC_LGLOG);
			for(i=0;i<LOGLEVELS;i++)
				if (dinfo.loglevel[i])
					SendMessage(winfo.loglevel[i]=GetDlgItem(hTabPage[4],IDC_LGLVL0+i),
						(UINT) BM_SETCHECK,
						(WPARAM) BST_CHECKED,
						(LPARAM) 0);
				else
					SendMessage(winfo.loglevel[i]=GetDlgItem(hTabPage[4],IDC_LGLVL0+i),
						(UINT) BM_SETCHECK,
						(WPARAM) BST_UNCHECKED,
						(LPARAM) 0);			
			dinfo.rcvbufchange=true;
			dinfo.lgbufchange=true;
			if (launchpad && (dinfo.loaderLock || dinfo.quickLaunch || dinfo.container)) ShowWindow(hDlg, SW_MINIMIZE);
			statusupdate=true;			
		}				
		return TRUE;
	case WM_ENTERSIZEMOVE:
		for(i=0;i<currenttabs;i++)
			ShowWindow(hTabPage[currentpage[i]], SW_HIDE);
		GetWindowRect(hDlg, &winfo.oldwnd);
		GetWindowRect(winfo.hscreen, &winfo.screen);
		break;
		//return TRUE;
	case WM_SIZING:
		//return DockProc(hDlg);
		DockProc(hDlg);
		break;
	case WM_EXITSIZEMOVE:		
		DockProc(hDlg);
		DynaLayoutProc(hDlg);
		TabPageAdjustProc();
		for(i=0;i<currenttabs;i++)
			ShowWindow(hTabPage[currentpage[i]], SW_SHOW);
		UpdateWindow(hDlg);		
/*		//Print frame cross
		GetWindowRect(winfo.screen, &wnd);
		printdim("Screen:", wnd);
		 
		 	*/
		//return TRUE;
		break;
	case WM_ACTIVATE:		
		if (IsChild(hDlg, (HWND) lParam)==FALSE)
		{
			switch ((int) wParam)
			{
			case WA_INACTIVE:
				if (!lostfocus)
				{
					lostfocus=true;		
					if (dinfo.autohide>0 && hide)
					{
						GetWindowRect(hDlg, &wnd);
						if (dinfo.autohide==1)
						{
							ShowWindow(winfo.idc_input, SW_HIDE);
							ShowWindow(winfo.id_updown, SW_HIDE);
							ShowWindow(winfo.id_close, SW_HIDE);
							winfo.idciam.top-=winfo.idcinput.heigth;
							winfo.oldwnd.bottom=wnd.bottom;
							wnd.bottom=winfo.idciam.heigth+winfo.frame.bottom;
						}
						else
							if (dinfo.autohide==2)
							{
								winfo.oldwnd.bottom=wnd.bottom;
								wnd.bottom=winfo.frame.bottom;
							}
						SetWindowPos(hDlg, HWND_TOPMOST, wnd.left, wnd.top, wnd.right-wnd.left, wnd.bottom-wnd.top,0);
						DynaLayoutProc(hDlg);
						TabPageAdjustProc();
						UpdateWindow(hDlg);
						dinfo.rcvbufchange=true;
					}
				}
				break;
			case WA_ACTIVE:
			case WA_CLICKACTIVE:
				if (lostfocus)
				{
					lostfocus=false;
					if (dinfo.autohide>0 && hide)
					{
						GetWindowRect(hDlg, &wnd);
						if (dinfo.autohide==1)
						{
							ShowWindow(winfo.idc_input, SW_SHOW);
							ShowWindow(winfo.id_updown, SW_SHOW);
							ShowWindow(winfo.id_close, SW_SHOW);
							winfo.idciam.top+=winfo.idcinput.heigth;
							wnd.bottom=winfo.oldwnd.bottom;
							winfo.oldwnd.bottom=wnd.bottom;
						}
						else
							if (dinfo.autohide==2)
							{
								wnd.bottom=winfo.oldwnd.bottom;
								winfo.oldwnd.bottom=wnd.bottom;
							}
						SetWindowPos(hDlg, HWND_TOPMOST, wnd.left, wnd.top, wnd.right-wnd.left, wnd.bottom-wnd.top,0);
						DynaLayoutProc(hDlg);
						TabPageAdjustProc();
						UpdateWindow(hDlg);
						dinfo.rcvbufchange=true;
					}										
				}				
				break;
			}
		}
		return TRUE;
	case WM_NOTIFY:		
		notifymessage=(LPNMHDR) lParam;
        switch (notifymessage->code)
		{ 
		case TCN_SELCHANGE:
			j=0;
			for(i=0;i<currenttabs;i++)
				if (notifymessage->hwndFrom==winfo.idc_tab[i]) break;
				else j+=tabnumbers[i];						
			ShowWindow(hTabPage[currentpage[i]], SW_HIDE);					
			ShowWindow(hTabPage[currentpage[i]=j+TabCtrl_GetCurSel(winfo.idc_tab[i])], SW_SHOW);			
			TabPageAdjustProc();
			break;
		}		
        return TRUE;
	case  WM_CTLCOLORSTATIC:  //This is simply the first message sent during a write to the text window!
		if (state==S_CLICKSTART)
		{
			state=S_WAITSTART;
			selfCreate = true; //This is so that vessel modules creating other vessels right on startup don't cause AddVessel to hang. It will be removed at first local vessel run in status display.
			PostMessage(winfo.idcstartorbi, BM_CLICK, 0, 0);			
		}
		if (state==S_CHOOSE)
		{
			state=S_CHOOSEDIALOG;
			SendMessage(winfo.idc_list, LB_RESETCONTENT, 0, 0);
			entry1=dinfo.vessellist->content(0);
			if (entry1->key!=0)
			{
				while (entry1!=0)
				{
					struct vesselinfo *vi=(vesselinfo*)entry1->data;
					if (!vi->ignore) SendMessage(winfo.idc_list, LB_ADDSTRING, 0, (LPARAM) vi->casedName);
					entry1=entry1->next;
				}
				SendMessage(winfo.idc_list, LB_SETCURSEL, (WPARAM) 0, 0);
				ShowWindow(winfo.idc_list, SW_SHOW);
				SetWindowText(winfo.idc_listtext, "Choose a vessel class and enter the vessel name. <ENTER> to proceed...");
			}
			else
			{
				SetWindowText(winfo.idc_listtext, "Enter the vessel name. <ENTER> to proceed...");
			}			
			ShowWindow(winfo.idc_iam, SW_HIDE);
			ShowWindow(winfo.idc_listtext, SW_SHOW);
		}
		return TRUE;
	case WM_SETCURSOR:
		if (GetFocus()==winfo.idc_input && lParam==0x02000001)
		{			
			short upKey=GetKeyState(VK_UP);
			short downKey=GetKeyState(VK_DOWN);
			if (upKey<0) wrapper.Up();
			if (downKey<0) wrapper.Down();		
		}
		break;
	}					
	return oapiDefDialogProc (hDlg, uMsg, wParam, lParam);
}

BOOL CALLBACK StatusProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{	
//	char line[LINESIZE];
	switch (uMsg)
	{
	case WM_COMMAND:
//		switch (LOWORD (wParam))
//		{
//		default:
			//do nothing
//			break;
//		}		
/*		sprintf(line, "[%d]ST: %.08X%.08X\r\n",
		testcounter++,		
		wParam,
		lParam);
		addtobuffer(line);		 */
		break;	
	}			
	return oapiDefDialogProc (hDlg, uMsg, wParam, lParam);
}

BOOL CALLBACK NetworkProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{	
	char line[LINESIZE];
	int j;

	switch (uMsg)
	{
	case WM_COMMAND:
		switch (LOWORD (wParam))
		{
		case IDC_CLNUDP:
			if (HIWORD(wParam)==EN_KILLFOCUS)
			{				
				GetWindowText(winfo.clnudp, line, LINESIZE);
				int clnudp=atoi(line);
				if (clnudp<0) clnudp=0;
				if (clnudp>0xFFFF) clnudp=0xFFFF; //0000-FFFF
				dinfo.clnudp=clnudp;
				SetWindowText(winfo.clnudp, itoa(clnudp, line, 10));				
			}
			break;
		case IDC_SRVTCP:
			if (HIWORD(wParam)==EN_KILLFOCUS)
			{				
				GetWindowText(winfo.srvtcp, line, LINESIZE);
				int srvtcp=atoi(line);
				if (srvtcp<0) srvtcp=0;
				if (srvtcp>0xFFFF) srvtcp=0xFFFF; //0000-FFFF
				dinfo.srvtcp=srvtcp;
				SetWindowText(winfo.srvtcp, itoa(srvtcp, line, 10));				
			}
			break;
		case IDC_CUSTOM:
		case IDC_HAIRPINNING:
		case IDC_CUSTOMSTUN:
			j=SendMessage(winfo.chkcustom,(UINT) BM_GETCHECK,(WPARAM) 0,(LPARAM) 0);
			EnableWindow(winfo.clnudp, j==BST_CHECKED);
			EnableWindow(winfo.chkhairpinning, j==BST_CHECKED);
			EnableWindow(winfo.chkcustomstun, j==BST_CHECKED);
			if (j==BST_CHECKED)
			{
				if (SendMessage(winfo.chkhairpinning,(UINT) BM_GETCHECK,(WPARAM) 0,(LPARAM) 0)==BST_CHECKED
					&& LOWORD (wParam)==IDC_HAIRPINNING)
				{
					SendMessage(winfo.chkcustomstun,(UINT) BM_SETCHECK,(WPARAM) BST_UNCHECKED,(LPARAM) 0);
					dinfo.clientconfig=1;
				}
				else if (SendMessage(winfo.chkcustomstun,(UINT) BM_GETCHECK,(WPARAM) 0,(LPARAM) 0)==BST_CHECKED
					&& LOWORD (wParam)==IDC_CUSTOMSTUN)
				{
					SendMessage(winfo.chkhairpinning,(UINT) BM_SETCHECK,(WPARAM) BST_UNCHECKED,(LPARAM) 0);
					dinfo.clientconfig=-1;
				}
				else dinfo.clientconfig=2;
			}
			else
			{
				dinfo.clientconfig=0;
				SendMessage(winfo.chkhairpinning,(UINT) BM_SETCHECK,(WPARAM) BST_UNCHECKED,(LPARAM) 0);
				SendMessage(winfo.chkcustomstun,(UINT) BM_SETCHECK,(WPARAM) BST_UNCHECKED,(LPARAM) 0);
			}			
			break;
		case IDC_ANALYSE:
			addtobuffer("\r\n=== STUN analysis ===\r\n(Check log progress)\r\n");
			wrapper.Analyse();
			EnableWindow(winfo.analyse, false);
			break;		
		case IDC_USERNAME:
			if (HIWORD(wParam)==EN_KILLFOCUS)
			{				
				GetWindowText(winfo.username, dinfo.username, LINESIZE);
			}
			break;
		case IDC_PASSWORD:
			if (HIWORD(wParam)==EN_KILLFOCUS)
			{				
				GetWindowText(winfo.password, dinfo.password, LINESIZE);
			}
			break;
		default:
/*			sprintf(line, "[%d]NW: %.08X%.08X\r\n",
			testcounter++,		
			wParam,
			lParam);
			addtobuffer(line);		 */
			break;
		}				
		break;
	}			
	return oapiDefDialogProc (hDlg, uMsg, wParam, lParam);
}

BOOL CALLBACK TimeProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{		
	char line[LINESIZE];
	int i;	
	switch (uMsg)
	{
	case WM_COMMAND:
		switch (LOWORD (wParam))
		{
		case IDC_SNTPSTATUS:			
			wrapper.GenerateSNTPLog();		
			break;
		case IDC_SNTPRESET:
			//TODO: transmit reset to NTP-Client
			break;		
		case IDC_WRNSYN:
			if (HIWORD(wParam)==EN_KILLFOCUS)
			{				
				GetWindowText(winfo.wrnsyn, line, LINESIZE);
				int wrnsyn=atoi(line);
				if (wrnsyn<0) wrnsyn=0;
				if (wrnsyn>3600000) wrnsyn=3600000; //not more then 3600s = 1 hour
				dinfo.wrnsyn=wrnsyn;
				SetWindowText(winfo.wrnsyn, itoa(dinfo.wrnsyn, line, 10));
				if (dinfo.alasyn<dinfo.wrnsyn)
				{
					dinfo.alasyn=dinfo.wrnsyn;
					SetWindowText(winfo.alasyn, itoa(dinfo.alasyn, line, 10));
				}
			}
			break;
		case IDC_ALASYN:
			if (HIWORD(wParam)==EN_KILLFOCUS)
			{				
				GetWindowText(winfo.alasyn, line, LINESIZE);
				int alasyn=atoi(line);
				if (alasyn<dinfo.wrnsyn) alasyn=dinfo.wrnsyn;
				if (alasyn>3600000) alasyn=3600000; //not more then 3600s = 1 hour
				dinfo.alasyn=alasyn;
				SetWindowText(winfo.alasyn, itoa(dinfo.alasyn, line, 10));
			}
			break;		
		default:
			//do nothing
			break;
		}		
/*		sprintf(line, "[%d]TM: %.08X%.08X\r\n",
		testcounter++,		
		wParam,
		lParam);
		addtobuffer(line);		 */
		break;
	}			
	return oapiDefDialogProc (hDlg, uMsg, wParam, lParam);
}

BOOL CALLBACK ShellProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{		
	char line[LINESIZE];
	byte j,k,l,m;
	
	switch (uMsg)
	{
	case WM_COMMAND:
		switch (LOWORD (wParam))
		{
		case IDC_AHNONE:
			dinfo.autohide=0;
			break;
		case IDC_AHSLINE:
			dinfo.autohide=1;
			break;
		case IDC_AHFULL:
			dinfo.autohide=2;
			break;
		case IDC_SFXNONE:
			dinfo.suffix=0;
			sprintf(dinfo.shsfxasc, "");		
			SetWindowText(winfo.shsfxasc, "");
			EnableWindow(winfo.shsfxasc, FALSE);
			break;
		case IDC_SFXCR:
			dinfo.suffix=1;
			sprintf(dinfo.shsfxasc, "\x01\r");		
			SetWindowText(winfo.shsfxasc, "0D");
			EnableWindow(winfo.shsfxasc, FALSE);
			break;
		case IDC_SFXLF:
			dinfo.suffix=2;
			sprintf(dinfo.shsfxasc, "\x01\n");		
			SetWindowText(winfo.shsfxasc, "0A");
			EnableWindow(winfo.shsfxasc, FALSE);			
			break;
		case IDC_SFXCRLF:
			dinfo.suffix=3;
			sprintf(dinfo.shsfxasc, "\x02\r\n");		
			SetWindowText(winfo.shsfxasc, "0D0A");
			EnableWindow(winfo.shsfxasc, FALSE);			
			break;
		case IDC_SFXASC:
			dinfo.suffix=4;
			EnableWindow(winfo.shsfxasc, TRUE);			
			break;
		case IDC_SHSFXASC:
			if (HIWORD(wParam)==EN_KILLFOCUS)
			{
				GetWindowText(winfo.shsfxasc, line, LINESIZE);
				//Conversion into string
				l=0;
				for(j=0;line[j]!=0;j++)
				{					
					if (line[j]>='a')
								k=line[j]-'a'+10;
					else
						if (line[j]>='A')
								k=line[j]-'A'+10;
						else
							if (line[j]>='0')
								k=line[j]-'0';
							else
								k=-1;
					if (line[++j]!=0)
					{
						if (k>=0 && k<=15)						
						{
							if (line[j]>='a')
										m=line[j]-'a'+10;
							else
								if (line[j]>='A')
										m=line[j]-'A'+10;
								else
									if (line[j]>='0')
										m=line[j]-'0';
									else
										m=-1;
							if (m>=0 && m<=15) k=k*16+m;
							else k=-1;
						}
						else k=-1;
					}
					else j--;
					if (k<0) k=255; //False convertion = FF
					dinfo.shsfxasc[++l]=(char) k;
				}
				dinfo.shsfxasc[0]=(char) l;

				//Reconvert into hex representation
				sprintf(line, "");
				for(j=1;j<=l;j++) sprintf(line,"%s%0.2X", line, (k=(byte)dinfo.shsfxasc[j]));
				SetWindowText(winfo.shsfxasc, line);				
			}
			break;
		case IDC_SHPREFIX:
			if (HIWORD(wParam)==EN_KILLFOCUS)
			{
				GetWindowText(winfo.shprefix, dinfo.shprefix, LINESIZE);
			}
			break;
		case IDC_SHPFXESC:
			if (HIWORD(wParam)==EN_KILLFOCUS)
			{
				GetWindowText(winfo.shpfxesc, line, LINESIZE);
				dinfo.shpfxesc[0]=line[0];
				SetWindowText(winfo.shpfxesc, dinfo.shpfxesc);			
			}
			break;		
		case IDC_SHSYSESC:
			if (HIWORD(wParam)==EN_KILLFOCUS)
			{
				GetWindowText(winfo.shsysesc, line, LINESIZE);
				dinfo.shsysesc[0]=line[0];
				SetWindowText(winfo.shsysesc, dinfo.shsysesc);			
			}
			break;
		case IDC_SHBUF:
			if (HIWORD(wParam)==EN_KILLFOCUS)
			{
				GetWindowText(winfo.shbuffer, line, LINESIZE);
				dinfo.shbuffer=atoi(line);
				if (dinfo.shbuffer<LINESIZE) dinfo.shbuffer=LINESIZE;
				if (dinfo.shbuffer>102400) dinfo.shbuffer=102400; //not more then 100KB = aprox. 1000 lines
				SetWindowText(winfo.shbuffer, itoa(dinfo.shbuffer, line, 10));
			}
			break;
		case IDC_SHCLEAR:
			delete [] dinfo.rcvbuf;
			dinfo.rcvbuf=0;
			dinfo.rcvbuf=new char[dinfo.buffersize=dinfo.shbuffer];
			dinfo.rcvbuf[0]=0x00;
			dinfo.rcvbuf[dinfo.buffersize-1]=0x00;
			SetWindowText(winfo.idc_iam, dinfo.rcvbuf);
			break;
		default:
			//do nothing			
			break;
		}				
		break;
	}			
	return oapiDefDialogProc (hDlg, uMsg, wParam, lParam);
}

BOOL CALLBACK LogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{		
	int i,j;
	char line[LINESIZE];
	switch (uMsg)
	{
	case WM_COMMAND:
		switch (LOWORD (wParam))
		{
		case IDC_LGBUFFER:
			if (HIWORD(wParam)==EN_KILLFOCUS)
			{
				GetWindowText(winfo.lgbuffer, line, LINESIZE);
				dinfo.lgbuffer=atoi(line);
				if (dinfo.lgbuffer<LINESIZE) dinfo.lgbuffer=LINESIZE;
				if (dinfo.lgbuffer>102400) dinfo.lgbuffer=102400; //not more then 100KB = aprox. 1000 lines
				SetWindowText(winfo.lgbuffer, itoa(dinfo.lgbuffer, line, 10));
			}
			break;
		case IDC_LGCLEAR:
			delete [] dinfo.lglog;
			dinfo.lglog=0;
			dinfo.lglog=new char[dinfo.lgbuffersize=dinfo.lgbuffer];
			sprintf(dinfo.lglog, "Log cleared.\r\n");			
			dinfo.lglog[dinfo.lgbuffersize-1]=0x00;
			SetWindowText(winfo.lglog, dinfo.lglog);		
			break;
		default:
			i=LOWORD(wParam)-IDC_LGLVL0;
			if (i>-1 && i<LOGLEVELS)
			{
				//Log level check
				j=SendMessage(winfo.loglevel[i],(UINT) BM_GETCHECK,(WPARAM) 0,(LPARAM) 0);
				if (j==BST_CHECKED) dinfo.loglevel[i]=true;
				if (j==BST_UNCHECKED) dinfo.loglevel[i]=false;
				break;
			}			
			//do nothing
/*			sprintf(line, "[%d]LG: %.08X%.08X\r\n",
			testcounter++,		
			wParam,
			lParam);
			addtobuffer(line);			 */
			break;
		}		
		
		break;
	}			
	return oapiDefDialogProc (hDlg, uMsg, wParam, lParam);
}

void WINAPI statustimer(DWORD sd)
{
	struct Sthreaddata *threaddata=(struct Sthreaddata *) sd;
	static char keystate[256];
	static VECTOR3 point;
	char line[LINESIZE], convert[LINESIZE];
	double oldstatus=0, timestamp=0, local_[CLKAV], local_av, local_min, local_max;
	struct MessageLoad *msg;
	NAVHANDLE xpdr;
	DOCKHANDLE dock;
	DWORD freq;
	int i, j, k, counter, m=0;
	bool navinfo=false, dockinfo=false, histDone=false;
	SYSTEMTIME mjddate;
	VESSEL *focus;
	struct IDload *entry;

	while (threaddata->activation)
	{
		//Check Nav information
		navinfo=false;
		EnterCriticalSection(&hashing);
		EnterCriticalSection(&orbiter);
		OBJHANDLE focobj=oapiGetFocusObject();
		if (oapiIsVessel(focobj)) focus=oapiGetFocusInterface();
		else focus=NULL;
		if (focus!=NULL)
		{
			sprintf(line,"%p",focus->GetHandle());
			if (clientData.GlobalsByHandle!=0)
			{
				entry=(struct IDload *)clientData.GlobalsByHandle->get(line);
				if (entry!=0)
				{
					xpdr=focus->GetTransponder();
					if (xpdr!=0)
					{
						freq=oapiGetNavChannel(xpdr)+1;
						sprintf(convert, "%.3X", freq);
						sprintf(line, "%s", convert+strlen(convert)-3);
					}
					else sprintf(line, "000");
					i=0;
					while(focus->GetNavRecvFreq(i)!=0)
					{
						freq=focus->GetNavRecv(i)+1;
						sprintf(convert, "%.3X", freq);
						i++;
						sprintf(line+i*3, "%s", convert+strlen(convert)-3);
					}
					j=0;
					line[i*3]=' ';
					while((dock=focus->GetDockHandle(j++))!=0)
					{
						xpdr=focus->GetIDS(dock);
						if (xpdr!=0) freq=oapiGetNavChannel(xpdr)+1;
						else freq=0;					
						sprintf(convert, "%.3X", freq);					
						sprintf(line+1+i*3, "%s", convert+strlen(convert)-3);
						i++;
					}
					i=strlen(line);				
					if (entry->NavInfo!=0)
					{
						if (strlen(entry->NavInfo)==(unsigned int)i)
						{
							if (strcmp(entry->NavInfo, line)!=0)
							{
								sprintf(entry->NavInfo, "%s", line);
								navinfo=true;
							}						
						}
						else
						{
							delete [] entry->NavInfo;
							entry->NavInfo=0;
							entry->NavInfo=new char[i+1];
							strcpy(entry->NavInfo, line);
							navinfo=true;
						}
					}
					else
					{
						entry->NavInfo=new char[i+1];
						strcpy(entry->NavInfo, line);
						navinfo=true;
					}
					j=entry->ID;
				}
			}			
		}
		LeaveCriticalSection(&orbiter);
		LeaveCriticalSection(&hashing);
		if (navinfo && timestamp<get_global_timestamp())
		{
			navinfo=false;
			timestamp=get_global_timestamp()+5;
			//send GINFO for navigation
			sprintf(convert, "GINFO %d\"N%s", j, line);
			wrapper.Log(100,convert);
			i=strlen(convert);
			k=(int)dinfo.shsfxasc[0];							
			//exchange input suffix with specified suffix
			for(j=1;j<=k;j++)
				//do not insert a NULL-character due to it's string terminating nature in C++
				//instead such, insert a 0x03-character (end of text)
				if ((convert[i-1+j]=dinfo.shsfxasc[j])==0x00) convert[i-1+j]=0x03;
			//add a string termination
			convert[i-1+j]=0x00;
			EnterCriticalSection(&(xthreaddata->mutex));			
			if (send(xthreaddata->com, convert, i+j, 0) == SOCKET_ERROR)
			{
			LeaveCriticalSection(&(xthreaddata->mutex));
				sprintf (line, "Error sending to %s:%d..", dinfo.srvipchar,ntohs(xthreaddata->remote_addr.sin_port));
				tlogging(20,line);								
			}
			else LeaveCriticalSection(&(xthreaddata->mutex));
		}

		//Animate pulses		
		if (oldstatus!=clientData.timestamp)
		{
			if (dinfo.pulser>0)
			{
				if (++dinfo.pulser>3) dinfo.pulser=1;
				wrapper.Propagate();
				oldstatus=clientData.timestamp;
			}
			else oldstatus=0;
		}

		//Display sync statistics
		sprintf(dinfo.synavmsg, "%d", dinfo.synav);
		sprintf(dinfo.synmxmsg, "%d", dinfo.synmx);
		sprintf(dinfo.synmnmsg, "%d", dinfo.synmn);

		//Display MJD PID data
		if (dinfo.pulser>0)
		{
			EnterCriticalSection(&image);
				MJDtoSystemTime(gimage.MJD + (get_global_timestamp() - gimage.last_MJD_update) / 86400,&mjddate);
			LeaveCriticalSection(&image);
			sprintf(line, "%s %s %d %0.2d:%0.2d:%0.2d %d",
						::DayOfWeek[mjddate.wDayOfWeek],
						MonthOfYear[mjddate.wMonth],
						mjddate.wDay,
						mjddate.wHour,
						mjddate.wMinute,
						mjddate.wSecond,
						mjddate.wYear);
			tlogging(6, line);
			sprintf(line, "e%f int%f de%f Acc%f",
					gimage.e, gimage.integral,
					gimage.e-gimage.e1, oapiGetTimeAcceleration());
			tlogging(6, line);
			
			//Calculate and display MJD PID statistics
			if (gimage.e<1000 && gimage.e>-1000)
			{
				local_[m]=gimage.e;
				if (histDone)
					counter=CLKAV;
				else
					counter=m+1;
				if (counter>2)
				{
					local_min=local_[0];
					local_max=local_[0];
					local_av=local_[0];
					for(i=1;i<counter;i++)
					{
						if (local_[i]<local_min) local_min=local_[i];												
						if (local_[i]>local_max) local_max=local_[i];
						local_av+=local_[i];
					}
					local_av/=counter;
				}
				else
					local_av=(local_[0]+local_[1])/counter;

				dinfo.mjdav=(int)(local_av*10);
				dinfo.mjdmx=(int)(local_max*10);
				dinfo.mjdmn=(int)(local_min*10);
				wrapper.Propagate();
					
				if (++m>=CLKAV)
				{						
					histDone=true;
					m=0;						
				}
			}
		}
		sprintf(dinfo.mjdavmsg, "%d", dinfo.mjdav);
		sprintf(dinfo.mjdmxmsg, "%d", dinfo.mjdmx);
		sprintf(dinfo.mjdmnmsg, "%d", dinfo.mjdmn);

		//Check message queue
		EnterCriticalSection(&messages);
		msg=clientData.MessageQueue;
		while(msg!=0)
		{
			if (msg->timestamp<get_global_timestamp())
			{
				if (msg->frequency==dinfo.radiofreq)
				{
					if (msg->object==oapiGetFocusObject())
					{
						sprintf(line, "*** INCOMING MESSAGE ***\r\nDistance: %em\r\n    Time: %es\r\n------------------------\r\n", 
								msg->distance, msg->distance/LIGHTSPEED);
						addtobuffer(line);
						sprintf(line, "%s\r\n------------------------\r\n", msg->message);
						addtobuffer(line);
					}
				}
				clientData.MessageQueue=msg->next;
				delete [] msg->message;
				msg->message=0;
				delete msg;
				msg=0;
				msg=clientData.MessageQueue;
			}
			else msg=0;
		}
		LeaveCriticalSection(&messages);

		//Check Dock information
		dockinfo=false;
		EnterCriticalSection(&hashing);
		EnterCriticalSection(&orbiter);
		focobj=oapiGetFocusObject();
		if (oapiIsVessel(focobj)) focus=oapiGetFocusInterface();
		else focus=NULL;
		if (focus!=NULL)
		{
			sprintf(line,"%p",focus->GetHandle());
			if (clientData.GlobalsByHandle!=0)
			{
				entry=(struct IDload *)clientData.GlobalsByHandle->get(line);
				if (entry!=0)
				{
					line[0]=0x00;
					i=0;
					freq=0;
					j=3;
					while((dock=focus->GetDockHandle(i))!=0)
					{
						j=i & 0x3;
						if (focus->GetDockStatus(dock)!=0) freq=freq | (1 << j);
						if (j==3)
						{
							sprintf(convert, "%X", freq);					
							sprintf(line+(i >> 2), "%s", convert);
							freq=0;
						}
						i++;
					}
					if (j<3)
					{
						sprintf(convert, "%X", freq);					
						sprintf(line+(i >> 2), "%s", convert);
					}
					i=strlen(line);
					dockinfo=true;
					if (entry->DockInfo!=0)
					{
						if (strlen(entry->DockInfo)==(unsigned int)i)
						{
							if (strcmp(entry->DockInfo, line)!=0) sprintf(entry->DockInfo, "%s", line);
							else dockinfo=false;
						}
						else
						{
							delete [] entry->DockInfo;
							entry->DockInfo=0;
							entry->DockInfo=new char[i+1];
							strcpy(entry->DockInfo, line);					
						}
					}
					else
					{
						entry->DockInfo=new char[i+1];
						strcpy(entry->DockInfo, line);				
					}
					j=entry->ID;
				}
			}
		}
		LeaveCriticalSection(&orbiter);
		LeaveCriticalSection(&hashing);
		if (dockinfo)
		{
			//send GINFO for docking
			sprintf(convert, "GINFO %d\"D%s", j, line);
			wrapper.Log(100,convert);
			i=strlen(convert);
			k=(int)dinfo.shsfxasc[0];							
			//exchange input suffix with specified suffix
			for(j=1;j<=k;j++)
				//do not insert a NULL-character due to it's string terminating nature in C++
				//instead such, insert a 0x03-character (end of text)
				if ((convert[i-1+j]=dinfo.shsfxasc[j])==0x00) convert[i-1+j]=0x03;
			//add a string termination
			convert[i-1+j]=0x00;
			EnterCriticalSection(&(xthreaddata->mutex));			
			if (send(xthreaddata->com, convert, i+j, 0) == SOCKET_ERROR)
			{
			LeaveCriticalSection(&(xthreaddata->mutex));
				sprintf (line, "Error sending to %s:%d..", dinfo.srvipchar,ntohs(xthreaddata->remote_addr.sin_port));
				tlogging(20,line);								
			}
			else LeaveCriticalSection(&(xthreaddata->mutex));
		}

		//Do recorded event transmission for focussed vessel
		EnterCriticalSection(&hashing);
		EnterCriticalSection(&orbiter);
		focobj=oapiGetFocusObject();
		if (oapiIsVessel(focobj)) focus=oapiGetFocusInterface();
		else focus=NULL;
		if (focus!=NULL)
		{
			sprintf(line,"%p",focus->GetHandle());
			if (clientData.GlobalsByHandle!=0)
			{
				entry=(struct IDload *)clientData.GlobalsByHandle->get(line);
				if (entry!=0)
				{
					int id=entry->ID;
		LeaveCriticalSection(&orbiter);
		LeaveCriticalSection(&hashing);
					char empty=0x0;
					char *event_types=&empty;
					char *events=&empty;
					double mjds=0;
					int res=OrbiterExtensions::GetVesselEvents(focus, &mjds, &event_types, &events, 1);
					if (res>0 || dinfo.statusPinger==focobj)
					{
						if (dinfo.statusPinger==focobj) dinfo.statusPinger=NULL;
						//send GINFO for event notification
						//NOTE: the actual usage of an event here is just for backwards compatibility, so the new client can connect to an old server without upsetting old clients with misformed event infos
						byte *mjd=(byte *)(void *)&mjds;
						sprintf(convert, "GINFO %d:E%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x\"%s\"%s", id, mjd[0], mjd[1], mjd[2], mjd[3], mjd[4], mjd[5], mjd[6], mjd[7], event_types, events);
						wrapper.Log(100, convert);
						i=strlen(convert);
						k=(int)dinfo.shsfxasc[0];							
						//exchange input suffix with specified suffix
						for(j=1;j<=k;j++)
							//do not insert a NULL-character due to it's string terminating nature in C++
							//instead such, insert a 0x03-character (end of text)
							if ((convert[i-1+j]=dinfo.shsfxasc[j])==0x00) convert[i-1+j]=0x03;
						//add a string termination
						convert[i-1+j]=0x00;
						EnterCriticalSection(&(xthreaddata->mutex));					
						if (send(xthreaddata->com, convert, i+j, 0) == SOCKET_ERROR)
						{
						LeaveCriticalSection(&(xthreaddata->mutex));
							sprintf (line, "Error sending to %s:%d..", dinfo.srvipchar,ntohs(xthreaddata->remote_addr.sin_port));
							tlogging(20,line);								
						}
						else LeaveCriticalSection(&(xthreaddata->mutex));
						//We snuff out all outstanding events here, because this triggers a state transmission in the new server, anyway. On older servers, it does no harm.
						do OrbiterExtensions::CleanVesselEvents(&event_types, &events, res);
						while((res=OrbiterExtensions::GetVesselEvents(focus, &mjds, &event_types, &events, 1))>0);
					}					
				}
				else
				{
					LeaveCriticalSection(&orbiter);
					LeaveCriticalSection(&hashing);
				}
			}
			else
			{
				LeaveCriticalSection(&orbiter);
				LeaveCriticalSection(&hashing);
			}
		}
		else
		{
			LeaveCriticalSection(&orbiter);
			LeaveCriticalSection(&hashing);
		}
						
		tstatusdisplay();		
		WaitForSingleObject(sleeper, STATUSREFRESH);		
	}
}

#define HEXDIGIT(MSB)	if (MSB>='0' && MSB<='9') MSB-='0';\
						else if (MSB>='A' && MSB<='F') MSB=MSB-'A'+10;\
						else MSB=0;

void WINAPI receiverthread(DWORD sd)
{
	struct Sthreaddata *threaddata=(struct Sthreaddata *) sd;
	char buf[LINESIZE], line[BUFFERSIZE], *linebreak, *bufp, *quotebreak;
	int numbytes, match, matchi, buflen, i, j;
	char	*pingmatch=IRCMATCH,		//matching 0
			*altpingmatch=ALTIRCMATCH,  //matching 0			
			*reqgmatch=REQUESTGLOBAL,	//matching 1
			*ginfomatch=GLOBALINFO,		//matching 2
			*radiomatch=RADIOTALK,		//matching 3
			*checkmatch=CHECKREPLY,		//matching 4
			*targetip=TARGETIP,			//matching 5
			*remoteip=REMOTEIP,			//matching 6
			*actualip=ACTUALIP,			//matching 7
			*listmatch=LISTREPLY,		//matching 8
			*joinmatch=JOINREPLY,		//matching 9
			*assignmatch=ASSIGNREPLY,	//matching 10
			*leavematch=LEAVEREPLY,		//matching 11
			*acceptmatch=ACCEPTREPLY,	//matching 12
			*pingreply=IRCREPLY,
			pingbuf[BUFFERSIZE];
	int matchlength=strlen(pingmatch);
	int replylength=strlen(pingreply);
	bool matching[MATCHES], flag, portfound;
	struct IDload *entry, *load;
	VESSELSTATUS2 targetstate;
	VECTOR3 location, direction;
	double temp, freq;
	struct MessageLoad *msg, *queue;
	unsigned long bucket, size;
	CHashData *entry2;
	struct KillLoad *kill;
	FILE *fileout;	
	DOCKHANDLE dock;
	struct vesselinfo *entry3=0;
	struct animationinfo *entry4=0;
	struct animationmap *entry5=0;
	char sendStateFileName[10]="SND456789";
	char receiveStateFileName[10]="RCV456789";

	targetstate.version=2;
	sendStateFileName[3]=0x00;
	receiveStateFileName[3]=0x00;
	while(true)
	{
		WaitForSingleObject(tcpconnect, INFINITE);
		ResetEvent(tcpconnect);
		if (!threaddata->activation) break;		

		//Create TCP/IP socket
		if ((threaddata->com = socket(AF_INET, SOCK_STREAM, 0)) == -1)
		{
			tlogging(20,"Socket Startup failed!");
			threaddata->com=0;
			dinfo.shcon=false;
			wrapper.Propagate();
			threaddata->activation=false;
			statusupdate=true;
		}
		else
		{
			tlogging(2,"Socket started...");
			//Connecting to server
			if (connect(threaddata->com, (struct sockaddr *) &threaddata->remote_addr, threaddata->sin_size) == -1)
			{
				tlogging(20,"Connect failed!");
				closesocket(threaddata->com);
				threaddata->com=0;
				dinfo.shcon=false;
				wrapper.Propagate();
				threaddata->activation=false;
				statusupdate=true;
			}
			else
			{
				tlogging(2,"Connected to server...");
				match=1;
				for (matchi=dinfo.ircpong?0:1;matchi<MATCHES;matchi++) matching[matchi]=true;
				sprintf(pingbuf, "");
				while (threaddata->activation)
				{			
					numbytes=recv(threaddata->com, buf, LINESIZE-1, 0);
					switch (numbytes)
					{
					//Info at error shutdown
					case SOCKET_ERROR:
						sprintf (line, "Error receiving from %s:%d..%X", dinfo.srvipchar,ntohs(threaddata->remote_addr.sin_port), numbytes);
						tlogging(20,line);
						if (threaddata->activation)
						{
							closesocket(threaddata->com);
							dinfo.shcon=false;
							wrapper.Propagate();
							threaddata->activation=false;
							statusupdate=true;
						}
						break;
					//Info at connection shutdown
					case 0:							
						if (threaddata->activation)
						{
							dinfo.shcon=false;
							wrapper.Propagate();
							threaddata->activation=false;
							statusupdate=true;
						}
						break;
					//Proceed on normal input
					default:
						//Set end mark
						buf[numbytes] = 0;
						wrapper.Log(102, buf);

						//Search for line break in input string and set it as end mark
						bufp=buf;
						linebreak=bufp;
						while (linebreak!=0)
						{
							//Matching keywords
							numbytes=strlen(bufp);							
							if (match>0 && match<matchlength)
								for(matchi=0;matchi<numbytes && match<matchlength;matchi++)
								{
									flag=true;
									if (matching[0])
									{
										if (bufp[matchi]!=pingmatch[match] && bufp[matchi]!=altpingmatch[match])
											matching[0]=false;
										else flag=false;
									}
									if (matching[1])
									{
										if (bufp[matchi]!=reqgmatch[match])
											matching[1]=false;
										else flag=false;
									}
									if (matching[2])
									{
										if (bufp[matchi]!=ginfomatch[match])
											matching[2]=flag=false;
										else flag=false;
									}
									if (matching[3])
									{
										if (bufp[matchi]!=radiomatch[match])
											matching[3]=flag=false;
										else flag=false;
									}									
									if (matching[4])
									{
										if (bufp[matchi]!=checkmatch[match])
											matching[4]=flag=false;
										else flag=false;
									}
									if (matching[5])
									{
										if (bufp[matchi]!=targetip[match])
											matching[5]=flag=false;
										else flag=false;
									}
									if (matching[6])
									{
										if (bufp[matchi]!=remoteip[match])
											matching[6]=flag=false;
										else flag=false;
									}
									if (matching[7])
									{
										if (bufp[matchi]!=actualip[match])
											matching[7]=flag=false;
										else flag=false;
									}
									if (matching[8])
									{
										if (bufp[matchi]!=listmatch[match])
											matching[8]=flag=false;
										else flag=false;
									}
									if (matching[9])
									{
										if (bufp[matchi]!=joinmatch[match])
											matching[9]=flag=false;
										else flag=false;
									}
									if (matching[10])
									{
										if (bufp[matchi]!=assignmatch[match])
											matching[10]=flag=false;
										else flag=false;
									}
									if (matching[11])
									{
										if (bufp[matchi]!=leavematch[match])
											matching[11]=flag=false;
										else flag=false;
									}
									if (matching[12])
									{
										if (bufp[matchi]!=acceptmatch[match])
											matching[12]=flag=false;
										else flag=false;
									}
									if (flag)
									{
										addtobuffer(pingbuf);
										match=0;
										break;
									}
									match++;										
								}							
							linebreak=strpbrk(bufp, "\n\r");
							if (linebreak!=0)
							{
								if ((linebreak[0]=='\n' && linebreak[1]=='\r')  ||
									(linebreak[1]=='\n' && linebreak[0]=='\r'))
								{
									linebreak[0]=0;
									linebreak++;
								}
								linebreak[0]=0;
								if (state==S_STARTED || match>=matchlength)
								{
									sprintf(pingbuf,"%s%s",pingbuf,bufp);
									//process keywords
									if (matching[0])
									{
										//Generate PING answer										
										for(matchi=1;matchi<replylength;matchi++)
											pingbuf[matchi-1]=pingreply[matchi];
									} else									
									if (matching[1])
									{
										//Generate REQUEST GLOBAL answer
										EnterCriticalSection(&hashing);
										sprintf(line, "%s", pingbuf+replylength-1);
										if (threaddata->user->GlobalsByID!=0)
										{											
											entry=(struct IDload *)threaddata->user->GlobalsByID->get(line);
											if (entry!=0)
											{
												if (entry->vessel==NULL)
												{
													sprintf(pingbuf, "%s%d\"G%e\"\"",ginfomatch+1,
																			entry->ID,
																			oapiGetMass(entry->object));
													oapiGetObjectName(entry->object, line, BUFFERSIZE-strlen(pingbuf));
													sprintf(pingbuf, "%s%s\"", pingbuf, line);
												}
												else
												{
													if (entry->linkage==1)
													{
														sprintf(pingbuf, "%s%d\"C%s\"\"%s\"", ginfomatch+1,
																			entry->ID,
																			entry->vessel->GetName(),
																			entry->vessel->GetClassNameA());
														itoa(entry->ID, sendStateFileName+3, 10);
														FILEHANDLE stateFile=oapiOpenFile(sendStateFileName, FileAccessMode::FILE_OUT, PathRoot::ROOT);
														EnterCriticalSection(&orbiter);
														if (entry->vessel->Version()>1) ((VESSEL2*)entry->vessel)->clbkSaveState(stateFile);
														ANIMATION *anim;
														UINT anims=entry->vessel->GetAnimPtr(&anim);
														sprintf(line, OMPSTATELABEL);
														for(UINT i=0;i<anims;i++)
														{
															if ((i % MAXANIMPERLINE) == 0 )
															{
																oapiWriteLine(stateFile, line);
																sprintf(line, "%X", i);
																quotebreak=line+strlen(line);
															}
															//Minimum double value serializing: inverse byte order, so full numbers have leading zeros, which will be skipped
															*quotebreak=' ';
															byte *s=(byte *)(void *)&(anim[i].state);
															for (int j=0;j<8;j++)
															{
																if (s[j]==0 && j<7 && *quotebreak==' ') continue;
																byte u=s[j] >> 4;
																if (u!=0 || *quotebreak!=' ') *(++quotebreak)=u>9?'A'+u-10:'0'+u;
																u = s[j] & 0x0F;
																*(++quotebreak)=u>9?'A'+u-10:'0'+u;
															}
															*(++quotebreak)=0x00;
														}
														oapiWriteLine(stateFile, line);														
														LeaveCriticalSection(&orbiter);
														oapiCloseFile(stateFile, FileAccessMode::FILE_OUT);
													}
													else sprintf(pingbuf, "%sNME %s", ginfomatch+1, line);
												}
											}
											else sprintf(pingbuf, "%sNON %s", ginfomatch+1, line);
										}
										else sprintf(pingbuf, "%sERR %s", ginfomatch+1, line);
										LeaveCriticalSection(&hashing);
									} else
									if (matching[2])
									{
										//Process GLOBAL INFO transmission
										EnterCriticalSection(&hashing);
										if (threaddata->user->GlobalsByID!=0)										
										{
											sprintf(line, "%s", pingbuf+replylength-1);
											quotebreak=strpbrk(line, "\":");
											if (quotebreak!=0)											
											{				
												quotebreak[0]=0x00;
												entry=(struct IDload *)threaddata->user->GlobalsByID->get(line);
												char cmd=*++quotebreak;
												if (entry!=0) switch(cmd)
												{ // We care about already received SIs here.
												case 'C':
													if (entry->vessel!=NULL && entry->linkage==0 && entry->lastInfoPing!=0)
													{
														sprintf(line, "%s", quotebreak+1);
														quotebreak=strpbrk(line, "\"");
														if (quotebreak!=0)
														{				
															quotebreak[0]=0x00;
															FILE *tester=NULL;
															sprintf(pingbuf, "Config\\%s.cfg", quotebreak+1);
															if ((tester = fopen (pingbuf, "r")) == NULL)
															{
																sprintf(pingbuf, "Config\\Vessels\\%s.cfg", quotebreak+1);
																tester = fopen (pingbuf, "r");
															}
															entry->lastInfoPing=0;
															if (tester!=NULL)
															{
																fclose(tester);
																AddLoad.id=entry;
																AddLoad.cls=quotebreak+1;
																AddLoad.name=line;
																LeaveCriticalSection(&hashing);
																SetEvent(startadd);
																WaitForSingleObject(stoppedadd, INFINITE);
																EnterCriticalSection(&hashing);
															}
														}
													}
													break;
												case 'N':
													if (entry->vessel!=NULL && entry->linkage==0)
													{																	
														//Set navigation information
														quotebreak++;
														i=0;
														matchi=0;
														while(quotebreak[0]!=0)
														{
															if (quotebreak[0]==' ') matchi=-1;
															else
															{
																line[i++]=quotebreak[0];
																if (i>2)
																{
																	line[3]=0x00;
																	j=strtol(line, 0, 16);
																EnterCriticalSection(&orbiter);
																	//NavReceiver
																	if (matchi>0)																	
																		if (j>0)
																			entry->vessel->SetNavRecv((matchi++)-1, j-1);
																	if (matchi==0)
																	{//Transponder
																		if (j>0)
																		{
																			entry->vessel->EnableTransponder(true);
																			entry->vessel->SetTransponderChannel(j-1);
																		}
																		else entry->vessel->EnableTransponder(false);
																		matchi=1;
																	}
																	//IDSSender
																	if (matchi<0)
																		if (j>0)
																			if ((dock=entry->vessel->GetDockHandle(abs(matchi--)-1))!=0)
																				entry->vessel->SetIDSChannel(dock, j-1);
																LeaveCriticalSection(&orbiter);
																	i=0;
																}
															}
															quotebreak++;
														}
													}
													break;
												case 'D':
													if (entry->vessel!=NULL && entry->linkage==0)
													{																	
														//Set dock information
														quotebreak++;														
														i=0;
														line[1]=0x00;
														while(quotebreak[0]!=0)
														{
															line[0]=quotebreak[0];															
															j=strtol(line, 0, 16);
														EnterCriticalSection(&orbiter);
															for(matchi=0;matchi<4;matchi++)
																if ((j & (1 << matchi))==0) entry->vessel->Undock(i++);
																else i++;															
														LeaveCriticalSection(&orbiter);														
															quotebreak++;
														}
													}
													break;
												case 'A':													
													EnterCriticalSection(&orbiter);
														//Put the accepted vessel into the kill list
														kill=new KillLoad;
														kill->next=threaddata->user->KillList;
														kill->object=entry->object;
														threaddata->user->KillList=kill;								
													LeaveCriticalSection(&orbiter);
													break;
												case '|':
													if (entry->vessel!=NULL && entry->linkage==0 && entry->vessel->Version()>1)
													{
														//Save line in temp file
														itoa(entry->ID, receiveStateFileName+3, 10);
														FILEHANDLE stateFile=oapiOpenFile(receiveStateFileName, FileAccessMode::FILE_APP, PathRoot::ROOT);
														oapiWriteLine(stateFile, quotebreak+1);
														oapiCloseFile(stateFile, FileAccessMode::FILE_APP);
														receiveStateFileName[3]=0x00;
													}
													break;
												case '-':
													if (entry->vessel!=NULL && entry->linkage==0 && entry->vessel->Version()>1)
													{
														//load temp file into Orbiter state
														itoa(entry->ID, receiveStateFileName+3, 10);
														FILEHANDLE stateFile=oapiOpenFile(receiveStateFileName, FileAccessMode::FILE_IN, PathRoot::ROOT);
														EnterCriticalSection(&orbiter);
															VESSELSTATUS2 status=dinfo.snapshot[entry->object];															
															//Fake thruster deletion
															VECTOR3 direction, location;
															location=_V(0,0,0);
															entry->vessel->GetThrusterDir(entry->thruster, direction);
															double temp=entry->vessel->GetThrusterLevel(entry->thruster)/entry->vessel->GetMass();
															entry->vessel->DelThruster(entry->thruster);
															entry->vessel->DelPropellantResource(entry->fuel);
															//Call load state function
															((VESSEL2*)entry->vessel)->clbkLoadStateEx(stateFile, &status);
															//Reset touchdown points, kill thruster efficiency and create fake thruster
															entry->vessel->SetTouchdownPoints(touchdown.tp1, touchdown.tp2, touchdown.tp3);
															for(int matchi=entry->vessel->GetThrusterCount()-1;matchi>=0;matchi--)
																entry->vessel->SetThrusterMax0(entry->vessel->GetThrusterHandleByIndex(matchi), 0);
															entry->fuel=entry->vessel->CreatePropellantResource(0.001); //1g fake fuel
															entry->thruster=entry->vessel->CreateThruster(
																location, direction, 1E9,
																entry->fuel, 1E15); //ISP: full thrust for 1g for 1000s
															temp*=entry->vessel->GetMass();
															entry->vessel->SetThrusterLevel(entry->thruster, temp);
														LeaveCriticalSection(&orbiter);
														oapiCloseFile(stateFile, FileAccessMode::FILE_IN);
														DeleteFile(receiveStateFileName);
														receiveStateFileName[3]=0x00;
													}
													break;
												case '^':
													if (entry->vessel!=NULL && entry->linkage==0)
													{
														strcpy(line, quotebreak+1);
														if ((quotebreak=strpbrk(line, " "))!=0)
														{
															int id;
															*quotebreak=0x00;
															sscanf(line, "%x", &id);
															double v=0;
															byte *s=(byte *)(void *)&v;
															do
															{
																strcpy(line, quotebreak+1);
																quotebreak=strpbrk(line, " ");
																if (quotebreak==0) quotebreak=line+strlen(line);
																char *q=quotebreak-1;
																for(int i=15;q>=line && i>=0;i--,q--)
																{
																	HEXDIGIT(*q);
																	s[i/2]|=*q<<((i%2)==0?4:0);
																}
																entry->vessel->SetAnimation(id++, v);
																v=0;																
															}
															while(*quotebreak!=0x00);
														}
													}
													break;
												}
												switch(cmd)
												{ // We DO NOT care about already received SIs here, or if the id is an object at all. These are triggers and informational printouts.
												case 'O':
													//TODO: instead of just printing a message, also update the internal offer list for GUI display.
													sprintf(pingbuf, ">>> Object %s has been offered to you <<<\r\n", line);
													addtobuffer(pingbuf);
													break;
												}
											}
										}
										LeaveCriticalSection(&hashing);
									} else
									if (matching[12])
									{
										//Process ACCPT transmission
										switch(*(pingbuf+replylength-1))
										{
										case 'O': //object accepted, add object as local vessel
											EnterCriticalSection(&hashing);
											if (threaddata->user->GlobalsByID!=0)										
											{
												sprintf(line, "%s", pingbuf+replylength+3);
												quotebreak=strpbrk(line, " ");
												if (quotebreak!=NULL)
												{
													*quotebreak=0x00;
													entry=(struct IDload *)threaddata->user->GlobalsByID->get(line);
													if (entry==0)
													{
														//Acceptance failed due to missed object - we are sorry for the inconvenience
														sprintf(line, "%s", pingbuf);
														line[replylength-1]='E';
														line[replylength+0]='R';
														line[replylength+1]='R';
														addtobuffer(line);
														addtobuffer("\r\n");
											LeaveCriticalSection(&hashing);
														break;
													}
													sprintf(line, "%s", quotebreak+5);
													quotebreak=strpbrk(line, "\"");
													if (quotebreak!=NULL)
													{
														*quotebreak=0x00;													
														//Move the accepted object with the given unmapped class name and local id
														MoveLoad.id=entry;
														MoveLoad.local=atol(quotebreak+7);
														MoveLoad.cls=line;
											LeaveCriticalSection(&hashing);
														SetEvent(startmove);
														WaitForSingleObject(stoppedmove, INFINITE);
														break;											
													}
												}
											}
											LeaveCriticalSection(&hashing);
											break;
										default:  //a number showing an available object, print the message - TODO: add to a GUI list
										case 'N': //no acceptable object available, print the message - TODO: clear a GUI list
										case 'E': //acceptance error, simply print the message
											addtobuffer(pingbuf);
											addtobuffer("\r\n");
											break;
										}										
									} else
									if (matching[3])
									{
										//Process RADIO transmission										
										sprintf(line, "%s", pingbuf+replylength-1);
										quotebreak=strpbrk(line, " ");
										if (quotebreak!=0)											
										{				
											quotebreak[0]=0x00;
											//Get timestamp
											temp=atof(line);

											sprintf(line, "%s", quotebreak+1);
											quotebreak=strpbrk(line, " ");
											if (quotebreak!=0)
											{				
												quotebreak[0]=0x00;
												//Get frequency
												freq=atof(line);

												sprintf(line, "%s", quotebreak+1);
												quotebreak=strpbrk(line, " ");
												if (quotebreak!=0)
												{				
													quotebreak[0]=0x00;
													//Get position x
													location.x=atof(line);

													sprintf(line, "%s", quotebreak+1);
													quotebreak=strpbrk(line, " ");
													if (quotebreak!=0)
													{				
														quotebreak[0]=0x00;
														//Get position y
														location.y=atof(line);

														sprintf(line, "%s", quotebreak+1);
														quotebreak=strpbrk(line, " ");
														if (quotebreak!=0)
														{				
															quotebreak[0]=0x00;
															//Get position z
															location.z=atof(line);

															//Get message
															sprintf(line, "%s", quotebreak+1);
															
															//Produce Message entry for every vessel
															if (threaddata->user->LocalsByID!=0)
															{
																size=threaddata->user->LocalsByID->get_size();
																for(bucket=0;bucket<size;bucket++)
																{
																EnterCriticalSection(&hashing);
																	entry2=threaddata->user->LocalsByID->content(bucket);
																	if (entry2->key==0)
																	{
																LeaveCriticalSection(&hashing);
																		continue;
																	}
																	while (entry2!=0)
																	{
																		//Create message for specific vessel
																		msg=new MessageLoad;
																		msg->message=new char[strlen(line)+1];
																		strcpy(msg->message, line);
																		msg->frequency=freq;																	
																		entry=(struct IDload *)entry2->data;
																		msg->object=entry->object;
																	EnterCriticalSection(&orbiter);
																		oapiGetGlobalPos(entry->object, &direction);
																	LeaveCriticalSection(&orbiter);
																		direction.x-=location.x;
																		direction.y-=location.y;
																		direction.z-=location.z;
																		msg->distance=sqrt(	direction.x*direction.x+
																							direction.y*direction.y+
																							direction.z+direction.z);
																		msg->timestamp=temp+msg->distance/LIGHTSPEED;
																		msg->next=0;
																		//Yank in message in queue
																	EnterCriticalSection(&messages);
																		if ((queue=threaddata->user->MessageQueue)!=0)
																		{
																			if (queue->timestamp>msg->timestamp)
																			{
																				msg->next=queue;
																				threaddata->user->MessageQueue=msg;
																				queue=0;
																			}
																			while(queue!=0)
																			if (queue->next!=0)
																			{
																				if (queue->next->timestamp>msg->timestamp)
																				{
																					msg->next=queue->next;
																					queue->next=msg;
																					queue=0;
																				}
																				else queue=queue->next;
																			}
																			else
																			{
																				queue->next=msg;
																				queue=0;
																			}
																		}
																		else threaddata->user->MessageQueue=msg;
																	LeaveCriticalSection(&messages);
																		entry2=entry2->next;
																	}
																LeaveCriticalSection(&hashing);
																}
															}
															else if (!launchpad)
															{
																//Create message for specific vessel
																msg=new MessageLoad;
																msg->message=new char[strlen(line)+1];
																strcpy(msg->message, line);
																msg->frequency=freq;
															EnterCriticalSection(&orbiter);
																msg->object=oapiGetFocusObject();
																oapiGetGlobalPos(msg->object, &direction);
															LeaveCriticalSection(&orbiter);
																direction.x-=location.x;
																direction.y-=location.y;
																direction.z-=location.z;
																msg->distance=sqrt(	direction.x*direction.x+
																					direction.y*direction.y+
																					direction.z+direction.z);
																msg->timestamp=temp+msg->distance/LIGHTSPEED;
																msg->next=0;
																//Yank in message in queue
															EnterCriticalSection(&messages);
																if ((queue=threaddata->user->MessageQueue)!=0)
																{
																	if (queue->timestamp>msg->timestamp)
																	{
																		msg->next=queue;
																		threaddata->user->MessageQueue=msg;
																		queue=0;
																	}
																	while(queue!=0)
																	if (queue->next!=0)
																	{
																		if (queue->next->timestamp>msg->timestamp)
																		{
																			msg->next=queue->next;
																			queue->next=msg;
																			queue=0;
																		}
																		else queue=queue->next;
																	}
																	else
																	{
																		queue->next=msg;
																		queue=0;
																	}
																}
																else threaddata->user->MessageQueue=msg;																
															LeaveCriticalSection(&messages);
															}
														}								
													}								
												}								
											}											
										}										
									} else
									if (matching[4] || matching[5] || matching[6] || matching[7] || matching[8] || matching[9] || matching[10] || matching[11])
									{
										while(true)
										{
											if (matching[4]  && state==S_SYSTEM)
											if (strncmp(pingbuf+replylength-1, "SYS ", 4)==0)
											{
												delete [] dinfo.SolarSystem;
												dinfo.SolarSystem=0;
												dinfo.SolarSystem=new char[strlen(pingbuf+replylength+2)];
												strcpy(dinfo.SolarSystem, pingbuf+replylength+3);												
												sprintf(pingbuf, "GINFO C \"%s\"\nchk v", versionlabel+strlen(VERSIONLABELPREFIX));
												state=S_VERSION;
												break;																								
											}
											if (matching[4]  && state==S_TIME)
											if (strncmp(pingbuf+replylength-1, "MJD ", 4)==0)
											{
												//Open scenario file for writing
												if ((fileout = fopen (dinfo.scenario, "w+")) == NULL)
												{
												  sprintf (line, "Client error opening scenario file `%s': %s", dinfo.scenario, strerror (errno));
												  logging(10, line);
												  exit (-1);
												}
												fprintf(fileout, QUICKHEADER, dinfo.SolarSystem, pingbuf+replylength+3);
												//Close scenario file
												if (fclose (fileout) == EOF)
												{
												  sprintf (line, "Client error closing scenario file: %s", strerror (errno));
												  logging(10,line);
												}
												dinfo.gbodies.clear();
												state=S_GETGBODIES;
												sprintf(pingbuf, "chk g");
												break;
											}
											if (matching[4]  && state==S_VERSION)
											if (strncmp(pingbuf+replylength-1, "VER ", 4)==0)
											{
												if (strncmp(pingbuf+replylength+3, "OMPServer", 9)==0 ||
													wrapper.CompareVersions(MINSERVERVERSION, pingbuf+replylength+3)<0)
												{
													sprintf(line, "Unable to connect to server version '%s'!\r\nMinimum supported version is '%s'.\r\n", pingbuf+replylength+3, MINSERVERVERSION);
													addtobuffer(line);
													sprintf(pingbuf, "quit");
													dinfo.shcon = false;
													if (dinfo.quickLaunch || dinfo.container) ShowWindow(hMain, SW_RESTORE);
													dinfo.quickLaunch = false;
													dinfo.container = false;
													break;
												}
												DeleteVesselList();
												dinfo.vessellist=new CHashTable(0);
												sprintf(pingbuf, "chk vs");
												state=S_VESSELS;
												break;
											}
											if (matching[4]  && state==S_READLINES)
											{
												//Open scenario file for writing
												if (fileout==NULL)
												{
													if ((fileout = fopen (dinfo.scenario, "w+")) == NULL)
													{
													  sprintf (line, "Client error opening scenario file `%s': %s", dinfo.scenario, strerror (errno));
													  logging(10, line);
													  exit (-1);
													}
												}
												if (strcmp(pingbuf+replylength-1, "EOF")==0)
												{
													//Close scenario file
													if (fclose (fileout) == EOF)
													{
													  sprintf (line, "Client error closing scenario file: %s", strerror (errno));
													  logging(10,line);
													}
													dinfo.gbodies.clear();
													state=S_GETGBODIES;
													sprintf(pingbuf, "chk g");
													break;
												}
												if (pingbuf[replylength-1]=='|')
												{													
													matchi=strlen(pingbuf);
													pingbuf[0]=0;
													j=replylength;
													pingbuf[matchi++]='\n';
													pingbuf[matchi]=0;
													for (i=j;i<matchi;i++)
													{
														switch(pingbuf[i])
														{														
														case '}':
															if (pingbuf[0]>1)
															{
																pingbuf[0]=0;																
																if (strncmp(pingbuf+j, "NAME}", 5)==0) fprintf(fileout, "%s", dinfo.vesselname);
																else if (strncmp(pingbuf+j, "CLASS}", 6)==0) fprintf(fileout, "%s", dinfo.vesselclass);
																j=i+1;																
															}
															break;
														case '{':
															if (pingbuf[0]==1)
															{
																pingbuf[0]++;
																j=i+1;																
															}
															break;
														case '$':
															if (pingbuf[0]==0)
															{
																pingbuf[0]++;
																fwrite(pingbuf+j, 1, i-j, fileout);
															}
															break;
														}
													}
													if (i>j) fwrite(pingbuf+j, 1, i-j, fileout);
													matching[3]=true;
													break;
												}
											}
											if (matching[4]  && state==S_GETGBODIES)
											{
												if (strcmp(pingbuf+replylength-1, "GBY END")==0)
												{			
													state=S_CLICKSTART;
													if (dinfo.quickLaunch || dinfo.container) ShowWindow(hMain, SW_RESTORE);
													sprintf(pingbuf, "ls a u d");
													break;
												}
												if (strncmp(pingbuf+replylength-1, "GBY\"", 4)==0)
												{
													sprintf(line, "%s", pingbuf+replylength+3);
													quotebreak=strpbrk(line, "\"");
													if (quotebreak!=0)											
													{				
														quotebreak[0]=0x00;
														char *gbodyname=new char[strlen(quotebreak+1)+1];
														gbodyname[0]=0x00;
														strcpy(gbodyname, quotebreak+1);
														dinfo.gbodies[atoi(line)]=gbodyname;
													}													
													matching[3]=true;
													break;
												}
											}
											if (matching[4]  && state==S_LOCATIONS)
											{												
												if (strcmp(pingbuf+replylength-1, "LOC END")==0)
												{			
													delete [] dinfo.vessellocation;
													dinfo.vessellocation=0;
													if (dinfo.clientconfig==1)
													{													
														//Server-LAN configuration
														sprintf(pingbuf, "ip server");
														state=S_IP;
													}
													else
													{
														state=S_LISTJOINS;
														portfound=false;
														sprintf(pingbuf, "ls o u");
													}
													break;
												}
												if (strncmp(pingbuf+replylength-1, "LOC\"", 4)==0)
												{
													sprintf(line, "%s", pingbuf+replylength+3);
													quotebreak=strpbrk(line, "\"");
													if (quotebreak!=0)											
													{				
														quotebreak[0]=0x00;
														delete [] dinfo.vessellocation;
														dinfo.vessellocation=0;
														dinfo.vessellocation=new char[strlen(quotebreak+1)+1];
														dinfo.vessellocation[0]=0x00;
														strcpy(dinfo.vessellocation, quotebreak+1);
														delete [] (char *)dinfo.locationlist->put(line, dinfo.vessellocation);
														dinfo.vessellocation=0;
													}													
													matching[3]=true;
													break;
												}
											}											
											if (matching[4]  && state==S_VESSELS)
											{
												if (strcmp(pingbuf+replylength-1, "VSL END")==0)
												{
													if (dinfo.quickLaunch || dinfo.container)
													{
														//Don't need locations in quick launch
														if (dinfo.clientconfig==1)
														{													
															//Server-LAN configuration
															sprintf(pingbuf, "ip server");
															state=S_IP;
														}
														else
														{
															state=S_LISTJOINS;
															portfound=false;
															sprintf(pingbuf, "ls o u");
														}
														break;
													}
													sprintf(pingbuf, "chk l");
													//Clear location list
													if (dinfo.locationlist->get(dinfo.vessellocation)==0)
													{
														delete [] dinfo.vessellocation;
														dinfo.vessellocation=0;
													}
													while (!dinfo.locationlist->empty)
														delete [] (char *) dinfo.locationlist->clear(false);
													dinfo.vessellocation=0;
													state=S_LOCATIONS;
													entry3=0;
													break;
												}											
												if (strncmp(pingbuf+replylength-1, "VSL\"", 4)==0)
												{
													sprintf(line, "%s", pingbuf+replylength+3);
													quotebreak=strpbrk(line, "\"");
													if (quotebreak!=0)											
													{				
														quotebreak[0]=0x00;
														if (line[0]!='*')
														{														
															entry3=new vesselinfo;															
															entry3->ignore=quotebreak[1]=='#' || quotebreak[1]=='@';
															if (quotebreak[1]=='@')
															{
																entry3->pattern=new char[strlen(quotebreak+2)+1];
																strcpy(entry3->pattern, quotebreak+2);
															}
															else entry3->pattern=0;
															entry3->casedName=new char[strlen(line)+1];
															strcpy(entry3->casedName, line);
															entry3=(vesselinfo *)dinfo.vessellist->put(_strlwr(line), entry3);
															if (entry3!=0)
															{
																delete [] entry3->pattern;
																delete [] entry3->casedName;
																delete entry3;
															}
															dinfo.vesselID=0;
														}
													}													
													matching[3]=true;
													break;																								
												}												
											}
											if (matching[7] && state==S_IP)
											{
												if (strncmp(pingbuf+replylength-1, " IP: ", 5)==0)
												{
													state=S_LISTJOINS;
													portfound=false;
													sprintf(pingbuf, "ls o u");
													break;
												}
											}
											if (matching[8] && state==S_LISTJOINS)
											{
												if (strcmp(pingbuf+replylength-1, "END")==0)
												{
													if (portfound)
													{														
														sprintf(pingbuf, "as \"%s\" \"%s\" %d", dinfo.username, dinfo.password, (dinfo.clientconfig<1?0:dinfo.clnudp));
														state=S_ASSIGN;
													}													
													else													
													{														
														sprintf(pingbuf, "jn \"%s\" %d \"%s\"", dinfo.username, (dinfo.clientconfig<1?0:dinfo.clnudp), dinfo.password);
														state=S_JOIN;
													}
													break;
												}											
												else
												{
													//Check port entries
													sprintf(line, "%s", pingbuf+replylength-1);
													quotebreak=strpbrk(line, "\"");
													if (quotebreak!=0)											
													{				
														quotebreak[0]=0x00;
														//Get name
														if (strcmp(dinfo.username, line)==0)
														{
															sprintf(line, "%s", quotebreak+1);
															quotebreak=strpbrk(line, "\"");
															if (quotebreak!=0)
															{				
																quotebreak[0]=0x00;
																//Get client port
																//if (atoi(line)==dinfo.clnudp)
																//{
																	portfound=true;
																	sprintf(line, "%s", quotebreak+1);
																	//Get server port
																	dinfo.srvudp=atoi(line);
																//}
															}
														}
													}
													matching[3]=true;
													break;																								
												}
											}
											if (matching[9] && state==S_JOIN)
											{
												if (strncmp(pingbuf+replylength-1, "ERR ", 4)!=0)												
												{
													dinfo.srvudp=atoi(pingbuf+replylength-1);
													fileout=NULL;
													if (dinfo.quickLaunch || dinfo.container)
													{
														//TODO: If we ever need quick location selection, do it here
														addtobuffer("Joined server...\r\n");
														state=S_TIME;
														if (dinfo.container) sprintf(pingbuf, "as container \"%s\"\nchk t", dinfo.password);
														else sprintf(pingbuf, "chk t");
														break;
													}
													state=S_CHOOSE;
													addtobuffer("Joined server...\r\n"); //Note: the order of this buffer adding is important, because it will trigger the dialogs window message that uses the new state
												}
												else
												{
													state=S_STOPPED;
													addtobuffer("\r\n===== Join error ====\r\n");
													if (strncmp(pingbuf+replylength+3, "USER", 4)==0)
														addtobuffer("User already connected! Use a different name or remove the user from the server.\r\n");
													else if (strncmp(pingbuf+replylength+3, "RECV", 4)==0)
														addtobuffer("Server full! Use a different server or remove user(s) from the server.\r\n");
													else if (strncmp(pingbuf+replylength+3, "PORT", 4)==0)
														addtobuffer("Port invalid! Server was unable to create a connection endpoint for the"
														" specified port. Double-check port number or contact server administrator.\r\n");
													else addtobuffer("Unknown error! Contact server administrator!\r\n");
													addtobuffer("\r\nYou are still connected to the server via text only. Use the edit box to"
														" enter chat messages and/or server commands. To retry, press the disconnect button "
														"(same as connect) and try again. If the problem persists, try also to close/open "
														"Orbiter\r\n=====================\r\n");
													if (dinfo.quickLaunch || dinfo.container) ShowWindow(hMain, SW_RESTORE);
													dinfo.quickLaunch = false;
													dinfo.container = false;
												}
												matching[3]=true;
												break;
											}
											if (matching[10] && state==S_ASSIGN)
											{
												if (strncmp(pingbuf+replylength-1, " ERR ", 5)!=0)												
												{
													//TODO: Start download of already instanced vessels instead of:
													fileout=NULL;
													addtobuffer("Logged in...\r\n");
													if (dinfo.quickLaunch || dinfo.container)
													{
														//TODO: If we ever need quick location selection, do it here
														state=S_TIME;
														if (dinfo.container) sprintf(pingbuf, "as container \"%s\"\nchk t", dinfo.password);
														else sprintf(pingbuf, "chk t");
														break;
													}
													state=S_CHOOSE;
												}
												else
												{
													state=S_STOPPED;
													addtobuffer("\r\n==== Assign error ===\r\n");
													if (strncmp(pingbuf+replylength+3, "USER", 4)==0)
														addtobuffer("User not found! Chances are this is a simple timing problem. Try to reconnect.\r\n");
													else if (strncmp(pingbuf+replylength+3, "PASS", 4)==0)
														addtobuffer("Wrong password! Try to reconnect with the right password or change user name.\r\n");
													else if (strncmp(pingbuf+replylength+3, "PORT", 4)==0)
														addtobuffer("Port invalid! Server was unable to create a connection endpoint for the "
														"specified port. Double-check port number or contact server administrator.\r\n");
													else addtobuffer("Unknown error! Contact server administrator!\r\n");
													addtobuffer("\r\nYou are still connected to the server via text only. Use the edit box to "
														"enter chat messages and/or server commands. To retry, press the disconnect button "
														"(same as connect) and try again. If the problem persists, try also to close/open "
														"Orbiter\r\n=====================\r\n");
													if (dinfo.quickLaunch || dinfo.container) ShowWindow(hMain, SW_RESTORE);
													dinfo.quickLaunch = false;
													dinfo.container = false;
												}
												matching[3]=true;
												break;
											}
											if (matching[11] && state==S_WAITSTOP)
											{
												if (strcmp(pingbuf+replylength-1, "OK")==0)
												{
													dinfo.shcon=false;		
													wrapper.Propagate();
													sprintf(pingbuf, "exit");
													addtobuffer("Left server...\r\n");													
													break;
												}
											}
											addtobuffer(pingbuf);
											addtobuffer("\r\n");
											matching[3]=true;										
											break;
										}
									}

									if (!matching[2] && !matching[3] && !matching[12])
									{
										int sendState=-1;
										bool ostate=false;
										int bufferOffset=0;
										FILEHANDLE sendFile;
										if (state==S_STARTED)
										{
											sprintf(pingbuf, "chk s");										
											state=S_SYSTEM;
										}
										if (sendStateFileName[3]!=0x00)
										{
											sendFile=oapiOpenFile(sendStateFileName, FileAccessMode::FILE_IN, PathRoot::ROOT);
											bufferOffset=strlen(sendStateFileName)-3+strlen(ginfomatch+1);
											sendState=1;
										}
										for(;sendState!=0;sendState++)
										{
											//send answer
											wrapper.Log(100,pingbuf);
											match=(int)dinfo.shsfxasc[0];
											buflen=strlen(pingbuf);
											//do not insert a NULL-character due to it's string terminating nature in C++
											//instead such, insert a 0x03-character (end of text)
											for(matchi=1;matchi<=match;matchi++)									
												if ((pingbuf[buflen-1+matchi]=dinfo.shsfxasc[matchi])==0x00) pingbuf[buflen-1+matchi]=0x03;
											//add a string termination
											pingbuf[buflen-1+matchi]=0x00;
											//if (dinfo.localecho) addtobuffer(pingbuf);
											EnterCriticalSection(&(threaddata->mutex));										
											if (send(threaddata->com, pingbuf, strlen(pingbuf), 0) == SOCKET_ERROR)
											{
											LeaveCriticalSection(&(threaddata->mutex));
												sprintf (line, "Error sending to %s:%d..", dinfo.srvipchar,ntohs(threaddata->remote_addr.sin_port));
												tlogging(20,line);								
											}
											else LeaveCriticalSection(&(threaddata->mutex));
											if (sendState>0)
											{
												char *oapiLine;
												if (oapiReadScenario_nextline(sendFile, oapiLine))
												{
													if (strcmp(oapiLine, OMPSTATELABEL)==0)
													{
														ostate=true;
														sprintf(pingbuf+bufferOffset, ":-");
													}
													else sprintf(pingbuf+bufferOffset, ":%c%s", ostate?'^':'|', oapiLine);
												}
												else
												{
													oapiCloseFile(sendFile, FileAccessMode::FILE_IN);
													DeleteFile(sendStateFileName);
													sendStateFileName[3]=0x00;
													sendState=-1;
												}
											}
										}
									}

									sprintf(pingbuf, "");

								}					
								else
								{
									addtobuffer(bufp);
									addtobuffer("\r\n");									
								}
								match=1;
								for (matchi=dinfo.ircpong?0:1;matchi<MATCHES;matchi++) matching[matchi]=true;
								bufp=++linebreak;
							}
							else
								if (bufp[0]!=0)
								{
									if (match>0) sprintf(pingbuf,"%s%s",pingbuf,bufp);
									else addtobuffer(bufp);
								}
						}			
						break;			
					}						
				}
				sprintf (line,"Connection to %s:%d closed..", dinfo.srvipchar,ntohs(threaddata->remote_addr.sin_port));
				tlogging(2,line);
			}			
		}		
	}
}

#pragma managed

void WINAPI synccontrol(DWORD sd)
{
	struct Sthreaddata *threaddata=(struct Sthreaddata *) sd;	
	dinfo.clocker=1;
	wrapper.Start();
	dinfo.ntp = wrapper.CheckSNTPServer();
	tlogging(1,"Sync Control started");
	while (threaddata->activation)
	{		
		WaitForSingleObject(sleeper, INFINITE);
	}	
	wrapper.Stop();
	tlogging(1,"Sync Control stopped");
}

#pragma unmanaged

void ApplyThrusters(UDPpacket &pac, VESSEL *vessel)
{
	if ((pac.contentKA.flags & 0x80)==0) return; //Just set thruster KA packets
	
	vessel->SetThrusterGroupLevel(THGROUP_HOVER, ((double)pac.contentKA.hover)/127);
	if (pac.contentKA.main>0)
	{
		vessel->SetThrusterGroupLevel(THGROUP_RETRO, 0);
		vessel->SetThrusterGroupLevel(THGROUP_MAIN, ((double)pac.contentKA.main)/127);
	}
	else
	{
		vessel->SetThrusterGroupLevel(THGROUP_RETRO, ((double)pac.contentKA.main)/-127);
		vessel->SetThrusterGroupLevel(THGROUP_MAIN, 0);
	}

	vessel->SetThrusterGroupLevel(THGROUP_ATT_PITCHDOWN, 0);
	vessel->SetThrusterGroupLevel(THGROUP_ATT_PITCHUP, 0);
	vessel->SetThrusterGroupLevel(THGROUP_ATT_YAWRIGHT, 0);
	vessel->SetThrusterGroupLevel(THGROUP_ATT_YAWLEFT, 0);
	vessel->SetThrusterGroupLevel(THGROUP_ATT_BANKRIGHT, 0);
	vessel->SetThrusterGroupLevel(THGROUP_ATT_BANKLEFT, 0);
	vessel->SetThrusterGroupLevel(THGROUP_ATT_LEFT, 0);
	vessel->SetThrusterGroupLevel(THGROUP_ATT_RIGHT, 0);
	vessel->SetThrusterGroupLevel(THGROUP_ATT_DOWN, 0);
	vessel->SetThrusterGroupLevel(THGROUP_ATT_UP, 0);
	vessel->SetThrusterGroupLevel(THGROUP_ATT_BACK, 0);
	vessel->SetThrusterGroupLevel(THGROUP_ATT_FORWARD, 0);
	
	if (pac.contentKA.pitch>0)
	{
		vessel->SetThrusterGroupLevel(THGROUP_ATT_PITCHDOWN, 0);
		vessel->SetThrusterGroupLevel(THGROUP_ATT_PITCHUP, ((double)pac.contentKA.pitch)/127);
	}
	else if (pac.contentKA.pitch<0)
	{
		vessel->SetThrusterGroupLevel(THGROUP_ATT_PITCHDOWN, ((double)pac.contentKA.pitch)/-127);
		vessel->SetThrusterGroupLevel(THGROUP_ATT_PITCHUP, 0);
	}
	if (pac.contentKA.yaw>0)
	{
		vessel->SetThrusterGroupLevel(THGROUP_ATT_YAWRIGHT, 0);
		vessel->SetThrusterGroupLevel(THGROUP_ATT_YAWLEFT, ((double)pac.contentKA.yaw)/127);
	}
	else if (pac.contentKA.yaw<0)
	{
		vessel->SetThrusterGroupLevel(THGROUP_ATT_YAWRIGHT, ((double)pac.contentKA.yaw)/-127);
		vessel->SetThrusterGroupLevel(THGROUP_ATT_YAWLEFT, 0);
	}
	if (pac.contentKA.bank>0)
	{
		vessel->SetThrusterGroupLevel(THGROUP_ATT_BANKRIGHT, 0);
		vessel->SetThrusterGroupLevel(THGROUP_ATT_BANKLEFT, ((double)pac.contentKA.bank)/127);
	}
	else if (pac.contentKA.bank<0)
	{
		vessel->SetThrusterGroupLevel(THGROUP_ATT_BANKRIGHT, ((double)pac.contentKA.bank)/-127);
		vessel->SetThrusterGroupLevel(THGROUP_ATT_BANKLEFT, 0);
	}
	if (pac.contentKA.sideward>0)
	{
		vessel->SetThrusterGroupLevel(THGROUP_ATT_LEFT, 0);
		vessel->SetThrusterGroupLevel(THGROUP_ATT_RIGHT, ((double)pac.contentKA.sideward)/127);
	}
	else if (pac.contentKA.sideward<0)
	{
		vessel->SetThrusterGroupLevel(THGROUP_ATT_LEFT, ((double)pac.contentKA.sideward)/-127);
		vessel->SetThrusterGroupLevel(THGROUP_ATT_RIGHT, 0);
	}
	if (pac.contentKA.upward>0)
	{
		vessel->SetThrusterGroupLevel(THGROUP_ATT_DOWN, 0);
		vessel->SetThrusterGroupLevel(THGROUP_ATT_UP, ((double)pac.contentKA.upward)/127);
	}
	else if (pac.contentKA.upward<0)
	{
		vessel->SetThrusterGroupLevel(THGROUP_ATT_DOWN, ((double)pac.contentKA.upward)/-127);
		vessel->SetThrusterGroupLevel(THGROUP_ATT_UP, 0);
	}
	if (pac.contentKA.forward>0)
	{
		vessel->SetThrusterGroupLevel(THGROUP_ATT_BACK, 0);
		vessel->SetThrusterGroupLevel(THGROUP_ATT_FORWARD, ((double)pac.contentKA.forward)/127);
	}
	else if (pac.contentKA.forward<0)
	{
		vessel->SetThrusterGroupLevel(THGROUP_ATT_BACK, ((double)pac.contentKA.forward)/-127);
		vessel->SetThrusterGroupLevel(THGROUP_ATT_FORWARD, 0);
	}
}

#define INFOPING	sprintf(pingbuf, "%s%d", reqgmatch+1, pac.content.ID);															\
					wrapper.Log(100, pingbuf);																						\
					match=(int)dinfo.shsfxasc[0];																					\
					buflen=strlen(pingbuf);																							\
					/*do not insert a NULL-character due to it's string terminating nature in C++									\
					  instead such, insert a 0x03-character (end of text)*/															\
					for(matchi=1;matchi<=match;matchi++)																			\
						if ((pingbuf[buflen-1+matchi]=dinfo.shsfxasc[matchi])==0x00) pingbuf[buflen-1+matchi]=0x03;					\
					/*add a string termination*/																					\
					pingbuf[buflen-1+matchi]=0x00;																					\
					entry1->lastInfoPing=get_global_timestamp();																	\
					/*if (dinfo.localecho) addtobuffer(pingbuf);*/																	\
					EnterCriticalSection(&(xthreaddata->mutex));																	\
						if (send(xthreaddata->com, pingbuf, strlen(pingbuf), 0) == SOCKET_ERROR)									\
						{																											\
					LeaveCriticalSection(&(xthreaddata->mutex));																	\
							sprintf (line, "Error sending to %s:%d..", dinfo.srvipchar,ntohs(xthreaddata->remote_addr.sin_port));	\
							tlogging(20,line);																						\
							break;																									\
						}																											\
					LeaveCriticalSection(&(xthreaddata->mutex));

// Receives packets on UDP-Port and maintain hostlist
void WINAPI hostrecvthread(DWORD sd)
{
	struct Sthreaddata *threaddata=(struct Sthreaddata *) sd;
	int numbytes, match, matchi, buflen, ppac;
	union UDPpacket pac;
	char line[LINESIZE], buf[LINESIZE], pingbuf[BUFFERSIZE], *reqgmatch=REQUESTGLOBAL;	
	struct IDload *entry, *entry1;
	struct NeighbourLoad *entry2;
	VESSELSTATUS2 targetstate, sourcestate;
	VECTOR3 location, direction;
	MATRIX3 rotation, temprot;
	double temp, latency;
	bool intol;
	struct animationinfo *entry3;
	unsigned short int tempint;
	bool reconnect;
	int retries=RETRIES;
	
	while (true)
	{
		//Create UDP socket		
		reconnect=false;
		WaitForSingleObject(udprecvconnect, INFINITE);
		ResetEvent(udprecvconnect);
		if (!threaddata->activation) break;
		threaddata->remote_addr.sin_port=htons(threaddata->thisport);
		if (dinfo.clientconfig>0) threaddata->com = socket(AF_INET, SOCK_DGRAM, 0);
		if (threaddata->com == -1)
		{
			numbytes=GetLastError();
			sprintf (line, "Receiver socket Startup failed (%d)!", numbytes);		
			tlogging(30,line);
			threaddata->com=0;
			dinfo.smcon=false;
			threaddata->activation=false;
			statusupdate=true;
		}
		else
		{							
			tlogging(3,"Receiver socket started...");
			match=0;
			numbytes = 0;
			if (dinfo.clientconfig>0) numbytes=setsockopt(threaddata->com, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char*)&match, sizeof(int));
			if (numbytes != 0)
			{
				numbytes=GetLastError();
				sprintf (line, "Receiver socket option EXCLUSIVEADDRUSE setting failed (%d)!", numbytes);		
				tlogging(30,line);
				closesocket(threaddata->com);
				threaddata->com=0;
				dinfo.smcon=false;
				threaddata->activation=false;
				statusupdate=true;
			}
			else
			{
				match=1;
				if (dinfo.clientconfig>0) numbytes=setsockopt(threaddata->com, SOL_SOCKET, SO_REUSEADDR, (char*)&match, sizeof(int));
				if (numbytes != 0)
				{
					numbytes=GetLastError();
					sprintf (line, "Receiver socket option REUSEADDR setting failed (%d)!", numbytes);		
					tlogging(30,line);
					closesocket(threaddata->com);
					threaddata->com=0;
					dinfo.smcon=false;
					threaddata->activation=false;
					statusupdate=true;
				}
				else
				{
					match=dinfo.curblk*3;
					if (dinfo.clientconfig>0) numbytes=setsockopt(threaddata->com, SOL_SOCKET, SO_RCVTIMEO, (char*)&match, sizeof(int));
					if (numbytes != 0)
					{
						numbytes=GetLastError();
						sprintf (line, "Receiver socket option SO_RCVTIMEO setting failed (%d)!", numbytes);
						tlogging(30,line);
						closesocket(threaddata->com);
						threaddata->com=0;
						dinfo.smcon=false;
						threaddata->activation=false;
						statusupdate=true;
					}
					else
					{
						tlogging(3,"Receiver socket options set...");
						//Bind socket to local address
						if (dinfo.clientconfig>0) numbytes=bind(threaddata->com, (struct sockaddr *)&threaddata->remote_addr, threaddata->sin_size);
						if (numbytes != 0)
						{
							numbytes=GetLastError();
							sprintf (line, "Receiver socket binding failed (%d)!", numbytes);										
							tlogging(30,line);
							closesocket(threaddata->com);
							threaddata->com=0;
							dinfo.smcon=false;
							threaddata->activation=false;
							statusupdate=true;
						}			
						else
						{
							tlogging(3,"Receiver socket bound to UDP port...");				
							targetstate.version=2;
							targetstate.flag=0;
							sourcestate.version=2;
							sourcestate.flag=0;

							while (threaddata->activation && !reconnect)
							{
								switch (numbytes = recvfrom(threaddata->com, pac.buffer, UDPSIZESI, 0,(LPSOCKADDR) &threaddata->remote_addr,  &threaddata->sin_size))
								{
								//Info at error shutdown
								case SOCKET_ERROR:
									numbytes=GetLastError();
									switch (numbytes)
									{
										case WSAETIMEDOUT:
											tlogging(3,"Receiver timed out. Reconnecting...");
											closesocket(threaddata->com);
											reconnect=true;
											break;
										case WSAECONNRESET:
											sprintf (line, "Error receiving on port %d..%d! Ignoring...", threaddata->thisport, numbytes);
											tlogging(30,line);
											break;
										default:
											sprintf (line, "Error receiving on port %d..%d!", threaddata->thisport, numbytes);
											if (retries>0 && threaddata->activation && dinfo.smcon==true && dinfo.shcon==true)
											{
												sprintf(line, "%s Retry %d/%d...", line, RETRIES-(--retries), RETRIES);
												tlogging(30,line);
												closesocket(threaddata->com);
												reconnect=true;											
											}
											else
											{
												tlogging(30,line);
												closesocket(threaddata->com);
												dinfo.smcon=false;
												threaddata->activation=false;
												statusupdate=true;
											}
											break;
									}								
									break;
								//Info at connection shutdown
								case 0:							
									if (threaddata->activation)
									{
										tlogging(3,"Disconnected...");										
										dinfo.smcon=false;
										threaddata->activation=false;
										statusupdate=true;
									}
									break;
								//Proceed on normal input
								default:			
									sprintf (line, "%d <<< ",threaddata->thisport);
									tlogging(3, line);
									latency=get_global_timestamp();
								EnterCriticalSection(&hashing);														
									switch (pac.content.flags & 0x07)
									{
									case 0: //KEEP ALIVE PACKET
										if ((pac.content.flags & 0x80)>0)
										{
											//Thruster keep alive
											sprintf(buf, "%d", pac.contentKA.GlobalID);
											if ((entry1=(IDload *)threaddata->user->GlobalsByID->get(buf))!=0)
											{
											EnterCriticalSection(&orbiter);
												if (entry1->vessel!=NULL)
												{
													ApplyThrusters(pac, entry1->vessel);
													sprintf(line, "%sTh %s %6.3fs", line, entry1->vessel->GetName(),
														latency-pac.contentKA.tsSent);
													tlogging(5, line);
												}
												else
												{
													sprintf(line, "%s thruster packet contains no vessel - dropped", line);
													tlogging(10, line);
												}
											LeaveCriticalSection(&orbiter);												
											}											
											break;
										}
										//Server keep alive
										if (!dinfo.ntp)
										{
										EnterCriticalSection(&ptp);
											if (PTPLoad.divider>=dinfo.ptpDivider)
											{
												if (PTPLoad.T1>=0 || PTPLoad.T3>=0)
												{
													char mes[LINESIZE];
													sprintf(mes, "%sPTP collision T1:%6.3fs T3:%6.3fs", line, PTPLoad.T1, PTPLoad.T3);
													tlogging(1, mes);
												}
												PTPLoad.T1=pac.contentKA.tsSent;
												PTPLoad.T2=latency;
												PTPLoad.T3=-1;
												PTPLoad.divider=0;
											}
											else PTPLoad.divider++;
										LeaveCriticalSection(&ptp);
										}
										if (threaddata->user->timestamp<pac.contentKA.tsSent)
										{											
											threaddata->user->timestamp=pac.contentKA.tsSent;
										EnterCriticalSection(&image);
											gimage.MJD=pac.contentKA.MJD;
											gimage.last_MJD_update=pac.contentKA.tsSent;
										LeaveCriticalSection(&image);
											sprintf(line, "%sKA %6.3fs", line, latency-pac.contentKA.tsSent);
											tlogging(5, line);
										}		
										else
										{
											sprintf(line, "%sKA Packet %6.3fs too late, using info anyway",
												line,
												threaddata->user->timestamp-pac.contentKA.tsSent);
											tlogging(1, line);
										}
										if (!pac.contentKA.Global4Local) break;										
										sprintf(line, "%d", pac.contentKA.LocalID);
										if ((entry=(IDload *)threaddata->user->LocalsByID->get(line))==0)
										{
											sprintf(line, "[L%d ?]G%d", pac.contentKA.LocalID, pac.contentKA.GlobalID);
											tlogging(7, line);
											break;
										}
										if (dinfo.deletedVessels.count(pac.contentKA.GlobalID)!=0)
										{
											sprintf(line, "[L%d]G%d !", pac.contentKA.LocalID, pac.contentKA.GlobalID);
											tlogging(7, line);
											break;
										}
										sprintf(line, "%d", pac.contentKA.GlobalID);
										if ((entry1=(IDload *)threaddata->user->GlobalsByID->get(line))!=0)
										{
											sprintf(line, "[L%d]G%d", pac.contentKA.LocalID, pac.contentKA.GlobalID);
											tlogging(7, line);
											break;
										}
										entry1			=new IDload;
										entry->GlobalID	=entry1;
										entry1->ID		=pac.contentKA.GlobalID;
										entry1->object	=entry->object;
										entry1->vessel	=entry->vessel;
										entry1->size	=entry->size;
										entry1->linkage	=1;
										entry1->MasterID=0;											
										entry1->refcount=0;
										entry1->NavInfo=0;
										entry1->DockInfo=0;
										entry1->lastJump=0;
										threaddata->user->GlobalsByID->put(line, entry1);
										sprintf(line, "%p", entry1->object);
										if ((entry=(IDload *)threaddata->user->GlobalsByHandle->put(line, entry1))!=0)
										{
											entry->tsSent=pac.contentKA.tsSent;
											entry->linkage=2;
											sprintf(line, "[L%d]G%d => G%d", pac.contentKA.LocalID, entry->ID, pac.contentKA.GlobalID);
											tlogging(7, line);											
										}
										else
										{
											sprintf(line, "[L%d]G%d", pac.contentKA.LocalID, pac.contentKA.GlobalID);
											tlogging(7, line);
										}
										break;
									case 1: //GROUP INFO
										if (threaddata->user->timestamp<pac.contentGI.tsSent)
										{											
											threaddata->user->timestamp=pac.contentGI.tsSent;
											EnterCriticalSection(&image);
												gimage.MJD=pac.contentGI.MJD;
												gimage.last_MJD_update=pac.contentGI.tsSent;
											LeaveCriticalSection(&image);
											sprintf(line, "%sGI %6.3fs", line, latency-pac.contentGI.tsSent);
											tlogging(5, line);
										}		
										else
										{
											sprintf(line, "%sGI packet's MJD %6.3fs too late, using rest of info anyway",
												line,
												threaddata->user->timestamp-pac.contentGI.tsSent);
											tlogging(1, line);
										}

										if (dinfo.deletedVessels.count(pac.contentGI.Source)>0)
										{
											//GI packet is dropped due to involved deleted object
											sprintf(line, "GI %d !", pac.contentGI.Source);
											tlogging(7, line);
											break;
										}
																				
										sprintf(line, "%d", pac.contentGI.Source);
										if ((entry=(IDload *)threaddata->user->GlobalsByID->get(line))==0)
										{
											sprintf(line, "GI %d ?", pac.contentGI.Source);
											tlogging(7, line);
											break;
										}
										sprintf(line, "%d:%d:%d", pac.contentGI.Source, pac.contentGI.ip, pac.contentGI.port);
										if ((entry2=(NeighbourLoad *)threaddata->user->Neighbours->get(line))==0)
										{
											//New neighbour information
											entry2=new NeighbourLoad;								
											entry2->ip=pac.contentGI.ip;
											entry2->port=pac.contentGI.port;
											entry2->Source=entry;									
											entry2->tsSent=pac.contentGI.tsSent;
											entry->refcount++;
											threaddata->user->Neighbours->put(line,entry2);
											sprintf(line, "GI %d <-", pac.contentGI.Source);
											tlogging(7, line);
											break;
										}
										//Neighbour information already registered
										if (entry2->tsSent>=pac.contentGI.tsSent)
										{
											sprintf(line, "GI packet %6.3fs too late",
													entry2->tsSent-pac.contentGI.tsSent);
											tlogging(1, line);
											break;
										}
										entry2->tsSent=pac.contentGI.tsSent;
										break;
									case 2: //STATE INFO
										if (dinfo.deletedVessels.count(pac.content.ID)>0 || dinfo.deletedVessels.count(pac.content.reference)>0) break;
										sprintf(buf, "%d", pac.content.reference);
										if ((entry=(IDload *)threaddata->user->GlobalsByID->get(buf))==0)
										{
											sprintf(line, "%s packet contains unknown reference ID %d - dropped",
													line,
													pac.content.reference);
											tlogging(10, line);
										}
										latency-=pac.content.tsSent;
										if (latency<0) latency=0; //negative latency is considered to be zero
										//TODO: display latency?

										EnterCriticalSection(&image);
											intol=gimage.e<=dinfo.tolerance && gimage.e>=-dinfo.tolerance;
										LeaveCriticalSection(&image);

										EnterCriticalSection(&orbiter);
										latency=(dinfo.snapshotMJD-pac.content.MJD)*86400; //Not network, but simulation latency is used from here
										if (entry->vessel==NULL)
										{
											//Reference is not a vessel											
											if (intol)
											{
												//Absolute state info
												temp=oapiGetMass(entry->object);												
												targetstate.rbody=entry->object;																		
												// Runge-Kutta 4th order integration
												posRK4(latency, pac.content.pos, pac.content.vel, pac.content.acc,
													pac.content.vrot, temp,	targetstate.rpos, targetstate.rvel);
												sprintf(line, "%s A", line); 
											}
											else
											{
												sprintf(line, "%s O", line);
												LeaveCriticalSection(&orbiter);
												tlogging(5, line);
											}
										}
										else
										{
											//Reference is a vessel
											intol=true;
											sourcestate=dinfo.snapshot[entry->object];
											//Simple integration
											pac.content.pos+=pac.content.vel*latency+pac.content.acc*latency*latency/2+sourcestate.rpos;											
											pac.content.vel+=sourcestate.rvel+pac.content.acc*latency;
											temp=oapiGetMass(sourcestate.rbody);								
											targetstate.rbody=sourcestate.rbody;							
											targetstate.rpos=pac.content.pos;
											targetstate.rvel=pac.content.vel;
											sprintf(line, "%s R", line);											
										}

										if (!intol) break;
										
										//Check absolute position for planet surface collision
										temp=length(targetstate.rpos);
										if (!(temp>0e0)) temp=FLT_MIN;
										if (oapiGetSize(targetstate.rbody)+0.1>temp)
										{
											targetstate.rvel=targetstate.rpos*0.5/temp;
											targetstate.rpos*=(oapiGetSize(targetstate.rbody)+0.15)/temp;
										}

										targetstate.arot=pac.content.arot;
										targetstate.vrot=pac.content.vrot;									
																			
										sprintf(buf, "%s%d", (pac.content.flags & 0x40)>0?"T":"",pac.content.ID);
										if ((entry1=(IDload *)threaddata->user->GlobalsByID->get(buf))!=0)
										{										
											//Vessel already known
											targetstate.base=0;
											targetstate.port=0;
											targetstate.status=0;										
											if (entry1->tsSent>pac.content.tsSent)
											{
												LeaveCriticalSection(&orbiter);
												sprintf(line, "%s packet %6.3fs too late",
													line,
													entry1->tsSent-pac.content.tsSent);
												tlogging(1, line);
												break;
											}																			
											entry1->tsSent=pac.content.tsSent;			
											sprintf(line, "%s %s", line, entry1->vessel->GetName());
											entry1->vessel->DefSetStateEx(&targetstate);
											entry1->vessel->SetGlobalOrientation(targetstate.arot);
											entry1->vessel->SetAngularVel(targetstate.vrot);
											//Refill fake fuel resource and redirect thruster for acceleration
											entry1->vessel->SetPropellantMass(entry1->fuel, 0.001);
											entry1->vessel->GetRotationMatrix(temprot);
											temp=length(pac.content.acc);
											InvertMatrix(temprot, rotation);
											RotateVector(pac.content.acc, rotation, direction);
											if (temp>0e0) direction/=temp;
											else direction=_V(1.0,0,0);
											entry3=0;
											entry1->vessel->SetThrusterDir(entry1->thruster, direction);
											temp*=entry1->vessel->GetMass()*1E-9;
											entry1->vessel->SetThrusterLevel(entry1->thruster, temp);											
											LeaveCriticalSection(&orbiter);

											sprintf(line, "%s %6.3fs", line, latency);
											tlogging(5, line);											

											if (entry1->lastInfoPing!=0 && get_global_timestamp()-entry1->lastInfoPing>10)
											{
												//send InfoPing
												INFOPING;
											}
											break;
										}

										//If latency too big, there is no need to create something that will be deleted by the
										//garbage collector immediately, anyway.
										if (latency*1000>dinfo.globalidle)
										{
											sprintf(line, "%s %d latency  (%6.3fs) too big", line, pac.content.ID, latency);
											LeaveCriticalSection(&orbiter);
											tlogging(1, line);
											break;
										}
										
										//Vessel unknown yet - create it
										targetstate.base=0;
										targetstate.port=0;
										targetstate.status=0;
										entry1=new IDload;
										entry1->ID=pac.content.ID;
										entry1->size=pac.content.size;
										entry1->linkage=0;
										entry1->tsSent=pac.content.tsSent;	
										entry1->refcount=0;
										entry1->lastJump=0;
										entry1->idle=0;
										entry1->justAdded=0;
										entry1->MasterID=0;
										entry1->GlobalID=0;
										entry1->NavInfo=0;
										entry1->DockInfo=0;
										threaddata->user->GlobalsByID->put(buf,entry1);
										sprintf(buf, "%s%d", (pac.content.flags & 0x40)>0?"TRK_":"ID_", pac.content.ID);
										location=_V(0,0,0);
										temp=length(pac.content.acc);
										selfCreate=true;
										entry1->object=oapiCreateVesselEx(buf,"OMPDefault", &targetstate);
										dinfo.snapshot[entry1->object]=targetstate;
										entry1->vessel=oapiGetVesselInterface(entry1->object);
										selfCreate=false;
										//Delete touch-down points
										entry1->vessel->SetTouchdownPoints(touchdown.tp1, touchdown.tp2, touchdown.tp3);
										//Delete thruster effects on vessel (done by fake thruster)
										for(matchi=entry1->vessel->GetThrusterCount()-1;matchi>=0;matchi--)
											entry1->vessel->SetThrusterMax0(entry1->vessel->GetThrusterHandleByIndex(matchi), 0);
										//Generate fake fuel resource and fake thruster for acceleration
										entry1->fuel=entry1->vessel->CreatePropellantResource(0.001); //1g fake fuel
										entry1->vessel->GetRotationMatrix(temprot);
										InvertMatrix(temprot, rotation);
										RotateVector(pac.content.acc, rotation, direction);
										if (temp>0e0) direction/=temp;
										else direction=_V(1.0,0,0);
										ApplyThrusters(pac, entry1->vessel);
										entry1->thruster=entry1->vessel->CreateThruster(
											location, direction, 1E9,
											entry1->fuel, 1E15); //ISP: full thrust for 1g for 1000s
										entry1->vessel->SetThrusterLevel(entry1->thruster, temp*entry1->vessel->GetMass()*1E-9);
										LeaveCriticalSection(&orbiter);

										sprintf(line, "%s %s %6.3fs", line, buf, latency);
										tlogging(5, line);

										if ((pac.content.flags & 0x40)!=0 || dinfo.container)
										{
											//Just tracking or container, no class info ping necessary
											entry1->lastInfoPing=0;
											break;
										}
										
										//send InfoPing
										INFOPING;
																				
										break;
									case 6: //PING PACKET
										if ((pac.contentPG.flags & 0x40)>0)							//Delete ping
										{
											sprintf(buf, "%d", pac.contentPG.id);
											if ((entry=(IDload *)threaddata->user->GlobalsByID->get(buf))==0)
											{
												sprintf(line, "%s delete ping references unknown ID %d - ignored",
														line,
														pac.contentPG.id);
												tlogging(10, line);
											}
											//Mark global id as garbage
											else entry->tsSent=pac.contentPG.tsSent-dinfo.globalidle-1; //1 second plus, just to be sure																						
										}
										else if ((pac.contentPG.flags & 0x80)>0) //PTP ping
										{
											EnterCriticalSection(&ptp);
												if (PTPLoad.T1>=0 && PTPLoad.T3>=0 &&
													PTPLoad.id==pac.contentPG.id && PTPLoad.id2==pac.contentPG.id2 && PTPLoad.id3==pac.contentPG.id3)
												{
													//PTP round trip completed, send sample to synchronizer
													wrapper.PTPSample(PTPLoad.T1, PTPLoad.T2, PTPLoad.T3, pac.contentPG.tsSent);
													PTPLoad.T1=PTPLoad.T2=PTPLoad.T3=-1;
												}
												else
												{
													sprintf(line, "%sPTP(%.4X%.4X%.4X) obsolete", line, pac.contentPG.id, pac.contentPG.id2, pac.contentPG.id3);
													tlogging(1, line);
												}
											LeaveCriticalSection(&ptp);
										}
										else if (threaddata->user->timestamp<pac.contentPG.tsSent)	//Standard ping only if timestamp is synced
										{											
											threaddata->user->timestamp=pac.contentPG.tsSent;
											sprintf(line, "%sPG%d %6.3fs", line, pac.contentPG.id, latency-pac.contentPG.tsSent);
											tlogging(5, line);
										}		
										else
										{
											sprintf(line, "%sPG%d Packet %6.3fs too late", line, pac.contentPG.id, threaddata->user->timestamp-pac.contentPG.tsSent);
											tlogging(1, line);
										}								
										break;
									default:
										sprintf(line, "%s Wrong packet type %.1X - Flags: %.2X",
												line, (pac.content.flags & 0x07), pac.content.flags);
										tlogging(10, line);
										break;
									}						
								LeaveCriticalSection(&hashing);			
									break;
								}
							}
							if (!reconnect)
							{
								sprintf (line,"Listening on port %d closed..", threaddata->thisport);
								tlogging(3,line);
							}
						}
					}
				}
			}
		}
		if (reconnect) SetEvent(udprecvconnect);
		else SetEvent(udprecvdisconnect);
	}	
}

#define FASTCLASS	wrapper.Log(100, buf);																							\
					int match=(int)dinfo.shsfxasc[0], buflen=strlen(buf), matchi=1;													\
					/*do not insert a NULL-character due to it's string terminating nature in C++									\
					  instead such, insert a 0x03-character (end of text)*/															\
					for(;matchi<=match;matchi++)																		\
						if ((buf[buflen-1+matchi]=dinfo.shsfxasc[matchi])==0x00) buf[buflen-1+matchi]=0x03;							\
					/*add a string termination*/																					\
					buf[buflen-1+matchi]=0x00;																						\
					EnterCriticalSection(&(xthreaddata->mutex));																	\
						if (send(xthreaddata->com, buf, strlen(buf), 0) == SOCKET_ERROR)											\
						{																											\
					LeaveCriticalSection(&(xthreaddata->mutex));																	\
							sprintf (line, "Error sending to %s:%d..", dinfo.srvipchar,ntohs(xthreaddata->remote_addr.sin_port));	\
							tlogging(20,line);																						\
							break;																									\
						}																											\
					LeaveCriticalSection(&(xthreaddata->mutex));

// Transmits packets on UDP-Port
void WINAPI hostsendthread(DWORD sd)
{
	struct Sthreaddata *threaddata=(struct Sthreaddata *) sd;
	int sendtoport, idlecounter, ppac, id;
	union UDPpacket pac, pacKA;
	char line[LINESIZE], lastevent, zip, buf[LINESIZE], *ginfomatch=GLOBALINFO;
	unsigned long int bucket, oldbucket, size, index, i;
	unsigned long int neighbourbucket, neighboursize, neighbourindex;
	CHashData *entry;
	IDload *vessel, *GBody, *master, *remotevessel;
	NeighbourLoad *neighbour;
	VESSELSTATUS2 vesselstate, sourcestate;	
	double temptime, temp;
	bool absolute=false, waitnow=false, startup=false;
	VECTOR3 location;
	
	unsigned short int tempint=0;
	int absolutereason=0;

	location=_V(0,0,0);
				
	while (true)
	{
		WaitForSingleObject(udpsendconnect, INFINITE);
		ResetEvent(udpsendconnect);
		if (!threaddata->activation) break;

		//Create UDP socket
		if ((threaddata->com = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
		{
			tlogging(30,"Transmitter socket Startup failed!");
			threaddata->com=0;
			dinfo.smcon=false;
			threaddata->activation=false;
			statusupdate=true;
			SetEvent(udpsenddisconnect);
			continue;
		}		
		tlogging(3,"Transmitter socket started...");

		absolutereason=0;
		if (setsockopt(threaddata->com, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char*)&absolutereason, sizeof(int)) != 0)
		{
			absolutereason=GetLastError();
			sprintf (line, "Transmitter socket option EXCLUSIVEADDRUSE setting failed (%d)!", absolutereason);		
			tlogging(30,line);
			closesocket(threaddata->com);
			threaddata->com=0;
			dinfo.smcon=false;
			threaddata->activation=false;
			statusupdate=true;
			sprintf (line, "Sending on socket %X stopped",threaddata->com);			
			tlogging(3, line);
			SetEvent(udpsenddisconnect);
			continue;
		}
		
		absolutereason=1;
		if (dinfo.clientconfig<0 && setsockopt(threaddata->com, SOL_SOCKET, SO_REUSEADDR, (char*)&absolutereason, sizeof(int)) != 0)
		{
			absolutereason=GetLastError();
			sprintf (line, "Transmitter socket option REUSEADDR setting failed (%d)!", absolutereason);		
			tlogging(30,line);
			closesocket(threaddata->com);
			threaddata->com=0;
			dinfo.smcon=false;
			threaddata->activation=false;
			statusupdate=true;
			sprintf (line, "Sending on socket %X stopped",threaddata->com);			
			tlogging(3, line);
			SetEvent(udpsenddisconnect);
			continue;
		}
		tlogging(3,"Transmitter socket options set...");
		
		if (dinfo.clientconfig<0 && bind(threaddata->com, (struct sockaddr *)&threaddata->remote_addr, threaddata->sin_size) != 0)
		{
			absolutereason=GetLastError();
			sprintf (line, "Receiver socket binding failed (%d)!", absolutereason);										
			tlogging(30,line);
			closesocket(threaddata->com);
			threaddata->com=0;
			dinfo.smcon=false;
			threaddata->activation=false;
			statusupdate=true;
			sprintf (line, "Sending on socket %X stopped",threaddata->com);			
			tlogging(3, line);
			SetEvent(udpsenddisconnect);
			continue;
		}			
		if (dinfo.clientconfig<0) tlogging(3,"Transmitter socket bound to UDP port...");				
		
		EnterCriticalSection(&hashing);				
			size=threaddata->user->LocalsByID->get_size();			
			neighboursize=threaddata->user->Neighbours->get_size();
		LeaveCriticalSection(&hashing);			
		bucket=0;	
		index=0;
		vesselstate.version=2;
		vesselstate.flag=0;
		sourcestate.version=2;
		sourcestate.flag=0;
		neighbourbucket=0;
		neighbourindex=0;
		startup=true;
					
		while (threaddata->activation)
		{
			vessel=NULL;
		EnterCriticalSection(&ptp);
		if (PTPLoad.T1>=0 && PTPLoad.T3<0)
		{
			//Send PTP ping
			memset(&pac, 0, UDPSIZEPG);
			pac.contentPG.flags=(char)0x86; //Ping packet with PTP flag set
			//Generate extended ID as split of first few words of T1 timestamp
			pac.contentPG.id=PTPLoad.id;
			pac.contentPG.id2=PTPLoad.id2;
			pac.contentPG.id3=PTPLoad.id3;
			//This is just a dummy time stamp to stay closer to the actual sent stamp
			pac.contentPG.tsSent=PTPLoad.T2;
			sendtoport=threaddata->user->sendtoport;							
			threaddata->remote_addr.sin_addr.S_un.S_addr=threaddata->user->ip_addr;
			threaddata->remote_addr.sin_port=htons(sendtoport);
			//Sending the packet to server
			if (sendto(threaddata->com, pac.buffer, UDPSIZEPG, 0, (LPSOCKADDR) &threaddata->remote_addr, threaddata->sin_size) == SOCKET_ERROR)
			{
				sprintf (line, "Port %d: Error sending to %s:%d..",
						threaddata->thisport,inet_ntoa(threaddata->remote_addr.sin_addr),sendtoport);
				tlogging(30,line);
				if (threaddata->activation)
				{
					closesocket(threaddata->com);
					dinfo.smcon=false;
					threaddata->activation=false;
					statusupdate=true;
				}
				PTPLoad.T1=PTPLoad.T2=PTPLoad.T3=-1;
			}
			else
			{
				sprintf (line, ">S%d PTP(%.4X%.4X%.4X)", sendtoport, pac.contentPG.id, pac.contentPG.id2, pac.contentPG.id3);
				tlogging(3, line);
				PTPLoad.T3=get_global_timestamp();
			}
		}
		LeaveCriticalSection(&ptp);
		EnterCriticalSection(&hashing);
			//Get next vessel's state information
			if (threaddata->user->LocalsByID->get_count()>0)
			{
				oldbucket=bucket;
				waitnow=false;
				do
				{
					if (bucket==0 && index==0) waitnow=true;
					entry=threaddata->user->LocalsByID->content(bucket++);
					if (bucket>=size) bucket=0;
					if (entry->key!=0)
					{
						for(i=index++;i>0;i--)
						{
							entry=entry->next;
							if (entry==0) index=0;
							break;
						}
					}
					else entry=0;						
				}
				while (oldbucket!=bucket && entry==0);
				if (entry!=0)
				{
					memset(&pac, 0, UDPSIZESI);
					vessel=(IDload *)entry->data;
					//Preparing State information packet for server
					bool justAdded=vessel->justAdded==1;
					pac.content.ID=vessel->ID;
					pac.content.size=vessel->size;
					//Synchronize with orbiter main thread
					EnterCriticalSection(&orbiter);
						pac.content.MJD=dinfo.snapshotMJD;
						vesselstate=dinfo.snapshot[vessel->object];						
						if (!vessel->vessel->GetThrustVector(location)) location=_V(0,0,0);
						if (vessel->vessel->GroundContact())
						{
							vessel->vessel->GetWeightVector(pac.content.acc);
							location-=pac.content.acc;							
						}							
						vessel->vessel->GlobalRot(location, pac.content.acc);
						pac.content.acc/=vessel->vessel->GetMass();
						pac.content.flags=(char)(vessel->object==oapiGetFocusObject()?0x82:0x02);
						if (justAdded)
						{
							vessel->justAdded=0;
							sprintf(buf, "%sL%d\"C%s\"\"%s\"", ginfomatch+1, vessel->ID, vessel->vessel->GetName(), vessel->vessel->GetClassNameA());
						}
					LeaveCriticalSection(&orbiter);
					sprintf(line, "%p", vesselstate.rbody);
					GBody=(IDload *)threaddata->user->GlobalsByHandle->get(line);
					if (GBody==0) tlogging(10, "ERROR - Vessel invalid!");
					else pac.content.reference=GBody->ID;
					pac.content.pos=vesselstate.rpos;
					pac.content.vel=vesselstate.rvel;						
					pac.content.arot=vesselstate.arot;
					pac.content.vrot=vesselstate.vrot;										
					pac.content.tsSent=get_global_timestamp();
					sendtoport=threaddata->user->sendtoport;							
					threaddata->remote_addr.sin_addr.S_un.S_addr=threaddata->user->ip_addr;
					threaddata->remote_addr.sin_port=htons(sendtoport);						
					//Updating index and bucket
					if (entry->next==0) index=0;
					else bucket=oldbucket;
		LeaveCriticalSection(&hashing);
					//Sending the packet to server						
					if (sendto(threaddata->com, pac.buffer, UDPSIZESI, 0,
						(LPSOCKADDR) &threaddata->remote_addr,
						threaddata->sin_size) == SOCKET_ERROR)
					{
						sprintf (line, "Port %d: Error sending to %s:%d..",
								threaddata->thisport,inet_ntoa(threaddata->remote_addr.sin_addr),sendtoport);
						tlogging(30,line);
						if (threaddata->activation)
						{
							closesocket(threaddata->com);
							dinfo.smcon=false;
							threaddata->activation=false;
							statusupdate=true;
						}
					}
					else
					{
						if (justAdded) { FASTCLASS }
						sprintf (line, ">S%d %d(%d)", sendtoport, pac.content.ID, pac.content.reference);
						tlogging(3, line);
						WaitForSingleObject(sleeper, dinfo.curdel);
					}												
				}
				else
				{
		LeaveCriticalSection(&hashing);
					tlogging(10, "ERROR - LocalsByID count inconsistent!");
				}
			}
			else
		LeaveCriticalSection(&hashing);
		EnterCriticalSection(&hashing);
			if (dinfo.removedToServer.size()>0)
			{		
				int cnt=-1;
				memset(&pac, 0, UDPSIZEPG);
				EnterCriticalSection(&orbiter);
					for(std::map<unsigned int, int>::iterator i=dinfo.removedToServer.begin();i!=dinfo.removedToServer.end();i++)
					{
						if (cnt<i->second)
						{									
							pac.contentPG.id=i->first;
							cnt=i->second;
						}
					}
					if (cnt>0) dinfo.removedToServer[pac.contentPG.id]=cnt-1;
					else if (cnt==0) dinfo.removedToServer.erase(pac.contentPG.id);
				LeaveCriticalSection(&orbiter);
				pac.contentPG.flags=(char)0x46; //Ping packet marked as ID delete
				pac.contentPG.tsSent=get_global_timestamp();
				sendtoport=threaddata->user->sendtoport;							
				threaddata->remote_addr.sin_addr.S_un.S_addr=threaddata->user->ip_addr;
				threaddata->remote_addr.sin_port=htons(sendtoport);						
		LeaveCriticalSection(&hashing);
				//Sending the delete ping packet to server
				if (cnt>=0) //Safety measure against thread-unsafe size change
				{
					if (sendto(threaddata->com, pac.buffer, UDPSIZEPG, 0,
						(LPSOCKADDR) &threaddata->remote_addr,
						threaddata->sin_size) == SOCKET_ERROR)
					{
						sprintf (line, "Port %d: Error sending to %s:%d..",
								threaddata->thisport,inet_ntoa(threaddata->remote_addr.sin_addr),sendtoport);
						tlogging(30,line);
						if (threaddata->activation)
						{
							closesocket(threaddata->com);
							dinfo.smcon=false;
							threaddata->activation=false;
							statusupdate=true;
						}
					}
					else
					{
						sprintf (line, ">S%d %d(D%d)", sendtoport, pac.contentPG.id, cnt);
						tlogging(3, line);
						WaitForSingleObject(sleeper, dinfo.curdel);
					}
				}
			}
			else
		LeaveCriticalSection(&hashing);
		EnterCriticalSection(&hashing);
			if (dinfo.removedToNeighbours.size()>0)
			{		
				int cnt=-1;
				std::pair<unsigned long int, unsigned short int> ip;
				memset(&pac, 0, UDPSIZEPG);
				EnterCriticalSection(&orbiter);
					for(std::map<unsigned int, std::map<std::pair<unsigned long int, unsigned short int>, int>>::iterator i=dinfo.removedToNeighbours.begin();i!=dinfo.removedToNeighbours.end();i++)
					{
						for(std::map<std::pair<unsigned long int, unsigned short int>, int>::iterator j=i->second.begin();j!=i->second.end();j++)
						{
							if (cnt<j->second)
							{									
								pac.contentPG.id=i->first;
								ip=j->first;
								cnt=j->second;
							}
						}
					}
					if (cnt>0) dinfo.removedToNeighbours[pac.contentPG.id][ip]=cnt-1;
					else if (cnt==0)
					{
						dinfo.removedToNeighbours[pac.contentPG.id].erase(ip);
						if (dinfo.removedToNeighbours[pac.contentPG.id].size()==0) dinfo.removedToNeighbours.erase(pac.contentPG.id);
					}
				LeaveCriticalSection(&orbiter);
				pac.contentPG.flags=(char)0x46; //Ping packet marked as ID delete
				pac.contentPG.tsSent=get_global_timestamp();						
				sendtoport=ip.second;							
				threaddata->remote_addr.sin_addr.S_un.S_addr=ip.first;
				threaddata->remote_addr.sin_port=htons(sendtoport);						
		LeaveCriticalSection(&hashing);
				//Sending the delete ping packet to neighbour
				if (cnt>=0) //Safety measure against thread-unsafe size change
				{
					if (sendto(threaddata->com, pac.buffer, UDPSIZEPG, 0,
						(LPSOCKADDR) &threaddata->remote_addr,
						threaddata->sin_size) == SOCKET_ERROR)
					{
						sprintf (line, "Port %d: Error sending to %s:%d..",
								threaddata->thisport,inet_ntoa(threaddata->remote_addr.sin_addr),sendtoport);
						tlogging(30,line);
					}
					else
					{
						sprintf (line, ">N%d %d(D%d)", sendtoport, pac.contentPG.id, cnt);
						tlogging(3, line);
						WaitForSingleObject(sleeper, dinfo.curdel);
					}
				}
			}
			else
		LeaveCriticalSection(&hashing);
		EnterCriticalSection(&hashing);
			//Send Keep Alives if no other action taken
			if (threaddata->user->LocalsByID->get_count()<=0 && dinfo.removedToServer.size()<=0 && dinfo.removedToNeighbours.size()<=0)
			{
				memset(&pac, 0, UDPSIZESI);
				EnterCriticalSection(&orbiter);
					pac.contentKA.MJD=dinfo.snapshotMJD;
				LeaveCriticalSection(&orbiter);
				pac.contentKA.tsSent=get_global_timestamp();
				sendtoport=threaddata->user->sendtoport;
				threaddata->remote_addr.sin_addr.S_un.S_addr=threaddata->user->ip_addr;
				threaddata->remote_addr.sin_port=htons(sendtoport);
		LeaveCriticalSection(&hashing);					
				pac.contentKA.Global4Local=false;
				pac.contentKA.GlobalID=0;
				pac.contentKA.LocalID=0;
				pac.contentKA.reserve2=0;		
				pac.contentKA.flags=0; //Keep-alive packet					
				waitnow=false;
				//Sending the packet to server						
				if (sendto(threaddata->com, pac.buffer, UDPSIZEKA, 0,
					(LPSOCKADDR) &threaddata->remote_addr,
					threaddata->sin_size) == SOCKET_ERROR)
				{
					sprintf (line, "Port %d: Error sending to %s:%d..",
							threaddata->thisport,inet_ntoa(threaddata->remote_addr.sin_addr),sendtoport);
					tlogging(30,line);
					if (threaddata->activation)
					{
						closesocket(threaddata->com);
						dinfo.smcon=false;
						threaddata->activation=false;
						statusupdate=true;
					}
				}
				else
				{
					sprintf (line, ">KA%d", sendtoport);
					tlogging(3, line);
					WaitForSingleObject(sleeper, dinfo.curblk);
				}
			}
			else
		LeaveCriticalSection(&hashing);
		EnterCriticalSection(&hashing);							
			//Sending next neighbour information
			if (threaddata->user->Neighbours->get_count()>0 && threaddata->activation && vessel!=0)
			{
		LeaveCriticalSection(&hashing);
				neighbourbucket=0;
				idlecounter=0;
				bool idle=vessel->idle<=0;
				do
				{
		EnterCriticalSection(&hashing);
					entry=threaddata->user->Neighbours->content(neighbourbucket);
					if (entry->key!=0)
					{
						for(i=neighbourindex++;i>0;i--)
						{
							entry=entry->next;
							if (entry==0)
							{
								neighbourbucket++;
								neighbourindex=0;									
							}
							break;
						}
					}
					else entry=0;
					if (idlecounter++>dinfo.maxidle)
					{
		LeaveCriticalSection(&hashing);
						idlecounter=0;
		EnterCriticalSection(&hashing);
					}					
					if (entry!=0)
					{
						memset(&pac, 0, UDPSIZESI);
						neighbour=(NeighbourLoad *)entry->data;
						//Preparing State information packet for client
						if (vessel->GlobalID==neighbour->Source)
						{
							absolute=true;
							remotevessel=neighbour->Source;								
						EnterCriticalSection(&orbiter);
							pac.content.MJD=dinfo.snapshotMJD;
							vesselstate=dinfo.snapshot[remotevessel->object];
							if (!remotevessel->vessel->GetThrustVector(location)) location=_V(0,0,0);
							if (remotevessel->vessel->GroundContact())
							{
								remotevessel->vessel->GetWeightVector(pac.content.acc);
								location-=pac.content.acc;
							}							
							remotevessel->vessel->GlobalRot(location, pac.content.acc);
							pac.content.acc/=remotevessel->vessel->GetMass();

							memset(&pacKA, 0, UDPSIZESI);
							pacKA.contentKA.hover=		(signed char)(remotevessel->vessel->GetThrusterGroupLevel(THGROUP_HOVER)		*  127);
							pacKA.contentKA.main=		(signed char)(remotevessel->vessel->GetThrusterGroupLevel(THGROUP_RETRO)		* -127);
							pacKA.contentKA.main+=		(signed char)(remotevessel->vessel->GetThrusterGroupLevel(THGROUP_MAIN)			*  127);
							pacKA.contentKA.pitch=		(signed char)(remotevessel->vessel->GetThrusterGroupLevel(THGROUP_ATT_PITCHDOWN)* -127);
							pacKA.contentKA.pitch+=		(signed char)(remotevessel->vessel->GetThrusterGroupLevel(THGROUP_ATT_PITCHUP)	*  127);
							pacKA.contentKA.yaw=		(signed char)(remotevessel->vessel->GetThrusterGroupLevel(THGROUP_ATT_YAWRIGHT)	* -127);
							pacKA.contentKA.yaw+=		(signed char)(remotevessel->vessel->GetThrusterGroupLevel(THGROUP_ATT_YAWLEFT)	*  127);
							pacKA.contentKA.bank=		(signed char)(remotevessel->vessel->GetThrusterGroupLevel(THGROUP_ATT_BANKRIGHT)* -127);
							pacKA.contentKA.bank+=		(signed char)(remotevessel->vessel->GetThrusterGroupLevel(THGROUP_ATT_BANKLEFT)	*  127);
							pacKA.contentKA.sideward=	(signed char)(remotevessel->vessel->GetThrusterGroupLevel(THGROUP_ATT_LEFT)		* -127);
							pacKA.contentKA.sideward+=	(signed char)(remotevessel->vessel->GetThrusterGroupLevel(THGROUP_ATT_RIGHT)	*  127);
							pacKA.contentKA.upward=		(signed char)(remotevessel->vessel->GetThrusterGroupLevel(THGROUP_ATT_DOWN)		* -127);
							pacKA.contentKA.upward+=	(signed char)(remotevessel->vessel->GetThrusterGroupLevel(THGROUP_ATT_UP)		*  127);
							pacKA.contentKA.forward=	(signed char)(remotevessel->vessel->GetThrusterGroupLevel(THGROUP_ATT_BACK)		* -127);
							pacKA.contentKA.forward+=	(signed char)(remotevessel->vessel->GetThrusterGroupLevel(THGROUP_ATT_FORWARD)	*  127);
							
							//Check for absolute or relative state vectors
							//
							// reason for going absolute:
							// 1.. master not in same gbody reference
							// 2.. no master specified or not session registered
							// 3.. jump displacement occured
							// 4.. relative distance subvisual and/or relative velocity over 1 km/s
							// 5.. attachment parent not session registered
							absolutereason=0;
							if (remotevessel->lastJump!=0) absolutereason=3;
							//Check for parent attachment
							ATTACHMENTHANDLE attach;
							int attachIndex=0;
							OBJHANDLE attachHandle=NULL;
							do if ((attach=remotevessel->vessel->GetAttachmentHandle(true, attachIndex++)) &&
									(attachHandle=remotevessel->vessel->GetAttachmentStatus(attach))!=NULL)
							{
								absolutereason=0;
								break;
							}
							while (attach);
							if (absolutereason)
						LeaveCriticalSection(&orbiter);
							else
							{
								//Transfer relativ state info for precision
								//if there is a master, take him as relative point instead of the target
								master=NULL;
								if (attachHandle)
								{
									sprintf(line, "%p", attachHandle);
									master=(IDload *) threaddata->user->GlobalsByHandle->get(line);
								}
								else if (remotevessel->MasterID>0)
								{
									sprintf(line, "%d", remotevessel->MasterID);
									master=(IDload *) threaddata->user->GlobalsByID->get(line);
								}
								if (!master)
								{
						LeaveCriticalSection(&orbiter);
									absolutereason=attachHandle?5:2;
								}
								else
								{
									sourcestate=dinfo.snapshot[master->object];
									DWORD w,h;									
									oapiGetViewportSize(&w, &h);
									double fov=oapiCameraAperture();
						LeaveCriticalSection(&orbiter);
									absolutereason=1;																		
									if (vesselstate.rbody==sourcestate.rbody)
									{
										//Check visual distance and relative velocity
										double subVisualDistance = (h>0 && fov>0)?0.25*h/tan(PI*fov/360.0):10000;
										double size=remotevessel->vessel->GetSize();
										double size2=master->vessel->GetSize();
										subVisualDistance*=size>size2?size:size2;
										if (length(vesselstate.rpos-sourcestate.rpos)>subVisualDistance ||
											length(vesselstate.rvel-sourcestate.rvel)>1000) absolutereason=4;
										else if (!attach && attachHandle) absolutereason=5;
										else
										{
											absolute=false;
											absolutereason=0;											
											pac.content.reference=master->ID;
											pac.content.pos=vesselstate.rpos-sourcestate.rpos;
											pac.content.vel=vesselstate.rvel-sourcestate.rvel;												
										}
									}
								}
							}
						EnterCriticalSection(&image);
							if (!absolute || (gimage.e<=dinfo.tolerance && gimage.e>=-dinfo.tolerance))
							{
						LeaveCriticalSection(&image);

								if (idle || absolute)
								{
									sprintf(line, "%p", vesselstate.rbody);
									GBody=(IDload *)threaddata->user->GlobalsByHandle->get(line);
									if (GBody==0) tlogging(10, "ERROR - vessel invalid!");
									else if (absolute)
									{
										//Transfer absolute state info for starter						
										pac.content.reference=GBody->ID;
										pac.content.pos=vesselstate.rpos;
										pac.content.vel=vesselstate.rvel;							
									}						
									pacKA.contentKA.GlobalID=pac.content.ID=remotevessel->ID;
									pac.content.size=remotevessel->size;
									pac.content.flags=(char)0x02; //StateInfo without Focus flag
									pac.content.arot=vesselstate.arot;
									pac.content.vrot=vesselstate.vrot;
									pacKA.contentKA.tsSent=pac.content.tsSent=get_global_timestamp();								
									if (remotevessel->lastJump>0 && (remotevessel->lastJump+5)<pac.content.tsSent) remotevessel->lastJump=0;
									sendtoport=neighbour->port;
									threaddata->remote_addr.sin_addr.S_un.S_addr=neighbour->ip;
									threaddata->remote_addr.sin_port=htons(sendtoport);
									if (entry->next==0)
									{
										neighbourbucket++;										
										neighbourindex=0;
									}
		LeaveCriticalSection(&hashing);
									//Sending the state information packet to neighbour
									if (sendto(threaddata->com, pac.buffer, UDPSIZESI, 0,
										(LPSOCKADDR) &threaddata->remote_addr,
										threaddata->sin_size) == SOCKET_ERROR)
									{
										sprintf (line, "Port %d: Error sending to %s:%d..",
												threaddata->thisport,
												inet_ntoa(threaddata->remote_addr.sin_addr),
												sendtoport);
										tlogging(3,line);
									}
									else
									{
										sprintf (line, ">N%d %d wrt %d (%d) %f",
											sendtoport,
											pac.content.ID,
											pac.content.reference,
											absolutereason,
											remotevessel->lastJump);
										tlogging(3, line);											
									}

									//Send thruster keep-alive to neighbour, too				
									pacKA.contentKA.Global4Local=false;
									pacKA.contentKA.LocalID=0;
									pacKA.contentKA.reserve2=0;
									pacKA.contentKA.flags=0x80; //Keep-alive packet	with thruster flag set
									//Sending the packet to server						
									if (sendto(threaddata->com, pacKA.buffer, UDPSIZEKA, 0,
										(LPSOCKADDR) &threaddata->remote_addr,
										threaddata->sin_size) == SOCKET_ERROR)
									{
										sprintf (line, "Port %d: Error sending to %s:%d..",
												threaddata->thisport,inet_ntoa(threaddata->remote_addr.sin_addr),sendtoport);
										tlogging(30,line);
										if (threaddata->activation)
										{
											closesocket(threaddata->com);
											dinfo.smcon=false;
											threaddata->activation=false;
											statusupdate=true;
										}
									}
									else
									{
										sprintf (line, ">KA%d", sendtoport);
										tlogging(3, line);										
									}

									if (neighbourbucket<neighboursize) WaitForSingleObject(sleeper, dinfo.curdel);
								}
								else
								{
		LeaveCriticalSection(&hashing);
								}
							}
							else
							{
						LeaveCriticalSection(&image);								
								if (entry->next==0)
								{
									neighbourbucket++;										
									neighbourindex=0;
								}				
		LeaveCriticalSection(&hashing);
							}
						}
						else
						{
							if (entry->next==0)
							{
								neighbourbucket++;									
								neighbourindex=0;
							}				
		LeaveCriticalSection(&hashing);
						}
					}
					else
					{
						neighbourbucket++;
		LeaveCriticalSection(&hashing);
					}
				}
				while (neighbourbucket<neighboursize);
				if (idle) vessel->idle=dinfo.idler;
				else vessel->idle--;
			}				
			else
		LeaveCriticalSection(&hashing);
			if (startup)
			{
				if (dinfo.clientconfig<1)
				{					
					getsockname(threaddata->com, (LPSOCKADDR) &rthreaddata->remote_addr, &rthreaddata->sin_size);
					rthreaddata->thisport=ntohs(rthreaddata->remote_addr.sin_port);
					rthreaddata->com=threaddata->com;
					SetEvent(udprecvconnect); //Set by senderthread if standard config
				}
				startup=false;
			}
			if (waitnow) WaitForSingleObject(sleeper, dinfo.curblk);
		}

		sprintf (line, "Sending on socket %X stopped",threaddata->com);			
		tlogging(3, line);
		SetEvent(udpsenddisconnect);
	}
}

double get_global_timestamp()
{
	return wrapper.Now(true);
}

double get_clock_acceleration(double T, double mjd, double stamp, double acceleration)
{
	if (!dinfo.clockcontroller) return acceleration;
	double v = mjd * 86400;
	EnterCriticalSection(&image);
		gimage.e= v - gimage.MJD * 86400 + gimage.last_MJD_update - stamp;
		if (acceleration<10 || gimage.e>0)
		{	
			gimage.integral += gimage.e * T;
			if (gimage.integral > 10) gimage.integral=1;
			if (gimage.integral < -10) gimage.integral=-1;
			acceleration=-dinfo.Kp*gimage.e-dinfo.Ki*gimage.integral-dinfo.Kd/T*(gimage.e-gimage.e1);
			if (acceleration<0.1) acceleration=0.1;
			if (acceleration>2) acceleration=2.0;
			if (acceleration<1.05 && acceleration>0.95) acceleration=1.0;
		}
		else if (gimage.e>-acceleration) acceleration=-gimage.e;
		gimage.e1=gimage.e;
	LeaveCriticalSection(&image);
	return acceleration;
}

// Garbage collector
void WINAPI garbagecollector(DWORD sd)
{
	struct Sthreaddata *threaddata=(struct Sthreaddata *) sd;
	char line[LINESIZE];
	unsigned long neighbucket, globalbucket, mark, neighsize, globalsize;
	CHashData *entry;
	NeighbourLoad *neighbour;
	IDload *global;	
	int idlecounter;
	double diff;
	struct KillLoad *kill;
					
	while (true)
	{
		WaitForSingleObject(startgc, INFINITE);
		ResetEvent(startgc);
		if (!threaddata->activation) break;
		
		tlogging(7, "GC started");
		neighsize=threaddata->user->Neighbours->get_size();		
		globalsize=threaddata->user->GlobalsByID->get_size();		
		neighbucket=0;
		globalbucket=0;		
		
		while (threaddata->activation)
		{
		EnterCriticalSection(&hashing);							
			//Checking next neighbour information
			if (!threaddata->user->Neighbours->empty && threaddata->activation)
			{				
				mark=neighbucket;
				idlecounter=0;
				do
				{
					if (idlecounter++>dinfo.maxidle)
					{
		LeaveCriticalSection(&hashing);
						idlecounter=0;
		EnterCriticalSection(&hashing);
					}
					entry=threaddata->user->Neighbours->content(neighbucket++);
					if (neighbucket>=neighsize) neighbucket=0;										
				}
				while (mark!=neighbucket && entry->key==0);				
				if (entry->key!=0)
				{
					do
					{
						neighbour=(NeighbourLoad *)entry->data;
						if ((diff=get_global_timestamp()-neighbour->tsSent)*1000>dinfo.neighidle)
						{
							//Delete neighbour entry
							sprintf(line, "N %s: DEL", entry->key);
							threaddata->user->Neighbours->del(entry->key);	
							if (neighbour->Source!=0)
								if (--(neighbour->Source->refcount)<0)
								{
									tlogging(10, "ERROR - Source reference count inconsistent!");
									neighbour->Source->refcount=0;
								}
							delete neighbour;
							neighbour=0;
							entry=0;														
						}
						else
						{
							sprintf(line, "N %s: %fs", entry->key, diff);							
							entry=entry->next; //Check next in bucket
						}
						tlogging(7, line);
					}
					while (entry!=0);
				}
				else tlogging(10, "ERROR - Neighbours count inconsistent in GC!");
			}												
			//Checking next global information			
			if (threaddata->user->GlobalsByID->get_count()>dinfo.gbodiescount+threaddata->user->LocalsByID->get_count() && threaddata->activation)
			{				
				mark=globalbucket;
				idlecounter=0;
				do
				{
					if (idlecounter++>dinfo.maxidle)
					{
		LeaveCriticalSection(&hashing);
						idlecounter=0;
		EnterCriticalSection(&hashing);
					}
					entry=threaddata->user->GlobalsByID->content(globalbucket++);
					if (globalbucket>=globalsize) globalbucket=0;			
					do
					{
						if (entry->key!=0)
						{
							global=(IDload *)entry->data;							
							if (!oapiIsVessel(global->object) || global->linkage==1)
							{
								entry=entry->next;
								global=0;
							}
						}
						else
						{
							entry=0;
							global=0;
						}
					}
					while (global==0 && entry!=0);					
				}
				while (mark!=globalbucket && entry==0);				
				if (entry!=0)
				{
					do
					{
						global=(IDload *)entry->data;
						if ((diff=get_global_timestamp()-global->tsSent)*1000>dinfo.globalidle && global->refcount==0)
						{
							EnterCriticalSection(&orbiter);
								//Put the old vessel into the kill list
								kill=new KillLoad;
								kill->next=threaddata->user->KillList;
								kill->object=global->object;
								threaddata->user->KillList=kill;								
							LeaveCriticalSection(&orbiter);
							sprintf(line, "G %s: DEL", entry->key);
							entry=0;							
						}
						else
						{
							sprintf(line, "G %s: %fs", entry->key, diff);
							entry=entry->next; //Check next in bucket
						}
						tlogging(7, line);
					}
					while (entry!=0);
				}
				else
				{
					//This happens, because streaming to the server was interrupted. The server garbaged the vessels
					//and their numbers... As soon as the client catches up, the server gives new ID, sends new globals
					//for locals, the globals are added, the old globals are not deleted! Globals count inconsistent!		
					tlogging(10, "ERROR - Globals count inconsistent in GC!");
				}
			}
			if (dinfo.deletedVessels.size()>0)
			{
				//Prune old deletion marks - global IDs might be re-used later
				double timestamp=get_global_timestamp();
				std::vector<unsigned int> tobedeleted;
				for(std::map<unsigned int, double>::iterator i=dinfo.deletedVessels.begin();i!=dinfo.deletedVessels.end();i++)
					if ((timestamp-i->second)*1000 > dinfo.globalidle) tobedeleted.push_back(i->first);
				for(int i=tobedeleted.size();i-->0;) dinfo.deletedVessels.erase(tobedeleted[i]);
			}
		LeaveCriticalSection(&hashing);
			tlogging(7, "Tick");
			WaitForSingleObject(sleeper, dinfo.curgc);
		}	

		tlogging(7, "GC stopped");
		SetEvent(stoppedgc);
	}	
}

void keyfilter(void *null, WPARAM &wparam, LPARAM &lparam)
{
	switch (wparam)
	{
	case VK_SHIFT:
		wparam=VK_LSHIFT;
		break;
	case VK_CONTROL:
		wparam=VK_LCONTROL;
		break;	
	case VK_MENU:
		wparam=VK_LMENU;
		break;	
	}
}

int keyhandler(void *null, char *keystate)
{
	OBJHANDLE focobj=oapiGetFocusObject();
	if (focobj==NULL || !oapiIsVessel(focobj)) return 0;
	VESSEL *me=oapiGetVesselInterface(focobj);
	//Check for missile key combinations
	if (!KEYDOWN(keystate, dinfo.missileLaunchKey) && !KEYDOWN(keystate,dinfo.missileTargetKey) && !KEYDOWN(keystate, dinfo.statusPingKey)) return 0;
	if (!KEYMOD_LSHIFT(keystate) && (dinfo.missileModifiers & 0x01)>0) return 0;
	if (!KEYMOD_RSHIFT(keystate) && (dinfo.missileModifiers & 0x02)>0) return 0;
	if (!KEYMOD_LCONTROL(keystate)&&(dinfo.missileModifiers & 0x04)>0) return 0;
	if (!KEYMOD_RCONTROL(keystate)&&(dinfo.missileModifiers & 0x08)>0) return 0;
	if (!KEYMOD_LALT(keystate)  &&  (dinfo.missileModifiers & 0x10)>0) return 0;
	if (!KEYMOD_RALT(keystate)  &&  (dinfo.missileModifiers & 0x20)>0) return 0;

	if (KEYDOWN(keystate, dinfo.statusPingKey))
	{
		if (dinfo.statusPinger==NULL) dinfo.statusPinger=focobj;
		return 1;
	}

	if (KEYDOWN(keystate, dinfo.missileTargetKey))
	{
		OBJHANDLE target;
		int i=0;
		std::vector<OBJHANDLE> objects;
		while (target=oapiGetVesselByIndex(i++))
		{
			VECTOR3 g,l,n;
			if (target==focobj) continue; //Don't target yourself
			oapiGetGlobalPos(target, &g);
			me->Global2Local(g, l);
			//Quick frustum culling
			if (l.z<0) continue;
			double d=length(l);
			if (0.01>d || d>5e4) continue; //TODO: make missile targeting range a server-set constant
			//Slow frustum culling
			double phi=acos((l/=d).z);
			if (phi>oapiCameraAperture()) continue;
			objects.push_back(target);
		}
		if (objects.size()>0) target=objects[dinfo.missileTargetRotate++ % objects.size()];
		dinfo.missileTarget=target;
		return 1;
	}

	//Check for missile launch pre-conditions
	if (me->GroundContact() || dinfo.missiles[me]<=0) return 1;
	if (me->GetAltitude()<-dinfo.missileOffset.y) return 1;

	//Launch missile
	char line[LINESIZE];
	VESSELSTATUS2 state=dinfo.snapshot[focobj];
	VECTOR3 offset;
	me->GlobalRot(dinfo.missileOffset, offset);
	state.rpos+=offset;
	sprintf(line, "M%d-%s", dinfo.missiles[me]--, me->GetName());
	((VESSEL3 *)oapiGetVesselInterface(oapiCreateVesselEx(line, "OMPMissile", &state)))->clbkGeneric(0x8000, 0, dinfo.missileTarget);
	
	return 1;
}