Coding Cookbook

By

Nicholas Kingsley

1 Copyright ©Nicholas Kingsley 2011

ISBN : 978-1-4709-5717-9

2 Introduction

This book contains a fair few of my routines that I have written over the many years of programming on a PC, starting from DarkBasic Professional and and then onto the wonderful GLBasic.

Unlike most other programming cookbooks, this one will, where possible describe how the routine came about and what it was originally used for.

There aren't any details on how to use the examples and you wouldn't, in most cases, be able to use the code as set out as they usually need other routines, but the code listed here should help you in your own programming endeavours and could give you a few ideas.

Formatting has been left as it was in the original file.

Code is listed for the following languages :

GLBasic C Javascript DarkBasic Professional DarkGame SDK BlitzMax PureBasic and one bit of PHP code

3 Index

GLBasic...... 6 Javascript...... 276 BufferReading...... 7 Export Javascript...... 277 C Headers...... 8 Shift Tiles #1 - #4...... 279 CRC32...... 12 Hosting Network Game...... 14 PureBasic...... 281 Joypad Filter...... 33 Mappy Test Program...... 282 Poke/Peek...... 35 DispBit...... 284 Mappy...... 38 Graphics Converter...... 286 MiscRoutines...... 66 Statistical Analysis Program...... 288 Network Routine...... 77 Viewport...... 81 DarkBasic Professional...... 293 SHA 512 Encryption...... 83 Screen Wipe Routines...... 294 AppTimer...... 90 Multiple Cameras...... 297 Client...... 92 LoadRoutine...... 301 Destruction...... 101 DrawLine...... 304 Fade...... 102 Gradient Lines...... 305 Language Selection …...... 104 Fade Screen...... 306 Hexadecimal...... 120 Sprite Print...... 307 Network Host...... 121 Bouncing Lines I...... 311 Lobby...... 134 Localisation...... 140 DarkGame SDK...... 314 Key/Value Mapping...... 144 A Game...... 315 Mouse Buffer...... 146 Shadow of the Beast...... 326 Network Message...... 147 Lots Of 3D Objects...... 330 On-screen Joystick...... 154 Bouncing Lines III...... 334 Progress...... 162 Setup...... 166 C...... 338 String...... 201 LinkList Routine...... 339 Vector...... 202 Mappy Routine...... 345 Validate IP4 Address #1...... 205 Validate IP4 Address #2...... 206 BlitzMax...... 361 Version Information...... 207 Config Data List...... 362 Email...... 208 Character Counter...... 364

4 GLBasic Cont... BlitzMax Cont... Pseudo 3D Road...... 209 LoadData...... 367 Message...... 211 Mappy...... 379 Flood fill routine...... 215 Select Files...... 217 PHP...... 394 JNR Examples #1...... 221 Online Hiscore Routine...... 395 Example #2...... 225 Example #3...... 231 Clock Graphic Demonstration...... 238 Basic INCBIN routine...... 240 Ball/Trailblazer-type Track Editor...... 241 Bouncing Lines IV...... 273

5 GLBasic

6 BufferReading

This routine was devised to allow large blocks of data to be read in quickly. This code was never really used, and was only written to see how easy it would be to access the usual C file I/O functions. INLINE typedef unsigned int size_t; extern "C" int open(char * filename, int flags, int mode); extern "C" int close(int fd); extern "C" void * memset ( void * ptr, int value, size_t num ); extern "C" int READ(int fd, void * ptr, int numbytes); #define _O_RDONLY 0 #define _O_WRONLY 1 #define _O_RDWR 2 #define _O_ACCMODE (O_RDONLY|O_WRONLY|O_RDWR) #define _O_APPEND 0x0008 #define _O_CREAT 0x0100 #define _O_TRUNC 0x0200 #define _O_EXCL 0x0400 #define _O_TEXT 0x4000 #define _O_BINARY 0x8000 #define _O_RAW _O_BINARY #define _O_TEMPORARY 0x0040 #define _O_RANDOM 0x0010 #define _O_SEQUENTIAL _O_RANDOM #define _O_SHORT_LIVED 0x1000 ENDINLINE FUNCTION BufferOpen%:fileName$ INLINE RETURN (DGNat) open((char *) fileName_Str.c_str(),_O_RDONLY | _O_BINARY,0); ENDINLINE ENDFUNCTION FUNCTION BufferClose%:handle% INLINE RETURN (DGNat) close(handle); ENDINLINE ENDFUNCTION FUNCTION BufferRead$:handle%,BYREF result% LOCAL temp$ temp$="" INLINE char buffer[4097]; int res; memset(&buffer,(char) 0,sizeof(buffer)); res=read(handle,(char *) &buffer,sizeof(buffer)-1); result=res; temp_Str.assign((char *) &buffer); ENDINLINE RETURN temp$ ENDFUNCTION

7 C Headers

This set of “extern” function definitions allows various C routines to be called. This was created as, to start with, all of my C routines had different sets of function definitions. INLINE typedef int size_t; // C commands // mem... commands extern "C" void *memset(void *s, int c, size_t n); extern "C" void *memcpy(void *dest, const void *src, size_t n); extern "C" int memcmp(const void *s1, const void *s2, size_t n); extern "C" void * memmove(void * destination, const void * source, size_t num ); // Memory allocation/freeing commands extern "C" void *realloc( void * ptr, size_t size ); extern "C" void *malloc ( size_t size ); extern "C" void *calloc(size_t nmemb, size_t size); extern "C" void free(void *ptr); // String commands extern "C" size_t strlen(const char *s); extern "C" char * strcpy ( char * destination, const char * source ); extern "C" char * strncpy ( char * destination, const char * source, size_t num ); extern "C" int strcmp ( const char * str1, const char * str2 ); extern "C" int sprintf ( char * str, const char * format, ... ); // Low level I/O extern "C" int access(const char *pathname, int mode); extern "C" int open(const char *pathname, int flags); extern "C" size_t READ(int fd, void *buf, size_t count); extern "C" int write(int fd, char *buf, size_t count); extern "C" int close(int fd); extern "C" int rename(const char *_old, const char *_new); // These are used with the open function #ifdef WIN32 typedef struct __RECT { long left; long top; long right; long bottom; } __RECT; typedef int HDC; /* Specifiy one of these flags TO define the access mode. */ #define _O_RDONLY 0 #define _O_WRONLY 1 #define _O_RDWR 2 /* Mask FOR access mode bits IN the _open flags. */ #define _O_ACCMODE (_O_RDONLY|_O_WRONLY|_O_RDWR) #define _O_APPEND 0x0008 /* Writes will add TO the END of the file. */ #define _O_RANDOM 0x0010 #define _O_SEQUENTIAL0x0020 #define _O_TEMPORARY 0x0040 /* Make the file dissappear after closing. * WARNING: Even IF NOT created by _open! */ #define _O_NOINHERIT 0x0080 #define _O_CREAT 0x0100 /* Create the file IF it does NOT exist. */ #define _O_TRUNC 0x0200 /* Truncate the file IF it does exist. */ #define _O_EXCL 0x0400 /* Open only IF the file does NOT exist. */ #define _O_SHORT_LIVED 0x1000 /* NOTE: Text is the DEFAULT even IF the given _O_TEXT bit is NOT on. */ #define _O_TEXT 0x4000 /* CR-LF IN file becomes LF IN memory. */ #define _O_BINARY 0x8000 /* INPUT AND output is NOT translated. */ #define _O_RAW _O_BINARY

8 #ifndef _NO_OLDNAMES /* POSIX/Non-ANSI names FOR increased portability */ #define O_RDONLY _O_RDONLY #define O_WRONLY _O_WRONLY #define O_RDWR _O_RDWR #define O_ACCMODE _O_ACCMODE #define O_APPEND _O_APPEND #define O_CREAT _O_CREAT #define O_TRUNC _O_TRUNC #define O_EXCL _O_EXCL #define O_TEXT _O_TEXT #define O_BINARY _O_BINARY #define O_TEMPORARY _O_TEMPORARY #define O_NOINHERIT _O_NOINHERIT #define O_SEQUENTIAL _O_SEQUENTIAL #define O_RANDOM _O_RANDOM #define SM_CXSCREEN 0 #define SM_CYSCREEN 1 #define HWND_BOTTOM ((HWND)1) #define HWND_NOTOPMOST ((HWND)(-2)) #define HWND_TOP ((HWND)0) #define HWND_TOPMOST ((HWND)(-1)) #define HWND_DESKTOP (HWND)0 #define HWND_MESSAGE ((HWND)(-3)) #define SWP_DRAWFRAME 0x0020 #define SWP_FRAMECHANGED 0x0020 #define SWP_HIDEWINDOW 0x0080 #define SWP_NOACTIVATE 0x0010 #define SWP_NOCOPYBITS 0x0100 #define SWP_NOMOVE 0x0002 #define SWP_NOSIZE 0x0001 #define SWP_NOREDRAW 0x0008 #define SWP_NOZORDER 0x0004 #define SWP_SHOWWINDOW 0x0040 #define SWP_NOOWNERZORDER 0x0200 #define SWP_NOREPOSITION SWP_NOOWNERZORDER #define SWP_NOSENDCHANGING 0x0400 #define SWP_DEFERERASE 0x2000 #define SWP_ASYNCWINDOWPOS 0x4000 #define SW_HIDE 0 #define SW_SHOWNORMAL 1 #define SW_SHOWNOACTIVATE 4 #define SW_SHOW 5 #define SW_MINIMIZE 6 #define SW_SHOWNA 8 #define SW_SHOWMAXIMIZED 11 #define SW_MAXIMIZE 12 #define SW_RESTORE 13 #define HORZRES 8 #define VERTRES 10 extern "C" __stdcall int GetSystemMetrics(int); extern "C" __stdcall int GetWindowRect(HWND hWnd,struct __RECT *lpRect); extern "C" __stdcall int GetClientRect(HWND hWnd,struct __RECT *lpRect); extern "C" __stdcall int SetWindowTextA(HWND hWnd,const char *lpString); extern "C" __stdcall HWND GetDesktopWindow(void); extern "C" __stdcall int SetWindowPos(HWND hWnd,HWND hWndInsertAfter,int X,int Y,int cx,int cy,int uFlags); extern "C" __stdcall int EnumDisplaySettingsA(const char*, unsigned int, void*); extern "C" __stdcall HWND GetForegroundWindow(void); extern "C" __stdcall int GetLastError(void); extern "C" __stdcall int GetSystemMetrics(int nIndex); extern "C" __stdcall HDC GetDC(HWND); extern "C" __stdcall int GetDeviceCaps(HDC,int); #endif #elif _WIN32_CE /* * Flag values FOR open(2) AND fcntl(2) * The kernel adds 1 TO the open modes TO turn it into some * combination of FREAD AND FWRITE. */ #define _FOPEN (-1) /* from sys/file.h, kernel use only */

9 #define _FREAD 0x0001 /* READ enabled */ #define _FWRITE 0x0002 /* write enabled */ #define _FAPPEND 0x0008 /* append (writes guaranteed at the END) */ #define _FMARK 0x0010 /* internal; mark during gc() */ #define _FDEFER 0x0020 /* internal; defer FOR NEXT gc pass */ #define _FASYNC 0x0040 /* signal pgrp when DATA ready */ #define _FSHLOCK 0x0080 /* BSD flock() shared lock present */ #define _FEXLOCK 0x0100 /* BSD flock() exclusive lock present */ #define _FCREAT 0x0200 /* open with file create */ #define _FTRUNC 0x0400 /* open with truncation */ #define _FEXCL 0x0800 /* error on open IF file exists */ #define _FNBIO 0x1000 /* non blocking I/O (sys5 style) */ #define _FSYNC 0x2000 /* do all writes synchronously */ #define _FNONBLOCK 0x4000 /* non blocking I/O (POSIX style) */ #define _FNDELAY _FNONBLOCK /* non blocking I/O (4.2 style) */ #define _FNOCTTY 0x8000 /* don't assign a ctty on this open */ #define O_ACCMODE (O_RDONLY|O_WRONLY|O_RDWR) #define O_RDONLY 0 /* +1 == FREAD */ #define O_WRONLY 1 /* +1 == FWRITE */ #define O_RDWR 2 /* +1 == FREAD|FWRITE */ #define O_APPEND _FAPPEND #define O_CREAT _FCREAT #define O_TRUNC _FTRUNC #define O_EXCL _FEXCL /* O_SYNC _FSYNC NOT posix, defined below */ /* O_NDELAY _FNDELAY set IN include/fcntl.h */ /* O_NDELAY _FNBIO set IN 5include/fcntl.h */ #define O_NONBLOCK _FNONBLOCK #define O_NOCTTY _FNOCTTY /* FOR machines which care - */ #if defined (_WIN32) || defined (__CYGWIN__) #define _FBINARY 0x10000 #define _FTEXT 0x20000 #define _FNOINHERIT 0x40000 #define O_BINARY _FBINARY #define O_TEXT _FTEXT #define O_NOINHERIT _FNOINHERIT /* The windows header files define versions with a leading underscore. */ #define _O_RDONLY O_RDONLY #define _O_WRONLY O_WRONLY #define _O_RDWR O_RDWR #define _O_APPEND O_APPEND #define _O_CREAT O_CREAT #define _O_TRUNC O_TRUNC #define _O_EXCL O_EXCL #define _O_TEXT O_TEXT #define _O_BINARY O_BINARY #define _O_RAW O_BINARY #define _O_NOINHERIT O_NOINHERIT #endif #ifndef _POSIX_SOURCE #define O_SYNC _FSYNC #endif #else // For all other platforms #define _O_ACCMODE 00003 #define _O_RDONLY 00 #define _O_WRONLY 01 #define _O_RDWR 02 #define _O_CREAT 00100 /* NOT fcntl */ #define _O_EXCL 00200 /* NOT fcntl */ #define _O_NOCTTY 00400 /* NOT fcntl */ #define _O_TRUNC 01000 /* NOT fcntl */ #define _O_APPEND 02000 #define _O_NONBLOCK 04000 /* NOT fcntl */ #define _O_NDELAY O_NONBLOCK #endif #ifndef IPHONE // This is for non-windows platforms

10 extern "C" __stdcall void SDL_WM_SetCaption(const char *,const char *); #endif ENDINLINE

11 CRC32

This routine was created before GLBasic was equipped with the ENCRYPT/DECRYPT set of commands, and is used to generate a CRC 32 value for a set of data. This code was originally used when I was working at Plastics Software (and was used, if I remember correctly as part of the copy protection system). It required no modification to be used in GLBasic. //! This calculates a CRC-32 value for a file //\param fileName$ - This is the filename that you want to calculate a CRC value for //\return - A signed 32-bit CRC value FUNCTION calculateCRC32%:fileName$ LOCAL channel% = 1 LOCAL bufferSize% = 1024 LOCAL d%[]; DIM d%[bufferSize%] LOCAL amount% LOCAL crc% LOCAL loop% LOCAL length crc%=0 IF DOESFILEEXIST(fileName$)=TRUE length=GETFILESIZE(fileName$) IF OPENFILE(channel%,fileName$,1) DEBUG "length : "+length+"\n" // Read in bytes FILESEEK channel%,0,0 amount%=readInBytes(channel%,d%[],bufferSize%,length) WHILE amount%>0 FOR loop%=0 TO amount%-1 crc%=calcCRC32(d%[loop%],crc%) NEXT amount%=readInBytes(channel%,d%[],bufferSize%,length) DEBUG "." WEND crc%=calcCRC32End(crc%) CLOSEFILE channel% ELSE DEBUG "Error : "+GETLASTERROR$()+"\n" ENDIF ENDIF RETURN crc% ENDFUNCTION @FUNCTION readInBytes%:handle%,d%[],size%,length LOCAL loop% LOCAL pos LOCAL diff loop%=0 WHILE ENDOFFILE(handle%)=FALSE AND loop%3 READULONG handle%,d%[loop%] ENDSELECT INC loop%,1 WEND RETURN loop% ENDFUNCTION

12 INLINE const unsigned long CRC[]={ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; ENDINLINE @FUNCTION calcCRC32%:value%,crc% INLINE RETURN (CRC[(unsigned char) (crc^(unsigned long) value)]^((crc<<8) & 0x00FFFFFF)); ENDINLINE ENDFUNCTION @FUNCTION calcCRC32End%:crc% INLINE RETURN (crc^0xFFFFFFFF); ENDINLINE ENDFUNCTION

13 Hosting Network Game

This routine was designed to allow the user to create or host a network game using a lobby system so that host players can see all players and all clients can join without needing to know the host's IP address.

It more or less works, but always seemed to be very inefficient. This was created, originally for one of my games, to see if I could get an efficient and easy to use network play system up and running. // ------// // Project: TestHostJoinNetworkGame // Start: Sunday, January 25, 2009 // IDE Version: 6.143 // Network messages // This is a broadcast message // A broadcast message will contain the following information // 2 character program ID (in hex), consisting of : // t12vsjrr rrrrrrrr iiiiiiii iiiiiiii // t - If this is 1, then its a test program // 1 - If this is 1, then the program is a timed demo program // 2 - If this is 1, then the program is a non-timed demo program // v - If this is 1, then the exact program version is needed to connect // s - If this is 1, then the server is allowed to play on its own (single player mode or sandbox mode) // - Reserved for future expansion // i - This is the program ID GLOBAL SEPERATOR$ = "," GLOBAL NETMESSAGE_BROADCAST$ = "BDC" GLOBAL BROADCAST_TESTPROGRAM% = 2147483648 // 31 GLOBAL BROADCAST_TIMEDDEMO% = 1073741824 // 30 GLOBAL BROADCAST_NONTIMEDDEMO% = 536870912 // 29 GLOBAL BROADCAST_EXACTVERSION% = 268435456 // 28 GLOBAL BROADCAST_SINGLEMODE% = 134217728 // 27 GLOBAL BROADCAST_JOINATANYTIME% = 67108864 // 26 GLOBAL NETMESSAGE_HOSTDISCONNECT$ = "HDC" // Host is disconnecting GLOBAL NETMESSAGE_CLIENTDISCONNECT$ = "CDC" // Client is disconnecting GLOBAL NETMESSAGE_HOSTKICK$ = "KCK" // Host wants to kick a client GLOBAL NETMESSAGE_REQUESTTOJOIN$ = "RTJ" // Client wants to join GLOBAL NETMESSAGE_NOROOMLEFT$ = "NRL" // No more clients can join GLOBAL NETMESSAGE_ALLOWJOIN$ = "ALJ" // Server has allowed join GLOBAL NETMESSAGE_CLIENTREADY$ = "CIR" // Client is ready GLOBAL NETMESSAGE_REQUESTPRESENCE$ = "RFP" // A request for presence has been made GLOBAL NETMESSAGE_PRESENCERESPONSE$ = "PRE" // The response to a request for presence GLOBAL NETMESSAGE_MESSAGE$ = "MSG" // All messages are sent to everyone GLOBAL NETMESSAGE_SENDPLAYERINFO$ = "SPP" // Send player information to all clients GLOBAL NETMESSAGE_GAMEINPROGRESS$ = "GIP" // A Gane is in progress GLOBAL NETMESSAGE_GAMEDATA$ = "GAD" // Game data for other machines to update display // This is for detecting whether a server (or client) has disconnected // If there is no reply within the allotted time, then its regarded as no longer being present TYPE tACK timeForACK timeForACKResponse ENDTYPE // Client and server types // This is a list of client machines connected TYPE tClientList ignore% // If this is TRUE, then the client is ignored because it no longer exists isReady% ipAddress% clientName$ ack AS tACK ENDTYPE // This is a list of availiable servers

14 TYPE tServerList connected% // Is the player connected to this server ? ipAddress% // IP address of server programID% // Details of program being hosted version% // Version of program numPlaying% // Current number of people connected TO the server maxPlayers% // Maximum number of people allowed operatingSystem$ // hostName$ ack AS tACK ENDTYPE // This contains all players, excluding the connecting computer

GLOBAL clientList[] AS tClientList GLOBAL serverList[] AS tServerList // GLOBAL listOfPlayers[] AS tListOfPlayers GLOBAL listKey$="listKey" GLOBAL messageListKey$="messageListKey" GLOBAL NOT_SELECTED% = -1 GLOBAL NOT_FOUND% = -1 GLOBAL TIMEFORACK = 1000.0 GLOBAL RESPONSETIMEFORACK = 2500.0 GLOBAL STATE_CONNECTIONSETUP% = 0 GLOBAL STATE_INITIALISE% = 1 GLOBAL STATE_SYNCPLAYERS% = 2 GLOBAL STATE_SETUPGAME% = 3 GLOBAL STATE_SETUPLEVEL% = 4 GLOBAL STATE_DOGAME% = 5 FUNCTION hostJoinNetworkGame:isHost%,maxPlayers%=4,maxNameLength%=8 LOCAL playerName$ DIM clientList[0] DIM serverList[0] // Does this for clients, as they can list many servers maxPlayers%=bAND(maxPlayers%,255) IF isHost%=TRUE playerName$="Host Plyr" ELSE playerName$="Join Plyr" ENDIF IF changeName(-1,playerName$,maxNameLength%)=FALSE SYSTEMPOINTER FALSE RETURN FALSE ENDIF RETURN setupDisplayNetwork(isHost%,playerName$,maxPlayers%,BROADCAST_TESTPROGRAM% +BROADCAST_SINGLEMODE%+1) ENDFUNCTION // Get player to enter their name FUNCTION changeName%:playerID%,BYREF name$,maxNameLength% LOCAL nameKey$="nameKey" LOCAL okKey$="okKey" LOCAL cancelKey$="cancelKey" LOCAL titleText$="Enter Your Name" LOCAL okText$="OK" LOCAL cancelText$="CANCEL" LOCAL maxWindowWidth% LOCAL maxWindowHeight% LOCAL screenWidth% LOCAL screenHeight% LOCAL fontWidth% LOCAL fontHeight% LOCAL temp$ GETSCREENSIZE screenWidth%,screenHeight% GETFONTSIZE fontWidth%,fontHeight%

15 maxWindowWidth%=MAX(maxNameLength%+1,LEN(titleText$))+2 // Title length. Now take into account the INC maxWindowWidth%,LEN(okText$)+LEN(cancelText$)+2 // OK/Cancel button, ... maxWindowWidth%=maxWindowWidth%*fontWidth% // And multiply by the font width maxWindowHeight%=(fontHeight%*2)+2 DDgui_UpdateFont(TRUE) DDgui_pushdialog((screenWidth%-maxWindowWidth%)/2,(screenHeight%-maxWindowHeight %)/2,maxWindowWidth%,maxWindowHeight%) DDgui_set("","MOVEABLE", TRUE) // can move the dialog at top bar DDgui_set("","TEXT",titleText$) DDgui_text(nameKey$,name$,MAX(maxNameLength%+1,LEN(titleText$))*fontWidth %,fontHeight%+4) DDgui_set(nameKey$, "TEXT",MID$(DDgui_get$(nameKey$, "TEXT"),0,maxNameLength%)) DDgui_button(okKey$,okText$,(LEN(okText$)*fontWidth%)+fontWidth%,fontHeight%+4) DDgui_button(cancelKey$,cancelText$,(LEN(cancelText$)*fontWidth%)+fontWidth %,fontHeight%+4) WHILE TRUE ALPHAMODE 0.0 DDgui_show(FALSE) // show the dialog + handle widgets IF DDgui_get(okKey$,"CLICKED") // Get the name out of the dialog temp$=MID$(DDgui_getitemtext$(nameKey$,0),0,maxNameLength%) DDgui_set(nameKey$,"TEXT",temp$)

IF temp$<>"" name$=temp$ DDgui_popdialog() SYSTEMPOINTER FALSE RETURN TRUE ELSE DDgui_msg("Please enter a name",FALSE) ENDIF ENDIF IF DDgui_get(cancelKey$,"CLICKED") DDgui_popdialog() SYSTEMPOINTER FALSE RETURN FALSE ENDIF SHOWSCREEN HIBERNATE WEND ENDFUNCTION // This is the main screen used by host and clients FUNCTION setupDisplayNetwork%:isHost%,playerName$,maxPlayers%,programID% LOCAL backKey$="backKey" LOCAL backText$="BACK" LOCAL kickKey$="kickKey" LOCAL kickText$="KICK" LOCAL startKey$="startKey" LOCAL startText$="START" LOCAL joinKey$="joinKey" LOCAL joinText$="JOIN" LOCAL readyKey$="readyKey" LOCAL readyText$="READY" LOCAL disconnectKey$ = "disconnectKey" LOCAL disconnectText$ = "DISCONNECT" LOCAL messageKey$="messageKey" LOCAL sendKey$="sendKey" LOCAL numPlayers% LOCAL port%=50130 // Apparently its best to use ports above 49152 LOCAL isJoined% LOCAL socket% LOCAL broadcastIP% LOCAL timeToSendBroadcast LOCAL result%

16 LOCAL msg$ LOCAL length% LOCAL thisIP% // IP Address if this computer LOCAL messageList$[] LOCAL selLine% LOCAL clientCount% // Number of clients connected to a server LOCAL connectedServer% LOCAL ver% // Version string as a 32-bit integer LOCAL cList AS tClientList LOCAL allReady% LOCAL screenWidth% LOCAL screenHeight% LOCAL width% = 640 LOCAL height%= 480 LOCAL speed LOCAL gameStarted% LOCAL state% LOCAL index% LOCAL thisIndex% IF SOCK_INIT()=FALSE DDgui_msg("Unable to initialise socket system",FALSE) RETURN FALSE ENDIF socket%=SOCK_UDPOPEN(port%) IF socket%<0 DDgui_msg("Unable to open UDP port : "+NETGETLASTERROR$(),FALSE) RETURN FALSE ENDIF

initAppTime() broadcastIP%=SOCK_GETIP("255.255.255.255") thisIP%=SOCK_GETIP("") numPlayers%=0 isJoined%=FALSE timeToSendBroadcast=0.0 selLine%=NOT_SELECTED% msg$="" clientCount%=0 connectedServer%=NOT_SELECTED% ver%=versionToInt("0.0.0.1") msg$="" gameStarted%=FALSE state%=STATE_CONNECTIONSETUP% GETSCREENSIZE screenWidth%,screenHeight% IF isHost%=TRUE // Setup the host network createHostJoinWindow(listKey$,"",screenWidth%,screenHeight,width%,height%) DDgui_spacer(0,1) DDgui_button(backKey$,backText$,0,0) DDgui_button(kickKey$,kickText$,0,0) DDgui_button(startKey$,startText$,0,0) INC numPlayers%,1 changeHostTitle(numPlayers%,maxPlayers%) ELSE // Setup the client network createHostJoinWindow(listKey$,"",screenWidth%,screenHeight,width%,height%) DDgui_button(joinKey$,joinText$,0,0) DDgui_button(readyKey$,readyText$,0,0) DDgui_button(backKey$,backText$,0,0) DDgui_button(disconnectKey$,disconnectText$,0,0) changeClientTitle(FALSE,"",0) ENDIF // Add in the message part DDgui_spacer(0,1) DDgui_list(messageListKey$, "", width%-16,48) DDgui_text(messageKey$,"",width%/2,0) DDgui_button(sendKey$,"Send",0,0)

17 updateAppTime() speed=0.0 WHILE TRUE ALPHAMODE 0.0 IF state%=STATE_CONNECTIONSETUP% DDgui_show(FALSE) // show the dialog + handle widgets ENDIF // Process all received messages result%=SOCK_RECV(socket%,msg$,1024) SELECT result% CASE -2 // Cant read at this time CASE -1 // An error DDgui_msg("Error :"+NETGETLASTERROR$(),FALSE) CASE 0 // CASE > 0 DIM messageList$[0] //DEBUG msg$+" " IF SPLITSTR(msg$,messageList$[],SEPERATOR$)>0 SELECT messageList$[0] CASE NETMESSAGE_BROADCAST$ // A broadcast message IF isHost%=FALSE

IF state%=STATE_CONNECTIONSETUP% processBroadcast(messageList$[]) IF connectedServer%=NOT_SELECTED% changeClientTitle(FALSE,"",BOUNDS(serverList[],0)) ELSE changeClientTitle(TRUE,SOCK_GETIP$ (serverList[connectedServer%].ipAddress%),BOUNDS(serverList[],0)) ENDIF ENDIF ENDIF CASE NETMESSAGE_HOSTDISCONNECT$ // The server has disconnected IF isHost%=FALSE processHostDisconnect(messageList$[]) ENDIF CASE NETMESSAGE_CLIENTDISCONNECT$ // A client has disconnected IF isHost%=TRUE AND gameStarted%=FALSE // If we're still in the server display system then removeClientFromClientList(messageList$[1],messageList$[2],numPlayers %) changeHostTitle(numPlayers%,maxPlayers%) ENDIF

// If we are in a game, then delete this client IF gameStarted%=TRUE

18 DIMDEL listOfPlayers[],messageList$[1] DDgui_msg("A player has become disconnected",FALSE) ENDIF CASE NETMESSAGE_REQUESTTOJOIN$ // A client is requesting to join IF isHost%=TRUE // Is there enough room IF numPlayers%

addClientToList(messageList$[1],messageList$[2],numPlayers%) sendServerAllowJoin(socket%,port%,messageList$[1]) changeHostTitle(numPlayers%,maxPlayers%) ELSE

sendServerGameInProgress(socket%,port%,messageList$[1]) ENDIF ELSE sendServerNoRoomLeft(socket%,port%,messageList$[1]) // connectedServer%=NOT_SELECTED% ENDIF ENDIF CASE NETMESSAGE_ALLOWJOIN$ // Do we allow a client to join ? IF isHost%=FALSE changeClientTitle(TRUE,SOCK_GETIP$(serverList[connectedServer %].ipAddress%),BOUNDS(serverList[],0)) serverList[connectedServer%].connected%=TRUE ENDIF CASE NETMESSAGE_NOROOMLEFT$ DDgui_msg("The server is full. You CANNOT join it",FALSE) connectedServer%=NOT_SELECTED% CASE NETMESSAGE_CLIENTREADY$ // The client is ready IF isHost%=TRUE IF gameStarted%=FALSE

changeClientStatus(messageList$[1],messageList$[2],messageList$[3]) ENDIF ENDIF CASE NETMESSAGE_HOSTKICK$ // Host kicks a client

19 // DDgui_msg("Here : "+connectedServer%+"\n",FALSE) IF isHost%=FALSE updateServerDisplay(connectedServer%) displayServerList() ENDIF CASE NETMESSAGE_REQUESTPRESENCE$ // Request for presence has been made IF isHost%=TRUE processHostACK(messageList$[1],thisIP%,socket%,port%) ELSE processClientACK(messageList$[1],thisIP%,socket%,port%) ENDIF CASE NETMESSAGE_PRESENCERESPONSE$ // The response for a request for presence // We need to reset the counters now# IF isHost%=TRUE

processHostACK(messageList$[1],thisIP%) ELSE processClientACK(messageList$[1],thisIP%) ENDIF CASE NETMESSAGE_MESSAGE$ // A message has been received processMessage(messageList$[3],messageList$[1]) CASE NETMESSAGE_SENDPLAYERINFO$ // Host sent all player IP address and names to all clients clientReceiveListOfPlayers(messageList$[1],messageList$[],thisIP%) IF isHost%=FALSE DDgui_popdialog() //DDgui_msg("Received list of players",FALSE) state%=STATE_INITIALISE% ENDIF CASE NETMESSAGE_GAMEDATA$ // Process all game data index%=messageList$[2] //DDgui_msg("IP : "+messageList$[1]+" "+index%,FALSE) //IF index%>=0 // listOfPlayers[index%].player.xPos=messageList$[4] // listOfPlayers[index%].player.yPos=messageList$[5] //ENDIF ENDSELECT ELSE DDgui_msg("Nothing to process",FALSE)

20 ENDIF ENDSELECT // Now we process the messages SELECT state% CASE STATE_CONNECTIONSETUP% IF DDgui_get(backKey$,"CLICKED") // Player wants to exit this screen // If its the host, send a message to all clients // If its the client, and we're connected, then we send a message to the host IF isHost%=TRUE sendServerDisconnect(socket%,broadcastIP%,port%) ELSE IF connectedServer %<>NOT_SELECTED% sendClientDisconnect(socket%,port%,thisIP%,playerName$,connectedServer%) ENDIF ENDIF shutdownGUI(socket%) RETURN FALSE ELSE IF DDgui_get(kickKey$,"CLICKED") // Host wants to kick a player IF selLine %=NOT_SELECTED% DDgui_msg("No clients have been selected to kick!",FALSE) ELSE // DDgui_msg("Send kick",FALSE) sendServerKick(socket%,port%,selLine%) // Now to remove this line DIMDEL clientList[],selLine% displayClientList() DEC numPlayers %,1

changeHostTitle(numPlayers%,maxPlayers%) ENDIF ELSE IF DDgui_get(startKey$,"CLICKED") // Host wants to start a game clientCount %=DDgui_get(listKey$,"COUNT") IF clientCount %=0 IF bAND(programID%,BROADCAST_SINGLEMODE%) allReady%=TRUE ELSE DDgui_msg("You need at least 1 client to join before you can start a game!",FALSE) allReady%=FALSE ENDIF ELSE // Check to see if all players are ready

21 allReady %=TRUE FOREACH cList IN clientList[] IF cList.isReady%=FALSE allReady%=FALSE BREAK ENDIF NEXT IF allReady%=FALSE DDgui_msg("You cannot start until all players are ready",FALSE) ENDIF ENDIF IF allReady %=TRUE gameStarted%=TRUE state %=STATE_INITIALISE%

hostSendPlayerInfo(socket%,port%,thisIP%,playerName$)

DDgui_popdialog() DDgui_msg("Size : "+BOUNDS(listOfPlayers[],0),FALSE) ENDIF ELSE IF DDgui_get(joinKey$,"CLICKED") // Client wants to join a server IF selLine %=NOT_SELECTED% DDgui_msg("You need to select a server to join!",FALSE) ELSE IF connectedServer%=NOT_SELECTED% connectedServer%=selLine% sendClientJoin(socket%,port%,connectedServer%,thisIP%,playerName$) ELSE DDgui_msg("You are already connected to a server!",FALSE) ENDIF ENDIF ELSE IF DDgui_get(readyKey$,"CLICKED") // Client wants to tell server that its ready IF connectedServer%=NOT_SELECTED% DDgui_msg("You need to connect to a server first!",FALSE) ELSE sendClientStatus(socket%,port%,connectedServer%,thisIP%,playerName$,TRUE) ENDIF ELSE IF DDgui_get(disconnectKey$,"CLICKED")

22 // Client wants to disconnect from a server IF connectedServer%=NOT_SELECTED% DDgui_msg("You need to select a server!",FALSE) ELSE sendClientDisconnect(socket%,port%,thisIP%,playerName$,connectedServer%) updateServerDisplay(connectedServer%) ENDIF ELSE IF DDgui_get(sendKey$,"CLICKED") msg$=DDgui_get$(messageKey$,"TEXT") IF msg$<>"" sendMessage(socket%,port%,playerName$,thisIP%,msg$) ENDIF ELSE selLine%=DDgui_get(listKey$,"SELECT")

ENDIF ENDIF ENDIF ENDIF ENDIF ENDIF ENDIF CASE STATE_INITIALISE% initialise() thisIndex%=findIPAddress(thisIP%) //DDgui_msg("Index : "+thisIndex %,FALSE) state%=STATE_SETUPGAME% CASE STATE_SETUPGAME% // setup the game //setupPlayers(1,thisIP%) //DDgui_msg("Game setup",FALSE) state%=STATE_DOGAME% CASE STATE_SETUPLEVEL% CASE STATE_DOGAME% // This is the main game routine // gameLoop(speed,1,TRUE) // IF listOfPlayers[thisIndex %].player.prevXPos<>listOfPlayers[thisIndex%].player.xPos OR _ // listOfPlayers[thisIndex %].player.prevYPos<>listOfPlayers[thisIndex%].player.yPos // sendGameData(socket%,port %,thisIP%,thisIndex%,listOfPlayers[thisIndex%].player,listOfPlayers[]) // listOfPlayers[thisIndex %].player.prevXPos=listOfPlayers[thisIndex%].player.xPos // listOfPlayers[thisIndex %].player.prevYPos=listOfPlayers[thisIndex%].player.yPos // ENDIF // // // Draw all players // PRINT BOUNDS(listOfPlayers[],0),128,0 // // FOR index%=0 TO BOUNDS(listOfPlayers[],0)-1 // IF listOfPlayers[index %].ipAddress%=thisIP% // DRAWRECT listOfPlayers[index%].player.xPos,listOfPlayers[index%].player.yPos,12,12,RGB(255,255,0) // ELSE

23 // DRAWRECT listOfPlayers[index%].player.xPos,listOfPlayers[index%].player.yPos,8,8,RGB(255,0,0) // ENDIF // // PRINT listOfPlayers[index %].playerName$,listOfPlayers[index%].player.xPos,listOfPlayers[index%].player.yPos-16 // NEXT // // MOUSESTATE listOfPlayers[thisIndex %].player.xPos,listOfPlayers[thisIndex%].player.yPos,listOfPlayers[thisIndex %].player.b1,listOfPlayers[thisIndex%].player.b2 ENDSELECT SHOWSCREEN // If we're the host, see if its time to send a broadcast messgae IF isHost%=TRUE IF timeToSendBroadcast<=0.0 sendBroadcastMessage(socket%,broadcastIP%,port%,programID %,1,playerName$,numPlayers%,maxPlayers%,thisIP%) timeToSendBroadcast=250.0 ELSE DEC timeToSendBroadcast,speed ENDIF ENDIF processTimingSystem(isHost%,socket%,port%,thisIP%,speed) speed=updateAppTime() WEND ENDFUNCTION // Shutdown socket system and/or just window ? FUNCTION shutdownGUI:socket%=0 IF socket%>0 SOCK_CLOSE(socket%) SOCK_SHUTDOWN ENDIF SYSTEMPOINTER FALSE DDgui_popdialog() ENDFUNCTION FUNCTION createHostJoinWindow:listKey$,value$,screenWidth%,screenHeight%,width%,height% GETSCREENSIZE screenWidth%,screenHeight% DDgui_pushdialog((screenWidth%-width%)/2,(screenHeight%-height%)/2,width%,height%) DDgui_set("","MOVEABLE", TRUE) // can move the dialog at top bar DDgui_list(listKey$, value$, width%-16,height%-128) ENDFUNCTION FUNCTION changeHostTitle%:numJoined%,maxPlayers% DDgui_set("","TEXT","Number Of Players In Game : "+numJoined%+"/"+maxPlayers%) ENDFUNCTION FUNCTION changeClientTitle%:isJoined%,server$,numServers% IF isJoined%=TRUE DDgui_set("","TEXT","Joined Server : "+server$) ELSE DDgui_set("","TEXT","Connect To Server ("+numServers%+" present)") ENDIF ENDFUNCTION

// Create broadcast message FUNCTION sendBroadcastMessage:socket%,broadcastIP%,port%,programDetails%,version %,hostName$, _ numPlaying%,maxPlayers%,thisIP% LOCAL temp$ LOCAL result% temp$=NETMESSAGE_BROADCAST$+SEPERATOR$+programDetails%+SEPERATOR$+version% +SEPERATOR$+ _ hostName$+SEPERATOR$+numPlaying%+SEPERATOR$+thisIP% +SEPERATOR$+maxPlayers%+ _ SEPERATOR$+PLATFORMINFO$("") DEBUG temp$

24 result%=SOCK_UDPSEND(socket%,temp$,broadcastIP%,port%) IF result%<0 DDgui_msg("Send error",FALSE) ENDIF ENDFUNCTION // Send the server is disconnecting message FUNCTION sendServerDisconnect:socket%,broadcastIP%,port% LOCAL temp$ LOCAL result% temp$=NETMESSAGE_HOSTDISCONNECT$ result%=SOCK_UDPSEND(socket%,temp$,broadcastIP%,port%) IF result%<0 ENDIF ENDFUNCTION // The host wants to kick a player FUNCTION sendServerKick:socket%,port%,selLine% LOCAL temp$ LOCAL result% temp$=NETMESSAGE_HOSTKICK$ result%=SOCK_UDPSEND(socket%,temp$,clientList[selLine%].ipAddress%,port%) IF result%<0 DDgui_msg("Send error",FALSE) ENDIF ENDFUNCTION

// Send message to client saying there is no room left FUNCTION sendServerNoRoomLeft:socket%,port%,destIP% LOCAL temp$ LOCAL result% LOCAL hostIP% temp$=NETMESSAGE_NOROOMLEFT$ result%=SOCK_UDPSEND(socket%,temp$,destIP%,port%) IF result%<0 DDgui_msg(NETGETLASTERROR$(),FALSE) ENDIF ENDFUNCTION // The server has allowed the client to join FUNCTION sendServerAllowJoin:socket%,port%,destIP% LOCAL temp$ LOCAL result% temp$=NETMESSAGE_ALLOWJOIN$ result%=SOCK_UDPSEND(socket%,temp$,destIP%,port%) IF result%<0 DDgui_msg(NETGETLASTERROR$(),FALSE) ENDIF ENDFUNCTION // Tell the client, the game is in progress FUNCTION sendServerGameInProgress:socket%,port%,destIP% LOCAL temp$ LOCAL result% temp$=NETMESSAGE_GAMEINPROGRESS$ result%=SOCK_UDPSEND(socket%,temp$,destIP%,port%) IF result%<0 DDgui_msg(NETGETLASTERROR$(),FALSE) ENDIF ENDFUNCTION // Client wants to join a server FUNCTION sendClientJoin:socket%,port%,selLine%,thisIP%,clientName$ LOCAL temp$ LOCAL result% temp$=NETMESSAGE_REQUESTTOJOIN$+SEPERATOR$+thisIP%+SEPERATOR$+clientName$ +SEPERATOR$ result%=SOCK_UDPSEND(socket%,temp$,serverList[selLine%].ipAddress%,port%) IF result%<0 DDgui_msg(NETGETLASTERROR$(),FALSE)

25 ENDIF ENDFUNCTION // Client wants to tell server that its ready FUNCTION sendClientStatus:socket%,port%,selLine%,thisIP%,clientName$,ready% LOCAL temp$ LOCAL result% temp$=NETMESSAGE_CLIENTREADY$+SEPERATOR$+thisIP%+SEPERATOR$+clientName$+SEPERATOR$ +ready% result%=SOCK_UDPSEND(socket%,temp$,serverList[selLine%].ipAddress%,port%) IF result%<0 DDgui_msg(NETGETLASTERROR$(),FALSE) ENDIF ENDFUNCTION // Client wants to disconnect from server FUNCTION sendClientDisconnect%:socket%,port%,thisIP%,clientName$,connectedLine% LOCAL temp$ LOCAL result% temp$=NETMESSAGE_CLIENTDISCONNECT$+SEPERATOR$+thisIP%+SEPERATOR$+clientName$ +SEPERATOR$ result%=SOCK_UDPSEND(socket%,temp$,serverList[connectedLine%].ipAddress%,port%) IF result%<0 DDgui_msg(NETGETLASTERROR$(),FALSE) ENDIF ENDFUNCTION FUNCTION sendClientDisconnectFromGame%:socket%,port%,ipAddress% LOCAL temp$ LOCAL result% LOCAL loop% LOCAL listOfPlayersIndex% // Find the index using the listOfPlayers array listOfPlayersIndex%=findIPAddress(ipAddress%) IF listOfPlayersIndex%=NOT_FOUND% RETURN FALSE ENDIF temp$=NETMESSAGE_CLIENTDISCONNECT$+SEPERATOR$+listOfPlayersIndex% FOR loop%=0 TO BOUNDS(listOfPlayers[],0)-1 IF loop%<>listOfPlayersIndex% result%=SOCK_UDPSEND(socket%,temp$,listOfPlayers[loop%].ipAddress %,port%) IF result%<0 DDgui_msg(NETGETLASTERROR$(),FALSE) ENDIF ENDIF NEXT RETURN TRUE ENDFUNCTION // Client process broadcast messages FUNCTION processBroadcast:messageList$[] LOCAL sList AS tServerList LOCAL loop AS tServerList LOCAL found% LOCAL modify% sList.programID%=INTEGER(messageList$[1]) sList.version%=INTEGER(messageList$[2]) sList.numPlaying%=INTEGER(messageList$[4]) sList.maxPlayers%=INTEGER(messageList$[6]) sList.hostName$=messageList$[3] sList.ipAddress%=INTEGER(messageList$[5]) sList.operatingSystem$=messageList$[7] resetACKTimes(sList.ack.timeForACK,sList.ack.timeForACKResponse) found%=FALSE modify%=FALSE IF BOUNDS(serverList[],0)>0 // Look to see if this set of details is present, and also if they need

26 updating FOREACH loop IN serverList[] IF sList.programID%=loop.programID% AND sList.version%=loop.version% AND sList.hostName$=loop.hostName$ AND _ sList.ipAddress%=loop.ipAddress% found%=TRUE // This prevents a new line being added, where none is needed IF sList.numPlaying%<>loop.numPlaying% modify%=TRUE // This mnakes sure the listbox is updated loop.numPlaying%=sList.numPlaying% ENDIF ENDIF NEXT ENDIF IF found%=FALSE DIMPUSH serverList[],sList ENDIF // Rebuild the list of servers, but only if its not already present or the number of players has changed IF found%=FALSE OR modify%=TRUE displayServerList() ENDIF ENDFUNCTION FUNCTION addClientToList:clientIP%,clientName$,BYREF numPlaying% LOCAL s AS tClientList s.isReady%=FALSE s.ignore%=FALSE s.ipAddress%=clientIP% s.clientName$=clientName$ resetACKTimes(s.ack.timeForACK,s.ack.timeForACKResponse) DIMPUSH clientList[],s displayClientList() INC numPlaying%,1 ENDFUNCTION FUNCTION displayClientList: LOCAL s AS tClientList LOCAL list$ list$="" FOREACH s IN clientList[] list$=list$+s.clientName$ IF s.isReady%=TRUE list$=list$+" (READY)" ENDIF list$=list$+"|" NEXT DDgui_set(listKey$,"TEXT",list$) ENDFUNCTION FUNCTION displayServerList: LOCAL sList AS tServerList LOCAL list$ LOCAL serverLine$ LOCAL numPlaying$ LOCAL os$ LOCAL vers$ LOCAL flags$ list$="" FOREACH sList IN serverList[] // We now align all information, so that we can display everything we need // Get the first 16 characters of the server name flags$="" IF bAND(sList.programID%,BROADCAST_TESTPROGRAM%) flags$=flags$+"!" ELSE flags$=flags$+"-" ENDIF

27 IF bAND(sList.programID%,BROADCAST_TIMEDDEMO%) flags$=flags$+"T" ELSE IF bAND(sList.programID%,BROADCAST_NONTIMEDDEMO%) flags$=flags$+"N" ELSE flags$=flags$+"-" ENDIF ENDIF IF bAND(sList.programID%,BROADCAST_EXACTVERSION%) flags$=flags$+"E" ELSE flags$=flags$+"-" ENDIF IF bAND(sList.programID%,BROADCAST_SINGLEMODE%) flags$=flags$+"S" ELSE flags$=flags$+"-" ENDIF IF sList.connected%=TRUE flags$=flags$+"C" ELSE flags$=flags$+"-" ENDIF WHILE LEN(flags$)<8 flags$=flags$+" " WEND serverLine$=MID$(sList.hostName$,0,MIN(16,LEN(sList.hostName$))) WHILE LEN(serverLine$)<20 serverLine$=serverLine$+" " WEND // Now we add the number of players and maximum numPlaying$=sList.numPlaying%+"/"+sList.maxPlayers% WHILE LEN(numPlaying$)<10 numPlaying$=numPlaying$+" " WEND // Add the operating system os$=sList.operatingSystem$ WHILE LEN(os$)<8 os$=os$+" " WEND // Version vers$=convertVersion$(sList.version%) WHILE LEN(vers$)<18 vers$=vers$+" " WEND list$=list$+flags$+serverLine$+numPlaying$+vers$+os$ list$=list$+"|" NEXT DDgui_set(listKey$,"TEXT",list$) ENDFUNCTION // Client processes server disconnect FUNCTION processHostDisconnect:messageList$[] ENDFUNCTION FUNCTION removeClientFromClientList%:ipAddr%,clientName$,BYREF numPlaying% LOCAL loop AS tClientList LOCAL found% found%=FALSE FOREACH loop IN clientList[] IF loop.clientName$=clientName$ found%=TRUE DELETE loop ENDIF NEXT

28 IF found%=TRUE displayClientList() DEC numPlaying%,1 ENDIF RETURN found% ENDFUNCTION FUNCTION changeClientStatus%:thisIP%,clientName$,isReady% LOCAL cList AS tClientList FOREACH cList IN clientList[] IF cList.ipAddress%=thisIP% AND cList.clientName$=clientName$ cList.isReady%=isReady% displayClientList() RETURN TRUE ENDIF NEXT RETURN FALSE ENDFUNCTION // Convert version information (in the form of x.x.x.x to an integer) FUNCTION versionToInt%:version$ LOCAL string$[] LOCAL count% LOCAL bit% LOCAL total%

count%=SPLITSTR(version$,string$[],".") total%=0 IF count%>0 count%=MIN(count%,4) bit%=1 WHILE count%>0 INC total%,bAND(string$[count%-1],255)+bit% bit%=bit%*256 DEC count%,1 WEND ENDIF RETURN total% ENDFUNCTION // Convert version value to string FUNCTION convertVersion$:ver% LOCAL result$ FOR loop%=1 TO 4 result$=bAND(ver%,255)+"."+result$ ver%=ver%/256 NEXT RETURN MID$(result$,0,LEN(result$)-1) ENDFUNCTION FUNCTION updateServerDisplay:BYREF connectedServer% serverList[connectedServer%].connected%=FALSE connectedServer%=NOT_SELECTED% changeClientTitle(FALSE,"",BOUNDS(serverList[],0)) ENDFUNCTION FUNCTION processTimingSystem:isHost%,socket%,port%,thisIP%,speed LOCAL clients AS tClientList LOCAL servers AS tServerList IF isHost%=TRUE // Go through all clients FOREACH clients IN clientList[] IF clients.ignore%=FALSE PRINT clients.ack.timeForACK+" "+clients.ack.timeForACKResponse,0,32 IF updateTimes(clients.ack.timeForACK,clients.ack.timeForACKResponse,socket%,port%,thisIP %,clients.ipAddress%,speed)=FALSE // This client no longer exists

29 clients.ignore%=TRUE sendClientDisconnectFromGame(socket%,port %,clients.ipAddress%) ENDIF ENDIF //PRINT "Time before sending request to client : "+clients.ack.timeForACK+"/"+clients.ack.timeForACKResponse,0,32 NEXT ELSE // Find connected server and process that one FOREACH servers IN serverList[] IF servers.connected%=TRUE IF updateTimes(servers.ack.timeForACK,servers.ack.timeForACKResponse,socket%,port%,thisIP %,servers.ipAddress%,speed)=FALSE // This server no longer exists ENDIF PRINT "Time before sending request to server : "+servers.ack.timeForACK+"/"+servers.ack.timeForACKResponse,0,0 ENDIF NEXT ENDIF ENDFUNCTION FUNCTION updateTimes%:BYREF timeForACK,BYREF timeForACKResponse,socket%,port%,thisIP %,destIP%,speed LOCAL temp$ LOCAL result% IF timeForACK>0.0 DEC timeForACK,speed IF timeForACK<=0.0 // Send a message to the relevent server, asking for confirmation temp$=NETMESSAGE_REQUESTPRESENCE$+SEPERATOR$+thisIP% result%=SOCK_UDPSEND(socket%,temp$,destIP%,port%) IF result%<0 DDgui_msg(NETGETLASTERROR$(),FALSE) ENDIF timeForACKResponse=RESPONSETIMEFORACK // DDgui_msg("Send message for response",FALSE) ENDIF ELSE DEC timeForACKResponse,speed IF timeForACKResponse<=0.0 // No response, so we assume disconnection RETURN FALSE ENDIF ENDIF RETURN TRUE ENDFUNCTION FUNCTION resetACKTimes:BYREF timeForACK,BYREF timeForACKResponse timeForACK=100.0 //TIMEFORACK timeForACKResponse=0.0 ENDFUNCTION // The host looks for the IP address of clients machines, so that we can reset everything here too // A replyto the request is then sent FUNCTION processHostACK:orgIP%,thisIP%=0,socket%=-1,port%=-1 LOCAL clients AS tClientList DEBUG "Receivd from : "+SOCK_GETIP$(thisIP%) FOREACH clients IN clientList[] IF clients.ipAddress%=orgIP% resetACKTimes(clients.ack.timeForACK,clients.ack.timeForACKResponse) ENDIF NEXT IF socket%>=0 sendACKResponse(orgIP%,thisIP%,socket%,port%) ENDIF

30 ENDFUNCTION // The client looks for the IP address of the server, so that we can reset everything here too // A reply to the request is then sent FUNCTION processClientACK:orgIP%,thisIP%,socket%=-1,port%=-1 LOCAL servers AS tServerList FOREACH servers IN serverList[] IF servers.ipAddress%=orgIP% resetACKTimes(servers.ack.timeForACK,servers.ack.timeForACKResponse) ENDIF NEXT IF socket%>=0 sendACKResponse(orgIP%,thisIP%,socket%,port%) ENDIF ENDFUNCTION FUNCTION sendACKResponse:orgIP%,thisIP%,socket%,port% LOCAL temp$ LOCAL result% temp$=NETMESSAGE_PRESENCERESPONSE$+SEPERATOR$+thisIP% result%=SOCK_UDPSEND(socket%,temp$,orgIP%,port%) IF result%<0 DDgui_msg(NETGETLASTERROR$(),FALSE) ENDIF ENDFUNCTION

FUNCTION sendMessage:socket%,port%,playerName$,thisIP%,msg$ LOCAL temp$ LOCAL result% temp$=NETMESSAGE_MESSAGE$+SEPERATOR$+playerName$+SEPERATOR$+thisIP%+SEPERATOR$ +msg$ result%=SOCK_UDPSEND(socket%,temp$,thisIP%,port%) IF result%<0 DDgui_msg(NETGETLASTERROR$(),FALSE) ENDIF ENDFUNCTION FUNCTION hostSendPlayerInfo:socket%,port%,serverIP%,serverName$ LOCAL temp$ LOCAL loop% LOCAL result% LOCAL count% count%=BOUNDS(clientList[],0)+1 temp$=NETMESSAGE_SENDPLAYERINFO$+SEPERATOR$+count%+SEPERATOR$ FOR loop%=0 TO count%-1 IF loop%=0 temp$=temp$+serverIP%+SEPERATOR$+serverName$+SEPERATOR$ ELSE temp$=temp$+clientList[loop%-1].ipAddress+SEPERATOR$+clientList[loop %-1].clientName$ ENDIF NEXT // Go through all the clients, sending this information FOR loop%=0 TO BOUNDS(clientList[],0)-1 result%=SOCK_UDPSEND(socket%,temp$,clientList[loop%].ipAddress%,port%) IF result%<0 DDgui_msg(NETGETLASTERROR$(),FALSE) ENDIF NEXT // Setup the server hostReceiveListOfPlayers(count%,temp$,serverIP%) ENDFUNCTION FUNCTION processMessage:msg$,name$ LOCAL temp$ temp$=DDgui_get$(messageListKey$,"TEXT") temp$=temp$+"|"+name$+" > "+msg$ DDgui_set(messageListKey$,"TEXT",temp$)

31 ENDFUNCTION FUNCTION hostReceiveListOfPlayers:amount%,pList$,thisIP% LOCAL list$[] DIM listOfPlayers[0] IF SPLITSTR(pList$,list$[],SEPERATOR$)>0 getListDetails(2,amount%,thisIP%,list$[]) ENDIF ENDFUNCTION FUNCTION clientReceiveListOfPlayers:amount%,list$[],thisIP% LOCAL start% LOCAL loop% LOCAL pList AS tListOfPlayers DIM listOfPlayers[0] getListDetails(2,amount%,thisIP%,list$[]) ENDFUNCTION FUNCTION getListDetails:start%,amount%,thisIP%,list$[] LOCAL loop% LOCAL pList AS tListOfPlayers // loop%=0 // WHILE loop%

32 Joypad Filter

This routine is designed to filter joystick movements and button presses. It came about because the values of a joystick at the time was jittering around the 1/1000 decimal position, and it needed to be smoothed out. The joystick button filtering was done so that the player/user couldn't continually hold a button down (which could case problems in things like menu's). CONSTANT JOYPAD_FILTER_XYZ% = 1 CONSTANT JOYPAD_FILTER_BUTTONS% = 2 TYPE tJoypad x y z button% hasMoved% hasPressed% ENDTYPE TYPE TJoyPad MAX_JOYPADS% = 10 MAX_JOYBUTTONS% = 32 JOYPAD_BUTTON_1% = 1 JOYPAD_BUTTON_2% = 2 JOYPAD_BUTTON_3% = 4 JOYPAD_BUTTON_4% = 8 joypadFilter[] AS tJoypad FUNCTION Initialise%: LOCAL loop1% LOCAL joy AS tJoypad LOCAL num% DIM self.joypadFilter[0] num%=GETNUMJOYSTICKS() self.Clear(joy) DEBUG "NUM : "+num%+"\n" FOR loop1%=1 TO MIN(num%,self.MAX_JOYPADS%) DIMPUSH self.joypadFilter[],joy NEXT RETURN num% ENDFUNCTION FUNCTION Clear%:joypad AS tJoypad joypad.x=0.0 joypad.y=0.0 joypad.z=0.0 joypad.button%=0 joypad.hasPressed%=FALSE joypad.hasMoved%=FALSE ENDFUNCTION FUNCTION readJoypadFilter%:joyIndex%,flags%,joypad AS tJoypad LOCAL loop% LOCAL temp$ LOCAL x,y,z LOCAL value% IF joyIndex%>=BOUNDS(self.joypadFilter[],0) RETURN FALSE ENDIF temp$=FORMAT$(2,0,GETJOYX(joyIndex%)); x=temp$ temp$=FORMAT$(2,0,GETJOYY(joyIndex%)); y=temp$ temp$=FORMAT$(2,0,GETJOYZ(joyIndex%)); z=temp$ DEBUG x+" "+y+" "+z+"\n" IF bAND(flags%,JOYPAD_FILTER_XYZ%) IF x<>0.0 OR y<>0.0 OR z<>0.0

33 IF self.joypadFilter[joyIndex%].hasMoved%=FALSE self.joypadFilter[joyIndex%].x=x self.joypadFilter[joyIndex%].y=y self.joypadFilter[joyIndex%].z=z self.joypadFilter[joyIndex%].hasMoved%=TRUE ENDIF x=0.0 y=0.0 z=0.0 ELSE IF self.joypadFilter[joyIndex%].hasMoved%=TRUE x=self.joypadFilter[joyIndex%].x y=self.joypadFilter[joyIndex%].y z=self.joypadFilter[joyIndex%].z self.joypadFilter[joyIndex%].hasMoved%=FALSE ELSE x=0.0 y=0.0 z=0.0 ENDIF ENDIF ENDIF joypad.x=x joypad.y=y joypad.z=z value%=0 FOR loop%=0 TO self.MAX_JOYBUTTONS%-1 IF GETJOYBUTTON(joyIndex%,loop%) INC value%,POW(2,loop%) ENDIF NEXT IF bAND(flags%,JOYPAD_FILTER_BUTTONS%) IF value%<>0 IF self.joypadFilter[joyIndex%].hasPressed%=FALSE self.joypadFilter[joyIndex%].button%=value% self.joypadFilter[joyIndex%].hasPressed%=TRUE ENDIF joypad.button%=0 ELSE IF self.joypadFilter[joyIndex%].hasPressed%=TRUE joypad.button%=self.joypadFilter[joyIndex%].button% self.joypadFilter[joyIndex%].hasPressed%=FALSE ELSE joypad.button%=0 ENDIF ENDIF ELSE joypad.button%=value% ENDIF IF ABS(x)=0.0 AND ABS(y)=0.0 AND ABS(z)=0.0 AND joypad.button%=0 RETURN FALSE ELSE RETURN TRUE ENDIF ENDFUNCTION ENDTYPE

34 Poke/Peek

This routine is designed to allow the user to allocate, de-allocate, write and ready bytes, words, longs and string to/from a defined area of memory. The code was based on the POKE/PEEK DBPro routines by Ian Mold and was modified slightly to use new instead of malloc/calloc. It was done as a test really, mainly to see if it would compile and run – and it does! // ------// // Project: TestMalloc // Start: Monday, March 02, 2009 // IDE Version: 6.174 FUNCTION dummy: ENDFUNCTION INLINE } typedef unsigned int size_t; extern "C" void *memcpy(void *destination, const void *source, size_t num ); extern "C" void *memset(void *ptr, int value, size_t num); template void __poke(DWORD Addr, val Value) { *((type*)(Addr))=(type)(Value); } template type __peek(DWORD Addr) { return *((type *)(Addr)); } namespace __GLBASIC__ { ENDINLINE FUNCTION AllocMem%:size% INLINE return (DGNat) new char[size]; ENDINLINE ENDFUNCTION FUNCTION FreeMem%:mem% INLINE char *temp; temp=(char *) mem; delete[] temp; ENDINLINE ENDFUNCTION FUNCTION ZeroMemory:mem%,size% INLINE memset((char *) mem,(char) 0,size); ENDINLINE ENDFUNCTION FUNCTION FillMemory:mem%,size%,val% INLINE memset((char *) mem,(char) val,size); ENDINLINE ENDFUNCTION FUNCTION CopyMemory%:store%,read%,size%=-1 INLINE char value; if (size<0) { value=__peek((DWORD) read); while (value!=0) { __poke((DWORD) store,value); read++;

35 store++; } return true; } else if (size>0) { memcpy((char *) store,(char *) read,size); return true; } else { return false; } ENDINLINE ENDFUNCTION FUNCTION pokeB:mem%,val% INLINE __poke((DWORD) mem,char(val)); ENDINLINE ENDFUNCTION FUNCTION pokeS:mem%,val% INLINE __poke((DWORD) mem,short(val)); ENDINLINE ENDFUNCTION

FUNCTION pokeL:mem%,val% INLINE __poke((DWORD) mem,val); ENDINLINE ENDFUNCTION FUNCTION pokeF:mem%,val INLINE __poke((DWORD) mem,(float) val); ENDINLINE ENDFUNCTION FUNCTION pokeDF:mem%,val INLINE __poke((DWORD) mem,val); ENDINLINE ENDFUNCTION FUNCTION pokeString:mem%,str$ INLINE memcpy((char *) mem,str_Str.c_str(),str_Str.len()); ENDINLINE ENDFUNCTION // ------FUNCTION peekB%:mem% INLINE return ((DGNat) (__peek(mem))); ENDINLINE ENDFUNCTION FUNCTION peekS%:mem% INLINE return ((DGNat) (__peek(mem))); ENDINLINE ENDFUNCTION FUNCTION peekL%:mem% INLINE return ((DGNat) (__peek(mem))); ENDINLINE ENDFUNCTION FUNCTION peekF:mem% INLINE

36 return ((DGInt) (__peek(mem))); ENDINLINE ENDFUNCTION FUNCTION peekDF:mem% INLINE return ((DGInt) (__peek(mem))); ENDINLINE ENDFUNCTION FUNCTION peekString$:mem%,length%=0 LOCAL temp$ LOCAL l% LOCAL p% temp$="" IF length%>0 FOR l%=0 TO length%-1 temp$=temp$+CHR$(peekB(mem%+l%)) NEXT ELSE p%=peekB(mem%) WHILE p%<>0 temp$=temp$+CHR$(p%) INC mem%,1 p%=peekB(mem%) WEND ENDIF

RETURN temp$ ENDFUNCTION

37 Mappy

This is a routine to allow the user to use Mappy created files. Mappy is a tilemap editor (http://www.tilemap.co.uk) that (sort of) simplies level editing. This routine is my third iteration, and fixes a problem with the BlitzMax and original C version. // ------// // Project: TestMappy // Start: Friday, April 09, 2010 // IDE Version: 7.322 CONSTANT MAPERROR_OK% = 0 INLINE } #include "mapdefs.h" #include "../../../Routines/CHeaders.h" #define ERROR_INVALIDCOORD -1 #define ERROR_OK 0 #define ERROR_NOFILENAME -2 #define ERROR_FILENOTFOUND -3 #define ERROR_FILENOTOPENED -4 #define ERROR_INVALIDHEADER -5 #define ERROR_OUTOFMEM -6 #define ERROR_INVALIDLAYER -7 #define ERROR_UNKNOWNVERSION -8 #define ERROR_TILENOTFOUND -9 #define ERROR_LAYERNOTINUSE -10 #define ERROR_LAYERINUSE -11 #define ERROR_MAPNOTLOADED -12 #define FMP05 0 #define FMP10 1 #define FMP10RLE 2 #define MAX_LAYERS 100 #define BACKGROUND 0 #define FOREGROUND1 1 #define FOREGROUND2 2 #define FOREGROUND3 3 #define HEADER_AUTHOR "ATHR" #define HEADER_MAP "MPHD" #define HEADER_PALETTE "CMAP" #define HEADER_BLOCKGRFX "BGFX" #define HEADER_BODY "BODY" #define HEADER_LAYER "LYR" #define HEADER_ANIMATION "ANDT" #define HEADER_BLOCKDATA "BKDT" #define HEADER_EDITOR "EDHD" #define HEADER_EPHD "EPHD" #define HEADER_NLYR "NLYR" // For more than 8 layers #define MAPPY_HEADER1 "FORM" #define MAPPY_HEADER2 "FMAP" #define HEADER_LSTR "LSTR" typedef struct __RGB { char r; char g; char b; } __RGB; // This class is exported from the CMappy.dll class CMappy { public: CMappy(void); ~CMappy(); void destroyMap(void); int loadMappyFile(const char *fileName,int extraBytes=0); int m_dTileSizeInBytes; int GetNumberOfLayers(void);

38 int GetMappyIndex(int x,int y); char *GetLayerNames(void); signed short GetTileAtPosition(int x,int y,int layerLevel); signed short WriteTileAtPosition(int x,int y,int layerLevel,signed short value); int FindTile(int layerNum,int *xP,int *yP,int tile); BLKSTR *GetBlockData(int block); ANISTR *GetAnimationData(int index); struct __RGB *GetPaletteColour(int index); char *GetExtraDataPointer(int x,int y,int layer); int CalculateExtraPosition(int x,int y); int returnLayerOffset(int blockNum,short which); int returnCurrentAnimationBlock(int block); int returnAnimationBlock(int block); // No error checking int addLayer(int layer); int deleteLayer(int layer); int copyLayer(int fromLayer,int toLayer); int clearLayer(int layer); int moveLayer(int fromLayer,int toLayer); void UpdateAnimations(void); signed short *returnLayer(int layr); // TODO: add your methods here. char *m_cpAuthor; DGNat *m_graphics; int m_dMapType; int m_dMapWidth,m_dMapHeight; int m_dBlockWidth,m_dBlockHeight; int m_dMapVersion,m_dLSB; int m_dMapDepth; int m_dNumBlockStructs,m_dNumBlockGFX; int m_dBlockGapX,m_dBlockGapY; int m_dBlockStaggerX,m_dBlockStaggerY,m_dBlockSize; int m_dClipMask; int m_dMapTrans8,m_dMapTransRed,m_dMapTransGreen,m_dMapTransBlue; int m_dNumAnimations; int m_dExtraBytesSize; private: bool readHeader(int handle,int *Mappy_FileSize); int swapByteOrder(int Thislong); int getMappySize(int handle,char *store); void MapInitAnims(void); signed short loadMappyWord(int handle,bool order); void loadMappySkipSection(int handle,int size); int processMapHeader(int handle,int size); void processPalette(int handle,int sectionLen); int processGraphics(int handle,int ChunkSize); signed short getTileMapLayer(int handle,int sectionLen,int layerLevel,int extraBytesSize); int processAnimation(int handle,int ChunkSize); int processBlockData(int handle,int ChunkSize); int processLayerString(int handle,int ChunkSize); int allocateLayer(int layerLevel,int ChunkSize,int extraBytes); int returnDiv(void); ANISTR *animations; long *animationSeq; struct __RGB rgb[256]; signed short *layer[MAX_LAYERS]; char *extraBytesLayer[MAX_LAYERS]; BLKSTR *blockStructs; char *m_layerName; size_t fread(void *ptr, size_t size, size_t nmemb, int stream); void fclose(int handle); int fgetc(int handle); }; size_t CMappy::fread(void *ptr, size_t size, size_t nmemb, int stream) { return read(stream,ptr,size*nmemb); } void CMappy::fclose(int handle) { close(handle); }

39 int CMappy::fgetc(int handle) { unsigned char one; read(handle,(unsigned char *) &one,1); return (int) one; } CMappy::CMappy() { register int loop; for (loop=0; loop

40 } int CMappy::swapByteOrder(int Thislong) { int hWord,ThisLong; hWord=((Thislong & 0xFFFF0000)/65536) & 65535; ThisLong=((Thislong & 255)*16777216)+((Thislong & 65280)*256)+((hWord & 255)*256)+ ((hWord & 65280)/256); return (ThisLong); } int CMappy::getMappySize(int handle,char *store) { char header[4]; int value; fread((char *) &header,1,sizeof(header),handle); if (store) { memcpy(store,(char *) &header,sizeof(header)); } fread((char *) &value,1,sizeof(value),handle); return (swapByteOrder(value)); } void CMappy::destroyMap(void) { register int loop;

if (m_cpAuthor) { free((void *) m_cpAuthor); m_cpAuthor=NULL; }

for (loop=0; loop

41 if (m_layerName) { free((void *) m_layerName); m_layerName=NULL; } } int CMappy::loadMappyFile(const char *fileName,int extraBytes) { int handle; int FilePosition; bool DecodeFlag; char ChunkHeader[5]={0}; int Mappy_FileSize,ChunkSize,originalChunkSize; DEBUG("Here\n"); if ((fileName==NULL) || strlen(fileName)==0) return (ERROR_NOFILENAME); if (access(fileName,00)==-1) return (ERROR_FILENOTFOUND); m_dExtraBytesSize=extraBytes; #if defined(WIN32) || defined(_WIN32_CE) handle=open(fileName,_O_RDONLY | _O_BINARY); #else handle=open(fileName,_O_RDONLY); #endif

if (handle!=-1) { if (readHeader(handle,&Mappy_FileSize)==false) { fclose(handle); return (ERROR_INVALIDHEADER); } originalChunkSize=0; FilePosition=12; do { DecodeFlag=false; ChunkSize=getMappySize(handle,(char *) &ChunkHeader); DEBUG(DGStr(ChunkHeader)); DEBUG("\n"); FilePosition+=8; if (memcmp(ChunkHeader,HEADER_AUTHOR,strlen(HEADER_AUTHOR))==0) { // Strings of author information // Allocate memory for this chunk if ((m_cpAuthor=(char *) calloc(1,ChunkSize+1))!=NULL) { int loop; fread((char *) m_cpAuthor,1,ChunkSize,handle); for (loop=0; loop

42 processMapHeader(handle,ChunkSize); DecodeFlag=true; } else if (memcmp(ChunkHeader,HEADER_PALETTE,strlen(HEADER_PALETTE))==0) { processPalette(handle,ChunkSize); DecodeFlag=true; } else if (memcmp(ChunkHeader,HEADER_BLOCKGRFX,strlen(HEADER_BLOCKGRFX))==0) { int status; if ((status=processGraphics(handle,ChunkSize))!=ERROR_OK) { fclose(handle); return (status); } DecodeFlag=true; } else if (memcmp(ChunkHeader,HEADER_BODY,strlen(HEADER_BODY))==0) { int status; if ((status=getTileMapLayer(handle,ChunkSize,0,m_dExtraBytesSize))!=ERROR_OK) { fclose(handle); return (status); } DecodeFlag=true; originalChunkSize=ChunkSize; } else if (memcmp((char *) &ChunkHeader,HEADER_LAYER,strlen(HEADER_LAYER))==0) { int layer; int status; layer=ChunkHeader[strlen(HEADER_LAYER)]-(char) '0'; if (layer>=0 && layer

43 { fclose(handle); return (status); } DecodeFlag=true; } else if (memcmp(ChunkHeader,HEADER_EDITOR,strlen(HEADER_EDITOR))==0) { loadMappySkipSection(handle,ChunkSize); DecodeFlag=true; } else if (memcmp(ChunkHeader,HEADER_EPHD,strlen(HEADER_EPHD))==0) { loadMappySkipSection(handle,ChunkSize); DecodeFlag=true; } else if (memcmp(ChunkHeader,HEADER_LSTR,strlen(HEADER_LSTR))==0) { int status; if ((status=processLayerString(handle,ChunkSize))!=ERROR_OK) { fclose(handle); return (status); } DecodeFlag=true; } else if (memcmp(ChunkHeader,HEADER_NLYR,strlen(HEADER_NLYR))==0) { short layer,status; long offset;

fread((char *) &offset,1,sizeof(offset),handle); layer=loadMappyWord(handle,false); offset-=12; // 8+sizeof(layer)+sizeof(offset); loadMappySkipSection(handle,offset); if (layer>=0 && layer

44 if (block>=0 && blockbgoff,1,sizeof(temp->bgoff),handle); fread((char *) &temp->fgoff,1,sizeof(temp->fgoff),handle); fread((char *) &temp->fgoff2,1,sizeof(temp->fgoff2),handle); fread((char *) &temp->fgoff3,1,sizeof(temp->fgoff3),handle); if (m_dMapType==FMP05) { int size;

if (m_dMapDepth == 15) { size = (m_dBlockWidth*m_dBlockHeight*2); } else { size = (m_dBlockWidth*m_dBlockHeight*(m_dMapDepth/8)); } temp->bgoff/=size; temp->fgoff/=size; temp->fgoff2/=size; temp->fgoff3/=size; } fread((char *) &temp->user1,1,sizeof(temp->user1),handle); fread((char *) &temp->user2,1,sizeof(temp->user2),handle); fread((char *) &temp->user3,1,sizeof(temp->user3),handle); fread((char *) &temp->user4,1,sizeof(temp->user4),handle); fread((char *) &temp->user5,1,sizeof(temp->user5),handle); fread((char *) &temp->user6,1,sizeof(temp->user6),handle); fread((char *) &temp->user7,1,sizeof(temp->user7),handle); fread((char *) &one,1,sizeof(one),handle); temp->tl=(one & 1 ? true : false); temp->tr=(one & 2 ? true : false); temp->bl=(one & 4 ? true : false); temp->br=(one & 8 ? true : false); temp->trigger=(one & 16 ? true : false); temp->unused1=(one & 32 ? true : false); temp->unused2=(one & 64 ? true : false); temp->unused3=(one & 128 ? true : false); temp++; i+=sizeof(BLKSTR); } } else { return (ERROR_OUTOFMEM); } loadMappySkipSection(handle,ChunkSize-i); return (ERROR_OK); } int CMappy::processLayerString(int handle,int ChunkSize) {

45 int size,loop; long offsetLayerName,numLayers; char *tempBuffer; if ((tempBuffer=(char *) calloc(1,ChunkSize+1))==NULL) { return ERROR_OUTOFMEM; } fread(tempBuffer,1,ChunkSize,handle); offsetLayerName=*(tempBuffer+0); // Offset into layer names numLayers=*(tempBuffer+sizeof(long)); // Number of layers size=ChunkSize-offsetLayerName; if ((m_layerName=(char *) calloc(1,size+1))==NULL) { return (ERROR_OUTOFMEM); } for (loop=0; loop

return (ERROR_OK); } int CMappy::processAnimation(int handle,int ChunkSize) { char *tempBuffer; signed char *temp; ANISTR *tempAnim; register int loop; register long sequenceSize; long *ptr; if ((tempBuffer=(char *) calloc(1,ChunkSize))==NULL) { return (ERROR_OUTOFMEM); } fread(tempBuffer,1,ChunkSize,handle); // Now to count backwards to get the number of animations m_dNumAnimations=0; sequenceSize=ChunkSize; temp=(signed char *) (tempBuffer+ChunkSize); while (1) { temp-=sizeof(ANISTR); sequenceSize-=sizeof(ANISTR); m_dNumAnimations++; if ((signed char) *(temp)==AN_END) { break; } } sequenceSize/=4; if ((animations=(ANISTR *) calloc(m_dNumAnimations,sizeof(ANISTR)))==NULL) { return (ERROR_OUTOFMEM); } tempAnim=(ANISTR *) temp; //(tempBuffer+ChunkSize); //(ChunkSize-sizeof(ANISTR))); for (loop=0; loopantype; animations[loop].anuser=tempAnim->anuser; animations[loop].andelay=tempAnim->andelay;

46 animations[loop].ancount=tempAnim->ancount; animations[loop].ancuroff=(tempAnim->ancuroff+ChunkSize)/(m_dMapType==FMP05 ? 4 : 1); animations[loop].anstartoff=(tempAnim->anstartoff+ChunkSize)/ (m_dMapType==FMP05 ? 4 : 1); animations[loop].anendoff=(tempAnim->anendoff+ChunkSize)/(m_dMapType==FMP05 ? 4 : 1); tempAnim++; } if ((animationSeq=(long *) calloc(sizeof(long),sequenceSize))==NULL) { return (ERROR_OUTOFMEM); } ptr=(long *) tempBuffer; for (loop=0; loop<(int) sequenceSize; loop++) { animationSeq[loop]=*(ptr); if (m_dMapType==FMP05) { animationSeq[loop]/=32; } ptr++; } // Now we've done that, we get the animation free(tempBuffer);

// Initilise the animations MapInitAnims(); return (ERROR_OK); } void CMappy::MapInitAnims(void) { int loop; if (m_dNumAnimations>0) { for (loop=0; loop

47 if (index>=0 && index24) { // Read next 4 bytes m_dMapTrans8=(int) fgetc(handle); m_dMapTransRed=(int) fgetc(handle); m_dMapTransGreen=(int) fgetc(handle); m_dMapTransBlue=(int) fgetc(handle); i+=4; } else { m_dMapTrans8=(int) 0; m_dMapTransRed=(int) 0xFF; m_dMapTransGreen=(int) 0; m_dMapTransBlue=(int) 0xFF; } if (size>28) {

48 m_dBlockGapX=(int) loadMappyWord(handle,false); m_dBlockGapY=(int) loadMappyWord(handle,false); m_dBlockStaggerX=(int) loadMappyWord(handle,false); m_dBlockStaggerY=(int) loadMappyWord(handle,false); i+=8; } else { m_dBlockGapX=m_dBlockWidth; m_dBlockGapY=m_dBlockHeight; m_dBlockStaggerX=0; m_dBlockStaggerY=0; } if (size>36) { m_dClipMask=(int) loadMappyWord(handle,false); i+=2; } else { m_dClipMask=0; } if (i!=size) { loadMappySkipSection(handle,size-i); } return (ERROR_OK); } struct __RGB *CMappy::GetPaletteColour(int index) { if (index>=0 && index<(int) (1<

49 { m_graphics[loop]=GENSPRITE(); if (m_graphics[loop]<0) { return ERROR_OUTOFMEM; } else { //SETTRANSPARENCY(RGB(m_dMapTransRed,m_dMapTransGreen,m_dMapTr ansBlue)); CREATESCREEN(1,m_graphics[loop],m_dBlockWidth,m_dBlockHeight); USESCREEN(1); switch (m_dMapDepth) { case 8 : for (y=0; yr,rgb->g,rgb->b)); } } } } break; case 15 : for (y=0; y

one=fgetc(handle); two=fgetc(handle);

pred=(one>>2)<<3; pred=pred | (pred>>5); pgreen=(one & 3)<<6; pgreen=pgreen | ((two>>5)<<3); pgreen=pgreen | (pgreen>>5); pblue=(two<<3) & 255; pblue=pblue | (pblue>>5); if (pred! =m_dMapTransRed || pgreen!=m_dMapTransGreen || pblue!=m_dMapTransBlue) { SETPIXEL(x,y,RGB(pred,pgreen,pblue)); } }

50 } break; case 16 : for (y=0; y

pred=(one>>3)<<3; pred=pred | (pred>>5); pgreen=(one & 7)<<5; pgreen=pgreen | ((two>>5)<<2); pgreen=pgreen | (pgreen>>6); pblue=(two<<3) & 255; pblue=pblue | (pblue>>5); if (pred! =m_dMapTransRed || pgreen!=m_dMapTransGreen || pblue!=m_dMapTransBlue) { SETPIXEL(x,y,RGB(pred,pgreen,pblue)); } } } break; case 24 : for (y=0; y

51 pblue=fgetc(handle); if (pred! =m_dMapTransRed || pgreen!=m_dMapTransGreen || pblue!=m_dMapTransBlue) { SETPIXEL(x,y,RGB(pred,pgreen,pblue)); } } } break; }; USESCREEN(-1); } } return ERROR_OK; } else { return ERROR_OUTOFMEM; } } int CMappy::GetNumberOfLayers(void) { register int loop,count;

count=0; for (loop=0; loop=0 && y>=0 && x=0 && layerLevel=0 && y>=0 && x=0 && layerLevel

52 register int lp; register char ignore; if (size==0) return; for (lp=0; lp0) { if ((extraBytesLayer[layerLevel]=(char *) calloc(extraBytes,m_dMapWidth*m_dMapHeight))==NULL) { return (ERROR_OUTOFMEM); } } return ERROR_OK; } signed short CMappy::getTileMapLayer(int handle,int sectionLen,int layerLevel,int extraBytesSize) { register int lp; signed short data,rleCount; signed short *ptr; int status; if ((status=allocateLayer(layerLevel,sizeof(data)*m_dMapWidth*m_dMapHeight,extraBytesSize)) ==ERROR_OK) { ptr=layer[layerLevel]; switch (m_dMapType) { case FMP05 : // FMP 0.5, I think case FMP10 : // FMP 1.0 for (lp=0; lp

53 if (rleCount>0) { while (rleCount>0) { fread((char *) &data,1,sizeof(data),handle); *(ptr)=data; ptr++; rleCount--; lp+=sizeof(data); } } else { fread((char *) &data,1,sizeof(data),handle); lp+=sizeof(data); while (rleCount<0) { *(ptr)=data; ptr++; rleCount++; } } } break; }; } return (status); } int CMappy::CalculateExtraPosition(int x,int y) { return ((x*m_dExtraBytesSize)+(y*m_dMapWidth*m_dExtraBytesSize)); } char *CMappy::GetExtraDataPointer(int x,int y,int layer) { if (layer>=0 && layer=0 && layerNum

54 else { return (ERROR_LAYERNOTINUSE); } } return (ERROR_INVALIDLAYER); } signed short *CMappy::returnLayer(int layr) { if (layr>=0 && layrbgoff; break; case FOREGROUND1 : return block->fgoff; break; case FOREGROUND2 : return block->fgoff2; break; case FOREGROUND3 : return block->fgoff3; break; default : return 0; break; }; } return 0; } int CMappy::returnCurrentAnimationBlock(int block) { ANISTR *a; if ((a=GetAnimationData(m_dNumAnimations-(0-block)))!=NULL) { return animationSeq[a->ancuroff]; } return 0; } int CMappy::returnAnimationBlock(int block) { ANISTR *a; a=(ANISTR *) &animations[m_dNumAnimations-(0-block)]; return animationSeq[a->ancuroff]; } int CMappy::addLayer(int layers) { if (layers>=0 && layers0 && m_dMapHeight>0) { return allocateLayer(layers,m_dMapWidth*m_dMapHeight, m_dExtraBytesSize); } else { return ERROR_MAPNOTLOADED; } }

55 return ERROR_INVALIDLAYER; } int CMappy::deleteLayer(int layers) { if (layers>=0 && layers=0 && fromLayer=0 && toLayer

memcpy(layer[toLayer],layer[fromLayer],m_dMapWidth*m_dMapHeight*sizeof(signed short)); return ERROR_OK; } else { return ERROR_LAYERINUSE; } } return ERROR_INVALIDLAYER; } int CMappy::clearLayer(int layers) { if (layers>=0 && layers

56 { int status; if (fromLayer>=0 && fromLayer=0 && toLayer

57 } break; case AN_ONCE : if (animations[loop].anstartoff!=animations[loop].anendoff) { animations[loop].ancuroff++; if (animations[loop].ancuroff==animations[loop].anendoff) { animations[loop].antype=AN_ONCES; animations[loop].ancuroff=animations[loop].anendoff; } } break; case AN_ONCEH : if (animations[loop].anstartoff!=animations[loop].anendoff) { if (animations[loop].ancuroff!=animations[loop].anendoff-1) { animations[loop].ancuroff++; } } break; case AN_PPFF : if (animations[loop].anstartoff!=animations[loop].anendoff) { animations[loop].ancuroff++; if (animations[loop].ancuroff==animations[loop].anendoff) { animations[loop].ancuroff-=2; animations[loop].antype=AN_PPFR; if (animations[loop].ancuroffanimations[loop].anendoff) { animations[loop].ancuroff--; } } } break; case AN_PPRR : if (animations[loop].anstartoff!=animations[loop].anendoff) {

58 animations[loop].ancuroff--; if (animations[loop].ancuroff==animations[loop].anstartoff-1) { animations[loop].ancuroff+=2; animations[loop].antype=AN_PPRF; if (animations[loop].ancuroff>animations[loop].anendoff) { animations[loop].ancuroff--; } } } break; case AN_PPRF : if (animations[loop].anstartoff!=animations[loop].anendoff) { animations[loop].ancuroff--; if (animations[loop].ancuroff==animations[loop].anendoff) { animations[loop].ancuroff-=2;

animations[loop].antype=AN_PPRR; if (animations[loop].ancuroff

59 //! Get the block depth FUNCTION Mappy_GetMapDepth%: INLINE return (DGNat) mappy.m_dMapDepth; ENDINLINE ENDFUNCTION //! Get the map author FUNCTION Mappy_GetAuthor$: LOCAL temp$ temp$="" INLINE temp_Str.assign(mappy.m_cpAuthor); ENDINLINE RETURN temp$ ENDFUNCTION //! Get version of Mappy used to create map FUNCTION Mappy_Version%: INLINE return (DGNat) mappy.m_dMapVersion; ENDINLINE ENDFUNCTION //! Get the map file format FUNCTION Mappy_GetMapFormat%: INLINE return (DGNat) mappy.m_dMapType; ENDINLINE ENDFUNCTION //! Is the map data in least significant byte order ? FUNCTION Mappy_IsLSB%: INLINE return (DGNat) mappy.m_dLSB; ENDINLINE ENDFUNCTION //! Add a new layer to the map FUNCTION Mappy_AddLayer%:layer% INLINE return (DGNat) mappy.addLayer(layer); ENDINLINE ENDFUNCTION //! Get the number of layers in the map FUNCTION Mappy_GetNumberOfLayers%: INLINE return (DGNat) mappy.GetNumberOfLayers(); ENDINLINE ENDFUNCTION //! Delete a layer FUNCTION Mappy_DeleteLayer%:layer% INLINE return (DGNat) mappy.deleteLayer(layer); ENDINLINE ENDFUNCTION //! Copy data from one layer to another FUNCTION Mappy_CopyLayer%:fromLayer%,toLayer% INLINE return (DGNat) mappy.copyLayer(fromLayer,toLayer); ENDINLINE ENDFUNCTION //! Clear data from a layer FUNCTION Mappy_ClearLayer%:layer% INLINE return (DGNat) mappy.clearLayer(layer); ENDINLINE ENDFUNCTION //! Move data from one layer to another

60 FUNCTION Mappy_MoveLayer%:fromLayer%,toLayer% INLINE return (DGNat) mappy.moveLayer(fromLayer,toLayer); ENDINLINE ENDFUNCTION //! Return block width of map FUNCTION Mappy_GetBlockWidth%: INLINE return (DGNat) mappy.m_dBlockWidth; ENDINLINE ENDFUNCTION //! Return block height of map FUNCTION Mappy_GetBlockHeight%: INLINE return (DGNat) mappy.m_dBlockHeight; ENDINLINE ENDFUNCTION //! Return number of blocks in map FUNCTION Mappy_GetNumberOfBlocks%: INLINE return (DGNat) mappy.m_dNumBlockGFX; ENDINLINE ENDFUNCTION FUNCTION Mappy_GetTileAtPosition%:x%,y%,layer% INLINE return (DGNat) mappy.GetTileAtPosition(x,y,layer); ENDINLINE ENDFUNCTION FUNCTION Mappy_GetLayerOffset%:tile%,which% INLINE return (DGNat) mappy.returnLayerOffset(tile,which); ENDINLINE ENDFUNCTION FUNCTION Mappy_GetCurrentAnimationBlock%:tile% INLINE return (DGNat) mappy.returnCurrentAnimationBlock(tile); ENDINLINE ENDFUNCTION FUNCTION Mappy_UpdateAnimations%: INLINE mappy.UpdateAnimations(); ENDINLINE ENDFUNCTION FUNCTION Mappy_GetLayerNames$: LOCAL temp$ temp$="" INLINE temp_Str.assign(mappy.GetLayerNames()); ENDINLINE RETURN temp$ ENDFUNCTION FUNCTION Mappy_DrawMap2%:xPos%,yPos%,layer% INLINE int x,y,tile,xp,yp; ALPHAMODE(0.0); for (y=0; y

61 { tile=mappy.returnAnimationBlock(tile); } block=mappy.GetBlockData(tile); if (block) { xp=(x*mappy.m_dBlockWidth)+xPos; yp=(y*mappy.m_dBlockHeight)+yPos; DRAWSPRITE(mappy.m_graphics[block->bgoff],xp,yp); if (block->fgoff>0) DRAWSPRITE(mappy.m_graphics[block->fgoff],xp,yp); if (block->fgoff2>0) DRAWSPRITE(mappy.m_graphics[block->fgoff2],xp,yp); if (block->fgoff3>0) DRAWSPRITE(mappy.m_graphics[block->fgoff3],xp,yp); } } } } ENDINLINE ENDFUNCTION FUNCTION Mappy_DrawMap: scroll_x%, scroll_y%, sx%, sy%, width%, height%, pIndex%, pX%, pY%, fromLayer%=0, toLayer%=-1, wrap_x% = FALSE, wrap_y% = FALSE //LOCAL layer, scr_x, scr_y, map_x, map_y //LOCAL block, frame //LOCAL npc_index // where in the NPC-list are we?

VIEWPORT sx, sy, width, height INLINE int layr,scr_x,scr_y,map_x,map_y,tile; signed short *layer; if (wrap_x==false) { if (scroll_x<0) scroll_x=0; if (scroll_x>(mappy.m_dMapWidth*mappy.m_dBlockWidth)-width-1) scroll_x=(mappy.m_dMapWidth*mappy.m_dBlockWidth)-width-1; } if (wrap_y==false) { if (scroll_y<0) scroll_y=0; if (scroll_y>(mappy.m_dMapHeight*mappy.m_dBlockHeight)-height-1) scroll_y=(mappy.m_dMapHeight*mappy.m_dBlockHeight)-height-1; }

for (layr=fromLayer; layr<=(toLayer<0 ? MAX_LAYERS-1 : toLayer); layr++) { layer=mappy.returnLayer(layr); if (layer) { for (scr_y=0-mappy.m_dBlockHeight- (MOD(scroll_y,mappy.m_dBlockHeight)); scr_y<=height; scr_y+=mappy.m_dBlockHeight) { for (scr_x=0-mappy.m_dBlockWidth- (MOD(scroll_x,mappy.m_dBlockWidth)); scr_x<=width; scr_x+=mappy.m_dBlockWidth) { map_x = (scr_x+scroll_x) / mappy.m_dBlockWidth; map_y = (scr_y+scroll_y) / mappy.m_dBlockHeight; if (wrap_x) { if (map_x<0) map_x+=mappy.m_dMapWidth; if (map_x>=mappy.m_dMapWidth) map_x- =mappy.m_dMapWidth; } if (wrap_y) { if (map_y<0) map_y+=mappy.m_dMapHeight;

62 if (map_y>=mappy.m_dMapHeight) map_y- =mappy.m_dMapHeight; }

if ((map_x>=0 && map_x=0 && map_ybgoff],scr_x,scr_y); if (block->fgoff>0) DRAWSPRITE(mappy.m_graphics[block->fgoff],scr_x,scr_y); if (block->fgoff2>0) DRAWSPRITE(mappy.m_graphics[block->fgoff2],scr_x,scr_y); if (block->fgoff3>0) DRAWSPRITE(mappy.m_graphics[block->fgoff3],scr_x,scr_y); } } } } } } } if (pIndex>=0) { DRAWSPRITE(mappy.m_graphics[pIndex],pX,pY); } ENDINLINE VIEWPORT 0, 0, 0, 0 DRAWLINE sx, sy, sx+width, sy, RGB(192,192,128) DRAWLINE sx+width, sy, sx+width, sy+height, RGB(192,192,128) DRAWLINE sx+width, sy+height, sx, sy+height, RGB(192,192,128) DRAWLINE sx, sy+height, sx, sy, RGB(192,192,128) ENDFUNCTION FUNCTION Mappy_Collision%:old_x%,old_y%,new_x%,new_y% INLINE int dir_x,dir_y,tile,layr,oldData,newData; bool collide,done_x,done_y; signed short *layer; BLKSTR *newBlock,*oldBlock; old_x/=mappy.m_dBlockWidth; new_x/=mappy.m_dBlockWidth; old_y/=mappy.m_dBlockHeight; new_y/=mappy.m_dBlockHeight; // Work out what direction we are traveling dir_x = new_x - old_x; // Positive = down, Negative = up dir_y = new_y - old_y; done_x=false; done_y=false; collide=false; for (layr=MAX_LAYERS-1; layr>=0; layr--) { layer=mappy.returnLayer(layr); if (layer) { oldData=mappy.GetTileAtPosition(old_x,old_y,layr);

63 newData=mappy.GetTileAtPosition(new_x,new_y,layr); if (oldData>0 || newData>0) { oldBlock=mappy.GetBlockData(oldData); newBlock=mappy.GetBlockData(newData); if (dir_x!=0 && done_x==false) { if (dir_x>0) { if (oldBlock->tr!=0 || newBlock->br!=0) { collide=true; } else if (newBlock->unused3) { collide=false; done_x=true; } } else { if (oldBlock->br!=0 || newBlock->tr!=0) { collide=true; } else if (newBlock->unused3) { collide=false; done_x=true; } } } if (dir_y!=0 && done_y==false) { if (dir_y>0) { if (oldBlock->bl!=0 || newBlock->tl!=0) { collide=true; } else if (newBlock->unused3) { collide=false; done_y=true; } } else { if (oldBlock->tl!=0 || newBlock->bl!=0) { collide=true; } else if (newBlock->unused3) { collide=false; done_y=true; } } } } } } return (DGNat) collide; ENDINLINE ENDFUNCTION //! Delete a map from memory FUNCTION Mappy_DestroyMap%: INLINE mappy.destroyMap();

64 ENDINLINE ENDFUNCTION //! Find a tile FUNCTION Mappy_FindTile%:tile%,layer%,BYREF x%,BYREF y% INLINE int tx,ty,stat; if ((stat=mappy.FindTile(layer,&tx,&ty,tile))==ERROR_OK) { x=tx; y=ty; } return stat; ENDINLINE ENDFUNCTION //! Display a sprite from the tiles FUNCTION Mappy_DisplaySprite%:spriteIndex%,x%,y% INLINE DRAWSPRITE(mappy.m_graphics[spriteIndex],x,y); ENDINLINE ENDFUNCTION FUNCTION Mappy_WriteTileAtPosition%:x%,y%,layer%,value% INLINE mappy.WriteTileAtPosition(x,y,layer,(short) value); ENDINLINE ENDFUNCTION

65 MiscRoutines

This set of routines allows the user to get the path to various areas of the operating system (like music and pictures) direct, change the programs window name and split a colour into its respective red, green and blue components. It uses some free code from RedHat in order to get the correct paths – the original code should be easy enough to find on the internet. The wrap value routine was modified from a routine on CodeProject. INLINE typedef int size_t; // C commands // mem... commands extern "C" void *memset(void *s, int c, size_t n); extern "C" void *memcpy(void *dest, const void *src, size_t n); extern "C" int memcmp(const void *s1, const void *s2, size_t n); extern "C" void * memmove(void * destination, const void * source, size_t num ); // Memory allocation/freeing commands extern "C" void * malloc ( size_t size ); extern "C" void *calloc(size_t nmemb, size_t size); extern "C" void free(void *ptr); // String commands extern "C" size_t strlen(const char *s); extern "C" char * strcpy ( char * destination, const char * source ); extern "C" char * strncpy ( char * destination, const char * source, size_t num ); extern "C" int strncmp ( const char * str1, const char * str2, size_t num ); extern "C" int strcmp ( const char * str1, const char * str2 ); extern "C" int sprintf ( char * str, const char * format, ... ); extern "C" int printf ( const char * format, ... ); extern "C" char * strchr ( const char * str, int character ); extern "C" size_t strlen ( const char * str ); extern "C" char * strcpy ( char * destination, const char * source ); extern "C" char * strcat ( char * destination, const char * source ); extern "C" char *strdup(const char *s1); // Low level I/O extern "C" int access(const char *pathname, int mode); extern "C" size_t read(int fd, void *buf, size_t count); extern "C" int write(int fd, char *buf, size_t count); extern "C" int close(int fd); extern "C" int rename(const char *_old, const char *_new); // Debug void assert (int expression) { } extern "C" char *getenv(char *); #ifndef IPHONE // This is for non-windows platforms extern "C" __stdcall void SDL_WM_SetCaption(const char *,const char *); #endif extern "C" void __stdcall glLightf(int, int, float); #define GL_LIGHT0 0x4000 #define GL_CONSTANT_ATTENUATION 0x1207 #define GL_LINEAR_ATTENUATION 0x1208 #define GL_QUADRATIC_ATTENUATION 0x1209 #ifdef _WIN32_WCE typedef int BOOL; typedef int LPBOOL; typedef unsigned int UINT; typedef char *LPCSTR; typedef const wchar_t *LPTSTR; typedef wchar_t WCHAR; typedef wchar_t TCHAR; extern "C" bool SHGetSpecialFolderPath(HWND hwndOwner,LPTSTR lpszPath,int nFolder,bool fCreate); extern "C" int WideCharToMultiByte(UINT CodePage,DWORD dwFlags,LPCWSTR lpWideCharStr,int cchWideChar,LPSTR lpMultiByteStr, int cbMultiByte,LPCSTR lpDefaultChar,LPBOOL lpUsedDefaultChar);

66 extern "C" size_t wcslen(const wchar_t *str); extern "C" size_t strlen ( const char * str ); const int CSIDL_APPDATA = 0x001A; const int CSIDL_MYPICTURES= 0x0027; const int CSIDL_MYMUSIC = 0x000d; #define MAX_PATH 260 #define CP_ACP 0 #elif WIN32 typedef long LONG; typedef LONG HRESULT; // No need to do anything for Windows DECLARE(SHGetFolderPathA,"shell32.dll",(HWND,int,HANDLE,DWORD,LPSTR),HRESULT); DECLARE(GetTempPathA,"kernel32.dll",(DWORD,LPSTR),DWORD); const int CSIDL_APPDATA = 0x001A; const int CSIDL_MYPICTURES= 0x0027; const int CSIDL_MYMUSIC = 0x000D; const int SHGFP_TYPE_CURRENT= 0; const int CSIDL_COMMON_APPDATA= 35; #define MAX_PATH 260 extern "C" int open(const char *pathname, int flags); typedef struct __RECT { long left; long top; long right; long bottom; } __RECT; typedef int HDC; #define SM_CXSCREEN 0 #define SM_CYSCREEN 1 #define HWND_BOTTOM ((HWND)1) #define HWND_NOTOPMOST ((HWND)(-2)) #define HWND_TOP ((HWND)0) #define HWND_TOPMOST ((HWND)(-1)) #define HWND_DESKTOP (HWND)0 #define HWND_MESSAGE ((HWND)(-3)) #define SWP_DRAWFRAME 0x0020 #define SWP_FRAMECHANGED 0x0020 #define SWP_HIDEWINDOW 0x0080 #define SWP_NOACTIVATE 0x0010 #define SWP_NOCOPYBITS 0x0100 #define SWP_NOMOVE 0x0002 #define SWP_NOSIZE 0x0001 #define SWP_NOREDRAW 0x0008 #define SWP_NOZORDER 0x0004 #define SWP_SHOWWINDOW 0x0040 #define SWP_NOOWNERZORDER 0x0200 #define SWP_NOREPOSITION SWP_NOOWNERZORDER #define SWP_NOSENDCHANGING 0x0400 #define SWP_DEFERERASE 0x2000 #define SWP_ASYNCWINDOWPOS 0x4000 #define SW_HIDE 0 #define SW_SHOWNORMAL 1 #define SW_SHOWNOACTIVATE 4 #define SW_SHOW 5 #define SW_MINIMIZE 6 #define SW_SHOWNA 8 #define SW_SHOWMAXIMIZED 11 #define SW_MAXIMIZE 12 #define SW_RESTORE 13 #define HORZRES 8 #define VERTRES 10 extern "C" __stdcall int GetSystemMetrics(int); extern "C" __stdcall int GetWindowRect(HWND hWnd,struct __RECT *lpRect); extern "C" __stdcall int GetClientRect(HWND hWnd,struct __RECT *lpRect); extern "C" __stdcall int SetWindowTextA(HWND hWnd,const char *lpString); extern "C" __stdcall HWND GetDesktopWindow(void);

67 extern "C" __stdcall int SetWindowPos(HWND hWnd,HWND hWndInsertAfter,int X,int Y,int cx,int cy,int uFlags); extern "C" __stdcall int EnumDisplaySettingsA(const char*, unsigned int, void*); extern "C" __stdcall HWND GetForegroundWindow(void); extern "C" __stdcall int GetLastError(void); extern "C" __stdcall int GetSystemMetrics(int nIndex); extern "C" __stdcall HDC GetDC(HWND); extern "C" __stdcall int GetDeviceCaps(HDC,int); #elif OSXUNI extern "C" int FSFindFolder(int vRefNum,int folderType,int createFolder,char *foundRef); extern "C" int FSRefMakePath(char *ref,char *path,int maxPathSize); const int kVolumeRootFolderType = ('r'<<24)|('o'<<16)|('o'<<8)| ('t'); const int kMusicDocumentsFolderType = 0xB5646F63; const int kPictureDocumentsFolderType = ('p'<<24)|('d'<<16)|('o'<<8)|('c'); const int kSharedUserDataFolderType = ('s'<<24)|('d'<<16)|('a'<<8)| ('t'); const int kTemporaryFolderType = ('t'<<24)|('e'<<16)|('m'<<8)| ('p');

int specificCode(int kind,char *store,int size); #define BUFFER_SIZE 1024 #elif IPHONE // Nothing #else typedef struct __password { char *pw_name; // user name char *pw_passwd; // user password int pw_uid; // user id int pw_gid; // group id char *pw_gecos; // real name char *pw_dir; // home directory char *pw_shell; // shell program } __password; #define O_RDONLY 0 #define O_TEXT 0x4000 extern "C" int open(const char *filename,int oflag,int pmode); extern "C" int getuid(void); extern "C" struct __password *getpwuid(int uid); #endif ENDINLINE INLINE #ifdef _WIN32_WCE char *convertCSIDLToMultiByte(int which) { WCHAR path[MAX_PATH+1]; int size; char *ptr; char t[256]; SHGetSpecialFolderPath((HWND) GLBASIC_HWND(),path,which,FALSE); if (wcslen(path)) { size=WideCharToMultiByte(0, 0, (LPCWSTR) path, -1, NULL, 0, NULL, NULL); if (size) { ptr=(char *) calloc(1,size+1); if (ptr) { WideCharToMultiByte(CP_ACP, 0, (LPCWSTR) path, -1, (LPSTR) ptr, size,NULL,NULL); return ptr; } } } return NULL; } #elif WIN32

68 #elif OSXUNI int specificCode(int kind,char *store,int size) { char ref[80]={0}; int kUserDomain=-32763; if (!FSFindFolder(kUserDomain,kind,FALSE,(char *) &ref)) { if (!FSRefMakePath((char *) &ref,store,size-1)) { return true; } } return false; } #elif IPHONE #else // This is needed so that a line can be read properly (as read reads up to a given number of characters) int readALine(int handle,char *store,int size) { char one; int index; index=0; memset(store,(char) 0,size); while (read(handle,(char *) &one,sizeof(one))>0) { if (index

69 // * // * Looks up a XDG user directory of the specified type. // * Example of types are "DESKTOP" and "DOWNLOAD". // * // * In case the user hasn't specified any directory for the specified // * type the value returned is @fallback. // * // * The return value is newly allocated and must be freed with // * free(). The return value is never NULL if @fallback != NULL, unless // * out of memory. // **/ static char *xdg_user_dir_lookup_with_fallback (const char *type, const char *fallback) { int file; char *home_dir, *config_home, *config_file; char buffer[512]={0}; char *user_dir; char *p, *d; int len; int relative; home_dir = getenv ("HOME"); if (home_dir == NULL) goto error; config_home = getenv ("XDG_CONFIG_HOME"); if (config_home == NULL || config_home[0] == 0) { config_file = (char*) malloc (strlen (home_dir) + strlen ("/.config/user- dirs.dirs") + 1); if (config_file == NULL) goto error; strcpy (config_file, home_dir); strcat (config_file, "/.config/user-dirs.dirs"); } else { config_file = (char*) malloc (strlen (config_home) + strlen ("/user- dirs.dirs") + 1); if (config_file == NULL) goto error; strcpy (config_file, config_home); strcat (config_file, "/user-dirs.dirs"); } file=open(config_file,O_RDONLY,0); free (config_file); if (file<0) { goto error; } user_dir = NULL; // Original version used fgets, which isn't availiable here, so I had to use read... while (readALine(file,(char *) &buffer,sizeof(buffer))>0) { /* Remove newline at end */ len = strlen (buffer); if (len > 0 && buffer[len-1] == '\n') buffer[len-1] = 0; p = buffer; while (*p == ' ' || *p == '\t') p++; if (strncmp (p, "XDG_", 4) != 0) continue; p += 4; if (strncmp (p, type, strlen (type)) != 0) continue; p += strlen (type);

70 if (strncmp (p, "_DIR", 4) != 0) continue; p += 4; while (*p == ' ' || *p == '\t') p++; if (*p != '=') continue; p++; while (*p == ' ' || *p == '\t') p++; if (*p != '"') continue; p++; relative = 0; if (strncmp (p, "$HOME/", 6) == 0) { p += 6; relative = 1; } else if (*p != '/') continue; if (relative) { user_dir = (char*) malloc (strlen (home_dir) + 1 + strlen (p) + 1); if (user_dir == NULL) goto error2; strcpy (user_dir, home_dir); strcat (user_dir, "/"); } else { user_dir = (char*) malloc (strlen (p) + 1); if (user_dir == NULL) goto error2; *user_dir = 0; } d = user_dir + strlen (user_dir); while (*p && *p != '"') { if ((*p == '\\') && (*(p+1) != 0)) p++; *d++ = *p++; } *d = 0; } error2: close (file); if (user_dir) return user_dir; error: if (fallback) return strdup (fallback); return NULL; } /** * xdg_user_dir_lookup: * @type: a string specifying the type of directory * @returns: a newly allocated absolute pathname * * Looks up a XDG user directory of the specified type. * Example of types are "DESKTOP" and "DOWNLOAD". * * The return value is always != NULL (unless out of memory), * and if a directory * for the type is not specified by the user the default

71 * is the home directory. Except for DESKTOP which defaults * to ~/Desktop. * * The return value is newly allocated and must be freed with * free(). **/ static char * xdg_user_dir_lookup (const char *type) { char *dir, *home_dir, *user_dir; dir = xdg_user_dir_lookup_with_fallback (type, NULL); if (dir != NULL) return dir; home_dir = getenv("HOME"); if (home_dir == NULL) return strdup ("/tmp"); /* Special case desktop for historical compatibility */ if (strcmp (type, "DESKTOP") == 0) { user_dir = (char*) malloc (strlen (home_dir) + strlen ("/Desktop") + 1); if (user_dir == NULL) return NULL; strcpy (user_dir, home_dir); strcat (user_dir, "/Desktop"); return user_dir; } return strdup (home_dir); } #ifdef STANDALONE_XDG_USER_DIR_LOOKUP int main (int argc, char *argv[]) { if (argc != 2) { fprintf (stderr, "Usage %s \n", argv[0]); exit (1); } printf ("%s\n", xdg_user_dir_lookup (argv[1])); return 0; } #endif #endif ENDINLINE FUNCTION getPicturesDir$: LOCAL temp$ temp$="" INLINE #ifdef _WIN32_WCE char *ptr; ptr=convertCSIDLToMultiByte(CSIDL_MYPICTURES); if (ptr) { temp_Str.assign(ptr); free(ptr); } #elif WIN32 char path[MAX_PATH+1]={0}; if (SHGetFolderPathA(NULL,CSIDL_MYPICTURES,NULL,SHGFP_TYPE_CURRENT,(char *) &path)==0) { temp_Str.assign((char *) &path); } #elif OSXUNI char buffer[BUFFER_SIZE]={0};

72 if (specificCode(kPictureDocumentsFolderType,(char *) &buffer,sizeof(buffer)-1)) { temp_Str.assign((char *) &buffer); } #elif IPHONE temp_Str.assign(PLATFORMINFO_Str("DOCUMENTS")); #else char *ptr; ptr=xdg_user_dir_lookup("PICTURES"); if (ptr) { temp_Str.assign(ptr); } #endif ENDINLINE RETURN returnAndReplaceBackslash$(temp$) ENDFUNCTION FUNCTION GetMusicDir$: LOCAL temp$ temp$="" INLINE #ifdef _WIN32_WCE char *ptr;

ptr=convertCSIDLToMultiByte(CSIDL_MYMUSIC); if (ptr) { temp_Str.assign(ptr); free(ptr); } #elif WIN32 char path[MAX_PATH+1]={0}; if (SHGetFolderPathA(NULL,CSIDL_MYMUSIC,NULL,SHGFP_TYPE_CURRENT,(char *) &path)==0) { temp_Str.assign((char *) &path); } #elif OSXUNI char buffer[BUFFER_SIZE]; if (specificCode(kMusicDocumentsFolderType,(char *) &buffer,sizeof(buffer)- 1)) { temp_Str.assign((char *) &buffer); } #elif IPHONE temp_Str.assign(PLATFORMINFO_Str("DOCUMENTS")); #else char *ptr; ptr=xdg_user_dir_lookup("MUSIC"); if (ptr) { temp_Str.assign(ptr); } #endif ENDINLINE RETURN returnAndReplaceBackslash$(temp$) ENDFUNCTION FUNCTION GetSharedDataDir$: LOCAL temp$ temp$="" INLINE #ifdef _WIN32_WCE

73 char *ptr; ptr=convertCSIDLToMultiByte(CSIDL_APPDATA); if (ptr) { int loop; for (loop=0; loop

if (specificCode(kSharedUserDataFolderType,(char *) &buffer,sizeof(buffer)- 1)) { temp_Str.assign((char *) &buffer); } #elif IPHONE temp_Str.assign(PLATFORMINFO_Str("DOCUMENTS")); #else char *ptr; ptr=xdg_user_dir_lookup("PUBLICSHARE"); if (ptr) { temp_Str.assign(ptr); } #endif ENDINLINE RETURN returnAndReplaceBackslash$(temp$) ENDFUNCTION FUNCTION returnAndReplaceBackslash$:text$ LOCAL temp$ temp$=REPLACE$(text$,"\\","/") IF RIGHT$(temp$,1)<>"/" THEN INC temp$,"/" RETURN temp$ ENDFUNCTION FUNCTION ChangeWindowText: version$ INLINE DGStr title; title=__glb_AppName(); #ifdef GLB_DEBUG title+=DGStr(" *"); #endif title+=DGStr(" - ")+DGStr(version_Str);

#ifdef IPHONE // Do nothing on the iPhone/iPod #elif _WIN32_WCE #elif GP2X

74 #elif GP2XWIZ #elif XBOXLINUX #elif PANDORA #elif WEBOS #elif WIN32 ::SetWindowTextA((HWND) GLBASIC_HWND(),title.c_str()); #else ::SDL_WM_SetCaption(title.c_str(), title.c_str()); #endif ENDINLINE ENDFUNCTION FUNCTION returnProgramName$: INLINE return DGStr(__glb_AppName()); ENDINLINE ENDFUNCTION FUNCTION ReturnProgramVersion$: INLINE return __GetPrpjectVersionGLB(); ENDINLINE ENDFUNCTION FUNCTION GetDefaultProjectSize%:BYREF width%,BYREF height% INLINE width=__DG_RESX; height=__DG_RESY; ENDINLINE ENDFUNCTION

FUNCTION getDesktopSize%:BYREF deskWidth%,BYREF deskHeight% INLINE #ifdef WIN32 deskWidth=GetSystemMetrics(SM_CXSCREEN); deskHeight=GetSystemMetrics(SM_CYSCREEN); #else deskWidth=0; deskHeight=0; #endif ENDINLINE ENDFUNCTION FUNCTION CentreWindow%: INLINE #ifdef _WIN32_WCE #elif WIN32 struct __RECT window; unsigned int deskWidth,deskHeight,windowWidth,windowHeight; int flags; HWND hWnd; hWnd=(HWND) GLBASIC_HWND(); deskWidth=GetSystemMetrics(SM_CXSCREEN); deskHeight=GetSystemMetrics(SM_CYSCREEN); if (::GetWindowRect(hWnd,&window)) { windowWidth=window.right-window.left; windowHeight=window.bottom-window.top; flags=SWP_SHOWWINDOW; if (windowWidth>deskWidth || windowHeight>deskHeight) { windowWidth=deskWidth; windowHeight=deskHeight; } else { flags|=SWP_NOSIZE; } ::SetWindowPos(hWnd,HWND_NOTOPMOST,(deskWidth-windowWidth)>>1,(deskHeight- windowHeight)>>1, windowWidth, windowHeight, flags); } else

75 { DEBUG("GetWindowRect (window) failed"); } #else #endif ENDINLINE ENDFUNCTION FUNCTION renameFile%:old$,new$ INLINE (rename(old_Str.c_str(),new_Str.c_str())==0 ? TRUE : FALSE); ENDINLINE ENDFUNCTION FUNCTION RGBR%:colour% INLINE return (DGNat) colour & 255; ENDINLINE ENDFUNCTION FUNCTION RGBG%:colour% INLINE return (DGNat) ((colour>>8) & 255); ENDINLINE ENDFUNCTION FUNCTION RGBB%:colour% INLINE return (DGNat) ((colour>>16) & 255); ENDINLINE ENDFUNCTION FUNCTION constrain:value,minRange,maxRange RETURN MIN(maxRange,MAX(value,minRange)) ENDFUNCTION FUNCTION wrap:value,minRange,maxRange LOCAL diff diff=maxRange-minRange IF value>=minRange IF value=minRange-diff RETURN value+diff ENDIF ENDIF RETURN MOD(value-minRange,diff)+minRange ENDFUNCTION FUNCTION _glLight%:index%,cnst%,linear%,quadratic INLINE glLightf(GL_LIGHT0+index, GL_CONSTANT_ATTENUATION, cnst); glLightf(GL_LIGHT0+index, GL_LINEAR_ATTENUATION, linear); glLightf(GL_LIGHT0+index, GL_QUADRATIC_ATTENUATION, quadratic); ENDINLINE ENDFUNCTION FUNCTION RoundDecimal: Real_Number, Decimal_Power //Round Decimal Numbers(power of 10) RETURN INTEGER( Real_Number * POW( 10, Decimal_Power ) ) / POW( 10, Decimal_Power )*1.0 ENDFUNCTION

76 Network Routine

This code sends and receives data for the network lobby routine. // This holds the networking stuff TYPE tNetList isHost% isReady% isThisComputer% // Is this the players computer playerID$ playerName$ timeForACK timeForACKResponse ENDTYPE GLOBAL NETLENGTH_OFFSET% = 64 GLOBAL JOB_LENGTH% = 3 GLOBAL ID_LENGTH% = 3 GLOBAL ACK_BASETIME = 5000.0 FUNCTION calcNumPlaying%:value% RETURN value%-NETLENGTH_OFFSET% ENDFUNCTION FUNCTION returnNumPlaying$:numPlaying% RETURN CHR$(NETLENGTH_OFFSET%+numPlaying%) ENDFUNCTION

FUNCTION createNumPlaying$:numPlaying% RETURN PLAYING$+returnNumPlaying$(numPlaying%) ENDFUNCTION FUNCTION returnPlayerIndex$:index% RETURN CHR$(NETLENGTH_OFFSET%+index%) ENDFUNCTION FUNCTION createPlayerIndex$:index% RETURN PLAYER$+returnPlayerIndex$(index%) ENDFUNCTION FUNCTION returnPlayerName$:name$ RETURN CHR$(NETLENGTH_OFFSET%+LEN(name$))+name$ ENDFUNCTION FUNCTION returnPlayerID$:playerID$ RETURN CHR$(NETLENGTH_OFFSET%+LEN(playerID$))+playerID$ ENDFUNCTION FUNCTION createPlayerName$:name$ RETURN PLAYERNAME$+returnPlayerName$(name$) ENDFUNCTION FUNCTION createPlayerID$:playerID$ RETURN PLAYERID$+returnPlayerID$(playerID$) ENDFUNCTION FUNCTION getPlayerID$:data$ LOCAL len% LOCAL pos% pos%=INSTR(data$,PLAYERID$) IF pos%<>-1 INC pos%,LEN(PLAYERID$) len%=ASC(MID$(data$,pos%,1))-NETLENGTH_OFFSET% INC pos%,1 RETURN MID$(data$,pos%,len%) ENDIF RETURN "" ENDFUNCTION FUNCTION getPlayerName$:data$ LOCAL len% LOCAL pos%

77 pos%=INSTR(data$,PLAYERNAME$) IF pos%<>-1 INC pos%,LEN(PLAYERNAME$) len%=ASC(MID$(data$,pos%,1))-NETLENGTH_OFFSET% INC pos%,1 RETURN MID$(data$,pos%,len%) ENDIF RETURN "" ENDFUNCTION // Create a string containing the number of players, each players name and the network ID string FUNCTION getPlayersList$:numPlaying% LOCAL temp$ LOCAL loop% temp$=createNumPlaying$(numPlaying%) // Get list of all players //DEBUG "Num playing : "+numPlaying FOR loop%=0 TO numPlaying%-1 temp$=temp$+returnPlayerIndex$(loop%) //temp$=temp$+returnPlayerName$(_getPlayerName$(loop%))+","+_getPlayerID$ (loop%)) NEXT RETURN temp$ ENDFUNCTION

FUNCTION sendAllPlayersMessage:hostID$,numPlaying% NETSENDMSG(hostID$,"",getPlayersList$(numPlaying%)) ENDFUNCTION // Process networking messages FUNCTION getNetworkMessage%:playerID$,BYREF job$,BYREF data$ LOCAL msg$ LOCAL id$ msg$=NETGETMSG$(playerID$) IF msg$<>"" job$=MID$(msg$,0,JOB_LENGTH%) data$=MID$(msg$,JOB_LENGTH%,LEN(msg$)) SELECT job$ CASE REQUESTFORPRESENCE$ id$=getPlayerID$(data$) DEBUG "Request from "+id$+"\n" IF id$<>"" sendACKResponse(playerID$,id$) ELSE networkDataError(playerID$,FALSE) ENDIF CASE COMPUTERPRESENT$ id$=getPlayerID$(data$) DEBUG "Response received from "+id$+"\n" IF id$<>"" // Find this player and reset time sendACKResponse(playerID$,id$,FALSE) ELSE networkDataError(playerID$,FALSE) ENDIF DEFAULT RETURN TRUE ENDSELECT ENDIF RETURN FALSE ENDFUNCTION

78 FUNCTION addPlayer:playerID$,isHost%,playerName$ LOCAL n AS tNetList n.isHost%=isHost% n.playerID$=playerID$ n.playerName$=playerName$ IF isHost%=TRUE n.isReady%=TRUE ELSE n.isReady%=FALSE ENDIF n.timeForACK=ACK_BASETIME+RND(1000.0) n.timeForACKResponse=0.0 DIMPUSH playerNetworkList[],n ENDFUNCTION FUNCTION doReady%:isReady%,playerID$ LOCAL n AS tNetList FOREACH n IN playerNetworkList[] IF n.playerID$=playerID$ n.isReady%=isReady% RETURN TRUE ENDIF NEXT RETURN FALSE ENDFUNCTION

FUNCTION deletePlayerID%:playerID$ LOCAL n AS tNetList FOREACH n IN playerNetworkList[] IF n.playerID$=playerID$ DELETE n RETURN TRUE ENDIF NEXT RETURN FALSE ENDFUNCTION FUNCTION sendKickMessage$:index%,hostID$ NETSENDMSG(hostID$,playerNetworkList[index%].playerID$,KICKMESSAGE$) RETURN playerNetworkList[index%].playerID$ ENDFUNCTION // Send a disconnect message from host to all clients or from client to host FUNCTION closeConnection:BYREF playerID$,toID$,BYREF beenAccepted%,BYREF isReady% // Send the disconnect message and close the connection IF playerID$<>"" NETSENDMSG(playerID$,toID$,DISCONNECT$+createPlayerID$(playerID$)) NETDESTROYPLAYER playerID$ DEBUG "Player destroyed" deletePlayerID(playerID$) playerID$="" beenAccepted%=FALSE isReady%=FALSE ENDIF ENDFUNCTION FUNCTION _getPlayerName$:index% RETURN playerNetworkList[index%].playerName$ ENDFUNCTION FUNCTION _getPlayerID$:index% RETURN playerNetworkList[index%].playerID$ ENDFUNCTION FUNCTION _getIsHost%:index% RETURN playerNetworkList[index%].isHost% ENDFUNCTION FUNCTION processACKTimes$:playerID$,speed LOCAL n AS tNetList

79 LOCAL y% LOCAL out$="" out$="" PRINT "This Computer : "+playerID$,0,0 y%=16 FOREACH n IN playerNetworkList[] IF n.playerID$<>playerID$ PRINT "Player : "+n.playerID$+" T/ACK : "+n.timeForACK+" R/Time : "+n.timeForACKResponse,0,y% INC y%,16 IF n.timeForACK>0.0 DEC n.timeForACK,speed IF n.timeForACK<=0.0 DEBUG "Send ACK Request from "+playerID$+" -> "+n.playerID$+"\n" IF NETSENDMSG(playerID$,n.playerID$,REQUESTFORPRESENCE$ +createPlayerID$(playerID$)) ELSE DEBUG "Net Send Error!\n" ENDIF n.timeForACKResponse=ACK_BASETIME+RND(1000.0) ENDIF ELSE IF n.timeForACKResponse>0.0 DEC n.timeForACKResponse,speed IF n.timeForACKResponse<0.0 out$=n.playerID$ ENDIF ENDIF ENDIF ENDIF NEXT RETURN out$ ENDFUNCTION FUNCTION sendACKResponse%:playerID$,toID$,sendMessage%=TRUE LOCAL n AS tNetList FOREACH n IN playerNetworkList[] DEBUG "Looking for "+playerID$+" Found : "+n.playerID$+"\n" IF n.playerID$=toID$ IF sendMessage%=TRUE DEBUG "Send ACK Response from "+playerID$+" -> "+toID$+"\n" IF NETSENDMSG(playerID$,toID$,COMPUTERPRESENT$+createPlayerID$ (playerID$)) ELSE DEBUG "Network send error\n" ENDIF ENDIF n.timeForACK=ACK_BASETIME+RND(1000.0) n.timeForACKResponse=0.0 RETURN TRUE ENDIF NEXT RETURN FALSE ENDFUNCTION

80 Viewport

This allows the user to easily create viewports. It was converted from the DBPro version and whilst it works, has never acually been used. // ------// // Project: Viewport // Start: Friday, March 06, 2009 // IDE Version: 6.184 TYPE tViewPort x% y% width% height% playerIndex% ENDTYPE CONSTANT VIEWPORT_SINGLEPLAYER% = 1 CONSTANT VIEWPORT_TWOPLAYER_HORZSPLIT% = 2 CONSTANT VIEWPORT_TWOPLAYER_VERTSPLIT% = 3 CONSTANT VIEWPORT_THREEPLAYER_HORZSPLIT% = 4 CONSTANT VIEWPORT_THREEPLAYER_VERTSPLIT% = 5 CONSTANT VIEWPORT_THREEPLAYER_TRISPLIT% = 6 CONSTANT VIEWPORT_FOURPLAYER% = 7 CONSTANT PLAYER_1% = 0 CONSTANT PLAYER_2% = 1 CONSTANT PLAYER_3% = 2 CONSTANT PLAYER_4% = 3 FUNCTION setupViewPorts:splitFlag% LOCAL screenWidth% LOCAL screenHeight% LOCAL halfX% LOCAL halfY% LOCAL loop% DIM playersViewPort[0] GETSCREENSIZE screenWidth%,screenHeight% SELECT splitFlag% CASE VIEWPORT_SINGLEPLAYER% // Single player, so use the full screen addViewPort(0,0,screenWidth%,screenHeight%,PLAYER_1%) CASE VIEWPORT_TWOPLAYER_HORZSPLIT% // Two player, horizontal split halfX%=screenWidth%/2 addViewPort(0,0,halfX%- 1,screenHeight%,PLAYER_1%) addViewPort(halfX% +1,0,screenWidth%-(halfX%+1),screenHeight%,PLAYER_2%) CASE VIEWPORT_TWOPLAYER_VERTSPLIT% // Two player, horizontal split halfY%=screenHeight%/2 addViewPort(0,0,screenWidth%,halfY%-1,PLAYER_1%) addViewPort(0,halfY% +1,screenWidth%,screenHeight%-(halfY%+1),PLAYER_2%) CASE VIEWPORT_THREEPLAYER_HORZSPLIT% // Three player, horizontal split halfX%=screenWidth%/3 FOR loop%=0 TO 2 addViewPort(loop %*(halfX%+1),0,halfX%-1,screenHeight%,PLAYER_1%+loop%) NEXT

81 CASE VIEWPORT_THREEPLAYER_VERTSPLIT% // Three player, vertical split halfY%=screenHeight%/3 FOR loop%=0 TO 2 addViewPort(0,loop%*(halfY%+1),screenWidth%,halfY%-1,PLAYER_1%+loop%) NEXT CASE VIEWPORT_THREEPLAYER_TRISPLIT% // Three player, triangle split halfX%=screenWidth%/2 halfY%=screenHeight%/2

addViewPort((screenWidth%-halfX%)/2,0,halfX%-1,halfY%-1,PLAYER_1%) addViewPort(0,halfY% +1,halfX%-1,screenHeight%-(halfY%+1),PLAYER_2%) addViewPort(halfX% +1,halfY%+1,screenWidth%-(halfX%+1),screenHeight%-(halfY%+1),PLAYER_3%) CASE VIEWPORT_FOURPLAYER% // Four player, horizontal and vertical split halfX%=screenWidth%/2 halfY%=screenHeight%/2 addViewPort(0,0,halfX%- 1,halfY%-1,PLAYER_1%) addViewPort(halfX% +1,0,screenWidth%-(halfX%+1),halfY%-1,PLAYER_2%) addViewPort(0,halfY% +1,halfX%-1,screenHeight%-(halfY%+1),PLAYER_3%) addViewPort(halfX% +1,halfY%+1,screenWidth%-(halfX%+1),screenHeight%-(halfY%+1),PLAYER_4%)

ENDSELECT ENDFUNCTION FUNCTION addViewPort:x%,y%,width%,height%,playerIndex% LOCAL viewP AS tViewPort viewP.x%=x% viewP.y%=y% viewP.width%=width% viewP.height%=height% viewP.playerIndex%=playerIndex% DIMPUSH playersViewPort[],viewP ENDFUNCTION FUNCTION displayAllPorts: LOCAL loop AS tViewPort FOREACH loop IN playersViewPort[] VIEWPORT loop.x%,loop.y%,loop.width%,loop.height% DRAWRECT 0,0,1024,768,RGB(255,255,0) NEXT ENDFUNCTION

82 SHA 512 Encryption

This routine creates a SHA 512 value for a set of data. This was written before GLBasic was updated with an ENCRYPT/DECRYPT set of commands.

The C part was written by Olivier Gay, with the GLBasic interface written by myself. // ------// // Project: SHA-2 // Start: Tuesday, November 17, 2009 // IDE Version: 7.177 INLINE /* FIPS 180-2 SHA-224/256/384/512 implementation * Last update: 02/02/2007 * Issue date: 04/30/2005 * * Copyright (C) 2005, 2007 Olivier Gay * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the project nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #define SHA512_DIGEST_SIZE ( 512 / 8) #define SHA512_BLOCK_SIZE (1024 / 8) #define SHFR(x, n) (x >> n) #define ROTR(x, n) ((x >> n) | (x << ((sizeof(x) << 3) - n))) #define ROTL(x, n) ((x << n) | (x >> ((sizeof(x) << 3) - n))) #define CH(x, y, z) ((x & y) ^ (~x & z)) #define MAJ(x, y, z) ((x & y) ^ (x & z) ^ (y & z)) #define SHA512_F1(x) (ROTR(x, 28) ^ ROTR(x, 34) ^ ROTR(x, 39)) #define SHA512_F2(x) (ROTR(x, 14) ^ ROTR(x, 18) ^ ROTR(x, 41)) #define SHA512_F3(x) (ROTR(x, 1) ^ ROTR(x, 8) ^ SHFR(x, 7)) #define SHA512_F4(x) (ROTR(x, 19) ^ ROTR(x, 61) ^ SHFR(x, 6)) #define UNPACK32(x, str) _ { _ *((str) + 3) = (uint8) ((x) ); _ *((str) + 2) = (uint8) ((x) >> 8); _ *((str) + 1) = (uint8) ((x) >> 16); _ *((str) + 0) = (uint8) ((x) >> 24); _ } #define UNPACK64(x, str) _ { _ *((str) + 7) = (uint8) ((x) ); _ *((str) + 6) = (uint8) ((x) >> 8); _ *((str) + 5) = (uint8) ((x) >> 16); _ *((str) + 4) = (uint8) ((x) >> 24); _ *((str) + 3) = (uint8) ((x) >> 32); _ *((str) + 2) = (uint8) ((x) >> 40); _

83 *((str) + 1) = (uint8) ((x) >> 48); _ *((str) + 0) = (uint8) ((x) >> 56); _ } #define PACK64(str, x) _ { _ *(x) = ((uint64) *((str) + 7) ) _ | ((uint64) *((str) + 6) << 8) _ | ((uint64) *((str) + 5) << 16) _ | ((uint64) *((str) + 4) << 24) _ | ((uint64) *((str) + 3) << 32) _ | ((uint64) *((str) + 2) << 40) _ | ((uint64) *((str) + 1) << 48) _ | ((uint64) *((str) + 0) << 56); _ } #define SHA512_SCR(i) _ { _ w[i] = SHA512_F4(w[i - 2]) + w[i - 7] _ + SHA512_F3(w[i - 15]) + w[i - 16]; _ } #define SHA512_EXP(a, b, c, d, e, f, g ,h, j) _ { _ t1 = wv[h] + SHA512_F2(wv[e]) + CH(wv[e], wv[f], wv[g]) _ + sha512_k[j] + w[j]; _ t2 = SHA512_F1(wv[a]) + MAJ(wv[a], wv[b], wv[c]); _ wv[d] += t1; _ wv[h] = t1 + t2; _ } typedef unsigned char uint8; typedef unsigned int uint32; typedef unsigned long long uint64; typedef struct { unsigned int tot_len; unsigned int len; unsigned char block[2 * SHA512_BLOCK_SIZE]; uint64 h[8]; } sha512_ctx; typedef sha512_ctx sha384_ctx; void sha512_init(sha512_ctx *ctx); void sha512_update(sha512_ctx *ctx, const unsigned char *message, unsigned int len); void sha512_final(sha512_ctx *ctx, unsigned char *digest); void sha512(const unsigned char *message, unsigned int len, unsigned char *digest); typedef unsigned int size_t; extern "C" void *memcpy(void *destination, const void *source, size_t num ); extern "C" void *memset(void *ptr, int value, size_t num); extern "C" int sprintf ( char * str, const char * format, ... ); extern "C" int open(char * filename, int flags, int mode); extern "C" int close(int fd); extern "C" void * memset ( void * ptr, int value, size_t num ); extern "C" int read(int fd, void * ptr, int numbytes); #define _O_RDONLY 0 #define _O_WRONLY 1 #define _O_RDWR 2 #define _O_ACCMODE (O_RDONLY|O_WRONLY|O_RDWR) #define _O_APPEND 0x0008 #define _O_CREAT 0x0100 #define _O_TRUNC 0x0200 #define _O_EXCL 0x0400 #define _O_TEXT 0x4000 #define _O_BINARY 0x8000 #define _O_RAW _O_BINARY #define _O_TEMPORARY 0x0040

84 #define _O_RANDOM 0x0010 #define _O_SEQUENTIAL _O_RANDOM #define _O_SHORT_LIVED 0x1000 uint64 sha512_h0[8] = {0x6a09e667f3bcc908ULL, 0xbb67ae8584caa73bULL, 0x3c6ef372fe94f82bULL, 0xa54ff53a5f1d36f1ULL, 0x510e527fade682d1ULL, 0x9b05688c2b3e6c1fULL, 0x1f83d9abfb41bd6bULL, 0x5be0cd19137e2179ULL};

uint64 sha512_k[80] = {0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL, 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, 0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL, 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL, 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL, 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL, 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL, 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL, 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, 0xd192e819d6ef5218ULL, 0xd69906245565a910ULL, 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL, 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL, 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL, 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL, 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, 0x113f9804bef90daeULL, 0x1b710b35131c471bULL, 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL, 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL}; sha512_ctx userData; unsigned char digest[SHA512_DIGEST_SIZE]; void sha512_transf(sha512_ctx *ctx, const unsigned char *message,unsigned int block_nb) { uint64 w[80]; uint64 wv[8]; uint64 t1, t2; const unsigned char *sub_block; int i, j; for (i = 0; i < (int) block_nb; i++) { sub_block = message + (i << 7); #ifndef UNROLL_LOOPS for (j = 0; j < 16; j++) { PACK64(&sub_block[j << 3], &w[j]); } for (j = 16; j < 80; j++) { SHA512_SCR(j); }

85 for (j = 0; j < 8; j++) { wv[j] = ctx->h[j]; } for (j = 0; j < 80; j++) { t1 = wv[7] + SHA512_F2(wv[4]) + CH(wv[4], wv[5], wv[6]) + sha512_k[j] + w[j]; t2 = SHA512_F1(wv[0]) + MAJ(wv[0], wv[1], wv[2]); wv[7] = wv[6]; wv[6] = wv[5]; wv[5] = wv[4]; wv[4] = wv[3] + t1; wv[3] = wv[2]; wv[2] = wv[1]; wv[1] = wv[0]; wv[0] = t1 + t2; } for (j = 0; j < 8; j++) { ctx->h[j] += wv[j]; } #else PACK64(&sub_block[ 0], &w[ 0]); PACK64(&sub_block[ 8], &w[ 1]); PACK64(&sub_block[ 16], &w[ 2]); PACK64(&sub_block[ 24], &w[ 3]); PACK64(&sub_block[ 32], &w[ 4]); PACK64(&sub_block[ 40], &w[ 5]); PACK64(&sub_block[ 48], &w[ 6]); PACK64(&sub_block[ 56], &w[ 7]); PACK64(&sub_block[ 64], &w[ 8]); PACK64(&sub_block[ 72], &w[ 9]); PACK64(&sub_block[ 80], &w[10]); PACK64(&sub_block[ 88], &w[11]); PACK64(&sub_block[ 96], &w[12]); PACK64(&sub_block[104], &w[13]); PACK64(&sub_block[112], &w[14]); PACK64(&sub_block[120], &w[15]);

SHA512_SCR(16); SHA512_SCR(17); SHA512_SCR(18); SHA512_SCR(19); SHA512_SCR(20); SHA512_SCR(21); SHA512_SCR(22); SHA512_SCR(23); SHA512_SCR(24); SHA512_SCR(25); SHA512_SCR(26); SHA512_SCR(27); SHA512_SCR(28); SHA512_SCR(29); SHA512_SCR(30); SHA512_SCR(31); SHA512_SCR(32); SHA512_SCR(33); SHA512_SCR(34); SHA512_SCR(35); SHA512_SCR(36); SHA512_SCR(37); SHA512_SCR(38); SHA512_SCR(39); SHA512_SCR(40); SHA512_SCR(41); SHA512_SCR(42); SHA512_SCR(43); SHA512_SCR(44); SHA512_SCR(45); SHA512_SCR(46); SHA512_SCR(47); SHA512_SCR(48); SHA512_SCR(49); SHA512_SCR(50); SHA512_SCR(51); SHA512_SCR(52); SHA512_SCR(53); SHA512_SCR(54); SHA512_SCR(55); SHA512_SCR(56); SHA512_SCR(57); SHA512_SCR(58); SHA512_SCR(59); SHA512_SCR(60); SHA512_SCR(61); SHA512_SCR(62); SHA512_SCR(63); SHA512_SCR(64); SHA512_SCR(65); SHA512_SCR(66); SHA512_SCR(67); SHA512_SCR(68); SHA512_SCR(69); SHA512_SCR(70); SHA512_SCR(71); SHA512_SCR(72); SHA512_SCR(73); SHA512_SCR(74); SHA512_SCR(75); SHA512_SCR(76); SHA512_SCR(77); SHA512_SCR(78); SHA512_SCR(79); wv[0] = ctx->h[0]; wv[1] = ctx->h[1]; wv[2] = ctx->h[2]; wv[3] = ctx->h[3]; wv[4] = ctx->h[4]; wv[5] = ctx->h[5]; wv[6] = ctx->h[6]; wv[7] = ctx->h[7]; j = 0; do { SHA512_EXP(0,1,2,3,4,5,6,7,j); j++; SHA512_EXP(7,0,1,2,3,4,5,6,j); j++; SHA512_EXP(6,7,0,1,2,3,4,5,j); j++; SHA512_EXP(5,6,7,0,1,2,3,4,j); j++; SHA512_EXP(4,5,6,7,0,1,2,3,j); j++; SHA512_EXP(3,4,5,6,7,0,1,2,j); j++; SHA512_EXP(2,3,4,5,6,7,0,1,j); j++; SHA512_EXP(1,2,3,4,5,6,7,0,j); j++; } while (j < 80); ctx->h[0] += wv[0]; ctx->h[1] += wv[1]; ctx->h[2] += wv[2]; ctx->h[3] += wv[3]; ctx->h[4] += wv[4]; ctx->h[5] += wv[5]; ctx->h[6] += wv[6]; ctx->h[7] += wv[7]; #endif /* !UNROLL_LOOPS */ } } void sha512_init(sha512_ctx *ctx) { #ifndef UNROLL_LOOPS

86 int i; for (i = 0; i < 8; i++) { ctx->h[i] = sha512_h0[i]; } #else ctx->h[0] = sha512_h0[0]; ctx->h[1] = sha512_h0[1]; ctx->h[2] = sha512_h0[2]; ctx->h[3] = sha512_h0[3]; ctx->h[4] = sha512_h0[4]; ctx->h[5] = sha512_h0[5]; ctx->h[6] = sha512_h0[6]; ctx->h[7] = sha512_h0[7]; #endif /* !UNROLL_LOOPS */ ctx->len = 0; ctx->tot_len = 0; } void sha512_update(sha512_ctx *ctx, const unsigned char *message, unsigned int len) { unsigned int block_nb; unsigned int new_len, rem_len, tmp_len; const unsigned char *shifted_message; tmp_len = SHA512_BLOCK_SIZE - ctx->len; rem_len = len < tmp_len ? len : tmp_len; memcpy(&ctx->block[ctx->len], message, rem_len); if (ctx->len + len < SHA512_BLOCK_SIZE) { ctx->len += len; return; } new_len = len - rem_len; block_nb = new_len / SHA512_BLOCK_SIZE; shifted_message = message + rem_len; sha512_transf(ctx, ctx->block, 1); sha512_transf(ctx, shifted_message, block_nb); rem_len = new_len % SHA512_BLOCK_SIZE; memcpy(ctx->block, &shifted_message[block_nb << 7], rem_len); ctx->len = rem_len; ctx->tot_len += (block_nb + 1) << 7; } void sha512_final(sha512_ctx *ctx, unsigned char *digest) { unsigned int block_nb; unsigned int pm_len; unsigned int len_b; #ifndef UNROLL_LOOPS int i; #endif block_nb = 1 + ((SHA512_BLOCK_SIZE - 17) < (ctx->len % SHA512_BLOCK_SIZE)); len_b = (ctx->tot_len + ctx->len) << 3; pm_len = block_nb << 7; memset(ctx->block + ctx->len, 0, pm_len - ctx->len); ctx->block[ctx->len] = 0x80; UNPACK32(len_b, ctx->block + pm_len - 4); sha512_transf(ctx, ctx->block, block_nb); #ifndef UNROLL_LOOPS for (i = 0 ; i < 8; i++) { UNPACK64(ctx->h[i], &digest[i << 3]); } #else UNPACK64(ctx->h[0], &digest[ 0]);

87 UNPACK64(ctx->h[1], &digest[ 8]); UNPACK64(ctx->h[2], &digest[16]); UNPACK64(ctx->h[3], &digest[24]); UNPACK64(ctx->h[4], &digest[32]); UNPACK64(ctx->h[5], &digest[40]); UNPACK64(ctx->h[6], &digest[48]); UNPACK64(ctx->h[7], &digest[56]); #endif /* !UNROLL_LOOPS */ } ENDINLINE FUNCTION SHA2_Initialise%: INLINE sha512_init(&userData); memset(&digest,(char) 0,sizeof(digest)); ENDINLINE ENDFUNCTION FUNCTION SHA2_Process%:data$,length% INLINE sha512_update(&userData,(const unsigned char *) data_Str.c_str(),length); ENDINLINE ENDFUNCTION FUNCTION SHA2_Finish$: LOCAL result$ INLINE sha512_final(&userData,(unsigned char *) &digest); unsigned char output[2 * SHA512_DIGEST_SIZE + 1]; int i; output[2 * SHA512_DIGEST_SIZE] = '\0'; for (i = 0; i < (int) SHA512_DIGEST_SIZE ; i++) { sprintf((char *) output + 2 * i, "%02x", digest[i]); } result_Str.assign((char *) &output); ENDINLINE RETURN result$ ENDFUNCTION FUNCTION SHA2_FromFile$:fileName$,BYREF error% LOCAL result$ result$="" INLINE int handle,length; unsigned char output[2 * SHA512_DIGEST_SIZE + 1]; int i; char buffer[4096]; error=0; handle=open((char *) fileName_Str.c_str(),_O_RDONLY | _O_BINARY,0); if (handle<0) { error=handle; } else { sha512_init(&userData); memset(&digest,(char) 0,sizeof(digest)); memset(&buffer,(char) 0,sizeof(buffer)); length=read(handle,(char *) &buffer,sizeof(buffer)); do { if (length<0) { error=length; break; } else if (length>0) { sha512_update(&userData,(const unsigned char *)

88 &buffer,length); length=read(handle,(char *) &buffer,sizeof(buffer)); } } while (length>0); } if (handle>0) { close(handle); } ENDINLINE // Now to finalise the result, if everything was okay IF error%=0 RETURN SHA2_Finish$() ELSE RETURN "" ENDIF ENDFUNCTION

89 AppTimer

This fixes movement to a computers speed. It is based on the original BlitzMax code by JoshK and works very well. Nothing really needed changing. //! This help file contains information for using the AppTiming system, which can be used to give a consistent movement speed //! independant of the processor speed //! The original code was for BlitzMax, and was written by JoshK //! Converted to GLBasic by Nicholas Kingsley //! You call the routines like : //! LOCAL blah AS TAppTime //! blah.Initialise_AppTime() //! speed=blah.Update_AppTime() TYPE TAppTime AppTime_UPS;AppTime_Iterator;AppTime_CurrentTime;AppTime_PauseStart;AppTime_Speed;AppTim e_DesiredLoopTime;AppTime_LastUpdateTime AppTime_LastUPSTime;AppTime_DesiredFrequency% //! This function initialises the AppTimer system, and must be called first. //\param frameRate% - This is the frame rate that you want all movement to be based on. It defaults to 60 FPS, which is the lowest rate that should be used. //\return Nothing is returned FUNCTION Initialise%:frameRate%=60 LOCAL sW%,sH% self.AppTime_UPS=0.0 self.AppTime_Iterator=0.0 self.AppTime_CurrentTime=0.0 self.AppTime_PauseStart=0.0 self.AppTime_Speed=1.0 self.AppTime_LastUpdateTime=0.0 self.AppTime_LastUPSTime=0.0 self.AppTime_DesiredLoopTime=1000.0/(frameRate*1.0) RETURN TRUE ENDFUNCTION //! This function updates the timing system. It needs to be called once per loop. //\param No parameters are passed //\return A movement value is returned. This should be muliplied by the amount you want to move. If the routine is paused, then 0.0 is returned FUNCTION Update: LOCAL time LOCAL elapsed IF self.AppTime_PauseStart<>0 RETURN 0.0 ENDIF time=GETTIMERALL() IF self.AppTime_LastUpdateTime=0.0 self.AppTime_Speed=1.0 self.AppTime_LastUPSTime=time ELSE elapsed=time-self.AppTime_LastUpdateTime IF elapsed=0.0 elapsed=1.0 SLEEP 1 INC time,1.0 ENDIF self.AppTime_Speed=elapsed/self.AppTime_DesiredLoopTime ENDIF self.AppTime_LastUpdateTime=time self.AppTime_CurrentTime=time INC self.AppTime_Iterator,1.0 // Its a float as it can go very large... IF self.AppTime_CurrentTime-self.AppTime_LastUPSTime>=1000.0 self.AppTime_UPS=self.AppTime_Iterator/((self.AppTime_CurrentTime-

90 self.AppTime_LastUPSTime)/1000) self.AppTime_LastUPSTime=self.AppTime_CurrentTime self.AppTime_Iterator=0 ENDIF RETURN self.AppTime_Speed ENDFUNCTION //! This function is used when your program is paused or not. If it is, then the step speed is still updated //\param pause% - If this is TRUE, then your program is paused. If FALSE, then it isn't //\return Nothing is returned FUNCTION Pause%:DoPause% LOCAL elapsed IF DoPause%=TRUE IF self.AppTime_PauseStart=0.0 self.AppTime_PauseStart=GETTIMERALL() self.AppTime_UPS=0 self.AppTime_Speed=0 ENDIF ELSE IF self.AppTime_PauseStart<>0.0 IF self.AppTime_LastUpdateTime<>0.0 elapsed=GETTIMERALL()-self.AppTime_PauseStart INC self.AppTime_LastUpdateTime,elapsed ENDIF self.AppTime_PauseStart=0.0 self.Update() ENDIF ENDIF ENDFUNCTION //! This function returns the updates per second, and is an approximation //\param No parameters are passed //\return Updates per second FUNCTION FPS: RETURN self.AppTime_UPS ENDFUNCTION ENDTYPE

91 Client

The client side of the network lobby routine. This is used when someone joins a lobby. //! Contains information about each server TYPE tServer ipAddress% name$ platform$ isPasswordProtected% numPlaying% gameType% state% allowHoles% // numRetry% // isAlive% // ping ENDTYPE //! Is client connected to a host ? TYPE tIsConnected isConnected% hostIP% selectedIndex% ENDTYPE TYPE TClient screenWidth%;screenHeight% serverList$="serversList" playersList$="playersList" passwordEditBox$="password" passwordBack$="pBack" passwordOk$="pOk" backButton$="back" backText$ joinButton$="join" joinText$ disconnectText$ chatListText$ chatTextBox$="chatTextBox" chatText$="chatText" chatMessages$ sendButton$="sendButton" sendText$ title$ listOfServers$ listOfPlayers$ isReady% ready$ notReady$ okay$ thisIP%;networkIP% socket% udpPort% name$ playerID% enterPasswordForServer$ errorWindowButton$="errorWindowOk" _errorWindowOpen% updateLobby% nextServerChallenge isConnected AS tIsConnected listOfServers[] AS tServer playerList[] AS tPlayer

92 errorList$[] // Display reason why client cant connect ERROR_SERVERISFULL% = 0 ERROR_INVALIDPASSWORD% = 1 ERROR_HOSTLEFT% = 2 ERROR_GAMEINPROGRESS% = 3 ERROR_KICKED% = 4 FUNCTION LoadLanguageText%:localisation AS TLocalisation self.title$=localisation.LocaliseText$("{{selectservertojoin}}") self.backText$=UCASE$(localisation.LocaliseText$("{{back}}")) self.joinText$=localisation.LocaliseText$("{{join}}") self.chatListText$=localisation.LocaliseText$("{{chatlist}}") self.sendText$=localisation.LocaliseText$("{{send}}") self.listOfServers$=localisation.LocaliseText$("{{listofservers}}") self.listOfPlayers$=localisation.LocaliseText$("{{listofplayers}}") self.ready$=localisation.LocaliseText$("{{ready}}") self.notReady$=localisation.LocaliseText$("{{notready}}") self.okay$=localisation.LocaliseText$("{{okay}}") self.disconnectText$=localisation.LocaliseText$("{{disconnect}}") self.enterPasswordForServer$=localisation.LocaliseText$ ("{{enterpasswordforserver}}") DIM self.errorList$[0] DIMPUSH self.errorList$[],localisation.LocaliseText$("{{serverisfull}}") DIMPUSH self.errorList$[],localisation.LocaliseText$("{{invalidpassword}}") DIMPUSH self.errorList$[],localisation.LocaliseText$("{{hostleft}}") DIMPUSH self.errorList$[],localisation.LocaliseText$("{{gameinprogress}}") DIMPUSH self.errorList$[],localisation.LocaliseText$("{{beenkicked}}") RETURN TRUE ENDFUNCTION FUNCTION Initialise%:setup AS TSetup self.Destroy() self.screenWidth%=setup.ReturnScreenWidth() self.screenHeight%=setup.ReturnScreenHeight() self.thisIP%=0 self.socket%=INVALID% RETURN TRUE ENDFUNCTION //! Called to destroy things when changing screen resolutions FUNCTION Destroy%: DIM self.playerList[0] ENDFUNCTION //! Called when closing program FUNCTION Finish%: self.Destroy() DIM self.errorList$[0] ENDFUNCTION FUNCTION SetupNetwork%:thisIP%,socket%,udpPort%,name$ DIM self.listOfServers[0] self.name$=name$ self.thisIP%=thisIP% self.socket%=socket% self.udpPort%=udpPort% self.networkIP%=SOCK_GETIP("255.255.255.255") self.isReady%=FALSE self.isConnected.isConnected%=FALSE self.isConnected.hostIP%=0 self._errorWindowOpen%=FALSE self.updateLobby%=UPDATE_NONE% self.chatMessages$="" self.nextServerChallenge=0.0 ENDFUNCTION FUNCTION CreateLobbyList%:setup AS TSetup LOCAL windowWidth%,windowHeight%,buttonSize%,listHeight%=20

93 LOCAL halfWidth%,yPos%,ySize% LOCAL sendButtonWidth%=84 setup.createAWindow(self.title$,windowWidth%,windowHeight%) buttonSize%=(windowWidth%/2)-8 halfWidth%=(windowWidth%/2)-7 DDgui_widget("",self.listOfServers$,halfWidth%,0) DDgui_widget("",self.listOfPlayers$,halfWidth%,0) DDgui_list(self.serverList$,"",halfWidth%,listHeight%) DDgui_list(self.playersList$,"",halfWidth%,listHeight%) DDgui_widget("",self.chatListText$,windowWidth%-8,0) DDgui_text(self.chatTextBox$,"",windowWidth%-8,listHeight%) DDgui_text(self.chatText$,"",windowWidth%-sendButtonWidth%-8,0) DDgui_set(self.chatTextBox$,"READONLY",TRUE) DDgui_button(self.sendButton$,self.sendText$,sendButtonWidth%,0) DDgui_button(self.backButton$,self.backText$,buttonSize%,0) DDgui_button(self.joinButton$,self.joinText$,buttonSize%,0) DDgui_show(FALSE) yPos%=DDgui_get(self.backButton$,"YPOS") +DDgui_get(self.backButton$,"HEIGHT") ySize%=((windowHeight%-yPos%)/2)+listHeight% DDgui_set(self.serverList$,"HEIGHT",ySize%) DDgui_set(self.playersList$,"HEIGHT",ySize%) DDgui_set(self.chatTextBox$,"HEIGHT",ySize%) DDgui_show(FALSE) ENDFUNCTION FUNCTION connectedToHostStatus%:connected%,ipAddress%=0 IF connected%=FALSE DIM self.playerList[0] DDgui_set(self.backButton$,"TEXT",self.backText$) DDgui_set(self.joinButton$,"TEXT",self.joinText$) DDgui_set(self.playersList$,"TEXT","") // Clear players list DDgui_set(self.serverList$,SLECTED$,-1) // Dont select a server // Deactivate the player list, chat boxes and join DDgui_set(self.serverList$,"READONLY",FALSE) DDgui_set(self.chatText$,"READONLY",TRUE) DDgui_set(self.sendButton$,"READONLY",TRUE) DDgui_set(self.playersList$,"READONLY",TRUE) self.nextServerChallenge=0.0 self.isReady%=FALSE self.isConnected.selectedIndex%=INVALID% ELSE DDgui_set(self.backButton$,"TEXT",self.disconnectText$) DDgui_set(self.joinButton$,"TEXT",self.ready$) DDgui_set(self.serverList$,"READONLY",TRUE) DDgui_set(self.chatText$,"READONLY",FALSE) DDgui_set(self.sendButton$,"READONLY",FALSE) DDgui_set(self.playersList$,"READONLY",FALSE) self.nextServerChallenge=GETTIMERALL()+5000.0 ENDIF self.isConnected.isConnected%=connected% self.isConnected.hostIP%=ipAddress% ENDFUNCTION FUNCTION CreateErrorWindow%:setup AS TSetup,errorType%,isPasswordEntry%=FALSE LOCAL windowWidth%,windowHeight%,title$ IF self._errorWindowOpen%=TRUE THEN RETURN TRUE self._errorWindowOpen%=TRUE IF isPasswordEntry%=TRUE title$=self.enterPasswordForServer$ ELSE title$="" ENDIF

94 setup.createAWindow(title$,windowWidth%,windowHeight%,80) IF isPasswordEntry%=TRUE LOCAL buttonSize% buttonSize%=(windowWidth%/2)-8 DDgui_text(self.passwordEditBox$,"",windowWidth%-8,0) DDgui_button(self.passwordBack$,self.backText$,buttonSize%,0) DDgui_button(self.passwordOk$,self.okay$,buttonSize%,0) ELSE DDgui_widget("",self.errorList$[errorType%-self.ERROR_SERVERISFULL %],windowWidth%-8,0) DDgui_button(self.errorWindowButton$,self.okay$,windowWidth%-8,0) ENDIF ENDFUNCTION FUNCTION ProcessGUI%:setup AS TSetup,networkMessage AS TProcessNetworkMessage IF DDgui_get(self.sendButton$,CLICKED$) networkMessage.ProcessChatMessage(self.playerList[],self.chatTextBox$,DDgui_get(self.pla yersList$,SLECTED$),self.chatMessages$,self.updateLobby%) ELSEIF DDgui_get(self.backButton$,CLICKED$) IF self.isConnected.isConnected%=FALSE // Just exit DDgui_popdialog() RETURN RESULT_LEAVELOBBY% ELSE // Connected networkMessage.sendClientDisconnect(self.isConnected.hostIP%) // Disconnect internally from the server self.connectedToHostStatus(FALSE) ENDIF ELSEIF DDgui_get(self.joinButton$,CLICKED$) IF self.isConnected.isConnected%=FALSE // Not connected self.isConnected.selectedIndex %=DDgui_get(self.serverList$,SLECTED$) DEBUG self.isConnected.selectedIndex%+"\n" IF self.isConnected.selectedIndex%>=0 IF self.listOfServers[self.isConnected.selectedIndex %].isPasswordProtected%=TRUE self.CreateErrorWindow(setup,0,TRUE) ELSE networkMessage.sendClientJoinServer(self.listOfServers[self.isConnected.selectedIndex %].ipAddress%,"") ENDIF ENDIF ELSE LOCAL loop AS tPlayer self.isReady%=NOT self.isReady% FOREACH loop IN self.playerList[] IF loop.ipAddress%=self.thisIP% loop.isReady%=self.isReady% IF self.isReady%=TRUE DDgui_set(self.joinButton$,"TEXT",self.notReady$) ELSE DDgui_set(self.joinButton$,"TEXT",self.ready$) ENDIF ELSE networkMessage.sendClientReadyStatus(loop.ipAddress%,self.isReady%) ENDIF NEXT ENDIF ELSEIF DDgui_get(self.errorWindowButton$,CLICKED$) OR DDgui_get(self.passwordBack$,CLICKED$) // Close an error window DDgui_popdialog()

95 self._errorWindowOpen%=FALSE // Make sure the server list is re-displayed in case of any updates self.updateLobby%=bOR(self.updateLobby%,UPDATE_SERVER%) ELSEIF DDgui_get(self.passwordOk$,CLICKED$) networkMessage.sendClientJoinServer(self.listOfServers[self.isConnected.selectedIndex %].ipAddress%,ENCRYPT$(PLATFORMINFO$("HOSTID"),DDgui_get$ (self.passwordEditBox$,"TEXT"))) DDgui_popdialog() self._errorWindowOpen%=FALSE ENDIF IF self._errorWindowOpen%=FALSE IF bAND(self.updateLobby%,UPDATE_SERVER%) self.buildServerList() self.updateLobby%=bXOR(self.updateLobby%,UPDATE_SERVER%) ENDIF IF bAND(self.updateLobby%,UPDATE_CLIENT%) self.buildPlayersList() self.updateLobby%=bXOR(self.updateLobby%,UPDATE_CLIENT%) ENDIF IF bAND(self.updateLobby%,UPDATE_CHAT%) DDgui_set(self.chatTextBox$,"TEXT",self.chatMessages$) self.updateLobby%=bXOR(self.updateLobby%,UPDATE_CHAT%) ENDIF ENDIF

RETURN RESULT_OK% ENDFUNCTION FUNCTION HandleMessage%:setup AS TSetup,messageData$[],networkMessage AS TProcessNetworkMessage LOCAL serverList AS tServer LOCAL playerList AS tPlayer LOCAL fromIP%,toIP%,sendID%,msg% msg%=INTEGER(messageData$[0]) sendID%=INTEGER(messageData$[NETMESSAGE_SENDID%]) fromIP%=INTEGER(messageData$[NETMESSAGE_FROMIP%]) toIP%=INTEGER(messageData$[NETMESSAGE_TOIP%]) SELECT msg% CASE MESSAGE_ACKNOWLEDGEMENT% // Handle acknowledgments from messages previously sent LOCAL message AS tNetData

networkMessage.RemoveAck(INTEGER(messageData$[NETMESSAGE_DATA%]),message) SELECT message.netCode% CASE MESSAGE_PING% // Whoever sent it is still alive! networkMessage.updatePing(fromIP %,messageData$[NETMESSAGE_TIME%],self.playerList[]) self.updateLobby %=bOR(self.updateLobby%,UPDATE_CLIENT%) ENDSELECT

CASE MESSAGE_CHALLENGERESPONSE%// A server has returned information about itself. We only update if no error window is being displayed self.updateServerList(messageData$[]) self.updateLobby%=bOR(self.updateLobby%,UPDATE_SERVER%) CASE MESSAGE_CLIENTCANJOIN% // The client has joined

96 self.updatePlayersList(messageData$[]) self.updateLobby%=bOR(self.updateLobby%,UPDATE_CLIENT%) // Send the acknowledgment for this message ID networkMessage.sendAcknowledgement(fromIP%,sendID%,msg%) self.connectedToHostStatus(TRUE,fromIP%)

CASE MESSAGE_UPDATECLIENTLIST% // Update client list self.updatePlayersList(messageData$[]) self.updateLobby%=bOR(self.updateLobby%,UPDATE_CLIENT%) CASE MESSAGE_SERVERISFULL% // Client is full self.CreateErrorWindow(setup,self.ERROR_SERVERISFULL%) CASE MESSAGE_INVALIDPASSWORD% // Invalid password self.CreateErrorWindow(setup,self.ERROR_INVALIDPASSWORD%) CASE MESSAGE_DISPLAYCHATMESSAGE% // Add a chat message to the chat list networkMessage.UpdateChatMessage(self.chatMessages$,self.updateLobby%,messageData$ [NETMESSAGE_DATA%],messageData$[NETMESSAGE_DATA%+1]) CASE MESSAGE_PING% // Someone send a PING command, so we acknowledge networkMessage.sendAcknowledgement(fromIP%,sendID%,msg%) CASE MESSAGE_KICKPLAYER% // Player has been kicked - Disconnect from server self.connectedToHostStatus(FALSE) // Show the message self.CreateErrorWindow(setup,self.ERROR_KICKED%)

CASE MESSAGE_READYSTATUS2% // A client has changed their status //LOCAL value% //,orgSender% //orgSende r%=INTEGER(messageData$[NETMESSAGE_DATA%]) //value %=INTEGER(messageData$[NETMESSAGE_DATA%]) networkMessage.updateStatus(self.playerList[],fromIP%,INTEGER(messageData$ [NETMESSAGE_DATA%])) self.updateLobby%=bOR(self.updateLobby%,UPDATE_CLIENT%) // Send the acknowledgment networkMessage.sendAcknowledgement(fromIP%,sendID%,msg%) CASE MESSAGE_STARTGAME% // We can start the game networkMessage.sendAcknowledgement(fromIP%,sendID%,msg%) RETURN RESULT_STARTGAME%

ENDSELECT

97 RETURN RESULT_OK% ENDFUNCTION //! Check to see if its time to send a message FUNCTION ProcessTimers%:networkMessage AS TProcessNetworkMessage LOCAL loop AS tPlayer // Is it time to send a request for servers to identify themselves ? IF ABS(GETTIMERALL()-self.nextServerChallenge)>=5000.0 networkMessage.sendServerChallenge(self.networkIP%) self.nextServerChallenge=GETTIMERALL() ENDIF IF self.isConnected.isConnected%=TRUE // Ping all clients according to their time FOREACH loop IN self.playerList[] IF loop.ipAddress%<>self.thisIP% IF ABS(GETTIMERALL()-loop.currentTime)>=5000.0 networkMessage.sendPing(loop.ipAddress%) loop.currentTime=GETTIMERALL() ENDIF ELSE ENDIF NEXT ENDIF ENDFUNCTION //! Update list of servers FUNCTION updateServerList%:networkData$[] LOCAL server AS tServer LOCAL loop%,found% DIM self.listOfServers[0] server.ipAddress%=INTEGER(networkData$[NETMESSAGE_FROMIP%]) server.name$=networkData$[NETMESSAGE_DATA%] server.numPlaying%=INTEGER(networkData$[NETMESSAGE_DATA%+1]) server.isPasswordProtected%=INTEGER(networkData$[NETMESSAGE_DATA%+2]) server.platform$=networkData$[NETMESSAGE_DATA%+3] server.gameType%=INTEGER(networkData$[NETMESSAGE_DATA%+4]) server.allowHoles%=INTEGER(networkData$[NETMESSAGE_DATA%+5]) server.state%=INTEGER(networkData$[NETMESSAGE_DATA%+6]) found%=INVALID% FOR loop%=0 TO BOUNDS(self.listOfServers[],0)-1 IF self.listOfServers[loop%].ipAddress%=server.ipAddress% // Already present, so update information self.listOfServers[loop%].numPlaying%=server.numPlaying% self.listOfServers[loop%].state%=server.state% found%=loop% BREAK ENDIF NEXT IF found%=INVALID DIMPUSH self.listOfServers[],server ENDIF ENDFUNCTION //! Build list of servers for display FUNCTION buildServerList%: LOCAL loop AS tServer LOCAL text$,loop2% LOCAL array$[]; DIM array$[5] LOCAL gameT$[]; DIMDATA gameT$[],">","3","1" text$="" FOREACH loop IN self.listOfServers[] FOR loop2%=0 TO BOUNDS(array$[],0)-1 array$[loop2%]="-" NEXT IF loop.isPasswordProtected%=TRUE THEN array$[0]="*" IF loop.allowHoles%=TRUE THEN array$[1]=LEFT$(yesNoText$[YES%],1) array$[2]=gameT$[loop.gameType%-GAME_LEAGUE%]

98 IF loop.state%<>AREA_LOBBY% THEN array$[3]="*" FOR loop2%=0 TO BOUNDS(array$[],0)-1 INC text$,array$[loop2%] NEXT INC text$," "+loop.name$+" ("+loop.platform$+")|" NEXT DDgui_set(self.serverList$,"TEXT",LEFT$(text$,LEN(text$)-1)) ENDFUNCTION //! Update the player list with current information FUNCTION updatePlayersList%:networkData$[] LOCAL player AS tPlayer LOCAL loop%,size%=4 DIM self.playerList[0] loop%=NETMESSAGE_DATA% WHILE loop%

FUNCTION doesPlayerExist%:ipAddress% LOCAL loop AS tPlayer FOREACH loop IN self.playerList[] IF loop.ipAddress%=ipAddress% THEN RETURN TRUE NEXT RETURN FALSE ENDFUNCTION FUNCTION buildPlayersList%: LOCAL loop AS tPlayer LOCAL text$ text$="" FOREACH loop IN self.playerList[] INC text$,"("+loop.ping%+") "+loop.name$+" (" IF loop.isReady% text$=text$+self.ready$ ELSE text$=text$+self.notReady$ ENDIF text$=text$+")|" NEXT DDgui_set(self.playersList$,"TEXT",LEFT$(text$,LEN(text$)-1)) ENDFUNCTION FUNCTION HandleNoResponse%:setup AS TSetup,noResponseIP% // Clear the playerslist self.connectedToHostStatus(FALSE) self.buildPlayersList() self.CreateErrorWindow(setup,self.ERROR_HOSTLEFT%) ENDFUNCTION FUNCTION SendUserDataToHost%:networkMessage AS TProcessNetworkMessage,messageID %,data$,reliable% RETURN networkMessage.sendUserMessage(self.isConnected.hostIP%,messageID %,data$,reliable%) ENDFUNCTION

FUNCTION getPlayerName$: RETURN self.name$ ENDFUNCTION FUNCTION ReturnIPAddress%:

99 RETURN self.thisIP% ENDFUNCTION ENDTYPE

100 Destruction

This grabs blocks of data and then simulates the display being destroyed by scaling and fading the blocks. Its a nice graphical test program, but beyond that it has no real use. TYPE tBlock image% x y dirX dirY alpha angle ENDTYPE TYPE TDestruction block[] AS tBlock FUNCTION TDestruction_Initialise%: LOCAL stp%=32 LOCAL screenWidth% LOCAL screenHeight% LOCAL block AS tBlock DIM self.block[0] GETSCREENSIZE screenWidth%,screenHeight% FOR y%=0 TO screenHeight%-1 STEP stp% FOR x%=0 TO screenWidth%-1 STEP stp% block.image%=GENSPRITE() IF block.image%<0 THEN RETURN FALSE GRABSPRITE block.image%,x%,y%,stp%-1,stp%-1 block.x=x%*1.0 block.y=y%*1.0 block.dirX=RND(4.0)-2.0 block.dirY=RND(4.0)-2.0 block.alpha=1.0 block.angle=0.0 DIMPUSH self.block[],block NEXT NEXT RETURN TRUE ENDFUNCTION FUNCTION TDestruction_Process%:speed LOCAL loop AS tBlock FOREACH loop IN self.block[] ALPHAMODE loop.alpha ROTOZOOMSPRITE loop.image,loop.x,loop.y,loop.angle,1.0 INC loop.x,loop.dirX*speed INC loop.y,loop.dirY*speed INC loop.angle,speed*0.1 IF loop.angle>=360.0 THEN loop.angle=0.0 NEXT ENDFUNCTION ENDTYPE

101 Fade

A screen fade routine. This uses GRABSPRITE, so may not work on Android devices. Using the AppTimer routine, it fades a screen to and from black. TYPE TFade screenWidth% screenHeight alpha = 0.0 tempSprite% = -1 FUNCTION Initialise%:screenWidth%,screenHeight% self.screenWidth%=screenWidth% self.screenHeight%=screenHeight% self.alpha=0.0 self.tempSprite%=-1 RETURN TRUE ENDFUNCTION FUNCTION Destroy%: IF self.tempSprite%>=0 THEN LOADSPRITE "",self.tempSprite% ENDFUNCTION FUNCTION fadeScreen%:fadeDir,appTime AS TAppTime,finalValue=1.0 LOCAL speed,sW%,sH% LOCAL stp LOCAL fadeSpeed=0.025 appTime.Update() IF fadeDir<0.0 self.tempSprite%=GENSPRITE() IF self.tempSprite%<0 THEN RETURN FALSE GRABSPRITE self.tempSprite%,0,0,self.screenWidth%-1,self.screenHeight %-1 stp=fadeSpeed self.alpha=0.0 ELSE stp=0.0-fadeSpeed IF self.tempSprite%<0 THEN RETURN FALSE ENDIF speed=0.0 appTime.Update() WHILE (fadeDir<0.0 AND self.alpha<=finalValue) OR (fadeDir>0 AND self.alpha>=0.0) ALPHAMODE -1.0+self.alpha DRAWSPRITE self.tempSprite%,0,0 SHOWSCREEN INC self.alpha,speed*stp speed=appTime.Update() WEND IF fadeDir>0.0 ALPHAMODE 0.0 DRAWSPRITE self.tempSprite%,0,0 //SHOWSCREEN LOADSPRITE "",self.tempSprite% // Might as well reuse it, and save memory! ENDIF RETURN TRUE ENDFUNCTION FUNCTION returnSprite%: RETURN self.tempSprite% ENDFUNCTION FUNCTION returnAlpha: RETURN -1.0+self.alpha

102 ENDFUNCTION ENDTYPE

103 Language Selection

This routine used DDgui to display the user's country (based on their internet IP address if available or system country code if internet isn't available), and allow them to select a language that text will be displayed in. The language file is stored in an INI file. // Commnent this constant if you already have it GLOBAL gDDguiMinControlDimension%=18 TYPE tCountry code$ description$ supportedLanguageCodes$[] spriteID% ENDTYPE TYPE tLanguage code$ description$ path$ fontFile$ ENDTYPE TYPE TFlags screenWidth%;screenHeight% countries[] AS tCountry languages[] AS tLanguage supportedLanguages[] AS tLanguage allowInternetAccess% = TRUE flagWidth%=-1;flagHeight%=-1 selectedLanguageCode$;selectedLanguagePath$;userCountryCode$ languagePath$="Media/" languageFile$ sureYouWantToContinueText$ visitWebSiteText$ SUPPORTEDLANGUAGES$ = "suppLang" LANGUAGE_SECTION$ = "LANGUAGECOUNTRY" COUNTRYLANGUAGE_KEY$= "CONTLANG" DONTSHOW_KEY$ = "DONTSHOW" LANGUAGELIST$ = "langList" DONTSHOW$ = "dontShow" FINISHED$ = "finished" BACK$ = "back" HEIGHT$ = "HEIGHT" flagButton$ = "flagButton" FUNCTION Initialise%: LOCAL country AS tCountry LOCAL language AS tLanguage LOCAL temp$ self.languageFile$=self.languagePath$+"Language.INI" IF DOESFILEEXIST(self.languageFile$)=FALSE THEN RETURN FALSE DIM self.countries[0] DIM self.languages[0] DIM self.supportedLanguages[0] // Read in all the countries and languages RESTORE countryText READ country.code$ WHILE country.code$<>"" READ country.description$ DIM country.supportedLanguageCodes$[0] READ temp$ WHILE temp$<>""

104 DIMPUSH country.supportedLanguageCodes$[],temp$ READ temp$ WEND DIMPUSH self.countries[],country READ country.code$ WEND RESTORE languageText READ language.code$ WHILE language.code$<>"" READ language.description$ DIMPUSH self.languages[],language READ language.code$ WEND SORTARRAY self.countries[],0 SORTARRAY self.languages[],0 self.flagWidth%=-1 self.flagHeight%=-1 RETURN TRUE ENDFUNCTION FUNCTION Destroy%: LOCAL loop AS tCountry FOREACH loop IN self.countries[] LOADSPRITE "",loop.spriteID% NEXT ENDFUNCTION FUNCTION LoadFlagSprites%: LOCAL loop AS tCountry SETSHOEBOX "Media/flags.sbx","" FOREACH loop IN self.countries[] loop.spriteID%=GENSPRITE() IF loop.spriteID%>=0 LOADSPRITE loop.code$+".png",loop.spriteID% IF self.flagWidth%<0 OR self.flagHeight%<0 GETSPRITESIZE loop.spriteID%,self.flagWidth %,self.flagHeight% ENDIF ENDIF NEXT SETSHOEBOX "","" ENDFUNCTION FUNCTION GetLanguageSelection%:localisation AS TLocalisation,optionsFile$,forceDisplay%,allowExit% LOCAL optionLanguageCode$,optionDontShowAgain%,languageList$,defaultPos%,index% LOCAL countryIndex% GETSCREENSIZE self.screenWidth%,self.screenHeight% // Do we need to display anything DEBUG "Options file : "+optionsFile$+"\n" INIOPEN optionsFile$ optionLanguageCode$=INIGET$ (self.LANGUAGE_SECTION$,self.COUNTRYLANGUAGE_KEY$,"") optionDontShowAgain%=INTEGER(INIGET$ (self.LANGUAGE_SECTION$,self.DONTSHOW_KEY$,FALSE)) INIOPEN "" // Load in the flag sprites self.LoadFlagSprites() self.userCountryCode$=self.GetUsersCountry$() countryIndex%=self.FindCountry(self.userCountryCode$) IF optionLanguageCode$<>"" IF optionDontShowAgain%=TRUE AND forceDisplay%=FALSE // Does the country code exist ? IF self.FindLanguage(optionLanguageCode$)>=0 self.StoreLanguageSelection(optionLanguageCode$,optionDontShowAgain%,FALSE)

105 localisation.LoadLanguage(self.languageFile$,optionLanguageCode$) RETURN TRUE ENDIF ENDIF ENDIF IF countryIndex%>=0 languageList$=self.ReturnListOfSupportedLanguages$() defaultPos%=self.ChooseDefaultLanguageForCountry(countryIndex%) IF localisation.LoadLanguage(self.languageFile$,self.supportedLanguages[defaultPos %].code$)=TRUE WHILE TRUE self.CreateWindow(localisation,countryIndex%,allowExit %,languageList$) DDgui_set(self.LANGUAGELIST$,"SELECT",defaultPos%) DDgui_set(self.LANGUAGELIST$,"SCROLL",defaultPos%) // Display the users flag as a background WHILE TRUE SMOOTHSHADING FALSE ALPHAMODE 0.0 DDgui_show(FALSE) SHOWSCREEN IF DDgui_get(self.FINISHED$,CLICKED$) index %=DDgui_get(self.LANGUAGELIST$,SLECTED$) IF index%>=0 IF self.UpdateText(localisation,index%,allowExit%)=TRUE // Valid language chosen - write the options file and exit and make sure the language text has been read in INIOPEN optionsFile$ INIPUT self.LANGUAGE_SECTION$,self.COUNTRYLANGUAGE_KEY$,self.supportedLanguages[index%].code$ INIPUT self.LANGUAGE_SECTION$,self.DONTSHOW_KEY$,INTEGER(DDgui_get(self.DONTSHOW$,SLECTED$)) INIOPEN "" DDgui_popdialog() SYSTEMPOINTER FALSE RETURN TRUE ENDIF ENDIF ELSEIF DDgui_get(self.LANGUAGELIST$,CLICKED$) index %=DDgui_get(self.LANGUAGELIST$,SLECTED$) IF index%>=0 // Load in the new language IF self.UpdateText(localisation,index%,allowExit%)=TRUE // Succeeded defaultPos%=index% ELSE // An error occured, so dont load in the new language ENDIF ENDIF ELSEIF DDgui_get(self.BACK$,CLICKED$) DDgui_popdialog() SYSTEMPOINTER FALSE RETURN -1 ELSEIF DDgui_get(self.flagButton$,CLICKED$) // User clicked on the flag index %=DDgui_get(self.LANGUAGELIST$,SLECTED$) IF index%>=0 self.loadWebPage(self.supportedLanguages[index%].code$,self.countries[countryIndex %].description$) ENDIF ENDIF

106 WEND WEND ENDIF ENDIF // An error RETURN FALSE ENDFUNCTION FUNCTION CreateWindow%:localisation AS TLocalisation,countryCode%,allowExit %,languageList$ LOCAL height%,fromBottom%,width%,bottomY%,pos% ?IFDEF WEBOS self.visitWebSiteText$=localisation.LocaliseText$ ("{{wanttovisitwebsite3}}") ?ELSE self.visitWebSiteText$=localisation.LocaliseText$ ("{{wanttovisitwebsite4}}") ?ENDIF DDgui_pushdialog(0,0,self.screenWidth-1,self.screenHeight-1) DDgui_button(self.flagButton$,"SPR_B"+self.countries[countryCode%].spriteID %,self.flagWidth%,self.flagHeight%) DDgui_widget("",self.countries[countryCode%].description$,self.screenWidth- (self.flagWidth%)-48,0) DDgui_set("","TEXT",localisation.LocaliseText$("{{selectyourlanguage}}")) DDgui_widget(self.SUPPORTEDLANGUAGES$,localisation.LocaliseText$ ("{{supportedlanguages}} :"),self.screenWidth-8,0) DDgui_list(self.LANGUAGELIST$,languageList$,self.screenWidth-12,32)

// Make sure this is ticked DDgui_checkbox(self.DONTSHOW$,localisation.LocaliseText$(" {{dontshow}}"),self.screenWidth-12,0) DDgui_set(self.DONTSHOW$,SLECTED$,TRUE) IF allowExit%=TRUE // Do we allow the user to exit without making a selection ? width%=self.screenWidth%/2 DDgui_button(self.BACK$,localisation.LocaliseText$("{{back}}"),width %,32) DDgui_button(self.FINISHED$,localisation.LocaliseText$ ("{{finish}}"),width%,32) ELSE DDgui_button(self.FINISHED$,localisation.LocaliseText$ ("{{finish}}"),self.screenWidth-8,32) ENDIF DDgui_show(FALSE) height%=DDgui_get(self.DONTSHOW$,self.HEIGHT$) +DDgui_get(self.FINISHED$,self.HEIGHT$) bottomY%=self.screenHeight%-5-height% fromBottom%=bottomY%-DDgui_get(self.LANGUAGELIST$,"YPOS") DDgui_set(self.LANGUAGELIST$,self.HEIGHT$,fromBottom%) ENDFUNCTION //! Update all text when a new language has been chosen FUNCTION UpdateText%:localisation AS TLocalisation,pos%,allowExit% IF localisation.LoadLanguage(self.languageFile$,self.supportedLanguages[pos %].code$)=TRUE DDgui_set("","TEXT",localisation.LocaliseText$ ("{{selectyourlanguage}}")) // Title DDgui_set(self.SUPPORTEDLANGUAGES$,"TEXT",localisation.LocaliseText$ ("{{supportedlanguages}} :")) DDgui_set(self.DONTSHOW$,"TEXT",localisation.LocaliseText$(" {{dontshow}}")) IF allowExit%=TRUE // Do we allow the user to exit without making a selection ? DDgui_set(self.BACK$,"TEXT",localisation.LocaliseText$ ("{{back}}")) DDgui_set(self.FINISHED$,"TEXT",localisation.LocaliseText$ ("{{finish}}")) ELSE DDgui_set(self.FINISHED$,"TEXT",localisation.LocaliseText$ ("{{finish}}")) ENDIF

107 RETURN TRUE ELSE RETURN FALSE ENDIF ENDFUNCTION //! Get users country (internet access required) FUNCTION GetUsersCountry$: LOCAL temp$,locale$ LOCAL array$[] locale$=UCASE$(PLATFORMINFO$("LOCALE")) IF self.allowInternetAccess%=TRUE DIM array$[0] temp$=NETWEBGET$("www.un- map.com","/ip2country/ip2country.php",80,512,1000) IF SPLITSTR(temp$,array$[],"+")=4 // Correct number of items - IP address, 2 code, 3 code, Description locale$=UCASE$(array$[1]) ENDIF ENDIF RETURN locale$ ENDFUNCTION //! Return the names of supported languages FUNCTION ReturnListOfSupportedLanguages$: LOCAL lCount%,temp$,loop% LOCAL SUPPORTED_SECTION$ = "SUPPORTED" LOCAL language AS tLanguage DIM self.supportedLanguages[0] temp$="" INIOPEN self.languageFile$ lCount%=INTEGER(INIGET$(SUPPORTED_SECTION$,"COUNT","0")) IF lCount%>0 FOR loop%=0 TO lCount%-1 language.code$=INIGET$(SUPPORTED_SECTION$,loop%,"") language.description$=self.GetLanguageName$(language.code$) IF language.code$<>"" DIMPUSH self.supportedLanguages[],language INC temp$,language.description$+"|" ENDIF NEXT ENDIF INIOPEN "" RETURN temp$ ENDFUNCTION //! Find a country code FUNCTION FindCountry%:countryCode$ LOCAL loop% FOR loop%=0 TO BOUNDS(self.countries[],0)-1 IF self.countries[loop%].code$=countryCode$ THEN RETURN loop% NEXT RETURN -1 ENDFUNCTION //! Find a language code FUNCTION FindLanguage%:languageCode$ LOCAL loop% FOR loop%=0 TO BOUNDS(self.languages[],0)-1 IF self.languages[loop%].code$=languageCode$ THEN RETURN loop% NEXT RETURN -1 ENDFUNCTION //! Get language name from a code FUNCTION GetLanguageName$:languageCode$

108 LOCAL index% index%=self.FindLanguage(languageCode$) IF index%>=0 RETURN self.languages[index%].description$ ELSE RETURN "" ENDIF ENDFUNCTION //! Store language selection FUNCTION StoreLanguageSelection%:languageCode$,dontShowAgain%,writeToFile% self.selectedLanguageCode$=languageCode$ self.selectedLanguagePath$=self.languagePath$+self.selectedLanguageCode$ +"/" ENDFUNCTION //! Return language path FUNCTION ReturnLanguagePath$: RETURN self.selectedLanguagePath$ ENDFUNCTION //! Return country code FUNCTION ReturnCountryCode$: RETURN self.userCountryCode$ ENDFUNCTION FUNCTION returnCountrySpriteIndex%:countryCode$ LOCAL index%

index%=self.FindCountry(countryCode$) IF index%>=0 THEN RETURN self.countries[index%].spriteID% RETURN -1 ENDFUNCTION FUNCTION GetDefaultLanguage$:countryIndex% LOCAL defCountryCode$ SELECT self.countries[countryIndex%].code$ CASE "UK" defCountryCode$="en" CASE "GB" defCountryCode$="en" CASE "US" defCountryCode$="en" CASE "DE" defCountryCode$="de" CASE "IT" defCountryCode$="it" DEFAULT defCountryCode$="en" ENDSELECT RETURN defCountryCode$ ENDFUNCTION //! Internal use only! Choose the default language of various countries //\param country$ - two character country code //\return Index into the language list, or 0 if the language code isn't supported FUNCTION ChooseDefaultLanguageForCountry%:countryIndex% LOCAL defCountryCode$ defCountryCode$=self.GetDefaultLanguage$(countryIndex%) IF defCountryCode$<>"" // Is the default code supported ? FOR loop%=0 TO BOUNDS(self.supportedLanguages[],0)-1 IF self.supportedLanguages[loop%].code$=defCountryCode$ THEN RETURN loop% NEXT ENDIF RETURN 0

109 ENDFUNCTION FUNCTION ReturnFlagWidth%: RETURN MAX(0,self.flagWidth%) ENDFUNCTION FUNCTION ReturnFlagHeight%: RETURN MAX(0,self.flagHeight%) ENDFUNCTION FUNCTION ReturnCountrySprite%:countryIndex% RETURN self.countries[countryIndex%].spriteID% ENDFUNCTION FUNCTION loadWebPage%:languageCode$,country$ LOCAL webURL$ IF DDgui_msg(self.visitWebSiteText$,TRUE) webURL$="http://"+languageCode$+".wikipedia.org/wiki/"+URLENCODE$ (country$) DEBUG webURL$+"\n" NETWEBEND webURL$ ENDIF ENDFUNCTION ENDTYPE //! What follows is a list of all country codes and their associated name in the local language. If more than one language is recognised, then //! I picked the first language in the Wikipedia entry, unless it cant be displayed properly in this Latin font //! I occasionally had to replace a few characters that aren't present with ones that are, as well. //! Some of this data was fetched from http://www.iso.org/iso/country_codes/iso_3166_code_lists/english_country_names_and_code_ elements.htm and another //! website that I cant remember the name of... //! At some point in the future, it may be worth using the three letter ISO code, to expand the list of used languages in some countries STARTDATA countryText: DATA "AC","Ascension Island", "en","" DATA "AD","Principat d'Andorra", "ca","" // Andorra DATA "AE","Dawlat al-Imarat al-‘Arabiyah al-Muttahidah", "ar","" // United Arab Emirates DATA "AF","Da Afganistan Islami Jomhoriyat", "fa","ps","" // Afghanistan DATA "AG","Antigua and Barbuda", "en","" DATA "AI","Anguilla", "en","" DATA "AL","Republika e Shqipërisë", "sq","" // Albania DATA "AM","Hayastani Hanrapetut’yun", "hy","" // Armenia DATA "AN","Nederlandse Antillen", "nl","" // Netherlands Antilies DATA "AO","República de Angola", "pt","" // Angola DATA "AP","African Regional Intellectual Property Organization","en","" DATA "AQ","Antarctica", "en","fr","es","no","nn","" DATA "AR","República Argentina", "es","" // Argentina DATA "AS","Amerika Samoa", "sm","" // American Samoa DATA "AT","Republik Österreich", "de","" // Austria DATA "AU","Australia", "en","" DATA "AW","Aruba", "nl","" DATA "AX","Landskapet Åland", "sv","" // Aland Islands DATA "AZ","Azerbaycan Respublikasi", "az","" // Azerbaijan DATA "BA","Bosna i Hercegovina", "bs","" // Bosnia and Herzegovina

110 DATA "BB","Barbados", "en","" DATA "BD","Gônoprojatontri Bangladesh", "bn","" // Bangladesh DATA "BE","Koninkrijk België", "de","fr","nl","" // Belguim DATA "BF","Burkina Faso", "fr","" DATA "BG","Republika Balgariya", "bg","" // Bulgaria DATA "BH","Mamlakat al-Bahrayn", "ar","" // Bahrain DATA "BI","Republika y'u Burundi", "fr","" // Burundi DATA "BJ","République du Bénin", "fr","" // Benin DATA "BL","Saint Barthélemy", "fr","" // St Bartholew DATA "BM","Bermuda", "en","" DATA "BN","Brunei Darussalam", "ms","" // Brunei DATA "BO","Estado Plurinacional de Bolivia", "es","qu","ay","" // Bolivia DATA "BR","República Federativa do Brasil", "pt","" // Brazil DATA "BS","Bahamas", "en","" DATA "BT","'Brug Gyal-khab", "dz","" // Bhutan DATA "BV","Bouvetøya", "no","nb","nn","se","" // Bouvet Island DATA "BW","Botswana", "en","tn","" DATA "BY","Belarus", "be","" // Uses cyrillic font DATA "BZ","Belize", "en","" DATA "CA","Canada", "en","fr","" DATA "CB","Unassigned", "en","" DATA "CC","Cocos (Keeling) Islands", "ms","en","" DATA "CD","République Démocratique du Congo", "fr","" // Congo DATA "CF","République centrafricaine", "fr","sg","" // Central African Republic DATA "CG","République du Congo", "fr","" // Congo DATA "CH","Schweizerische Eidgenossenschaft", "de","fr","it","rm","" // Switzerland DATA "CI","République de Côte d'Ivoire", "fr","" // Ivory Coast DATA "CK","Kuki 'Airani", "en","mi","" // Cook Islands DATA "CL","República de Chile", "es","" // Chile DATA "CM","Cameroon", "en","fr","" DATA "CN","Zhonghuá Rénmín Gònghéguó", "zh","" // China DATA "CO","República de Colombia", "es","" // Columbia DATA "CP","Île de la Passion", "fr","" // Clipperton Island DATA "CR","República de Costa Rica", "es","" // Costa Rica DATA "CS","Ceskoslovensko", "cs","sk","" // Czechoslovakia DATA "CU","República de Cuba", "es","" // Cuba DATA "CV","República de Cabo Verde", "pt","" // Cape Verde DATA "CX","Christmas Island", "en","" DATA "CY","Kypriakí Dimokratía",

111 "el","tr","" // Cyprus DATA "CZ","Ceská republika", "cs","" // Czech Republic DATA "DE","Bundesrepublik Deutschland", "de","" // Germany DATA "DG","Diego Garcia", "en","" DATA "DJ","République de Djibouti", "fr","ar","" // Djibouti DATA "DK","Kongeriget Danmark", "da","en","" // Denmark DATA "DM","Dominica", "en","" DATA "DO","República Dominicana", "es","" // Dominican Republic DATA "DZ","République algérienne démocratique et populaire","ar","" // Algeria DATA "EA","Ciudad Autónoma de Ceuta", "es","" // Ceuta DATA "EC","República del Ecuador", "es","" // Equador DATA "EE","Eesti Vabariik", "et","" // Estonia DATA "EG","Gumhuriyyat Misr al-'Arabiyya", "ar","" // Egypt DATA "EH","Sáhara Occidental", "ar","es","fr","" // Western Sahara DATA "ER","Hagere Ertra", "ti","ar","" // Eritrea DATA "ES","Reino de España", "ca","es","eu","gl","" // Spain DATA "ET","ye-Ityoppya Federalawi Dimokrasiyawi Ripeblik", "am","ti","" // Ethiopia DATA "EU","European Union", "cs", "da", "nl", "en", "et", "fi", "fr", "de", "el", "hu", "it", "lv", "lt", "mt", "pl", "pt", "sk", "sl", "es", "sv","" DATA "FI","Suomen tasavalta", "fi","sv","" // Finland DATA "FJ","Matanitu Tu-Vaka-i-koya ko Vit", "en","fj","" // Fiji DATA "FK","Falkland Islands", "en","" DATA "FM","Micronesia", "en","" DATA "FO","Føroyar", "fo","" // Faroe Islands DATA "FR","République française", "br","fr","oc","" // France DATA "FS","Unassigned", "en","" DATA "FX","République française", "br","fr","oc","" // France DATA "GA","République Gabonaise", "fr","" // Gambon DATA "GB","United Kingdom", "cy","en","gd","gv","kw","" DATA "GD","Grenada", "en","" DATA "GE","Sakartvelo", "ka","" // Georgia DATA "GF","Guyane", "fr","" // French Guiana DATA "GG","Guernsey", "en","fr","" DATA "GH","Republic of Ghana", "en","" DATA "GI","Gibraltar", "en","es","it","pt","" DATA "GL","Kalaallit Nunaat", "kl","" // Greenland DATA "GM","Gambia", "en","wo","ff","" DATA "GN","République de Guinée", "fr","" // Guinea DATA "GP","Guadeloupe", "fr","" DATA "GQ","República de Guinea Ecuatorial",

112 "es","fr","" //Equatorial Guinea DATA "GR","Elliniki´ Dimokratía", "el","" // Greece DATA "GS","S. Georgia and S. Sandwich Isls.", "en","" DATA "GT","República de Guatemala", "es","" // Guatemala DATA "GU","Guåhan", "en","ch","" // Guam DATA "GW","República da Guiné-Bissau", "pt","" // Guinea-Bissau DATA "GY","Co-operative Republic of Guyana", "en","" DATA "HK","Hong Kong", "en","zh","" DATA "HM","Heard and McDonald Islands", "" DATA "HN","República de Honduras", "es","" // Honduras DATA "HR","Republika Hrvatska", "hr","" // Croatia DATA "HT","République d'Haïti", "fr","ht","" // Haiti DATA "HU","Magyar Köztársaság", "hu","" // Hungary DATA "HW","Unassigned", "en","" DATA "IC","Islas Canarias", "es","" // Canary Islands DATA "ID","Republik Indonesia", "id","" // Indonesia DATA "IE","Republic of Ireland", "en","ga","" DATA "IL","Medinat Yisra'el", "he","ar","" // Israel DATA "IM","Isle of Mann", "en","gv","" DATA "IN","Bharat Ganarajya", "ar","bn","en","hi","mr","ta","te","" // India DATA "IO","British Indian Ocean Territory", "en","" DATA "IQ","Jumhuriyat Al-Iraq", "ar","" // Iraq DATA "IR","Jomhuri-ye Eslami-ye Iran", "fa","" // Iran DATA "IS","Ísland", "is","" // Iceland DATA "IT","Repubblica italiana", "it","" // Italy DATA "JE","Jersey", "en","pt","" DATA "JI","Unassigned", "en","" DATA "JM","Jamaica", "en","" DATA "JO","Al-Mamlakah al-'Urdunniyyah al-Hašimiyyah", "ar","" // Jordan DATA "JP","Nippon-koku", "ja","" // Japan DATA "KE","Kenya", "en","" DATA "KG","Kirgiz Respublikasi", "ky","ru","" // Kyrgyzstan DATA "KH","Preah Réachéa Nachâk Kâmpuchéa", "km","" // Cambodia DATA "KI","Kiribati", "en","" DATA "KM","Union des Comores", "ar","fr","" // Comoros DATA "KN","Saint Kitts and Nevis", "kn","" DATA "KP","Choson Minjujuui Inmin Konghwaguk", "ko","" // North Korea DATA "KR","Daehanmin-guk", "ko","" // South Korea DATA "KS","Unassigned", "en","" DATA "KW","Dawlat al-Kuwait", "ar","" // Kuwait DATA "KY","Cayman Islands", "en",""

113 DATA "KZ","Qazaqstan Respublïkasi", "kk","ru","" // Kazakhstan DATA "LA","Sathalanalat Paxathipatai Paxaxon Lao", "lo","" // Laos DATA "LB","al-Jumhuriyah al-Lubnaniyah", "ar","" // Lebanon DATA "LC","Saint Lucia", "en","" DATA "LI","Fürstentum Liechtenstein", "de","" // Liechtenstein DATA "LK","Sri Lanka", "si","ta","" DATA "LR","Liberia", "en","" DATA "LS","Muso oa Lesotho", "st","en","zu","xh","" // Lesotho DATA "LT","Lietuvos Respublika", "lt","" // Lithuania DATA "LU","Großherzogtum Luxemburg", "de","fr","lb","" // Luxembourg DATA "LV","Latvijas Republika", "lv","" // Latvia DATA "LY","Al-Jamahiriyyah al-'Arabiyyah al-Libiyyah aš-Ša'biyyah al-Ištirakiyyah al-'Uzma ", "ar","" // Libya DATA "MA","Al-Mamlakatu l-Magribiyah", "ar","" // Morocco DATA "MB","Unassigned", "en","" DATA "MC","Principauté de Monaco", "fr","it","en","" // Monaco DATA "MD","Republica Moldova", "mo","ru","" // Moldovia DATA "MF","Saint-Martin/Sint Maarten", "fr","nl","" // Saint Martin DATA "MG","Repoblikan'i Madagasikara", "fr","mg","" // Madagascar DATA "MH","Republic of the Marshall Islands", "en","mh","" DATA "MI","Unassigned", "en","" DATA "MK","Republika Makedonija", "mk","" // Macedonia DATA "ML","République du Mali", "fr","bm","" // Mali DATA "MM","Pyidaunzu Thanmada Myama Nainngandaw", "my","" // Burma/Myanmar DATA "MN","Mongyol ulus", "mm","" // Mongolia DATA "MO","Região Administrativa Especial de Macau da República Popular da China", "zh","pt","" // Macau DATA "MP","Sankattan Siha Na Islas Mariånas", "ch","en","" // Northern Mariana Islands DATA "MQ","Martinique", "fr","" DATA "MR","Al-Jumhuriyyah al-Islamiyyah al-Muritaniyyah", "ar","" // Mauritania DATA "MS","Montserrat", "en","" DATA "MT","Repubblika ta' Malta", "mt","" // Malta DATA "MU","Republik Moris République de Maurice", "en","fr","" // Mauritius DATA "MV","Divehi Rajjey ge Jumhuriyya", "dv","" // Maldives DATA "MW","Chalo cha Malawi", "ny","en","" // Malawi DATA "MX","Estados Unidos Mexicanos", "es","" // Mexico DATA "MY","Malaysia", "ms","" DATA "MZ","República de Moçambique", "pt","sw","" // Mozambique DATA "NA","Republic of Namibia", "en","af","de","" DATA "NC","Nouvelle-Calédonie", "fr","" // New Caledonia DATA "NE","République du Niger", "fr","" // Niger

114 DATA "NF","Norfolk Island", "en","" DATA "NG","Republik Nijeriya", "en","ha","yo","ig","ff","" // Nigeria DATA "NI","República de Nicaragua", "es","" // Nicaragua DATA "NK","Unassigned", "en","" DATA "NL","Nederland", "nl","" // Netherlands DATA "NO","Kongeriket Norge", "nn","no","nb","se","" // Norway DATA "NP","Sanghiya Loktantrik Ganatantra Nepal", "ne","en","" // Nepal DATA "NR","Ripublik Naoero", "na","en","" // Nauru DATA "NT","Neutral Zone", "ar","" DATA "NU","Niue", "en","" DATA "NZ","New Zealand", "en","mi","" DATA "OM","Saltanat 'Uman", "ar","" // Oman DATA "PA","República de Panamá", "es","" // Panama DATA "PE","República del Perú", "es","" // Peru DATA "PF","Polynésie française", "fr","" // French Polynesia DATA "PG","Independen Stet bilong Papua Niugini", "en","" // Papa new Guinea DATA "PH","Republika ng Pilipinas", "en","tl","" // Philippines DATA "PK","Islami Jumhuri-ye Pakistan", "ur","" // Pakistan DATA "PL","Rzeczpospolita Polska", "pl","" // Poland DATA "PM","Collectivité territoriale de Saint-Pierre-et-Miquelon", "fr","" // Saint Pierre and Miquelon DATA "PN","Pitcairn", "en","" DATA "PR","Estado Libre Asociado de Puerto Rico", "es","" // Puerto Rico DATA "PS","Palestinian Territory, Occupied", "ar","" DATA "PT","República Portuguesa", "pt","" // Portugal DATA "PW","Beluu er a Belau", "en","" // Palau DATA "PY","República del Paraguay", "es","gn","" // Paraguay DATA "QA","Dawlat Qatar", "ar","" // Qatar DATA "RE","Réunion", "fr","" // Reunion DATA "RO","România", "ro","" // Romania DATA "RS","Republika Srbija", "sr","" // Serbia DATA "RU","Rossiyskaya Federatsiya", "ru","" // Russian Federation DATA "RW","Repubulika y'u Rwanda", "rw","en","fr","" // Rwanda DATA "SA","al-Mamlaka al-'Arabiyya as-Su'udiyya", "ar","" // Saudi Arabia DATA "SB","Solomon Islands", "en","" DATA "SC","Seychelles", "en","" DATA "SD","Jumhuriyat al Sudan", "ar","" // Sudan DATA "SE","Konungariket Sverige", "sv","" // Sweden DATA "SG","Singapore", "en","ms","zh","ta","" DATA "SH","St. Helena", "en",""

115 DATA "SI","Republika Slovenija", "sl","" // Slovenia DATA "SJ","Svalbard og Jan Mayen", "no","ru","" // Svalbard and Jan Mayen islands DATA "SK","Slovenská republika", "sk","" // Slovakia DATA "SL","Sierra Leone", "en","" DATA "SM","Serenissima Repubblica di San Marino", "it","" // San Marino DATA "SN","République du Sénégal", "fr","wo","" // Senegal DATA "SO","Jamhuuriyadda Soomaaliya", "so","ar","" // Somalia DATA "SR","Republiek Suriname", "nl","" // Suriname DATA "ST","República Democrática de São Tomé e Príncipe", "pt","" // São Tomé and Príncipe DATA "SU","Soyuz Sovietskikh Sotsialisticheskikh Respublik","ru","" // Russia DATA "SV","República de El Salvador", "es","" // El Salvador DATA "SY","Al-Jumhuriyyah al-'Arabiyyah as-Suriyyah", "ar","" // Syria DATA "SZ","Umbuso weSwatini", "en","ss","" // Swaziland DATA "TA","Tristan da Cunha", "en","" DATA "TC","Turks and Caicos Islands", "en","" DATA "TD","République du Tchad", "fr","ar","" // Chad DATA "TF","Territoire des Terres australes et antarctiques françaises", "fr","" // French Southern and Antarctic Lands DATA "TG","République Togolaise", "fr","ee","" // Togo DATA "TH","Ratcha Anachak Thai", "th","" // Thailand DATA "TJ","Jumhurii Tojikiston", "tg","" // Tajikistan DATA "TK","Tokelau", "en","" DATA "TL","Repúblika Demokrátika Timór-Lest", "pt","en","id","" // East Timor DATA "TM","Türkmenistan", "tk","" // Turkmenistan DATA "TN","al-Jumhuriyya at-Tunisiyya République Tunisienne","ar","ru","" // Tunisia DATA "TO","Pule'anga Fakatu'i 'o Tong", "to","en","" // Tonga DATA "TP","Repúblika Demokrátika Timór-Lest", "pt","en","id","" // East Timor - This isn't used DATA "TR","Türkiye Cumhuriyeti", "tr","" // Turkey DATA "TT","Trinidad and Tobago", "en","" DATA "TV","Tuvalu", "tv","" DATA "TW","Taiwan/Formosa", "zh","" // Taiwan DATA "TZ","Jamhuri ya Muungano wa Tanzania", "sw","" // Tanzania DATA "UA","Ukrayina", "uk","" // Ukraine DATA "UG","Jamhuri ya Uganda", "en","" // Uganda DATA "UK","United Kingdom", "cy","en","gd","gv","kw","" DATA "UM","US Minor Outlying Islands", "en","" DATA "US","United States of America", "en","" DATA "UY","República Oriental del Urugua", "es","" // Uruguay DATA "UZ","O‘zbekiston Respublikasi", "uz","" // Uzbekistan DATA "VA","Stato della Città del Vaticano", "it","la","" // Vatican City DATA "VC","Saint Vincent and the Grenadines", "en","" DATA "VE","República Bolivariana de Venezuela", "es","" // Venezuela

116 DATA "VG","Virgin Islands (British)", "en","" DATA "","Virgin Islands (U.S)", "en","" DATA "VN","Cong hòa xã hoi chu nghia Viet Nam", "vi","" DATA "VS","Unassigned", "en","" // Vietnam DATA "VU","Ripablik blong Vanuatu", "bi","en","" // Vanuatu DATA "WF","Territoire des îles Wallis et Futuna", "fr","" // Wallis and Futuna DATA "WK","Unassigned", "en","" DATA "WS","Malo Sa'oloto Tuto'atasi o Samoa", "sm","en","" // Samoa DATA "XK","Republika e Kosovës", "sq","sr","tr","bs","" // Kosovo DATA "YE","Al-Jumhuriyyah al-Yamaniyyah", "ar","" // Yemen DATA "YT","Collectivité départementale de Mayotte", "fr","" // Mayotte DATA "YU","Jugoslavija", "mk","sl","hr","" // Yugoslavia DATA "ZA","South Africa", "en","af","zu","xh","tn","st","ss","nr","" DATA "ZM","Zambia", "en","" DATA "ZR","République du Zaïre", "fr","" // Zaire DATA "ZW","Zimbabwe", "en","" DATA "","" ENDDATA //! This lists the available languages in the two character ISO code. Some languages aren't listed here as they only have a 3 character code. //! At some point it might be worth changing to the 3 character version STARTDATA languageText: DATA "ab","Apsua byzš°a" // Abkhaz DATA "aa","Afaraf" // Afar DATA "af","Afrikaans" // Afrikaans DATA "ak","Akan" // Akan DATA "sq","Shqip" // Albanian DATA "am","amarañña" // Amharic DATA "ar","al-'arabiyyah" // Arabic DATA "an","Aragonés" // Argonese DATA "hy","Hayeren" // Armenian DATA "as","Ôxômiya" // Assamese DATA "av","Awar mats'" // Avar DATA "ae","avesta" // Avestan DATA "ay","aymar aru" // Aymara DATA "az","azerbaycan dili" // Azerbaijani DATA "bm","bamanankan" // Bambara DATA "ba","Basqort tele" // Bashkir DATA "eu","euskara" // Basque DATA "be","byelaruskaya mova" // Belarusian DATA "bn","Bangla" // Bengali DATA "bh","Bihari" // Bihari DATA "bi","Bislama" // Bislama DATA "bs","bosanski jezik"// Bosnian DATA "br","brezhoneg" // Breton DATA "bg","Balgarski ezik"// Bulgarian DATA "my","mranma bhasa" // Burmese DATA "ca","Català" // Catalan/Valencian DATA "ch","Chamoru" // Chamorro DATA "ce","Noxçiyn mott" // Chechen DATA "ny","chiChewa" // Chichewa/Chewa/Nyanja DATA "zh","Hànyu" // Chinese DATA "cv","Cavašla" // Chuvash DATA "kw","Kernewek" // Cornish DATA "co","corsu" // Corsican DATA "cr","Nehiyawewin" // Cree DATA "hr","hrvatski" // Croatian DATA "cs","cesky" // Czech DATA "da","dansk" // Danish DATA "dv","Dhivehi" // Divehi DATA "nl","Nederlands" // Dutch DATA "dz","rdzong-kha" // Dzongkha

117 DATA "en","English" // English DATA "eo","Esperanto" // Esperanto DATA "et","eesti" // Estonian DATA "ee","Euegbe" // Ewe DATA "fo","føroyskt" // Faroese DATA "fj","vosa Vakaviti" // Fijian DATA "fi","suomi" // Finish DATA "fr","français" // French DATA "ff","Fulfulde" // Fula DATA "gl","Galego" // Galician DATA "ka","Kartuli" // Georgian DATA "de","Deutsch" // German DATA "el","Elliniká" // Greek DATA "gn","avañe'e" // Guaraní DATA "gu","Gujrati" // Gujarati DATA "ht","Kreyòl ayisyen"// Hatian DATA "ha","Hausa" // Hausa DATA "he","Ivrit" // Hebrew DATA "hz","Otjiherero" // Herero DATA "hi","Hindi" // Hindi DATA "ho","Hiri Motu" // Hiri Motu DATA "hu","Magyar" // Hungarian DATA "ia","Interlingua" // Interlingua DATA "id","Bahasa Indonesia" // Indonesian DATA "ie","Interlingue/Occidental" // Interlingue DATA "ga","Gaeilge" // Irish DATA "ig","Asusu Igbo" // Igbo DATA "ik","Iñupiaq" // Inupiaq DATA "io","Ido" // Ido DATA "is","Íslenska" // Icelandic DATA "it","Italiano" // Italian DATA "iu","Inuktitut" // Inuktitut DATA "ja","Nihongo" // Japanese DATA "jv","basa Jawa" // Javanese DATA "kl","kalaallisut" // Kalaallisut, Greenlandic DATA "kn","kannada" // Kannada DATA "kr","Kanuri" // Kanuri DATA "ks","kasšur" // Kashmiri DATA "kk","Qazaq tili" // Kazakh DATA "km","Phisa Khmer" // Khmer DATA "ki","Gikuyu" // Kikuyu DATA "kw","Kernewek" // Cornish DATA "rw","Ikinyarwanda" // Kinyarwanda DATA "ky","Kyrgyz tili" // Kyrgyz DATA "kv","komi kyv" // Komi DATA "kg","KiKongo" // Kongo DATA "ko","Hangugeo" // Korean DATA "ku","Kurdî" // Kurdish DATA "kj","Kuanyama" // Kwanyama DATA "la","latine" // Latin DATA "lb","Lëtzebuergesch"// Luxembourgish DATA "lg","Luganda" // Luganda DATA "li","Limburgs" // Limburgish DATA "ln","Lingála" // Lingala DATA "lo","phasa lao" // Lao DATA "lt","lietuviu kalba"// Lithuanian DATA "lu","Tshiluba" // Bantu DATA "lv","latviešu valoda" // Latvian DATA "gv","Gaelg" // Manx DATA "mk","Makedonski jazik" // Macedonian DATA "mg","Malagasy fiteny" // Malagasy DATA "ms","bahasa Melayu" // Malay DATA "ml","malayalam " // Malayalam DATA "mt","Malti" // Maltese DATA "mi","te reo Maori" // Maroi DATA "mr","Marathi" // Marathi DATA "mh","Kajin M¸ajel" // Marshallese DATA "mn","Mongol" // Mongolian DATA "na","Ekakairu" // Nauru DATA "nv","Diné bizaad" // Navajo DATA "nb","Norsk bokmål" // Norwegian DATA "nd","isiNdebele" // North Ndebele DATA "ne","Nepali" // Nepalese DATA "ng","Owambo" // Ndonga DATA "nn","Norsk nynorsk" // Norwegian DATA "no","Norsk" // Norwegian DATA "ii","Nuosuhxop" // Nuosu

118 DATA "nr","isiNdebele" // South Ndebele DATA "oc","Occitan" // Occitan DATA "oj","Anishinaabemowin" // Ojibwe DATA "cu","sloveniskyi jezyku" // Old Church Slavonic DATA "om","Afaan Oromoo" // Oromo DATA "or","odia" // Oriya DATA "os","Ironau" // Ossetic DATA "pa","Pañjabi" // Punjabi DATA "pi","Pali" // Pali DATA "fa","Farsi" // Persian DATA "pl","polski" // Polish DATA "ps","Pakhto" // Pashto DATA "pt","Português" // Portuguese DATA "qu","Runa Simi" // Quechua DATA "rm","rumantsch" // Romansh DATA "rn","kiRundi" // Kirundi DATA "ro","româna" // Romanian DATA "ru","Russkiy yazyk" // Russian DATA "sa","samskrtam" // Sanskrit DATA "sc","sardu" // Sardinian DATA "sd","Sindhi" // Sindhi DATA "se","Davvisámegiella" // North Sami DATA "sm","gagana fa'a Samoa" // Samoan DATA "sg","yângâ tî sängö"// Sango DATA "sr","srpski" // Serbian DATA "gd","Gàidhlig" // Scottish Gaelic DATA "sn","chiShona" // Shona DATA "si","sinhala" // Sinhala DATA "sk","slovencina" // Slovakian DATA "sl","slovenšcina" // Slovenian DATA "so","Soomaaliga" // Somali DATA "st","Sesotho" // Southern Sotho DATA "es","español" // Spanish DATA "su","Basa Sunda" // Sundanese DATA "sw","Kiswahili" // Swahili DATA "ss","SiSwati" // Swati DATA "sv","svenska" // Swedish DATA "ta","tamil" // Tamil DATA "te","telugu" // Telugu DATA "tg","tojiki" // Tajik DATA "th","phasa thai" // Thai DATA "ti","tigriñña" // Tigrinya DATA "bo","bod skad" // Standard Tibetan DATA "tk","Türkmen" // Turkmen DATA "tl","Wikang" // Tagalog DATA "tn","Setswana" // Tswana DATA "to","faka Tonga" // Tonga DATA "tr","Türkçe" // Turkish DATA "ts","Xitsonga" // Tsonga DATA "tt","tatarça" // Tatar DATA "tw","Twi" // Twi DATA "ty","Reo Tahiti" // Tahitian DATA "ug","Uyghurche" // Uyghur DATA "uk","ukrayins'ka mova" // Ukrainian DATA "ur","Urdu" // Urdu DATA "uz","O'zbek" // Uzbek DATA "ve","Tshivenda" // Venda DATA "vi","Tieng Viet" // Vietnamese DATA "vo","Volapük" // Volapuk DATA "wa","Walon" // Walloon DATA "cy","Cymraeg" // Welsh DATA "wo","Wollof" //Wolof DATA "fy","Frysk" // Western Frisian DATA "xh","isiXhosa" // Xhosa DATA "yi","yidish" // Yiddish DATA "yo","Yorùbá" // Yoruba DATA "za","Saw cuengh" // Zhuang DATA "zu","isiZulu" // Zulu DATA "","" ENDDATA

119 Hexadecimal

This routine converts integers to and from a hexadecimal number. Its mainly used by my “Graphic to DATA” GLBasic routine. TYPE THex //! Convert a decimal number to a hexadecimal number //\param value% - Number of be converted //\param length% - Number of characters that the result will be padded to //\return The hexidecimal value as a string FUNCTION decToHex$:value%,length%=4 LOCAL digit% LOCAL temp% LOCAL result$ IF length%<=0 RETURN "0" ENDIF result$="" FOR digit%=length% TO 1 STEP -1 temp%=MOD(value%,16) IF temp%<10 result$=CHR$(temp%+48)+result$ ELSE result$=CHR$((temp%-10)+65)+result$ ENDIF value%=value%/16 NEXT RETURN result$ ENDFUNCTION //! Convert a hexadecimal number of an integer //\param hex$ - Hexidecimal string //\return The decimal value of the passed string FUNCTION hexToDec%:hex$ LOCAL i% LOCAL j% LOCAL loop% i%=0 j%=0 FOR loop%=0 TO LEN(hex$)-1 i%=ASC(MID$(hex$,loop%,1))-48 IF 9

120 Networking Host

This routine creates a hosting session for a network game and processes all incoming and outgoing data. It's horribly complicated... TYPE tGameOptions numPlaying% password$ numRounds% gameType% allowHoles% tileIndex% ENDTYPE TYPE THost minLeagueRounds% = 3 maxLeagueRounds% = 16 gameOptions AS tGameOptions screenWidth%;screenHeight% thisIP% socket% udpPort% name$ // Game setting variables minPlayers% maxPlayers% networkLobbyTitle$ setupHostGame$ numberOfPlayersWidget$="Number Of Players :" numberOfPlayersText$="numPlayers" numberOfPlayersInput$ passwordWidget$="Enter A Password :" passwordText$="password" passwordInput$ optionsButton$="optionsButton" backButton$="backButton" backText$ finishedButton$="finished" finishedText$ kickButton$="kickButton" kickText$ startButton$="startButton" startText$ sendButton$="send" okay$ sendText$ ready$ notReady$ allowHoleWidget$="allowHoleWidget" allowHoleRadio$="allowHoleRadio" allowHoleText$ gameListType$="gameListType" runRoundsSlider$="numRoundsSlider" tileButton$="tileButton" listOfPlayers$ playersList$="playersList" chatListText$ chatTextBox$="chatText" chatText$="chat" gameTypeText$ leagueRoundsText$ tileTypeText$ chatMessages$ TILE_DEFAULT% = -2 TILE_RANDOM% = -1 TILE_1% = 0

121 MAX_TILES% = 8 needAtLeastTwoText$ playerList[] AS tPlayer tileList[] AS tTile gameTypeName$[] errorList$[] gameList$ checkForGameStart% gameState% errorWindowButton$="errorButton" _errorWindowOpen% _gameSetupWindowOpen% updateLobby% ERROR_NOTEVERYONEISREADY% = 0 ERROR_MINPLAYERSNOTREACHED% = 1 ERROR_INVALIDNUMBEROFPLAYERS% = 2 FUNCTION LoadLanguageText%:localisation AS TLocalisation self.backText$=UCASE$(localisation.LocaliseText$("{{back}}")) self.finishedText$=localisation.LocaliseText$("{{finished}}") self.sendText$=localisation.LocaliseText$("{{send}}") self.kickText$=localisation.LocaliseText$("{{kick}}") self.startText$=localisation.LocaliseText$("{{start}}")

self.listOfPlayers$=localisation.LocaliseText$("{{listofplayers}} :") self.chatListText$=localisation.LocaliseText$("{{chatlist}} :") self.networkLobbyTitle$=localisation.LocaliseText$("{{networklobby}}") self.setupHostGame$=localisation.LocaliseText$("{{setupgame}}") self.needAtLeastTwoText$=localisation.LocaliseText$ ("{{needatleast2players}}") self.ready$=localisation.LocaliseText$("{{ready}}") self.notReady$=localisation.LocaliseText$("{{notready}}") self.allowHoleText$=localisation.LocaliseText$("{{allowtileswithholeS}}") self.okay$=localisation.LocaliseText$("{{okay}}") self.gameTypeText$=localisation.LocaliseText$("{{gametype}} :") self.leagueRoundsText$=localisation.LocaliseText$("{{leaguerounds}} :") self.tileTypeText$=localisation.LocaliseText$("{{tiletype}} :") DIM self.gameTypeName$[0] DIMPUSH self.gameTypeName$[],localisation.LocaliseText$("{{leaguegame}}") DIMPUSH self.gameTypeName$[],localisation.LocaliseText$("{{upto3}}") DIMPUSH self.gameTypeName$[],localisation.LocaliseText$("{{single}}") DIM self.errorList$[0] DIMPUSH self.errorList$[],localisation.LocaliseText$ ("{{noteveryoneisready}}") DIMPUSH self.errorList$[],localisation.LocaliseText$ ("{{minplayersnotreached}}") DIMPUSH self.errorList$[],localisation.LocaliseText$ ("{{invalidnumplayers}}") RETURN TRUE ENDFUNCTION FUNCTION Initialise%:setup AS TSetup,minPlayers%,maxPlayers% LOCAL loop% LOCAL SCREEN%= 1 LOCAL tile AS tTile LOCAL temp$ self.Destroy() self.screenWidth%=setup.ReturnScreenWidth() self.screenHeight%=setup.ReturnScreenHeight() self.gameList$=""

122 FOREACH temp$ IN self.gameTypeName$[] INC self.gameList$,temp$+"|" NEXT self.gameList$=LEFT$(self.gameList$,LEN(self.gameList$)-1) self.thisIP%=0 self.socket%=INVALID% self.minPlayers%=minPlayers% IF maxPlayers%>minPlayers% THEN self.maxPlayers%=minPlayers%+1 self.maxPlayers%=MIN(self.maxPlayers%,maxPlayers%) // Setup initial game options self.gameOptions.numPlaying%=self.minPlayers% self.gameOptions.password$="" self.gameOptions.numRounds%=self.minLeagueRounds% self.gameOptions.gameType%=GAME_LEAGUE% // League self.gameOptions.allowHoles%=TRUE self.gameOptions.tileIndex%=0 // Split up the sprites FOR loop%=self.TILE_DEFAULT% TO self.MAX_TILES%-1 tile.index%=GENSPRITE() IF tile.index%<0 THEN RETURN FALSE tile.code%=loop% tile.tileToUse%=loop-self.TILE_1% CREATESCREEN SCREEN%,tile.index %,setup.returnSpriteWidth(SPRITE_TILES1%,FALSE),setup.returnSpriteHeight(SPRITE_TILES1%, FALSE) USESCREEN SCREEN% ALPHAMODE 0.0 SMOOTHSHADING FALSE IF loop%>=self.TILE_1% setup.DrawSpr(SPRITE_TILES1%,loop-self.TILE_1%,0.0,0.0) ELSE SELECT loop% CASE self.TILE_DEFAULT% // Default tile for level setup.DrawSpr(SPRITE_DEFAULT%,-1,0.0,0.0) CASE self.TILE_RANDOM% // Random tile for level setup.DrawSpr(SPRITE_RANDOM%,-1,0.0,0.0) ENDSELECT ENDIF USESCREEN -1 DIMPUSH self.tileList[],tile NEXT RETURN TRUE ENDFUNCTION FUNCTION Destroy%: DIM self.tileList[0] ENDFUNCTION FUNCTION Finish%: DIM self.gameTypeName$[0] DIM self.errorList$[0] ENDFUNCTION FUNCTION SetupNetwork%:thisIP%,socket%,udpPort%,name$ DIM self.playerList[0] self.thisIP%=thisIP% self.socket%=socket% self.udpPort%=udpPort% self.name$=name$ self.gameState%=AREA_LOBBY%

123 self.chatMessages$="" self.checkForGameStart%=FALSE self._gameSetupWindowOpen%=FALSE self._errorWindowOpen%=FALSE self.updateLobby%=UPDATE_NONE% ENDFUNCTION FUNCTION CreateLobbySetupWindow%:setup AS TSetup LOCAL windowWidth%,windowHeight%,buttonSize% LOCAL optionOffset%=120 LOCAL optionWidth%=110 IF self._gameSetupWindowOpen%=TRUE THEN RETURN FALSE self._gameSetupWindowOpen%=TRUE setup.createAWindow(self.setupHostGame$,windowWidth%,windowHeight%) buttonSize%=(windowWidth%/2)-8 DDgui_widget("",self.numberOfPlayersWidget$,buttonSize%,0) DDgui_widget("",self.passwordWidget$,buttonSize%,0) DDgui_text(self.numberOfPlayersText$,self.gameOptions.numPlaying %,buttonSize%,0) DDgui_text(self.passwordText$,self.gameOptions.password$,buttonSize%,0) DDgui_widget("",self.gameTypeText$,buttonSize%,0) DDgui_combo(self.gameListType$,LEFT$(self.gameList$,LEN(self.gameList$)- 1),buttonSize%,0) DDgui_set(self.gameListType$,SLECTED$,self.gameOptions.gameType%- GAME_LEAGUE%) DDgui_widget("",self.leagueRoundsText$,buttonSize%,0) DDgui_slider(self.runRoundsSlider$,self.gameOptions.numRounds%,buttonSize%) DDgui_set(self.runRoundsSlider$,"MINVAL",self.minLeagueRounds%) DDgui_set(self.runRoundsSlider$,"MAXVAL",self.maxLeagueRounds%) DDgui_set(self.runRoundsSlider$,"STEP",1) DDgui_set(self.runRoundsSlider$,TEXT$,self.gameOptions.numRounds%) DDgui_widget(self.allowHoleWidget$,"Allow Holes ?",buttonSize%,0) DDgui_radio(self.allowHoleRadio$,yesNo$,buttonSize%) IF self.gameOptions.allowHoles%=TRUE DDgui_set(self.allowHoleRadio$,SLECTED$,YES%) // YES is always first ELSE DDgui_set(self.allowHoleRadio$,SLECTED$,NO%) ENDIF DDgui_widget("",self.tileTypeText$,buttonSize%,0) DDgui_button(self.tileButton$,"SPR_B"+self.tileList[self.gameOptions.tileIndex %].index%,optionWidth%,0) DDgui_button(self.backButton$,self.backText$,buttonSize%,0) DDgui_button(self.finishedButton$,self.finishedText$,buttonSize%,0) ENDFUNCTION FUNCTION CreateErrorWindow%:setup AS TSetup,errorType% LOCAL windowWidth%,windowHeight%,title$ IF self._errorWindowOpen%=TRUE THEN RETURN TRUE self._errorWindowOpen%=TRUE setup.createAWindow(title$,windowWidth%,windowHeight%,80) DDgui_widget("",self.errorList$[errorType%-self.ERROR_NOTEVERYONEISREADY %],windowWidth%-8,0) DDgui_button(self.errorWindowButton$,self.okay$,windowWidth%-8,0) ENDFUNCTION FUNCTION CreateLobbyWindow%:setup AS TSetup LOCAL windowWidth%,windowHeight%,buttonSize%,ySize%,yPos%,listHeight%=20 LOCAL sendButtonWidth%=84 setup.createAWindow(self.networkLobbyTitle$,windowWidth%,windowHeight%) buttonSize%=(windowWidth%/3)-10

124 DDgui_widget("",self.listOfPlayers$,windowWidth%-8,0) DDgui_list(self.playersList$,"",windowWidth%-8,listHeight%) DDgui_widget("",self.chatListText$,windowWidth%-8,0) DDgui_text(self.chatTextBox$,"",windowWidth%-8,listHeight%) DDgui_text(self.chatText$,"",windowWidth%-sendButtonWidth%-8,0) DDgui_set(self.chatTextBox$,"READONLY",TRUE) DDgui_button(self.sendButton$,self.sendText$,sendButtonWidth%,0) DDgui_button(self.optionsButton$,"<",10,0) DDgui_button(self.backButton$,self.backText$,buttonSize%,0) DDgui_button(self.kickButton$,self.kickText$,buttonSize%,0) DDgui_button(self.startButton$,self.startText$,buttonSize%,0) DDgui_show(FALSE) yPos%=DDgui_get(self.backButton$,"YPOS") +DDgui_get(self.backButton$,"HEIGHT") DEBUG "Pos : "+yPos%+"\n" ySize%=((windowHeight%-yPos%)/2)+listHeight% DEBUG "Size : "+ySize%+"\n" DDgui_set(self.playersList$,"HEIGHT",ySize%) DDgui_set(self.chatTextBox$,"HEIGHT",ySize%) DDgui_show(FALSE) ENDFUNCTION FUNCTION ProcessGUI%:setup AS TSetup,networkMessage AS TProcessNetworkMessage IF DDgui_get(self.optionsButton$,CLICKED$) OR (self.IsFirstRun()=TRUE AND self._gameSetupWindowOpen%=FALSE) // If the error message is being, then dont do anything self.CreateLobbySetupWindow(setup) ELSEIF self._gameSetupWindowOpen%=TRUE // Handle game setup window stuff IF DDgui_get(self.gameListType$,CLICKED$) LOCAL temp% // Enable or disable the number of rounds depending on whether a single game was selected or not temp%=DDgui_get(self.gameListType$,SLECTED$) IF temp%>=0 IF temp%+GAME_LEAGUE%=GAME_SINGLE% DDgui_set(self.runRoundsSlider$,"READONLY",TRUE) ELSE DDgui_set(self.runRoundsSlider$,"READONLY",FALSE) ENDIF ENDIF DEBUG temp%+"\n" ELSEIF DDgui_get(self.tileButton$,CLICKED$) INC self.gameOptions.tileIndex% IF self.gameOptions.tileIndex%>=BOUNDS(self.tileList[],0) THEN self.gameOptions.tileIndex%=0 DDgui_set(self.tileButton$,"TEXT","SPR_B"+self.tileList[self.gameOptions.tileIndex %].index%) ELSEIF DDgui_get(self.finishedButton$,CLICKED$) LOCAL temp$ // Deal with password temp$=DDgui_get$(self.passwordText$,"TEXT") IF temp$<>"" self.gameOptions.password$=ENCRYPT$(PLATFORMINFO$ ("HOSTID"),temp$) // If blank, then no password needed ELSE self.gameOptions.password$="" ENDIF // Get the game type self.gameOptions.gameType %=INTEGER(DDgui_get(self.gameListType$,SLECTED$))+GAME_LEAGUE% DEBUG self.gameOptions.numPlaying%+"\n" // Allow holes? IF INTEGER(DDgui_get(self.allowHoleRadio$,SLECTED$))=YES% self.gameOptions.allowHoles%=TRUE ELSE

125 self.gameOptions.allowHoles%=FALSE ENDIF // Maximum number of players self.gameOptions.numPlaying %=INTEGER(DDgui_get(self.numberOfPlayersText$,"TEXT")) IF self.gameOptions.numPlaying%<2 // Need at least 2 players! self.CreateErrorWindow(setup,self.ERROR_MINPLAYERSNOTREACHED%) //DDgui_msg(self.needAtLeastTwoText$,FALSE) ELSEIF self.gameOptions.numPlaying%self.maxPlayers% self.CreateErrorWindow(setup,self.ERROR_INVALIDNUMBEROFPLAYERS%) ELSE DDgui_popdialog() self._gameSetupWindowOpen%=FALSE // Is this the first run ? IF self.IsFirstRun()=TRUE // Add the player to the player list self.addToClientList(self.thisIP %,self.name$,TRUE) self.updateLobby%=bOR(self.updateLobby %,UPDATE_CLIENT%) ELSE // Send an update message LOCAL loop AS tPlayer

FOREACH loop IN self.playerList[] IF loop.ipAddress%=self.thisIP% ELSE self.sendServerDetails(networkMessage,self.thisIP%) ENDIF NEXT ENDIF ENDIF ENDIF ELSE IF DDgui_get(self.backButton$,CLICKED$) // Leaving lobby or setup window // Exit networking or just close the window ? DDgui_popdialog() IF self._gameSetupWindowOpen%=FALSE // Exit lobby RETURN RESULT_LEAVELOBBY% ELSE self._gameSetupWindowOpen%=FALSE ENDIF ENDIF IF self._errorWindowOpen%=TRUE IF DDgui_get(self.errorWindowButton$,CLICKED$) DDgui_popdialog() self._errorWindowOpen%=FALSE ENDIF ELSE // Lobby stuff IF DDgui_get(self.sendButton$,CLICKED$) networkMessage.ProcessChatMessage(self.playerList[],self.chatText$,DDgui_get(self.player sList$,SLECTED$),self.chatMessages$,self.updateLobby%) ELSEIF DDgui_get(self.startButton$,CLICKED$) IF BOUNDS(self.playerList[],0)

126 // Tell all clients to start FOREACH loop IN self.playerList[] IF loop.ipAddress %<>self.thisIP% networkMessage.sendServerClientStartGame(loop.ipAddress%) ENDIF NEXT self.checkForGameStart%=TRUE ENDIF ENDIF ENDIF ELSEIF DDgui_get(self.kickButton$,CLICKED$) LOCAL index% index%=DDgui_get(self.playersList$,SLECTED$) IF index%>=0 IF self.playerList[index%].ipAddress %<>self.thisIP% networkMessage.sendServerKickPlayer(self.playerList[index%].ipAddress%)

self.removeFromClientList(self.playerList[index%].ipAddress%) self.updateLobby%=bOR(self.updateLobby %,UPDATE_CLIENT%) ENDIF ENDIF ENDIF ENDIF ENDIF IF self._errorWindowOpen%=FALSE AND self._gameSetupWindowOpen%=FALSE IF bAND(self.updateLobby%,UPDATE_CLIENT%) self.buildClientDisplay() self.updateLobby%=bXOR(self.updateLobby%,UPDATE_CLIENT%) ENDIF IF bAND(self.updateLobby%,UPDATE_CHAT%) DDgui_set(self.chatTextBox$,"TEXT",self.chatMessages$) self.updateLobby%=bXOR(self.updateLobby%,UPDATE_CHAT%) ENDIF ENDIF IF self.checkForGameStart%=TRUE LOCAL isReady% // Check to make sure everyone has acknowledged game start. // This allows disconnections just before we start isReady%=TRUE FOREACH loop IN self.playerList[] IF loop.isReady%=FALSE THEN isReady%=FALSE NEXT IF isReady%=TRUE // Everyone is ready RETURN RESULT_STARTGAME% ENDIF ENDIF RETURN RESULT_OK% ENDFUNCTION FUNCTION HandleMessage%:setup AS TSetup,messageData$[],networkMessage AS TProcessNetworkMessage LOCAL sendID%,fromIP%,toIP%,msg% msg%=INTEGER(messageData$[NETMESSAGE_CODE%]) sendID%=INTEGER(messageData$[NETMESSAGE_SENDID%]) fromIP%=INTEGER(messageData$[NETMESSAGE_FROMIP%]) toIP%=INTEGER(messageData$[NETMESSAGE_TOIP%]) SELECT msg% CASE MESSAGE_ACKNOWLEDGEMENT% // Handle acknowledgments from messages previously sent LOCAL

127 message AS tNetData LOCAL temp$[]

networkMessage.RemoveAck(INTEGER(messageData$[NETMESSAGE_DATA%]),message) SELECT message.netCode% CASE MESSAGE_CLIENTCANJOIN% // Client has acknowledged the request to join, so

CASE MESSAGE_PING% // Whoever sent it is still alive! networkMessage.updatePing(fromIP %,messageData$[NETMESSAGE_TIME%],self.playerList[]) self.updateLobby %=bOR(self.updateLobby%,UPDATE_CLIENT%)

CASE RESULT_STARTGAME% // A client has acknowledge start game message LOCAL loop AS tPlayer

FOREACH loop IN self.playerList[]

IF loop.ipAddress%=fromIP% loop.isReady%=TRUE ENDIF NEXT ENDSELECT CASE MESSAGE_CHALLENGESERVER% // A computer wants to know about this server self.sendServerDetails(networkMessage,fromIP%) CASE MESSAGE_DISPLAYCHATMESSAGE% // Add a chat message to the chat list networkMessage.UpdateChatMessage(self.chatMessages$,self.updateLobby%,messageData$ [NETMESSAGE_DATA%],messageData$[NETMESSAGE_DATA%+1]) CASE MESSAGE_PING% // Someone send a PING command, so we know the receipticant is alive networkMessage.sendAcknowledgement(fromIP%,sendID%,msg%) //networkM essage.updatePing(fromIP%,messageData$[NETMESSAGE_TIME%],self.playerList[]) //self.upd ateLobby%=bOR(self.updateLobby%,UPDATE_CLIENT%) CASE MESSAGE_READYSTATUS% // Client status has changed //LOCAL value% //value %=INTEGER(messageData$[NETMESSAGE_DATA%]) networkMessage.updateStatus(self.playerList[],fromIP%,INTEGER(messageData$ [NETMESSAGE_DATA%])) self.updateLobby%=bOR(self.updateLobby%,UPDATE_CLIENT%) // Send the acknowledgment networkMessage.sendAcknowledgement(fromIP%,sendID%,msg%)

128 // Now we tell all the players about it //FOREACH loop IN self.playerList[] // IF loop.ipAddress%=self.thisIP% // self.buildClientDisplay() // ELSE // networkMessage.sendServerReadyStatus(loop.ipAddress%,fromIP%,value%) // ENDIF //NEXT CASE MESSAGE_CLIENTDISCONNECTED% // A client has left LOCAL temp$

self.removeFromClientList(fromIP%) // Inform any other clients temp$=self.buildClientListToSend$() FOREACH loop IN self.playerList[] IF loop.ipAddress%=self.thisIP% // Add to list self.updateLobby%=bOR(self.updateLobby%,UPDATE_CLIENT%) ELSE networkMessage.sendServerUpdateClientList(loop.ipAddress%,temp$) ENDIF NEXT CASE MESSAGE_WANTTOJOIN% // A client wants to join this server LOCAL ok %,temp$ IF self.gameOptions.password$<>"" IF BOUNDS(messageData$[],0)<8 networkMessage.sendServerInvalidPassword(fromIP%) ok%=FALSE ELSEIF DECRYPT$(PLATFORMINFO$("HOSTID"),self.gameOptions.password$)=DECRYPT$ (PLATFORMINFO$("HOSTID"),messageData$[NETMESSAGE_DATA%+1]) ok%=TRUE ELSE // Invalid password networkMessage.sendServerInvalidPassword(fromIP%) ok%=FALSE ENDIF ELSE // Are we in a game ? // if gameState% etc...

129 ok %=TRUE ENDIF IF ok %=TRUE // Have we reached the player limit ? IF BOUNDS(self.playerList[],0)

FOREACH loop IN self.playerList[] IF loop.ipAddress%=self.thisIP% // Add to list self.addToClientList(fromIP%,messageData$[NETMESSAGE_DATA%],FALSE) self.updateLobby%=bOR(self.updateLobby%,UPDATE_CLIENT%) ELSEIF loop.ipAddress%=fromIP%

// Tell the user they can join networkMessage.sendServerClientCanJoin(fromIP%,temp$) ELSE networkMessage.sendServerUpdateClientList(loop.ipAddress%,temp$) ENDIF NEXT ELSE networkMessage.sendServerFull(fromIP%) ENDIF ENDIF ENDSELECT RETURN RESULT_OK% ENDFUNCTION //! Process timers FUNCTION ProcessTimers%:networkMessage AS TProcessNetworkMessage LOCAL loop AS tPlayer // Ping all clients according to their time FOREACH loop IN self.playerList[] IF loop.ipAddress%<>self.thisIP% IF ABS(GETTIMERALL()-loop.currentTime)>=5000.0 networkMessage.sendPing(loop.ipAddress%) loop.currentTime=GETTIMERALL() ENDIF ENDIF NEXT ENDFUNCTION //! Send response to broadcast request or update game options FUNCTION sendServerDetails%:networkMessage AS TProcessNetworkMessage,fromIP% LOCAL temp% IF self.gameOptions.password$="" temp%=FALSE ELSE temp%=TRUE ENDIF

130 networkMessage.sendServerChallengeResponse(fromIP %,BOUNDS(self.playerList[],0),temp%,self.gameOptions.gameType%, _ self.gameOptions.allowHoles%,self.gameState%) ENDFUNCTION //! Are we showing the host setup window for the first time, or being modified after the first setup FUNCTION IsFirstRun%: IF BOUNDS(self.playerList[],0)=0 RETURN TRUE ELSE RETURN FALSE ENDIF ENDFUNCTION //! Remove from list of clients FUNCTION removeFromClientList%:ipAddress% LOCAL client AS tPlayer FOREACH client IN self.playerList[] IF client.ipAddress%=ipAddress% DELETE client ENDIF NEXT ENDFUNCTION //! Add player (client or host to client list) FUNCTION addToClientList%:ipAddress%,name$,IsHost% LOCAL client AS tPlayer LOCAL loop%,found% client.IsHost%=IsHost% client.ipAddress%=ipAddress% IF IsHost%=TRUE client.isReady%=TRUE ELSE client.isReady%=FALSE ENDIF client.name$=name$ client.ping=0.0 client.isAlive%=TRUE client.reTries%=4 DEBUG "nAME : "+client.name$+"\n" found%=INVALID% FOR loop%=0 TO BOUNDS(self.playerList[],0)-1 IF self.playerList[loop%].ipAddress%=client.ipAddress% found%=loop% BREAK ENDIF NEXT IF found%=INVALID% DIMPUSH self.playerList[],client ENDIF ENDFUNCTION //! Build display of clients FUNCTION buildClientDisplay%: LOCAL loop AS tPlayer LOCAL text$ text$="" FOREACH loop IN self.playerList[] INC text$,"("+loop.ping%+") "+loop.name$+" (" IF loop.isReady%=TRUE INC text$,self.ready$ ELSE INC text$,self.notReady$ ENDIF INC text$,")|" NEXT DDgui_set(self.playersList$,"TEXT",LEFT$(text$,LEN(text$)-1)) ENDFUNCTION

131 //! Build list of clients to send FUNCTION buildClientListToSend$:toAddName$="",toAddIP%=0 LOCAL temp$ LOCAL loop AS tPlayer temp$="" FOREACH loop IN self.playerList[] INC temp$,loop.name$+SEPERATOR$+loop.ipAddress%+SEPERATOR$ +loop.IsHost%+SEPERATOR$+loop.isReady%+SEPERATOR$ NEXT IF toAddName$<>"" THEN INC temp$,toAddName$+SEPERATOR$+toAddIP%+SEPERATOR$ +FALSE+SEPERATOR$+FALSE+SEPERATOR$ DEBUG temp$+"\n" RETURN LEFT$(temp$,LEN(temp$)-1) ENDFUNCTION FUNCTION ShutdownSocketSystem%: IF self.socket%<>INVALID% SOCK_CLOSE(self.socket%) self.socket%=INVALID% ENDIF ENDFUNCTION //! Is everyone ready ? FUNCTION IsEveryoneReady%: LOCAL loop AS tPlayer

FOREACH loop IN self.playerList[] IF loop.isReady%=FALSE THEN RETURN FALSE NEXT RETURN TRUE ENDFUNCTION FUNCTION ResetAllACK%: LOCAL loop AS tPlayer FOREACH loop IN self.playerList[] loop.hasACK%=FALSE NEXT ENDFUNCTION //! Have all clients received the last message ? FUNCTION HaveAllClientsReceivedMessage%:fromIP% LOCAL yes% LOCAL loop AS tPlayer yes%=TRUE FOREACH loop IN self.playerList[] IF loop.ipAddress%=fromIP% THEN loop.hasACK%=TRUE IF loop.hasACK%=FALSE THEN yes%=FALSE NEXT RETURN yes% ENDFUNCTION FUNCTION HandleNoResponse%:setup AS TSetup,noResponseIP% // Remove the player from the players list self.removeFromClientList(noResponseIP%) self.updateLobby%=bOR(self.updateLobby%,UPDATE_CLIENT%) ENDFUNCTION FUNCTION getPlayerName$: RETURN self.name$ ENDFUNCTION FUNCTION SendUserDataToClients%:networkMessage AS TProcessNetworkMessage,messageID %,data$,reliable%,singleIP% LOCAL loop AS tPlayer IF singleIP%=0 FOREACH loop IN self.playerList[] IF loop.IsHost%=FALSE networkMessage.sendUserMessage(loop.ipAddress%,messageID

132 %,data$,reliable%) ENDIF NEXT ELSE networkMessage.sendUserMessage(singleIP%,messageID%,data$,reliable%) ENDIF ENDFUNCTION FUNCTION ReturnIPAddress%: RETURN self.thisIP% ENDFUNCTION FUNCTION ReturnNumberOfPlayers%: RETURN BOUNDS(self.playerList[],0) ENDFUNCTION FUNCTION ClearAllReady%: LOCAL loop AS tPlayer FOREACH loop IN self.playerList[] loop.isReady%=FALSE NEXT ENDFUNCTION ENDTYPE

133 Lobby

This routine creates a network lobby system. This part is mainly for creating the DDGui controlled window and buttons and calling the relevant functions if, like the “Join” button is pressed.

CONSTANT MESSAGE_CHALLENGESERVER$ = "getChallenge" CONSTANT MESSAGE_CHALLENGERESPONSE$ = "challengeResponse" CONSTANT MESSAGE_DISPLAYCHATMESSAGE$ = "displayChatMsg" CONSTANT MESSAGE_SERVERHASSHUTDOWN$ = "serverShutDown" CONSTANT AREA_LOBBY% = 0 CONSTANT AREA_INGAME% = 1 CONSTANT RESULT_SOCKETERROR% = -127 CONSTANT RESULT_SERVERHASDISCONNECTED% = -3 CONSTANT RESULT_CLIENTHASDISCONNECTED% = -2 CONSTANT RESULT_LEAVELOBBY% = -1 CONSTANT RESULT_OK% = 0 CONSTANT RESULT_STARTGAME% = 1 CONSTANT RESULT_NONETWORKMODE% = 127 CONSTANT RESULT_USERMESSAGE% = 255

TYPE TLobby host AS THost client AS TClient isNetworkMode% = FALSE screenWidth%;screenHeight% enterYourNameText$ nameEntryText$="name" backButton$="back" abortButton$="abort" backText$ finishedButton$="finished" finishedText$ minMessageRange% maxMessageRange% isPlayerHost% udpPort%=49152 networkData$[] networkMessage AS TProcessNetworkMessage FUNCTION LoadLanguageText%:localisation AS TLocalisation IF self.host.LoadLanguageText(localisation)=TRUE IF self.client.LoadLanguageText(localisation)=TRUE self.enterYourNameText$=localisation.LocaliseText$ ("{{enteryourname}} :") self.backText$=localisation.LocaliseText$("{{back}}") self.finishedText$=localisation.LocaliseText$("{{finished}}") RETURN TRUE ENDIF ENDIF RETURN FALSE ENDFUNCTION FUNCTION Initialise%:setup AS TSetup,minPlayers%,minMessageRange%=- 1,maxMessageRange%=-1 self.Destroy() IF self.host.Initialise(setup,minPlayers%,setup.ReturnMaxPlayers())=FALSE THEN RETURN FALSE IF self.client.Initialise(setup)=FALSE THEN RETURN FALSE self.screenWidth%=setup.ReturnScreenWidth() self.screenHeight%=setup.ReturnScreenHeight()

134 self.minMessageRange%=minMessageRange% self.maxMessageRange%=maxMessageRange% RETURN TRUE ENDFUNCTION //! Destroy only things that need to be destroyed when changing screen resolutions FUNCTION Destroy%: self.client.Destroy() self.host.Destroy() DIM self.networkData$[0] self.isNetworkMode%=FALSE ENDFUNCTION FUNCTION Finish%: self.client.Destroy() self.host.Destroy() self.Destroy() self.client.Finish() self.host.Finish() ENDFUNCTION FUNCTION createNameWindow%: LOCAL width%=170 LOCAL height%=80 LOCAL buttonSize% SETFONT FONT_DEFAULT% buttonSize%=(width%/2)-8 DDgui_pushdialog((self.screenWidth%-width%)/2,(self.screenHeight%-height %)/2,width%,height%) DDgui_set("","TEXT","") DDgui_widget("",self.enterYourNameText$,width%-8,0) DDgui_text(self.nameEntryText$,"",width%-8,0) DDgui_button(self.backButton$,self.backText$,buttonSize%,0) DDgui_button(self.finishedButton$,self.finishedText$,buttonSize%,0) ENDFUNCTION FUNCTION createWaitingWindow%: LOCAL width% LOCAL height% width%=self.screenWidth% height%=52 DDgui_pushdialog((self.screenWidth%-width%)/2,(self.screenHeight%-height %)/2,width%,height%) DDgui_set("","TEXT","") DDgui_widget("a","Please Wait - Contacting GLBasicWorld Server") DDgui_set("a","ALIGN",0) DDgui_button(self.abortButton$,"Abort",self.screenWidth%-8,0) DDgui_set("a","ALIGN",0) ENDFUNCTION FUNCTION SetUserDefinedMessageRange%:minRange%,maxRange% self.minMessageRange%=minRange% self.maxMessageRange%=maxRange% ENDFUNCTION FUNCTION CreateOnlineGame%:setup AS TSetup,doHostGame% LOCAL result$ LOCAL udpPort%=80 LOCAL HOST% = 1 LOCAL JOIN% = 0 LOCAL Disconnect% = -1 LOCAL name$,thisIP%,socket%,status% LOCAL socket2% IF self.nameEntry(name$)=FALSE THEN RETURN FALSE IF self.InitialiseSocketSystem(thisIP%)=FALSE THEN RETURN FALSE IF doHostGame%=TRUE // Add the host to the list of players self.createWaitingWindow() DDgui_show(FALSE) SHOWSCREEN

135 result$=NETWEBGET$("www.GLBasic.com","/netsession.php? session=spots&host="+HOST%,udpPort%,1024,1000) DDgui_popdialog() IF result$="HOSTING" // All is well ELSE // An error ENDIF // NETJOINGAME ELSE self.createWaitingWindow() DDgui_show(FALSE) SHOWSCREEN result$="0" WHILE result$="0" // Loop until there is a server ALPHAMODE 0.0 SMOOTHSHADING FALSE DDgui_show(FALSE) SHOWSCREEN result$=NETWEBGET$("www.GLBasic.com","/netsession.php? session=spots&host="+JOIN%,udpPort%,1024,500) IF DDgui_get(self.abortButton$,CLICKED$) DDgui_popdialog() RETURN FALSE ENDIF DEBUG "Result : "+result$+"\n" IF result$="0" OR result$="" // Finding server SLEEP 1000 HIBERNATE ENDIF WEND ENDIF ENDFUNCTION FUNCTION IsHost%: RETURN self.isPlayerHost% ENDFUNCTION FUNCTION IsNetworkModeActive%: RETURN self.isNetworkMode% ENDFUNCTION FUNCTION CreateNetworkGame%:setup AS TSetup,doHostGame% LOCAL name$,thisIP%,status%,socket%,disCount% IF self.nameEntry(name$)=FALSE THEN RETURN FALSE IF self.InitialiseSocketSystem(thisIP%)=FALSE THEN RETURN FALSE socket%=SOCK_UDPOPEN(self.udpPort%) IF socket%<>-1 self.isPlayerHost%=doHostGame% self.isNetworkMode%=TRUE IF self.networkMessage.Initialise(socket%,self.udpPort%,thisIP %,name$,self.IsHost())=TRUE IF self.isPlayerHost%=TRUE // Create TCP connection self.host.SetupNetwork(thisIP%,socket%,self.udpPort %,name$) self.host.CreateLobbyWindow(setup) ELSE self.client.SetupNetwork(thisIP%,socket%,self.udpPort %,name$) self.client.CreateLobbyList(setup) self.client.connectedToHostStatus(FALSE) ENDIF DIM self.networkData$[0]

136 status%=RESULT_OK% disCount%=0 WHILE status%<>RESULT_STARTGAME% AND status %<>RESULT_LEAVELOBBY% SMOOTHSHADING FALSE ALPHAMODE 0.0 DDgui_show(FALSE) SHOWSCREEN IF self.IsHost()=TRUE status %=self.host.ProcessGUI(setup,self.networkMessage) IF disCount%>0 self.host.HandleNoResponse(setup,self.networkMessage.ReturnFirstDisconnect()) ENDIF ELSE status %=self.client.ProcessGUI(setup,self.networkMessage) IF disCount%>0 self.client.HandleNoResponse(setup,self.networkMessage.ReturnFirstDisconnect()) ENDIF ENDIF IF disCount%>0 THEN self.networkMessage.DeleteFirstDisconnect() IF status%=RESULT_OK% status%=self.ProcessNetworkMessage(setup) disCount%=self.networkMessage.DisconnectCount() ENDIF WEND DDgui_popdialog() RETURN status% ENDIF ELSE RETURN RESULT_SOCKETERROR% ENDIF ENDFUNCTION FUNCTION ProcessNetworkMessage%:setup AS TSetup LOCAL result%,msg% IF self.IsNetworkModeActive()=TRUE IF self.IsHost()=TRUE self.host.ProcessTimers(self.networkMessage) ELSE self.client.ProcessTimers(self.networkMessage) ENDIF // Has anyone disconnected ? IF self.networkMessage.ProcessUDP(self.networkData$[])>0 // There is a message to deal with // Is it a user-defined message ? msg%=INTEGER(self.networkData$[NETMESSAGE_CODE%]) IF (self.IsUserMessage(msg%)=FALSE) AND (msg %=MESSAGE_ACKNOWLEDGEMENT% AND self.IsUserMessage(INTEGER(self.networkData$ [NETMESSAGE_DATA%+3])))=FALSE // No, its a routine defined system, so process IF self.IsHost() result %=self.host.HandleMessage(setup,self.networkData$[],self.networkMessage) ELSE result %=self.client.HandleMessage(setup,self.networkData$[],self.networkMessage) ENDIF ELSE result%=RESULT_USERMESSAGE% ENDIF ELSE result%=RESULT_OK% ENDIF ELSE result%=RESULT_NONETWORKMODE% ENDIF

137 RETURN result% ENDFUNCTION //! Is the message a user-defined one ? FUNCTION IsUserMessage%:messageID% IF messageID%>=self.minMessageRange% AND messageID%<=self.maxMessageRange% RETURN TRUE ELSE RETURN FALSE ENDIF ENDFUNCTION //! Get network data (for user messages) FUNCTION GetNetworkData%:data$[] DIM data$[0] data$[]=self.networkData$[] RETURN BOUNDS(data$[],0) ENDFUNCTION FUNCTION InitialiseSocketSystem%:BYREF thisIP% IF SOCK_INIT() thisIP%=SOCK_GETIP("") DEBUG "IP : "+thisIP%+" "+SOCK_GETIP$(thisIP%)+"\n" RETURN TRUE ELSE RETURN FALSE ENDIF ENDFUNCTION

FUNCTION ShutdownSocketSystem%: SOCK_SHUTDOWN ENDFUNCTION FUNCTION nameEntry%:BYREF name$ self.createNameWindow() WHILE TRUE SMOOTHSHADING FALSE ALPHAMODE 0.0 DDgui_show(FALSE) SHOWSCREEN IF DDgui_get(self.finishedButton$,CLICKED$) name$=DDgui_get$(self.nameEntryText$,"TEXT") IF name$<>"" DDgui_popdialog() RETURN TRUE ELSE ENDIF ELSEIF DDgui_get(self.backButton$,CLICKED$) DDgui_popdialog() RETURN FALSE ENDIF WEND ENDFUNCTION //! Get either host or client name FUNCTION ReturnPlayerName$: IF self.IsHost() RETURN self.host.getPlayerName$() ELSE RETURN self.client.getPlayerName$() ENDIF ENDFUNCTION //! Return the number of players. Only used by the host FUNCTION ReturnNumberOfPlayers%: IF self.IsHost() RETURN self.host.ReturnNumberOfPlayers() ELSE RETURN 0 ENDIF ENDFUNCTION //! Send user data to host or clients

138 FUNCTION SendUserDataToClients%:messageID%,data$,reliable%,singleIP%=0 RETURN self.host.SendUserDataToClients(self.networkMessage,messageID %,data$,reliable%,singleIP%) ENDFUNCTION FUNCTION SendUserDataToHost%:messageID%,data$,reliable% IF self.IsHost()=FALSE RETURN self.client.SendUserDataToHost(self.networkMessage,messageID %,data$,reliable%) ELSE //RETURN self.client.SendUserDataToHost(self.networkMessage,messageID %,data$,reliable%) ENDIF ENDFUNCTION FUNCTION ReturnIPAddress%: IF self.IsHost() RETURN self.host.ReturnIPAddress() ELSE RETURN self.client.ReturnIPAddress() ENDIF ENDFUNCTION FUNCTION ClearAllReady%: IF self.IsHost() THEN self.host.ClearAllReady() ENDFUNCTION ENDTYPE

139 Localisation

This routine is based on a BlitzBasic (author unknown) routine to localise text, enabling text to be displayed in different languages as long as the required key/value pair is present, and the correct language file is loaded. It was modified to take into account GLBasic's string positioning differences to BlitzMax and the extra niceties that the former language has. //! Flags which are passed to the Initialisation function CONSTANT LANGUAGE_ACTIVE% = 1 //! Convert a string between two sets of curly brackets {{}} into the associated text that has been loaded in //! There are various special commands that can retrieve the contends of PLATFORMINFO$ //! Text cannot be recursive TYPE TLocalisation flags% languageDef$ languageVer$ languageAuth$ localisationStack$[] map AS TMap //! Initialise the localisation system //\param flags% - If bit 1 is set, the localisation will be used. If this bit is not set, then text will be returned without being converted //\return TRUE - If this function initialised properly, FALSE otherwise FUNCTION Initialise%:languageFlag%=0 self.Destroy() IF self.map.Initialise() self.flags%=languageFlag% self.languageDef$="" self.languageVer$="" self.languageAuth$="" RETURN TRUE ELSE RETURN FALSE ENDIF ENDFUNCTION //! Destroy the TLocalisation type //\param None //\return None FUNCTION Destroy%: DIM self.localisationStack$[0] self.map.Destroy() ENDFUNCTION //! Manually set language details. Doesn't really need to be called //\param langDef$ - This is the two character language ID code //\param langVer$ - This is the language version (in the format of x.x.x.x) //\param langAuth$ - This is the language author FUNCTION SetLanguageDetails%:langDef$,langVer$,langAuth$ self.languageDef$=langDef$ self.languageVer$=langVer$ self.languageAuth$=langAuth$ ENDFUNCTION //! Load a language file. This is a standard INI file in the format of = //\param override$ - If this is an empty string, then the routine will assume the language file to be used is called language.txt, and is stored in the directory return from PLATFORMINFO$("LOCALE") //\return TRUE if the language file was read in correctly, FALSE if the file doesn't exist or a handle could not be opened FUNCTION LoadLanguage%:languageFile$,languageCode$,override$="" LOCAL language$,temp$,result% LOCAL loop%,keyCount%,key$,value$ LOCAL KEY_SECTION$="KEYS" LOCAL KEY_SUPPORTED$="SUPPORTED" IF override$<>"" languageCode$=PLATFORMINFO$("LOCALE")

140 ENDIF IF DOESFILEEXIST(languageFile$)=FALSE THEN RETURN FALSE IF GETFILESIZE(languageFile$)=0 THEN RETURN FALSE languageCode$=LCASE$(languageCode$) INIOPEN languageFile$ keyCount%=INTEGER(INIGET$(KEY_SECTION$,"COUNT",0)) result%=FALSE IF keyCount%>0 self.Destroy() self.SetLanguageDetails(languageCode$,INIGET$ (KEY_SUPPORTED$,"VERSION",""),INIGET$(KEY_SUPPORTED$,"AUTHOR","")) FOR loop%=0 TO keyCount%-1 key$=INIGET$(KEY_SECTION$,loop%,"") IF key$<>"" value$=INIGET$(languageCode$,key$,"") IF value$<>"" self.map.Add(key$,value$) ENDIF ENDIF NEXT IF self.map.Count()>0 THEN result%=TRUE ENDIF INIOPEN ""

RETURN result% ENDFUNCTION //! Display the language header information and all keys & text //\param Nothing //\return Nothing FUNCTION DebugLanguage%: DEBUG "Language ID : "+self.GetLanguageID$()+"\n" DEBUG "Language Version : "+self.GetLanguageVersion$()+"\n" DEBUG "Language Author : "+self.GetLanguageAuthor$()+"\n" self.map.StatusReport() ENDFUNCTION //! Get the language ID code //\param None //\return The language ID code FUNCTION GetLanguageID$: RETURN self.languageDef$ ENDFUNCTION //! Get the language file version //\param None //\return The language file version FUNCTION GetLanguageVersion$: RETURN self.languageVer$ ENDFUNCTION //! Get the language file author //\param None //\return The language file author FUNCTION GetLanguageAuthor$: RETURN self.languageAuth$ ENDFUNCTION //! Convert (if the appropriate flag is set) text between {{}} to a given string //\param string$ - This contains the text to convert //\return Returns the result of the conversion (if any) FUNCTION LocaliseText$:string$ LOCAL loop% LOCAL one$,tmpPrevChar$,tmpText$ LOCAL tmpCount% LOCAL tmpOpening%[] IF bAND(self.flags%,LANGUAGE_ACTIVE%) // Check for cyclic tokens FOR loop%=0 TO BOUNDS(self.localisationStack$[],0)-1 IF self.localisationStack$[loop%]=string$

141 DEBUG "Warning : Cyclic localisation string found : "+string$+" at position "+loop%+"\n" RETURN "* ERROR *" ENDIF NEXT DIMPUSH self.localisationStack$[],string$ DIM tmpOpening%[LEN(string$)/2] FOR loop%=0 TO BOUNDS(tmpOpening%[],0)-1; tmpOpening%[loop%]=0; NEXT loop%=0 tmpPrevChar$="" tmpCount%=0 WHILE loop%

ENDSELECT string$=MID$ (string$,0,tmpOpening%[tmpCount%]-2)+tmpText$+MID$(string$,loop%+1) INC loop %,LEN(tmpText$)-(loop%+3-tmpOpening%[tmpCount%]) tmpPrevChar$="" ENDIF ELSE tmpPrevChar$=one$ ENDIF DEFAULT

142 tmpPrevChar$="" ENDSELECT INC loop% WEND DIMDEL self.localisationStack$[],-1 ENDIF RETURN string$ ENDFUNCTION ENDTYPE

143 Key/Value Mapping

This routine was created to allow quick and easy key/value storage and retrieval. The data stored is always sorted in order, and a binary search routine is used to retrieve a value for a given key. This routine was needed for the Localisation routine and had to be fast. Up until recently the original code had a slight bug in that it was possible for a key that was present to be ignored. //! TMap allows the mapping of a string to a value. It uses a fast binary search on sorted key values to quick find the associated value for a key TYPE tKeyValue key$ value$ used% ENDTYPE TYPE TMap list[] AS tKeyValue INVALID% = -1 //! Initialise type //\param None //\return TRUE FUNCTION Initialise%: RETURN self.Destroy() ENDFUNCTION //! Destroy type //\param None //\return TRUE FUNCTION Destroy%: DIM self.list[0] RETURN TRUE ENDFUNCTION //! Add a key and value to the internal array, and sort array afterwards. There can only be one unique key //\param key$ - Key to add //\param value$ - Value of key //\return TRUE if the key and value has been added, FALSE otherwise FUNCTION Add%:key$,value$ LOCAL temp AS tKeyValue IF self.search(key$)=self.INVALID% // Found temp.key$=key$ temp.value$=value$ temp.used%=FALSE DIMPUSH self.list[],temp SORTARRAY self.list[],0 RETURN TRUE ELSE DEBUG "Duplicate found : "+key$+ "Value : "+value$+"\n" ENDIF RETURN FALSE ENDFUNCTION //! Internal use only - DO NOT USE. Used to search for a key string as quickly as possibly //\param key$ - Key to search for //\return self.INVALID% if key not found, <>self.INVALID% is the index that the key was found FUNCTION search%:key$ LOCAL up%,down%,mid% up%=0 down%=BOUNDS(self.list[],0) WHILE up%key$ down%=mid% // MAX(mid%-1,up%) ELSEIF self.list[mid%].key$

144 ELSE self.list[mid%].used%=TRUE RETURN mid% // Found ENDIF WEND RETURN self.INVALID% ENDFUNCTION //! Generates debug information, which outputs all key and associated values to the GLBasic output window //\param None //\return NONE FUNCTION StatusReport%: LOCAL loop AS tKeyValue FOREACH loop IN self.list[] DEBUG "Key : "+loop.key$+" Value : "+loop.value$+" Used : "+loop.used %+"\n" NEXT DEBUG "Number of keys and values : "+BOUNDS(self.list[],0)+"\n" ENDFUNCTION //! Returns the value of a key (if present) //\param key$ - Key to search //\param notFound$ - Default text to return of key has not been found //\return Value of key, or the contents of notFound$ if key has not been found FUNCTION GetValue$:key$,notFound$="NOT_FOUND" LOCAL index%

index%=self.search(key$) IF index%=self.INVALID% RETURN notFound$ ELSE RETURN self.list[index%].value$ ENDIF ENDFUNCTION //! Delete a key and value pair //\param key$ - Key to delete //\return TRUE if the key has been found, FALSE otherwise FUNCTION DeleteKey%:key$ LOCAL index% index%=self.search(key$) IF index%=self.INVALID% RETURN FALSE ELSE DIMDEL self.list[],index% RETURN TRUE ENDIF ENDFUNCTION //! Return the number of keys //\param none //\return The number of keys in the local array FUNCTION Count%: RETURN BOUNDS(self.list[],0) ENDFUNCTION ENDTYPE

145 Mouse Buffer

This is designed to buffer mouse button pressed and retrieve a mouse position. The mouse buttons are buffered so that continually holding them down wont cause problems in things like menus. TYPE tMouse mx% my% b1% b2% ENDTYPE TYPE TMouseBuffer mouseDown%[] FUNCTION Initialise%: self.Destroy() DIM self.mouseDown%[2] self.mouseDown%[0]=FALSE self.mouseDown%[1]=FALSE RETURN TRUE ENDFUNCTION FUNCTION Destroy%: DIM self.mouseDown%[0] ENDFUNCTION FUNCTION Clear%:mouse AS tMouse,px%=-1,py%=-1,setPos%=TRUE IF px%>=0 THEN mouse.mx%=px% IF py%>=0 THEN mouse.my%=py% mouse.b1%=FALSE mouse.b2%=FALSE IF setPos%=TRUE THEN SETMOUSE mouse.mx%,mouse.my% ENDFUNCTION FUNCTION mouseProcess:mouse AS tMouse // BYREF mx%,BYREF my%,BYREF button1%,BYREF button2% LOCAL ma% LOCAL mb% MOUSESTATE mouse.mx%,mouse.my%,ma%,mb% IF ma% self.mouseDown%[0]=TRUE mouse.b1%=FALSE ELSEIF self.mouseDown%[0]=TRUE mouse.b1%=TRUE self.mouseDown%[0]=FALSE ELSE mouse.b1%=FALSE ENDIF IF mb% self.mouseDown%[1]=TRUE mouse.b2%=FALSE ELSEIF self.mouseDown%[1]=TRUE mouse.b2%=TRUE self.mouseDown%[1]=FALSE ELSE mouse.b2%=FALSE ENDIF ENDFUNCTION ENDTYPE

146 Network Message

Another routine for sending network messages using UDP. It is still rather complicated! CONSTANT MESSAGE_CHALLENGESERVER% = 0 // "getChallenge" - client requests servers to announce themselves CONSTANT MESSAGE_CHALLENGERESPONSE% = 1 // "challengeResponse" - server has responded to challenge CONSTANT MESSAGE_DISPLAYCHATMESSAGE%= 2 // "displayChatMsg" - server has sent text message to all connected machines CONSTANT MESSAGE_SERVERHASSHUTDOWN% = 3 // "serverShutDown" - server has shut down CONSTANT MESSAGE_WANTTOJOIN% = 4 // "wantToJoin" - a client wants to join CONSTANT MESSAGE_INVALIDPASSWORD% = 5 // "invalidPassword" - client has password wrong CONSTANT MESSAGE_CLIENTCANJOIN% = 6 // "clientCanJoin" - a client can join CONSTANT MESSAGE_SERVERISFULL% = 7 // "serverIsFull" - the server is full CONSTANT MESSAGE_UPDATECLIENTLIST% = 8 // "updateClientList" - Update client list CONSTANT MESSAGE_CLIENTDISCONNECTED%= 9 // "clientDisconnected" - Client has disconnected CONSTANT MESSAGE_READYSTATUS% = 10 // "readyStatus" - Client status has changed CONSTANT MESSAGE_READYSTATUS2% = 11 // "readyStatus2" - Tell all clients about status change CONSTANT MESSAGE_KICKPLAYER% = 12 // "kickPlayer" - Tell a player that they have been kicked off a server CONSTANT MESSAGE_ACKNOWLEDGEMENT% = 128 // "ACK" - Acknowledge a transmission CONSTANT MESSAGE_PING% = 254 // "ping" - Is a computer alive ? CONSTANT MESSAGE_STARTGAME% = 255 // "startGame" - Sent to all connected players TYPE tNetData socket% port% fromIP% toIP% netCode% data$ sendID% reTry% ENDTYPE TYPE tPlayer IsHost% ipAddress% name$ ping% reTries% isAlive% isReady% hasACK% // Has player acknowledged last tranmission currentTime ENDTYPE CONSTANT NETMESSAGE_CODE% = 0 CONSTANT NETMESSAGE_FROMIP% = 1 CONSTANT NETMESSAGE_TOIP% = 2 CONSTANT NETMESSAGE_PORT% = 3 CONSTANT NETMESSAGE_SENDID% = 4 CONSTANT NETMESSAGE_TIME% = 5 CONSTANT NETMESSAGE_DATA% = 6 CONSTANT UPDATE_NONE% = 0 CONSTANT UPDATE_CHAT% = 1 CONSTANT UPDATE_SERVER% = 2 CONSTANT UPDATE_CLIENT% = 4

147 TYPE TNetMessage netMessage AS tNetData resendMessageTime //! Send a network message FUNCTION _send%:socket%,toIP%,port% LOCAL msg$ msg$=self.netMessage.netCode%+SEPERATOR$+self.netMessage.fromIP%+SEPERATOR$ +self.netMessage.toIP%+SEPERATOR$+self.netMessage.port%+SEPERATOR$+ _ self.netMessage.sendID%+SEPERATOR$+self.resendMessageTime IF self.netMessage.data$<>"" THEN INC msg$,SEPERATOR$+self.netMessage.data$ IF SOCK_UDPSEND(socket%,msg$,toIP%,port%)<0 DEBUG "error sending network message : "+msg$+" : "+NETGETLASTERROR$ ()+"\n" ELSE DEBUG "Message sent : "+msg$+"\n" ENDIF ENDFUNCTION //! Send a message FUNCTION AddMessage%:socket%,port%,fromIP%,toIP%,netCode%,data$,sendID% LOCAL result% self.netMessage.socket%=socket% self.netMessage.port%=port% self.netMessage.fromIP%=fromIP% self.netMessage.toIP%=toIP% self.netMessage.netCode%=netCode% self.netMessage.data$=data$ self.netMessage.reTry%=3 self.netMessage.sendID%=sendID% self.resendMessageTime=GETTIMERALL() // Now we send self._send(self.netMessage.socket%,self.netMessage.toIP %,self.netMessage.port%) RETURN TRUE ENDFUNCTION //! Handle resending messages if they are reliable FUNCTION HandleMessage%: LOCAL msg$ IF ABS(GETTIMERALL()-self.resendMessageTime)>=1000.0 // Resend the data self.resendMessageTime=GETTIMERALL() self._send(self.netMessage.socket%,self.netMessage.toIP %,self.netMessage.port%) DEC self.netMessage.reTry% IF self.netMessage.reTry%<=0 RETURN FALSE ENDIF self.resendMessageTime=GETTIMERALL() ENDIF RETURN TRUE ENDFUNCTION FUNCTION IsCorrectSendID%:sendID% IF self.netMessage.sendID%=sendID% RETURN TRUE ELSE RETURN FALSE ENDIF ENDFUNCTION FUNCTION ReturnNetCode%: RETURN self.netMessage.netCode% ENDFUNCTION

148 FUNCTION CanDelete%:netCode%,sendID% IF self.netMessage.netCode%=netCode% AND self.netMessage.sendID%<=sendID% RETURN TRUE ELSE RETURN FALSE ENDIF ENDFUNCTION FUNCTION ReturnToIP%: RETURN self.netMessage.toIP% ENDFUNCTION FUNCTION ReturnMessageData%:messageData AS tNetData messageData=self.netMessage ENDFUNCTION ENDTYPE TYPE TProcessNetworkMessage idNumber% socket% udpPort% thisIP% playerName$ IsHost% platform$ netMessage[] AS TNetMessage lastRecv%[] disconnectList%[]

FUNCTION Initialise%:socket%,udpPort%,thisIP%,playerName$,IsHost% LOCAL loop% DIM self.netMessage[0] DIM self.lastRecv%[32] DIM self.disconnectList%[0] FOR loop%=0 TO BOUNDS(self.lastRecv%[],0)-1 self.lastRecv%[loop%]=0 NEXT self.idNumber%=1 self.socket%=socket% self.udpPort%=udpPort% self.thisIP%=thisIP% self.playerName$=playerName$ self.IsHost%=IsHost% self.platform$=PLATFORMINFO$("") RETURN TRUE ENDFUNCTION FUNCTION IsNewMsg%:ID%,data% LOCAL i% FOR i% = BOUNDS(self.lastRecv%[],0)-2 TO 0 STEP -1 self.lastRecv%[i%+1] = self.lastRecv%[i%] NEXT self.lastRecv%[0] = ID% FOR i% = 1 TO BOUNDS(self.lastRecv%[],0)-1 // DEBUG i%+" "+self.lastRecv%[i%]+" "+self.lastRecv%[0]+"\n" IF self.lastRecv%[i%] = self.lastRecv%[0] // Duplicate message RETURN FALSE ENDIF NEXT RETURN TRUE ENDFUNCTION //! Process network messages, which are always at least 5 arrays in size FUNCTION ProcessUDP%:networkData$[] LOCAL result%,msg$ LOCAL loop AS TNetMessage LOCAL code%

149 DIM networkData$[0] result%=SOCK_RECV(self.socket%,msg$,65535) SELECT result% CASE -2 // Not ready to be read yet //RETURN FALSE CASE -1 // An error //RETURN FALSE CASE 0 // Nothing to do //RETURN FALSE DEFAULT // Data to be processed IF SPLITSTR(msg$,networkData$[],SEPERATOR$)>=5 code%=INTEGER(networkData$[NETMESSAGE_CODE %]) IF (code%=MESSAGE_CHALLENGESERVER% AND INTEGER(networkData$[NETMESSAGE_FROMIP%])<>self.thisIP%) OR _ (code%<>MESSAGE_CHALLENGESERVER%) IF self.IsNewMsg(INTEGER(networkData$[NETMESSAGE_SENDID%]),code%)=FALSE // It is not a new message, so delete DEBUG "Duplicate message : "+INTEGER(networkData$[NETMESSAGE_SENDID%])+"\n" DIM networkData$[0] ENDIF ELSE DIM networkData$[0] ENDIF ENDIF ENDSELECT // Handle reliable packets FOREACH loop IN self.netMessage[] IF loop.HandleMessage()=FALSE DIMPUSH self.disconnectList%[],loop.ReturnToIP() DELETE loop ENDIF NEXT RETURN BOUNDS(networkData$[],0) ENDFUNCTION FUNCTION DisconnectCount%: RETURN BOUNDS(self.disconnectList%[],0) ENDFUNCTION FUNCTION DeleteFirstDisconnect%: DIMDEL self.disconnectList%[],0 ENDFUNCTION FUNCTION ReturnFirstDisconnect%: RETURN self.disconnectList%[0] ENDFUNCTION FUNCTION AddMessage%:toIP%,netCode%,data$,isReliable% LOCAL message AS TNetMessage LOCAL status% status%=message.AddMessage(self.socket%,self.udpPort%,self.thisIP%,toIP %,netCode%,data$,self.idNumber%) IF isReliable%=TRUE THEN DIMPUSH self.netMessage[],message INC self.idNumber% RETURN status% ENDFUNCTION FUNCTION RemoveAck%:ackID%,message AS tNetData LOCAL loop AS TNetMessage LOCAL tmp% // DEBUG "Looking for message : "+ackID%+"\n" tmp%=INVALID% FOREACH loop IN self.netMessage[] IF loop.IsCorrectSendID(ackID%)=TRUE loop.ReturnMessageData(message)

150 tmp%=loop.ReturnNetCode() DELETE loop ENDIF NEXT IF tmp%<>INVALID% FOREACH loop IN self.netMessage[] IF loop.CanDelete(tmp%,ackID%) DELETE loop ENDIF NEXT ENDIF // DEBUG "TMP:"+tmp%+"\n" ENDFUNCTION FUNCTION ProcessChatMessage%:ipList[] AS tPlayer,chatEditBox$,index%,BYREF storedText$,BYREF updateLobby% LOCAL text$ LOCAL loop AS tPlayer text$=DDgui_get$(chatEditBox$,"TEXT") IF text$<>"" IF index%=-1 // Send chat message to everyone FOREACH loop IN ipList[] IF loop.ipAddress%=self.thisIP% self.UpdateChatMessage(storedText$,updateLobby %,self.playerName$,text$) ELSE self.sendChatMessage(loop.ipAddress%,text$) ENDIF NEXT ELSE // Send to one person IF ipList[index%].ipAddress%=self.thisIP% self.UpdateChatMessage(storedText$,updateLobby %,self.playerName$,text$) ELSE self.sendChatMessage(ipList[index%].ipAddress%,text$) ENDIF ENDIF DDgui_set(chatEditBox$,"TEXT","") ENDIF ENDFUNCTION FUNCTION UpdateChatMessage%:BYREF text$,BYREF updateLobby%,from$,message$ IF LEN(text$)>0 THEN INC text$,"\n" INC text$,"<"+from$+"> "+message$ updateLobby%=bOR(updateLobby%,UPDATE_CHAT%) ENDFUNCTION FUNCTION updateStatus%:ipList[] AS tPlayer,fromIP%,status% LOCAL loop AS tPlayer FOREACH loop IN ipList[] IF loop.ipAddress%=fromIP% loop.isReady%=status% RETURN TRUE ENDIF NEXT RETURN FALSE ENDFUNCTION //! Update player ping value FUNCTION updatePing%:fromIP%,time,playerList[] AS tPlayer LOCAL loop AS tPlayer DEBUG "X1\n" FOREACH loop IN playerList[] IF loop.ipAddress%=fromIP% loop.ping%=INTEGER(ABS(GETTIMERALL()-time)/1000) DEBUG GETTIMERALL()+" "+time+"\n" loop.isAlive%=TRUE loop.reTries%=4

151 RETURN TRUE ENDIF NEXT DEBUG "X2\n" RETURN FALSE ENDFUNCTION // Data starts from index 5 // Server messages // ------//! Send a chat message to all client machines FUNCTION sendChatMessage%:toIP%,text$ self.AddMessage(toIP%,MESSAGE_DISPLAYCHATMESSAGE%,self.playerName$ +SEPERATOR$+text$,FALSE) ENDFUNCTION //! Send a response to a computer requesting a server notification // Add game type and game state later FUNCTION sendServerChallengeResponse%:toIP%,numClients%,usePassword%,gameType %,allowHoles%,gameState% self.AddMessage(toIP%,MESSAGE_CHALLENGERESPONSE%,self.playerName$ +SEPERATOR$+numClients%+SEPERATOR$+usePassword%+SEPERATOR$+self.platform$+SEPERATOR$+ _ gameType%+SEPERATOR$+allowHoles%+SEPERATOR$ +gameState%,FALSE) ENDFUNCTION //! Send a message to all clients (that aren't the server), that the server has shut down FUNCTION sendServerShutDown%:toIP%,area% self.AddMessage(toIP%,MESSAGE_SERVERHASSHUTDOWN%,area%,FALSE) ENDFUNCTION //! Send a message to the client that the password is invalid FUNCTION sendServerInvalidPassword%:toIP% self.AddMessage(toIP%,MESSAGE_INVALIDPASSWORD%,"",FALSE) ENDFUNCTION //! Let a client know that they can join FUNCTION sendServerClientCanJoin%:toIP%,clientList$ self.AddMessage(toIP%,MESSAGE_CLIENTCANJOIN%,clientList$,TRUE) ENDFUNCTION //! Let the client know that the server is full FUNCTION sendServerFull%:toIP% self.AddMessage(toIP%,MESSAGE_SERVERISFULL%,"",TRUE) ENDFUNCTION //! Tell clients to update their client list FUNCTION sendServerUpdateClientList%:toIP%,list$ self.AddMessage(toIP%,MESSAGE_UPDATECLIENTLIST%,list$,TRUE) ENDFUNCTION FUNCTION sendServerReadyStatus%:toIP%,fromIP%,state% self.AddMessage(toIP%,MESSAGE_READYSTATUS2%,fromIP%+SEPERATOR$+state%,TRUE) ENDFUNCTION FUNCTION sendServerKickPlayer%:toIP%,reason$="" self.AddMessage(toIP%,MESSAGE_KICKPLAYER%,reason$,FALSE) ENDFUNCTION FUNCTION sendServerClientStartGame%:toIP% self.AddMessage(toIP%,MESSAGE_STARTGAME%,"",TRUE) ENDFUNCTION // Client messages // ------//! Get all servers to respond FUNCTION sendServerChallenge%:toIP% self.AddMessage(toIP%,MESSAGE_CHALLENGESERVER%,self.thisIP%,FALSE) ENDFUNCTION //! Send a request to join a server

152 FUNCTION sendClientJoinServer%:toIP%,password$ self.AddMessage(toIP%,MESSAGE_WANTTOJOIN%,self.playerName$+SEPERATOR$ +password$,FALSE) ENDFUNCTION //! Tell host that client is disconnected FUNCTION sendClientDisconnect%:toIP% self.AddMessage(toIP%,MESSAGE_CLIENTDISCONNECTED%,"",FALSE) ENDFUNCTION //! Tell the host that we are ready (or not) FUNCTION sendClientReadyStatus%:toIP%,state% self.AddMessage(toIP%,MESSAGE_READYSTATUS%,state%,FALSE) ENDFUNCTION FUNCTION sendUserMessage%:toIP%,messageID%,data$,reliable% RETURN self.AddMessage(toIP%,messageID%,data$,reliable%) ENDFUNCTION // Server and client messages // ------FUNCTION sendPing%:toIP% self.AddMessage(toIP%,MESSAGE_PING%,"",TRUE) ENDFUNCTION FUNCTION sendAcknowledgement%:toIP%,messageID%,messageCode% self.AddMessage(toIP%,MESSAGE_ACKNOWLEDGEMENT%,messageID%+SEPERATOR$ +self.thisIP%+SEPERATOR$+self.playerName$+SEPERATOR$+messageCode%,FALSE) ENDFUNCTION ENDTYPE

153 On-screen Joystick

This routine allows an on-screen joystick (and buttons if needed) to control objects in the users programs. This was originally created for use on a webOS device, but I never developed anything that needed it. CONSTANT OSJERROR_OK% = 0 // No Error CONSTANT OSJERROR_CONFIGNOTFOUND% = 1 // Config file not found CONSTANT OSJERROR_NOFILEHANDLE% = 2 // No file handles available CONSTANT OSJERROR_CANTOPENFILE% = 3 // Config file cant be opened CONSTANT OSJERROR_INVALIDNUMPARAMS% = 4 // Invalid number of parameters CONSTANT OSJERROR_GRAPHICNOTFOUND% = 5 // Graphic filename could not be found CONSTANT OSJERROR_OUTOFGRAPHICROOM% = 6 // No more graphics can be used //! Configuration type for joystick bases TYPE tJoystickBaseConfig index% // Index in array id% // Sprite ID width% // Width of sprite height% xThickness% // Thickness of border yThickness% ENDTYPE //! Configuration type for joystick knobs TYPE tJoystickKnobConfig index% knobID% shadowID% width% height% knobWidth% knobHeight% ENDTYPE //! Configuration type of joystick buttons TYPE tJoystickButtonConfig index% standardID% pressedID% width% height% ENDTYPE //! Mouse details TYPE tMouse mouseX% mouseY% diffX% diffY% b1% b2% ENDTYPE //! Button details TYPE tButton standardID% pressedID% buttonIndex% x% y% width% height% // pressed% bitValue% ENDTYPE // OSJ Joystick Base TYPE TjoystickBase spriteID%;knobID% x%;width% y%;height% minBaseWidth%;maxBaseWidth% minBaseHeight%;maxBaseHeight%

154 knobCentreX%;knobCentreY% knobWidth%;knobHeight% xOffset%;yOffset% buttons[] AS tButton buttonBitValue% FUNCTION Initialise%:spriteID%,x%,y%,width%,height%,xThickness%,yThickness%,knobID %,knobWidth%,knobHeight% DIM self.buttons[0] self.spriteID%=spriteID% self.x%=x% self.y%=y% self.width%=width% self.height%=height% self.knobID%=knobID% self.knobWidth%=knobWidth% self.knobHeight%=knobHeight% self.minBaseWidth%=0-xThickness% self.maxBaseWidth%=xThickness% self.minBaseHeight%=0-yThickness% self.maxBaseHeight%=xThickness% self.xOffset%=0 // Centre of joystick base self.yOffset%=0 // Calculate the centre of the knob self.knobCentreX%=(width%/2)+self.x% self.knobCentreY%=(height%/2)+self.y% self.buttonBitValue%=0 ENDFUNCTION FUNCTION Destroy%: DIM self.buttons[0] ENDFUNCTION //! Display joystick base, joystick shadow + knob or just joystick, and then any buttons FUNCTION Display%: LOCAL loop AS tButton // Display joystick base ALPHAMODE -0.5 DRAWSPRITE self.spriteID%,self.x%,self.y% // Display the knob ALPHAMODE 0.0 DRAWSPRITE self.knobID %,self.CalculateKnobXPosition(),self.CalculateKnobYPosition() // Display buttons FOREACH loop IN self.buttons[] IF bAND(self.buttonBitValue%,loop.bitValue%) AND loop.pressedID%>=0 DRAWANIM loop.pressedID%,loop.buttonIndex%,loop.x%,loop.y% ELSE DRAWANIM loop.standardID%,loop.buttonIndex%,loop.x%,loop.y% ENDIF NEXT ENDFUNCTION FUNCTION CalculateKnobXPosition%: RETURN self.knobCentreX%+(self.xOffset%-(self.knobWidth%/2)) ENDFUNCTION FUNCTION CalculateKnobYPosition%: RETURN self.knobCentreY%+(self.yOffset%-(self.knobHeight%/2)) ENDFUNCTION FUNCTION CheckForPosition%:mouseData[] AS tMouse LOCAL px%,py%,processed% LOCAL button AS tButton LOCAL mouse AS tMouse px%=self.CalculateKnobXPosition()

155 py%=self.CalculateKnobYPosition() //DEBUG b1%+" "+px%+" "+py%+" "+self.knobWidth%+" "+self.knobHeight%+"\n" processed%=FALSE self.buttonBitValue%=0 FOREACH mouse IN mouseData[] IF mouse.b1% IF BOXCOLL(mouse.mouseX%,mouse.mouseY%,1,1,px%,py %,self.knobWidth%,self.knobHeight%) //DRAWRECT px%,py%,self.knobWidth%,self.knobHeight %,RGB(255,0,0) self.xOffset%=MAX(self.minBaseWidth%,MIN(self.xOffset% +mouse.diffX%,self.maxBaseWidth%)) self.yOffset%=MAX(self.minBaseHeight%,MIN(self.yOffset% +mouse.diffY%,self.maxBaseHeight%)) processed%=TRUE ELSEIF BOXCOLL(mouse.mouseX%,mouse.mouseY%,1,1,self.x%,self.y %,self.width%,self.height%) self.xOffset%=MAX(self.minBaseWidth%,MIN(mouse.mouseX%- self.knobCentreX%,self.maxBaseWidth%)) self.yOffset%=MAX(self.minBaseHeight%,MIN(mouse.mouseY%- self.knobCentreY%,self.maxBaseHeight%)) processed%=TRUE ENDIF // Check to see if a button is pressed FOREACH button IN self.buttons[] IF BOXCOLL(mouse.mouseX%,mouse.mouseY%,1,1,button.x %,button.y%,button.width%,button.height%) //button.pressed%=TRUE INC self.buttonBitValue%,button.bitValue% PRINT "Pressed!",mouse.mouseX%+10,mouse.mouseY% +10 //ELSE //button.pressed%=FALSE ENDIF NEXT ENDIF NEXT IF processed%=FALSE self.xOffset%=0 self.yOffset%=0 ENDIF ENDFUNCTION FUNCTION AddButton%:standardSprite%,pressedSprite%,buttonIndex%,width%,height %,distance%,angle%,xPos%,yPos% LOCAL button AS tButton LOCAL dx,dy button.standardID%=standardSprite% button.pressedID%=pressedSprite% button.buttonIndex%=buttonIndex% button.width%=width% button.height%=height% dx=distance%*COS(angle) dy=distance%*SIN(angle) IF xPos%<0 OR yPos%<0 button.x%=self.knobCentreX% button.y%=self.knobCentreY% ELSE button.x%=xPos% button.y%=yPos% ENDIF INC button.x%,INTEGER(dx)-(width%/2) INC button.y%,INTEGER(dy)-(height%/2) button.bitValue%=POW(2,buttonIndex%) DIMPUSH self.buttons[],button DEBUG button.bitValue%+"\n" ENDFUNCTION //! Return value of buttons pressed

156 FUNCTION ReturnButtonsValue%: RETURN self.buttonBitValue% ENDFUNCTION //! Return joystick X Value FUNCTION ReturnJoystickXValue%: RETURN self.xOffset% ENDFUNCTION //! Return joystick Y Value FUNCTION ReturnJoystickYValue%: RETURN self.yOffset% ENDFUNCTION ENDTYPE // OSJ Base TYPE ToSJ screenWidth%;screenHeight% joystickBaseConfig[] AS tJoystickBaseConfig joystickKnobConfig[] AS tJoystickKnobConfig joystickButtonsConfig[] AS tJoystickButtonConfig joystickBase[] AS TjoystickBase mouse[] AS tMouse lastMouse[] AS tMouse //! Initialise the OSJ system FUNCTION Initialise%:configFileName$ LOCAL Handle%,line$,loop% LOCAL array$[]

GETSCREENSIZE self.screenWidth%,self.screenHeight% DIM self.joystickBaseConfig[0] DIM self.joystickKnobConfig[0] DIM self.joystickButtonsConfig[0] DIM self.joystickBase[0] DIM self.mouse[5] DIM self.lastMouse[5] IF DOESFILEEXIST(configFileName$)=FALSE THEN RETURN OSJERROR_CONFIGNOTFOUND % // Read in the config data and process Handle%=GENFILE() IF Handle%<0 THEN RETURN OSJERROR_NOFILEHANDLE% IF OPENFILE(Handle%,configFileName$,1) READLINE Handle%,line$ DEBUG "X:"+ENDOFFILE(Handle%)+"\n" WHILE ENDOFFILE(Handle%)=FALSE DIM array$[0] DEBUG "Line : "+line$+"\n" IF SPLITSTR(line$,array$[],"=")=2 // In the format of = SELECT array$[0] CASE "JOYSTICKBASE" // Handle joystick base lines self.processJoystickBase(array$[1]) CASE "JOYSTICKKNOB" // Joystick knobs self.processJoystickKnob(array$[1]) CASE "JOYSTICKBUTTONS" // Buttons self.processJoystickButtons(array$[1]) ENDSELECT ENDIF READLINE Handle%,line$ DEBUG "X:"+ENDOFFILE(Handle%)+"\n" WEND CLOSEFILE Handle%

157 FOR loop%=0 TO MIN(BOUNDS(self.mouse[],0),GETMOUSECOUNT())-1 self.mouse[loop%].mouseX%=0 self.mouse[loop%].mouseY%=0 self.mouse[loop%].b1%=FALSE self.mouse[loop%].b2%=FALSE NEXT self.lastMouse[]=self.mouse[] RETURN OSJERROR_OK% ELSE RETURN OSJERROR_CANTOPENFILE% ENDIF ENDFUNCTION //! Destroy the OSJ system FUNCTION Destroy%: LOCAL jb AS tJoystickBaseConfig LOCAL jk AS tJoystickKnobConfig LOCAL jbb AS tJoystickButtonConfig // Delete all joystick base graphics FOREACH jb IN self.joystickBaseConfig[] LOADSPRITE "",jb.id% NEXT FOREACH jk IN self.joystickKnobConfig[] LOADSPRITE "",jb.id% NEXT

FOREACH jbb IN self.joystickButtonsConfig[] LOADSPRITE "",jbb.standardID% IF jbb.pressedID%>=0 THEN LOADSPRITE "",jbb.pressedID% NEXT DIM self.joystickBaseConfig[0] DIM self.joystickKnobConfig[0] DIM self.joystickButtonsConfig[0] DIM self.joystickBase[0] DIM self.mouse[0] ENDFUNCTION //! Process joystick base //! Joystick base data is in the format of [, x thickness [, y thickness]] FUNCTION processJoystickBase%:data$ LOCAL array$[],size% LOCAL jb AS tJoystickBaseConfig DIM array$[0] size%=SPLITSTR(data$,array$[],",") IF size%=0 THEN RETURN OSJERROR_INVALIDNUMPARAMS% IF DOESFILEEXIST(array$[0])=FALSE THEN RETURN OSJERROR_GRAPHICNOTFOUND% jb.id%=GENSPRITE() IF jb.id%<0 THEN RETURN OSJERROR_OUTOFGRAPHICROOM% LOADSPRITE array$[0],jb.id% GETSPRITESIZE jb.id%,jb.width%,jb.height% IF size%>1 jb.xThickness%=INTEGER(array$[1]) ELSE jb.xThickness%=0 ENDIF IF size%>2 jb.yThickness%=INTEGER(array$[2]) ELSE jb.yThickness%=0 ENDIF jb.index%=BOUNDS(self.joystickBaseConfig[],0) DIMPUSH self.joystickBaseConfig[],jb RETURN OSJERROR_OK%

158 ENDFUNCTION //! Process joystick knobs //! Joystick knob data is in the format of , [(shadow filename | "") [, kbob width, knob height]] FUNCTION processJoystickKnob%:data$ LOCAL array$[],size%,index% LOCAL jk AS tJoystickKnobConfig DIM array$[0] size%=SPLITSTR(data$,array$[],",") IF size%<2 THEN RETURN OSJERROR_INVALIDNUMPARAMS% IF DOESFILEEXIST(array$[0])=FALSE THEN RETURN OSJERROR_GRAPHICNOTFOUND% jk.knobID%=GENSPRITE() IF jk.knobID%<0 THEN RETURN OSJERROR_OUTOFGRAPHICROOM% LOADSPRITE array$[0],jk.knobID% GETSPRITESIZE jk.knobID%,jk.width%,jk.height% IF DOESFILEEXIST(array$[2])=TRUE jk.shadowID%=GENSPRITE() IF jk.shadowID%<0 THEN RETURN OSJERROR_OUTOFGRAPHICROOM% LOADSPRITE array$[1],jk.shadowID% ELSE jk.shadowID%=-1 ENDIF

// Check to see if the knob size needs to be regulated IF BOUNDS(array$[],0)>2 jk.knobWidth%=INTEGER(array$[2]) ELSE jk.knobWidth%=jk.width% ENDIF IF BOUNDS(array$[],0)>3 jk.knobHeight%=INTEGER(array$[3]) ELSE jk.knobHeight%=jk.height% ENDIF jk.index%=BOUNDS(self.joystickKnobConfig[],0) DIMPUSH self.joystickKnobConfig[],jk RETURN OSJERROR_OK% ENDFUNCTION //! Process joystick buttons FUNCTION processJoystickButtons%:data$ LOCAL array$[],size%,index% LOCAL jb AS tJoystickButtonConfig DIM array$[0] size%=SPLITSTR(data$,array$[],",") IF size%<>4 THEN RETURN OSJERROR_INVALIDNUMPARAMS% IF DOESFILEEXIST(array$[0])=FALSE THEN RETURN OSJERROR_GRAPHICNOTFOUND% jb.width%=INTEGER(array$[2]) jb.height%=INTEGER(array$[3]) jb.standardID%=GENSPRITE() IF jb.standardID%<0 THEN RETURN OSJERROR_OUTOFGRAPHICROOM% LOADANIM array$[0],jb.standardID%,jb.width%,jb.height% IF DOESFILEEXIST(array$[1])=TRUE // Graphic for when button is pressed jb.pressedID%=GENSPRITE() IF jb.pressedID%<0 THEN RETURN OSJERROR_OUTOFGRAPHICROOM% LOADANIM array$[1],jb.pressedID%,jb.width%,jb.height% ELSE jb.pressedID%=-1 ENDIF jb.index%=BOUNDS(self.joystickButtonsConfig[],0)

159 DIMPUSH self.joystickButtonsConfig[],jb RETURN OSJERROR_OK% ENDFUNCTION FUNCTION AddJoystick%:index%,posIndex%,knobIndex% LOCAL joystickBase AS TjoystickBase LOCAL x%,y%,xOffset%=2,yOffset%=2 SELECT posIndex% CASE 0 // Centre to screen x%=(self.screenWidth%- self.joystickBaseConfig[index%].width%)/2 y%=(self.screenHeight%- self.joystickBaseConfig[index%].height%)/2 CASE 1 // Top left x%=xOffset% y%=yOffset% CASE 2 // Top middle x%=(self.screenWidth%- self.joystickBaseConfig[index%].width%)/2 y%=yOffset% CASE 3 // Top right x%=self.screenWidth%-xOffset%- self.joystickBaseConfig[index%].width% y%=yOffset% CASE 4 // Middle left x%=xOffset% y%=(self.screenHeight%- self.joystickBaseConfig[index%].height%)/2 CASE 5 // Middle right x%=self.screenWidth%-xOffset%- self.joystickBaseConfig[index%].width% y%=(self.screenHeight%- self.joystickBaseConfig[index%].height%)/2 CASE 6 // Bottom left x%=xOffset% y%=self.screenHeight%-yOffset%- self.joystickBaseConfig[index%].height% CASE 7 // Bottom middle x%=(self.screenWidth%- self.joystickBaseConfig[index%].width%)/2 y%=self.screenHeight%-yOffset%- self.joystickBaseConfig[index%].height% CASE 8 // Bottom right x%=self.screenWidth%-xOffset%- self.joystickBaseConfig[index%].width% y%=self.screenHeight%-yOffset%- self.joystickBaseConfig[index%].height% ENDSELECT DEBUG BOUNDS(self.joystickBaseConfig[],0)+" "+BOUNDS(self.joystickKnobConfig[],0)+"\n" joystickBase.Initialise(self.joystickBaseConfig[index%].id%,x%,y%, _ self.joystickBaseConfig[index %].width%,self.joystickBaseConfig[index%].height%, _ self.joystickBaseConfig[index %].xThickness%,self.joystickBaseConfig[index%].yThickness%, _ self.joystickKnobConfig[knobIndex %].knobID%,self.joystickKnobConfig[knobIndex%].knobWidth %,self.joystickKnobConfig[knobIndex%].knobHeight%) DIMPUSH self.joystickBase[],joystickBase ENDFUNCTION FUNCTION AddButton%:joystickIndex%,buttonIndex%,whichButton%,distance,angle,xPos %=-1,yPos%=-1 self.joystickBase[joystickIndex %].AddButton(self.joystickButtonsConfig[buttonIndex%].standardID%, _ self.joystickButtonsConfig[buttonIndex%].pressedID%,whichButton%, _ self.joystickButtonsConfig[buttonIndex%].width %,self.joystickButtonsConfig[buttonIndex%].height%, _ distance,angle,xPos%,yPos%) ENDFUNCTION

160 FUNCTION Display%: LOCAL loop AS TjoystickBase FOREACH loop IN self.joystickBase[] loop.Display() NEXT ENDFUNCTION FUNCTION Process%: LOCAL loop% LOCAL jb AS TjoystickBase FOR loop%=0 TO MIN(BOUNDS(self.mouse[],0),GETMOUSECOUNT())-1 self.lastMouse[]=self.mouse[] SETACTIVEMOUSE loop% MOUSESTATE self.mouse[loop%].mouseX%,self.mouse[loop%].mouseY %,self.mouse[loop%].b1%,self.mouse[loop%].b2% self.mouse[loop%].diffX%=self.mouse[loop%].mouseX%- self.lastMouse[loop%].mouseX% self.mouse[loop%].diffY%=self.mouse[loop%].mouseY%- self.lastMouse[loop%].mouseY% IF self.mouse[loop%].b1%<>self.lastMouse[loop%].b1% self.mouse[loop%].diffX%=0 self.mouse[loop%].diffY%=0 ENDIF DEBUG self.mouse[loop%].b1%+" "+self.lastMouse[loop%].b1%+" "+self.mouse[loop%].diffX%+" "+self.mouse[loop%].diffY%+" "

//DEBUG self.mouse[loop%].mouseLastX%+" "+self.mouse[loop %].mouseLastY%+"\n" NEXT FOREACH jb IN self.joystickBase[] jb.CheckForPosition(self.mouse[]) NEXT ENDFUNCTION FUNCTION ReturnButtonsBitValue%:joystickIndex% RETURN self.joystickBase[joystickIndex%].ReturnButtonsValue() ENDFUNCTION FUNCTION ReturnJoystickXValue%:joystickIndex% RETURN self.joystickBase[joystickIndex%].ReturnJoystickXValue() ENDFUNCTION FUNCTION ReturnJoystickYValue%:joystickIndex% RETURN self.joystickBase[joystickIndex%].ReturnJoystickYValue() ENDFUNCTION ENDTYPE

161 Progress

This routine creates a progress wheel which can be updated to denote activity. A percentage value can also be displayed too. This was created when DRAWRECT would only display shapes as white on Android devices – something else was needed to denote progress, and thus this routine was born. TYPE TProgress spriteID% = -1 sprX%;sprY% textX%;textY%;textFont% angle perc$="000" FUNCTION Initialise%:useFont%=-1 LOCAL array%[] LOCAL loop%,text$,dupSize%=4,hexSize%=8 LOCAL screenWidth%,screenHeight%,index%,sprWidth%,sprHeight% LOCAL fW%,fH% self.spriteID%=GENSPRITE() IF self.spriteID%>=0 GETSCREENSIZE screenWidth%,screenHeight% self.angle=0.0 RESTORE progress_1 READ sprWidth%,sprHeight% IF sprWidth%*sprHeight%<=0 THEN RETURN FALSE DIM array%[sprWidth%*sprHeight%] index%=0 READ text$ WHILE text$<>"------" IF LEN(text$)<8 THEN RETURN FALSE loop%=0 WHILE loop%0 array%[index%]=value% INC index% DEC amount% WEND INC loop%,hexSize%+dupSize%+1 ELSE array%[index%]=self.hexToDec(MID$(text$,loop%,hexSize%)) INC loop%,hexSize% INC index% ENDIF WEND READ text$ WEND IF MEM2SPRITE(array%[],self.spriteID%,sprWidth%,sprHeight%)=FALSE THEN RETURN FALSE self.sprX%=(screenWidth%-sprWidth%)/2 self.sprY%=(screenHeight%-sprHeight%)/2 IF useFont%>=0 SETFONT useFont% ENDIF GETFONTSIZE fW%,fH%

162 self.textX%=self.sprX%+((sprWidth%-(LEN(self.perc$)*fW%))/2) self.textY%=self.sprY%+((sprHeight%-fH%)/2) self.textFont%=useFont% RETURN TRUE ENDIF RETURN FALSE ENDFUNCTION FUNCTION Destroy%: IF self.spriteID%>=0 THEN LOADSPRITE "",self.spriteID% ENDFUNCTION FUNCTION Update%:percentage%=-1 ALPHAMODE 0.0 SMOOTHSHADING FALSE ROTOZOOMSPRITE self.spriteID%,self.sprX%,self.sprY%,self.angle,1.0 IF self.textFont%>=0 THEN SETFONT self.textFont% IF percentage>=0 AND percentage%<=100 THEN PRINT RIGHT$(self.perc$ +percentage%,LEN(self.perc$)),self.textX%,self.textY% SHOWSCREEN INC self.angle,0.5 IF self.angle>=360.0 THEN self.angle=0.0 ENDFUNCTION //! Convert a hexadecimal number of an integer //param hex$ - Hexidecimal string //\return The decimal value of the passed string FUNCTION hexToDec%:hex$ LOCAL i% LOCAL j% LOCAL loop% i%=0 j%=0 FOR loop%=0 TO LEN(hex$)-1 i%=ASC(MID$(hex$,loop%,1))-48 IF 9

163 0D0FFFFD2D2FF00FFD7FF00FFDAFF00FFDCFF*001800000000*000300FF00FF*0006FF0000FFFF1B1BFF00FF 4AFF00FF83FF*000F0000000000FFB7FF00FFB8FF" DATA "FFBBBBFFFFBEBEFFFFC1C1FFFFC4C4FFFFC6C6FFFFCACAFFFFCDCDFF00FFCFFF00FFD2FF*001C00000000*0 00200FF00FF*0004FF0000FFFF1919FFFF4141FFFF8383FF00FF86FF00FF89FF*000D0000000000FFB1FF00F FB4FFFFB7B7FFFFB8B8FFFFBBBBFFFFBEBEFFFFC1C1FFFFC4C4FFFFC7C7FF00FFCAFF00FFCDFF*001E000000 00*000200FF00FF*0002FF0000FFFF1818FF" DATA "FF4040FFFF8484FFFF8686FFFF8989FF00FF8BFF00FF8EFF*000B0000000000FFACFF00FFAEFFFFB1B1FFFF B4B4FFFFB6B6FFFFB8B8FFFFBBBBFFFFBEBEFFFFC1C1FF00FFC4FF00FFC7FF*00200000000000FF00FF00FF0 7FFFF1515FFFF3F3FFFFF8484FFFF8787FFFF8989FFFF8C8CFFFF8E8EFF00FF91FF00FF94FF*000A00000000 00FFABFFFFACACFF" DATA "FFAEAEFFFFB1B1FFFFB3B3FFFFB6B6FFFFB8B8FFFFBBBBFF00FFBEFF00FFC1FF*00220000000000FF15FF00 FF48FFFF8484FFFF8787FFFF8A8AFFFF8D8DFFFF8F8FFFFF9292FFFF9494FF00FF95FF*00090000000000FFA 4FF00FFA8FFFFAAAAFFFFACACFFFFADADFFFFB0B0FFFFB2B2FFFFB5B5FF00FFB8FF00FFBBFF*002400000000 00FF84FF00FF87FF" DATA "FF8A8AFFFF8D8DFFFF9090FFFF9393FFFF9595FFFF9797FF00FF98FF00FF9DFF*00080000000000FFA2FFFF A4A4FFFFA6A6FFFFA8A8FFFFABABFFFFADADFFFFAFAFFF00FFB2FF00FFB5FF*00260000000000FF8AFF00FF8 DFFFF9191FFFF9393FFFF9696FFFF9898FFFF9B9BFFFF9D9DFF00FF9EFF*00070000000000FF9CFF00FF9FFF FFA1A1FFFFA3A3FF" DATA "FFA5A5FFFFA7A7FFFFAAAAFF00FFABFF00FFAFFF*00280000000000FF91FF00FF95FFFF9797FFFF9A9AFFFF 9C9CFFFF9E9EFFFFA0A0FF00FFA1FF00FFA5FF*00060000000000FF9AFFFF9C9CFFFF9E9EFFFFA0A0FFFFA2A 2FFFFA3A3FFFFA6A6FF00FFA8FF*002A0000000000FF99FFFF9B9BFFFF9D9DFFFFA0A0FFFFA2A2FFFFA4A4FF FFA5A5FF00FFA7FF" DATA "*00050000000000FF95FF00FF98FFFF9999FFFF9B9BFFFF9C9CFFFF9E9EFFFFA1A1FF00FFA1FF00FFA6FF*0 02A0000000000FF9BFF00FF9FFFFFA1A1FFFFA3A3FFFFA5A5FFFFA7A7FFFFA9A9FF00FFAAFF00FFAEFF*0004 0000000000FF93FFFF9595FFFF9696FFFF9797FFFF9999FFFF9B9BFFFF9D9DFF00FF9FFF*002C0000000000F FA3FFFFA5A5FFFFA7A7FF" DATA "FFA9A9FFFFABABFFFFADADFFFFAEAEFF00FFB0FF*00040000000000FF91FFFF9191FFFF9292FFFF9494FFFF 9696FFFF9797FF00FF98FF00FF9DFF*002C0000000000FFA5FF00FFA9FFFFABABFFFFADADFFFFAFAFFFFFB0B 0FFFFB2B2FF00FFB2FF*00030000000000FF89FF00FF8CFFFF8D8DFFFF8F8FFFFF9090FFFF9292FFFF9393FF 00FF93FF*002E00000000" DATA "00FFAFFFFFAFAFFFFFB1B1FFFFB3B3FFFFB4B4FFFFB6B6FF00FFB7FF00FFBBFF*00020000000000FF87FFFF 8989FFFF8A8AFFFF8B8BFFFF8C8CFFFF8D8DFFFF8F8FFF00FF91FF*002E0000000000FFB1FFFFB4B4FFFFB5B 5FFFFB7B7FFFFB8B8FFFFB9B9FFFFBBBBFF00FFBCFF*00020000000000FF85FFFF8585FFFF8686FFFF8888FF FF8989FFFF8A8AFF" DATA "00FF8BFF00FF8FFF*002E0000000000FFB4FF00FFB8FFFFBABAFFFFBBBBFFFFBCBCFFFFBDBDFFFFBEBEFF00 FFBEFF*00020000000000FF73FFFF8181FFFF8282FFFF8383FFFF8585FFFF8686FF00FF85FF*003000000000 00FFBEFFFFBEBEFFFFBFBFFFFFC0C0FFFFC1C1FFFFC2C2FF00FFC2FF0000000000FF00FF00FF35FFFF5454FF FF7979FFFF8080FF" DATA "*0002FF8181FF00FF83FF*00300000000000FFC0FFFFC2C2FFFFC4C4FF*0002FFC5C5FFFFC6C6FF00FFC7FF 00FFCBFF00FF00FF*0004FF0000FFFF0D0DFF00FF43FF00FF81FF*00300000000000FFC2FF00FFC6FFFFC8C8 FFFFC9C9FF*0002FFCACAFFFFCBCBFF00FFCDFF00FF00FF*0005FF0000FF00FF04FF*00320000000000FFCCF FFFCCCCFFFFCDCDFF*0002FFCECEFFFFCFCFFF00FFCFFF" DATA "00FF00FF*0005FF0000FF00FF00FF*00320000000000FFD0FF*0002FFD1D1FF*0002FFD2D2FFFFD3D3FF00F FD3FF00FF00FF*0005FF0000FF00FF00FF*00320000000000FFD5FFFFD5D5FF*0003FFD6D6FFFFD7D7FF00FF D7FF00FF00FF*0005FF0000FF00FF00FF*00320000000000FFDAFF*0003FFDADAFF*0002FFDBDBFF00FFDBFF 00FF00FF*0005FF0000FF00FF00FF*00320000000000FFDEFF*0005FFDFDFFF00FFDFFF" DATA "00FF00FF*0005FF0000FF00FF00FF*00320000000000FFE3FF*0005FFE3E3FF00FFE3FF00FF00FF*0005FF0 000FF00FF00FF*00320000000000FFE7FFFFE8E8FF*0004FFE7E7FF00FFE7FF00FF00FF*0005FF0000FF00FF 00FF*00320000000000FFECFF*0002FFECECFF*0003FFEBEBFF00FFEBFF00FF00FF*0005FF0000FF*000200F F00FF*00300000000000FFF6FF00FFF2FFFFF1F1FF*0002FFF0F0FF*0002FFEFEFFF00FFEDFF" DATA "*000200FF00FF*0005FF0000FF00FF00FF*00300000000000FFF8FFFFF6F6FF*0002FFF5F5FFFFF4F4FFFFF 3F3FF00FFF2FF00FFEFFF0000000000FF00FF*0005FF0000FF00FF00FF*00300000000000FFC8FFFFFBFBFFF FFAFAFFFFF9F9FFFFF8F8FFFFF7F7FF00FFF7FF*00020000000000FF00FF*0005FF0000FF*000200FF00FF*0 02E0000000000FF00FF00FF59FFFF6976FFFFCCD0FFFFFEFEFF" DATA "FFFDFDFFFFFBFBFF00FFD2FF*00020000000000FF00FF*0006FF0000FF00FF00FF*002E0000000000FF00FF *0004FF0015FFFF293BFFFF848EFF00FFBFFF*000200000000*000200FF00FF*0005FF0000FF00FF00FF*002 E0000000000FF00FF*0005FF0015FF00FF2BFF00FF84FF*00030000000000FF00FF*0005FF0000FF*000200F F00FF*002C00000000*000200FF00FF*0005FF0015FF00FF00FF*00040000000000FF00FF*0006FF0000FF" DATA "00FF00FF*002C0000000000FF00FF*0006FF0015FF00FF00FF*000400000000*000200FF00FF*0005FF0000 FF*000200FF00FF*002A00000000*000200FF00FF*0005FF0015FF*000200FF00FF*00050000000000FF00FF *0006FF0000FF00FF00FF*002A0000000000FF00FF*0006FF0015FF00FF00FF*000600000000*000200FF00F F*0005FF0000FF*000200FF00FF*002800000000*000200FF00FF*0005FF0015FF*000200FF00FF*00070000

164 000000FF00FF*0006FF0000FF*000200FF00FF" DATA "*002600000000*000200FF00FF*0006FF0015FF00FF00FF*000800000000*000200FF00FF*0006FF0000FF* 000200FF00FF*002400000000*000200FF00FF*0006FF0015FF*000200FF00FF*00090000000000FF00FF*00 07FF0000FF*000200FF00FF*002200000000*000200FF00FF*0007FF0015FF00FF00FF*000A00000000*0002 00FF00FF*0007FF0000FF00FFA1FF00FFFAFF*002000000000*000200FF00FF*0007FF0015FF*000200FF00F F*000B00000000*000200FF00FF*0005FF0000FFFFEBEBFF" DATA "FFFAFAFF00FFF6FF00FFF3FF*001E00000000*000200FF00FF*0007FF0015FF*000200FF00FF*000D000000 00*000200FF00FF*0003FF0000FFFFE5E5FFFFFBFBFFFFF7F7FFFFF3F3FF00FFEFFF00FFECFF*001C0000000 0*000200FF00FF*0007FF0015FF*000200FF00FF*000F00000000*000200FF00FFFF0000FFFFE1E1FFFFFBFB FFFFF8F8FFFFF4F4FFFFF0F0FFFFECECFF00FFE6FF00FFE3FF00FFE1FF*001800000000" DATA "*000300FF00FF*0007FF0015FF*000200FF00FF*00110000000000FF00FF00FF9EFFFFFBFBFFFFF8F8FFFFF 5F5FFFFF0F0FFFFEDEDFFFFE9E9FFFFE5E5FFFFE1E1FF00FFDBFF00FFD7FF00FFD5FF*001400000000*00030 0FF00FF*0008FF0015FF*000200FF00FF*00130000000000FFFBFF00FFF8FFFFF5F5FFFFF1F1FFFFEEEEFFFF EAEAFFFFE7E7FFFFE2E2FFFFDEDEFFFFDADAFFFFD5D5FF" DATA "00FFD0FF00FFC9FF00FFC7FF00FFC5FF*000E00000000*000400FF00FF*0009FF0015FF*000200FF00FF*00 150000000000FFF5FF00FFF1FFFFEFEFFFFFEBEBFFFFE8E8FFFFE3E3FFFFE0E0FFFFDCDCFFFFD7D7FFFFD3D3 FFFFCFCFFFFFC9C9FFFFC5C5FF00FFC0FF00FFB9FF00FFB7FF00FFB5FF*000800000000*000400FF00FF*000 BFF0015FF*000200FF00FF*00170000000000FFEFFF00FFEBFF" DATA "FFE9E9FFFFE5E5FFFFE1E1FFFFDEDEFFFFDADAFFFFD5D5FFFFD1D1FFFFCDCDFFFFC8C8FFFFC3C3FFFFBFBFF FFFB9B9FFFFB5B5FF00FFB0FF00FFA9FF00FFA4FF00FF9FFF00FF9AFF00FF95FF00FF90FF00FF8BFF00FF86F F00FF45FF*000DFF0015FF*000200FF00FF*00190000000000FFE9FF00FFE7FF00FFE3FFFFDFDFFFFFDCDCFF FFD7D7FFFFD3D3FF" DATA "FFCFCFFFFFCBCBFFFFC6C6FFFFC2C2FFFFBDBDFFFFB8B8FFFFB4B4FFFFAEAEFFFFAAAAFFFFA5A5FFFF9F9FF FFF9B9BFFFF9595FFFF9090FFFF8B8BFFFF8686FFFF8181FFFF1022FF*000AFF0015FF*000300FF00FF*001C 0000000000FFDFFF00FFDDFF00FFD9FFFFD5D5FFFFD1D1FFFFCECEFFFFC9C9FFFFC5C5FFFFC1C1FFFFBBBBFF FFB7B7FFFFB3B3FF" DATA "FFAEAEFFFFA9A9FFFFA4A4FFFF9F9FFFFF9B9BFFFF9595FFFF9191FFFF8C8CFFFF8787FFFF8282FFFF3743F F*0008FF0015FF*000300FF00FF*00200000000000FFD5FF00FFD3FF00FFCFFFFFCBCBFFFFC7C7FFFFC3C3FF FFBFBFFFFFBABAFFFFB6B6FFFFB2B2FFFFADADFFFFA9A9FFFFA4A4FFFF9F9FFFFF9B9BFFFF9595FFFF9191FF FF8C8CFFFF8787FF" DATA "FF8383FFFF5C62FF*0006FF0015FF*000300FF00FF*00240000000000FFCBFF00FFC9FF00FFC7FF00FFC1FF FFBDBDFFFFB9B9FFFFB5B5FFFFB1B1FFFFACACFFFFA8A8FFFFA3A3FFFF9F9FFFFF9A9AFFFF9595FFFF9191FF FF8D8DFFFF8888FFFF8484FFFF7E7EFF*0003FF0015FF*000400FF00FF*00290000000000FFBDFF00FFBBFF0 0FFB9FF00FFB5FF00FFAFFFFFACACFF" DATA "FFA8A8FFFFA3A3FFFF9E9EFFFF9A9AFFFF9595FFFF9191FFFF8D8DFFFF8888FFFF8484FF00FF61FF00FF2AF F*000300FF00FF*00300000000000FFACFF00FFAAFF00FFA7FF00FFA3FF00FF9EFF00FF99FF00FF95FF00FF9 1FF00FF8CFF00FF88FF00FF86FF00FF84FF*001A00000000" DATA "------" ENDDATA

165 Setup

This is the routine all my games call to initialise and load all the data in a pretty efficient way. It is designed to allow easy sprite and 3D display as well as fading music down and playing sound effects. It also interfaces with PeeJay/Moru's proportional font routine for text display. CONSTANT MAX_PLATFORMLENGTH% = 12 CONSTANT FONT_DEFAULT% = 0 CONSTANT INVALID% = -1 CONSTANT NO_DATA$ = "NO_DATA" CONSTANT NO_PLATFORM$ = "N/A" CONSTANT NO_COUNTRY$ = "N/A" CONSTANT INVALID_DATA% = -1 // So it can be used with file handles, sprites etc CONSTANT CLICKED$ = "CLICKED" CONSTANT SLECTED$ = "SELECT" CONSTANT TEXT$ = "TEXT" CONSTANT HANDLE_LEFT% = -1 CONSTANT HANDLE_TOP% = -1 CONSTANT HANDLE_MIDDLE% = 0 CONSTANT HANDLE_RIGHT% = 1 CONSTANT HANDLE_BOTTOM% = 1 CONSTANT SEPERATOR$ = "/" CONSTANT SETUP_VISITWEBSITE% = -1 CONSTANT SETUP_NOTHING% = 0 CONSTANT SETUP_RESOLUTIONCHANGE% = 1 CONSTANT SETUP_STANDARDEXIT% = 2 CONSTANT DEF_RESX = 640.0 // All graphics are based on this resolution CONSTANT DEF_RESY = 480.0 CONSTANT MENU_MUSICSFX% = 0 CONSTANT MENU_SCREENRESOLUTION% = 1 CONSTANT MENU_NAMES% = 2 CONSTANT MENU_EXTRA% = 3 CONSTANT MENU_OKAY% = 4 CONSTANT MENU_RESET% = 5 CONSTANT MENU_CANCEL% = 6 CONSTANT MENU_HISCOREUPLOAD% = 7 CONSTANT MENU_WEB% = 8

// Types //! This is for printing text with proportional fonts TYPE tText text$ x // Floating point value for movement y width% height% fontID% shadow% = 0 alpha = 0.0 col% = 7 // Which colour index to use colour%[4] // RGB for 4 corners ENDTYPE TYPE tSprite spriteID% spriteIndex% width% height% x y angle alpha scale ENDTYPE //! This holds available screen resolutions TYPE tScreenResolutions width%

166 height% ENDTYPE //! Information about file loaded TYPE tLoadDataList isBumpMap% isAnim% // Is an animated sprite idNumber% xSize% ySize% channel% // Only used by the sound system handleXOffset% // Only used by sprites handleYOffset% fileName$ // Only used by music _xSize // For 3D objects _ySize _zSize ENDTYPE //! For options TYPE tNameMenu widget$ nameField$ // name$ ENDTYPE //! This holds the list of options for the program. An id of < 0 denotes a userOption call TYPE tProgramOptions buttonID$ spriteID% description$ ENDTYPE TYPE tOptionsMenu widget1$ = "w1" widget2$ = "w2" widget3$ = "w3" widget8$ = "w4" mVol$ = "mv" sVol$ = "sv" screenResList$ = "srl" fullScreen$ = "fs" showBackground$ = "sb" names[] AS tNameMenu uploadHiscores$ = "uls" saveAndExit$ = "se" exit$ = "e" restore$ = "r" visitWebSite$= "vw" // tab$ ENDTYPE TYPE tHiscoreMenu tabName$ localTitle$ webTitle$ localScores$ webScores$ back$ ENDTYPE //! Contains hiscore data (used by local and web) TYPE tHiscore name$ score% platform$ country$ playerMarker% playerIndex% ENDTYPE //! Main program options TYPE tOptions screenWidth% screenHeight% halfScreenWidth%

167 halfScreenHeight% useFullScreen% // Is program using full screen ? mVolume // Music volume sVolume // SFX volume showBackground% // Show animated background webUpload% // Allow hiscore uploading names$[] // Player names updateProgram% // Allow program updating ? ENDTYPE //! Generic rectangle type TYPE tiRECT x% y% width% height% ENDTYPE TYPE tfRECT x y width height ENDTYPE //! Generic 3D z, y and z type TYPE D3DVECTOR x y z ENDTYPE //! This type setups the program TYPE TSetup PROGRAM_NAME$ PROGRAM_VERSION$ // Name of program - used to store hiscores shoeBoxGraphics$ shoeBoxSounds$ NUM_PLAYERS% NUM_NAMESIZE% MAX_HISCORES% MAX_MODES% NAMES_TABINDEX% doLogo% // Originally in tBaseData mediaPath$ = "Media/" webSite$ = "" // Base website platform$ = "" baseDir$ = "" // Base directory for use sharedData$ = "" // Directory used to share data between all users optionsFile$ = "" // Filename for options file localHiscoreFile$ = "" // Local hiscore table tempFile$ = "" // Holds temporary data only userLevels$ = "" // Holds user created levels country$ = "" // Local of user password$ = "" // Web hiscore password languagePath$ = "" // Path containing language file and font optionsMenu AS tOptionsMenu hiscoreMenu AS tHiscoreMenu // Originally in tOptionsData default_screenWidth% default_screenHeight% default_internetAvailable% default_allowVisitWebSite% default_useFullScreen% options AS tOptions programOptions[] AS tProgramOptions programOptionsList$ // Original in tScale maxScale

168 handleX% handleY% // List of screen resolutions screenResolutions[] AS tScreenResolutions fontLoadData[] AS tLoadDataList ttFontLoadData[] AS tLoadDataList spriteLoadData[] AS tLoadDataList soundLoadData[] AS tLoadDataList musicLoadData[] AS tLoadDataList objectLoadData[] AS tLoadDataList localHiscoreList[] AS tHiscore webHiscoreList[] AS tHiscore descriptions$[] font AS TFont optionsText$ saveUpdateText$ saveText$ uploadingHiscoresText$ downloadingHiscoresText$ unableToUploadDataText$ unableToDownloadHiscoresText$ quitGameText$ yesText$ noText$ musicVolumeText$ sfxVolumeText$ useFullScreenText$ submitHiscoresText$ showBackgroundText$ // Only in English at the moment nameLengthText$ playerText$ // Add space at end invalidAnimSpriteText$ invalidColourText$ wantToVisitWebSiteText$ fullScreenModeUnavailable$ unableToDeleteFile$ availableScreenResolutionsText$ duplicateNameText$ noNameText$ backText$ webAccessText$ LOAD_SOUND% = 0 LOAD_MUSIC% = 1 LOAD_FIXEDFONT% = 2 LOAD_PROFONT%= 3 LOAD_2DOBJ = 4 LOAD_3DOBJ = 5 loadDirList$[] params$[] allocateSoundIDErrorText$ allocateFontIDErrorText$ unableToFindFileText$ allocate3DObjectErrorText$ allocate2DObjectErrorText$ notFound$ backgroundYIndex backgroundSprite% moveStep% WHITE% FUNCTION Initialise_Program%:hiscoreName$,handleX%=0,handleY %=0,shoeBoxGraphics$="",shoeBoxSounds$="",NUM_PLAYERS%=1,NUM_NAMESIZE%=8,MAX_HISCORES %=25,MAX_MODES%=4,webSite$="" LOCAL loop$ LOCAL name AS tNameMenu self.WHITE%=RGB(255,255,255) DIM self.spriteLoadData[0]

169 DIM self.fontLoadData[0] DIM self.objectLoadData[0] DIM self.soundLoadData[0] DIM self.musicLoadData[0] DIM self.ttFontLoadData[0] DIM self.screenResolutions[0] DIM self.localHiscoreList[0] DIM self.webHiscoreList[0] DIM self.optionsMenu.names[0] DIM self.programOptions[0] DIM self.params$[0] self.processListOfScreenResolutions(self.default_screenWidth %,self.default_screenHeight%, _ self.default_internetAvailable%, _ self.default_allowVisitWebSite%) self.PROGRAM_VERSION$=PLATFORMINFO$("VERSION") //ReturnProgramVersion$() self.PROGRAM_NAME$=hiscoreName$ //LCASE$(hiscoreName$) // Remove all spaces - used with uploading hiscores. It is case-sensitive. This is for the hiscore names self.shoeBoxGraphics$=shoeBoxGraphics$ self.shoeBoxSounds$=shoeBoxSounds$ self.NUM_PLAYERS%=NUM_PLAYERS% self.NUM_NAMESIZE%=NUM_NAMESIZE% self.MAX_HISCORES%=MAX_HISCORES% self.MAX_MODES%=MAX_MODES% self.handleX%=handleX% self.handleY%=handleY% self.optionsFile$="" self.localHiscoreFile$="" self.languagePath$="" self.country$="" self.NAMES_TABINDEX%=-1 DIM self.loadDirList$[6] self.loadDirList$[self.LOAD_SOUND%]="Sound" self.loadDirList$[self.LOAD_MUSIC%]="Music" self.loadDirList$[self.LOAD_FIXEDFONT%]="Fonts" self.loadDirList$[self.LOAD_PROFONT%]="Fonts" self.loadDirList$[self.LOAD_2DOBJ%]="2DGraphics" self.loadDirList$[self.LOAD_3DOBJ%]="3DGraphics" // Create all needed directorys, but continue if there was an error# self.baseDir$=PLATFORMINFO$("APPDATA")+SEPERATOR$+hiscoreName$ IF DOESDIREXIST(self.baseDir$)=FALSE THEN CREATEDIR(self.baseDir$) IF DOESDIREXIST(self.baseDir$) self.optionsFile$=self.baseDir$+SEPERATOR$+"OPTIONS.INI" self.localHiscoreFile$=self.baseDir$+SEPERATOR$+"HISCORE.INI" ENDIF self.sharedData$=GetSharedDataDir$()+hiscoreName$ IF DOESDIREXIST(self.sharedData$)=FALSE THEN CREATEDIR(self.sharedData$) self.userLevels$=self.sharedData$+SEPERATOR$+"UserLevels" IF DOESDIREXIST(self.userLevels$)=FALSE THEN CREATEDIR(self.userLevels$) self.tempFile$=PLATFORMINFO$("TEMP") IF DOESDIREXIST(self.tempFile$)=FALSE THEN CREATEDIR(self.tempFile$) INC self.tempFile$,"/TEMP.DAT" self.doLogo%=FALSE ChangeWindowText(self.PROGRAM_VERSION$) self.platform$=PLATFORMINFO$("") self.webSite$=webSite$ IF self.webSite$="" OR self.default_internetAvailable%=FALSE THEN self.default_allowVisitWebSite%=FALSE DEBUG "Program Initialisation values\n" DEBUG "------\n"

170 DEBUG "Program Name : "+self.PROGRAM_NAME$+" ("+self.PROGRAM_VERSION$+")\n" DEBUG "Base Dir : "+self.baseDir$+"\n" DEBUG "Shared Dir : "+self.sharedData$+"\n" DEBUG "User Levels : "+self.userLevels$+"\n" DEBUG "Options File : "+self.optionsFile$+"\n" DEBUG "Local Hiscore File : "+self.localHiscoreFile$+"\n" DEBUG "Temp File : "+self.tempFile$+"\n" DEBUG "Program Website : "+self.webSite$+"\n" DEBUG "------\n" IF SPLITSTR(UCASE$(GETCOMMANDLINE$()),self.params$[]," ")>0 FOREACH loop$ IN self.params$[] IF loop$="DELETEOPTIONS" KILLFILE self.optionsFile$ ELSEIF loop$="DELETEHISCORES" KILLFILE self.localHiscoreFile$ ENDIF NEXT ENDIF self.backgroundSprite%=-1 // Setup the main menu system FOR loop%=0 TO self.NUM_PLAYERS%-1 name.widget$="namewidget"+loop% name.nameField$="nameedit"+loop% DIMPUSH self.optionsMenu.names[],name NEXT RETURN TRUE ENDFUNCTION //! Destroy this type FUNCTION Destroy%: LOCAL l AS tLoadDataList LOCAL programOptions AS tProgramOptions DEBUG "Stopping Music\n" STOPMUSIC DEBUG "Stopping Sounds\n" HUSH DEBUG "Deleting Sprites\n" FOREACH l IN self.spriteLoadData[] IF l.isBumpMap%=TRUE LOADBUMPTEXTURE "",l.idNumber% ELSE LOADSPRITE "",l.idNumber% ENDIF NEXT DEBUG "Deleting Fonts\n" FOREACH l IN self.fontLoadData[] LOADFONT "",l.idNumber% NEXT DEBUG "Deleting Sounds\n" FOREACH l IN self.soundLoadData[] LOADSOUND "",l.idNumber%,1 NEXT DEBUG "Deleting 3D Objects\n" FOREACH l IN self.objectLoadData[] X_LOADOBJ "",l.idNumber% NEXT FOREACH programOptions IN self.programOptions[] LOADSPRITE "",programOptions.spriteID% NEXT DEBUG "Destroying fonst\n" self.font.Destroy() DEBUG "Deleting arrays\n" DIM self.spriteLoadData[0] DIM self.fontLoadData[0]

171 DIM self.objectLoadData[0] DIM self.soundLoadData[0] DIM self.musicLoadData[0] DIM self.ttFontLoadData[0] ENDFUNCTION // Called only when program exits FUNCTION Finish%: self.Destroy() DIM self.screenResolutions[0] DIM self.optionsMenu.names[0] DIM self.options.names$[0] DIM self.localHiscoreList[0] DIM self.webHiscoreList[0] DIM self.programOptions[0] DIM self.descriptions$[0] ENDFUNCTION FUNCTION LoadLanguageText%:localisation AS TLocalisation DIM self.descriptions$[0] self.optionsText$=localisation.LocaliseText$("{{options}}") ?IFDEF WEBOS self.wantToVisitWebSiteText$=localisation.LocaliseText$ ("{{wanttovisitwebsite1}}") ?ELSE self.wantToVisitWebSiteText$=localisation.LocaliseText$ ("{{wanttovisitwebsite2}}") ?ENDIF

self.saveText$=localisation.LocaliseText$("{{save}}") self.uploadingHiscoresText$=localisation.LocaliseText$ ("{{uploadinghiscores}}") self.downloadingHiscoresText$=localisation.LocaliseText$ ("{{downloadinghiscores}}") self.unableToUploadDataText$=localisation.LocaliseText$ ("{{unabletouploaddata}}") self.unableToDownloadHiscoresText$=localisation.LocaliseText$ ("{{unabletodownloaddata}}") self.quitGameText$=localisation.LocaliseText$("{{quit}}") self.yesText$=localisation.LocaliseText$("{{yes}}") self.noText$=localisation.LocaliseText$("{{no}}") self.musicVolumeText$=localisation.LocaliseText$("{{musicvolume}}") self.sfxVolumeText$=localisation.LocaliseText$("{{sfxvolume}}") self.useFullScreenText$=localisation.LocaliseText$("{{usefullscreen}}") self.submitHiscoresText$=localisation.LocaliseText$("{{submithiscores}}") self.nameLengthText$=REPLACE$(localisation.LocaliseText$ ("{{namelength}}"),"%0",self.NUM_NAMESIZE%) self.playerText$=localisation.LocaliseText$("{{player}} ") // Add space at end self.invalidColourText$=localisation.LocaliseText$ ("{{invalidnumberofcolourvalues}}") self.invalidAnimSpriteText$=localisation.LocaliseText$ ("{{invalidnumberofanimatedspritevalues}}") self.showBackgroundText$=localisation.LocaliseText$("{{showbackground}}") self.allocateSoundIDErrorText$=localisation.LocaliseText$ ("{{unabletoallocatesoundid}}") self.allocateFontIDErrorText$=localisation.LocaliseText$ ("{{unabletoallocatefontid}}") self.unableToFindFileText$=localisation.LocaliseText$("{{unabletofindfile}} ") self.allocate3DObjectErrorText$=localisation.LocaliseText$ ("{{unabletoallocate3dobjectid}}") self.allocate2DObjectErrorText$=localisation.LocaliseText$ ("{{unabletoallocate2dobjectid}}") self.fullScreenModeUnavailable$=localisation.LocaliseText$ ("{{fullscreenmodeunavailable}}") self.notFound$=localisation.LocaliseText$(" {{notfound}}") self.unableToDeleteFile$=localisation.LocaliseText$("{{unabletodeletefile}} ") self.availableScreenResolutionsText$=localisation.LocaliseText$ ("{{availablescreenres}}") self.duplicateNameText$=localisation.LocaliseText$("{{nameisduplicated}}") self.noNameText$=localisation.LocaliseText$("{{nonamepresent}}") self.backText$=localisation.LocaliseText$("{{back}}")

172 DIMPUSH self.descriptions$[],localisation.LocaliseText$ ("{{musicandsfxvolume}}") DIMPUSH self.descriptions$[],localisation.LocaliseText$ ("{{screenresolutions}}") DIMPUSH self.descriptions$[],localisation.LocaliseText$("{{playernames}}") DIMPUSH self.descriptions$[],localisation.LocaliseText$("{{otheroptions}}") DIMPUSH self.descriptions$[],localisation.LocaliseText$("{{saveoptions}}") DIMPUSH self.descriptions$[],localisation.LocaliseText$ ("{{restoretodefault}}") DIMPUSH self.descriptions$[],localisation.LocaliseText$ ("{{abandonchanges}}") DIMPUSH self.descriptions$[],localisation.LocaliseText$ ("{{allowhiscoreuploading}}") DIMPUSH self.descriptions$[],localisation.LocaliseText$("{{visitwebsite}}") self.webAccessText$=localisation.LocaliseText$("{{internetaccess}}") ENDFUNCTION //! Return name length text FUNCTION ReturnNameLengthText$: RETURN self.nameLengthText$ ENDFUNCTION //! Return shared data path FUNCTION ReturnSharedDataFile$: RETURN self.sharedData$ ENDFUNCTION FUNCTION ReturnHiscorePad$: LOCAL temp$,value%

temp$="" value%=self.MAX_HISCORES% WHILE value%>0 temp$=temp$+"0" value%=value%/10 WEND RETURN temp$ ENDFUNCTION FUNCTION ReturnHiscoreSize%: RETURN self.MAX_HISCORES% ENDFUNCTION FUNCTION ReturnMaxPlayers%: RETURN self.NUM_PLAYERS% ENDFUNCTION FUNCTION ReturnNameLength%: RETURN self.NUM_NAMESIZE% ENDFUNCTION FUNCTION ReturnProgramVersion$: RETURN self.PROGRAM_VERSION$ ENDFUNCTION FUNCTION ReturnLanguagePath$: RETURN self.languagePath$ ENDFUNCTION FUNCTION ReturnMaxModes%: RETURN self.MAX_MODES% ENDFUNCTION FUNCTION ReturnIsWebAllowed%: RETURN self.default_internetAvailable% ENDFUNCTION FUNCTION ReturnOptionsFile$: RETURN self.optionsFile$ ENDFUNCTION //! Get upload status FUNCTION ReturnUploadStatus%: RETURN self.options.webUpload% ENDFUNCTION

173 FUNCTION SetLanguageDetails%:languagePath$,countryCode$ self.languagePath$=languagePath$ self.country$=countryCode$ DEBUG "C:"+self.country$+"\n" ENDFUNCTION FUNCTION TileSprite%:spriteIndex%,dx%,dy% // DEC dx%,self.spriteLoadData[spriteIndex%].xSize% // DEC dy%,self.spriteLoadData[spriteIndex%].ySize% STARTPOLY self.spriteLoadData[spriteIndex%].idNumber%, FALSE POLYVECTOR 0, 0, dx%, dy%, self.WHITE% POLYVECTOR 0, self.options.screenHeight%, dx%, dy% +self.options.screenHeight%, self.WHITE% POLYVECTOR self.options.screenWidth%, self.options.screenHeight%, dx %+self.options.screenWidth%, dy%+self.options.screenHeight%, self.WHITE% POLYVECTOR self.options.screenWidth%, 0, dx%+self.options.screenWidth%, dy%, self.WHITE% ENDPOLY ENDFUNCTION //! Set background sprite FUNCTION setBackgroundSprite%:spr%=-1 self.backgroundSprite%=spr% ENDFUNCTION

//! Display a background FUNCTION doBackground%:speed IF self.options.showBackground%=TRUE AND self.backgroundSprite%>=0 self.TileSprite(self.backgroundSprite%,self.backgroundYIndex- self.spriteLoadData[self.backgroundSprite%].xSize%,self.backgroundYIndex- self.spriteLoadData[self.backgroundSprite%].ySize%) INC self.backgroundYIndex,speed*0.125 IF self.backgroundYIndex>=MAX(self.options.screenWidth %,self.options.screenHeight%)*1.0 self.backgroundYIndex=0.0 ENDIF ENDIF ENDFUNCTION FUNCTION box%:x%,y%,width%,height%,c1%,c2%,c3%,c4% STARTPOLY -1 POLYVECTOR width%,y%,0,0,c1% POLYVECTOR x%,y%,0,0,c2% POLYVECTOR x%,height%,0,0,c3% POLYVECTOR width%,height%,0,0,c4% ENDPOLY ENDFUNCTION //! Save or load the chosen menu option from last time FUNCTION saveLoadMenuOption%:doLoad%,currentOption%,maxOptions%=8 LOCAL OPTION_SECTION$ = "OPTION" LOCAL OPTION_KEY$ = "VALUE" LOCAL option% INIOPEN self.optionsFile$ IF doLoad% option%=INTEGER(INIGET$(OPTION_SECTION$,OPTION_KEY$,"0")) IF option%<0 option%=0 ELSEIF option%>=maxOptions% option%=maxOptions% ENDIF ELSE INIPUT OPTION_SECTION$,OPTION_KEY$,currentOption% ENDIF INIOPEN "" RETURN option% ENDFUNCTION //! Save or load options to INI file

174 FUNCTION saveLoadConfiguration:userOptions AS TUserOptions,doLoad% LOCAL DISPLAY_SECTION$ = "DISPLAY" LOCAL DISPLAY_SCREENWIDTH$= "SCREENWIDTH" LOCAL DISPLAY_SCREENHEIGHT$ = "SCREENHEIGHT" LOCAL DISPLAY_SCREENFULL$ = "SCREENFULL" LOCAL DISPLAY_DOBACKGROUND$ = "DISPLAYBACKGROUND" LOCAL VOLUME_SECTION$ = "VOLUME" LOCAL VOLUME_MUSIC$ = "MUSICVOLUME" LOCAL VOLUME_SFX$ = "MUSICSFX" LOCAL WEB_SECTION$ = "INTERNET" LOCAL WEB_UPLOAD$ = "WEBUPLOAD" LOCAL PROGRAM_UPDATE$ = "PROGRAMUPDATE" LOCAL NAME_KEY$ = "NAME" LOCAL NAME_SECTION$ = "NAMES" LOCAL loop%,error$,dummy$ INIOPEN self.optionsFile$ IF doLoad%=TRUE ?IFDEF PROGRAM_ALLOWRESOLUTIONCHANGE self.options.screenWidth%=INTEGER(INIGET$ (DISPLAY_SECTION$,DISPLAY_SCREENWIDTH$,self.default_screenWidth%)) self.options.screenHeight%=INTEGER(INIGET$ (DISPLAY_SECTION$,DISPLAY_SCREENHEIGHT$,self.default_screenHeight)) ?ELSE self.options.screenWidth%=self.default_screenHeight% self.options.screenHeight%=self.default_screenWidth% ?ENDIF self.options.halfScreenWidth%=self.options.screenWidth%/2 self.options.halfScreenHeight%=self.options.screenHeight%/2 self.maxScale=MIN(1.6,MIN(self.options.screenWidth %/DEF_RESX,self.options.screenHeight%/DEF_RESY)) ?IFDEF PROGRAM_ALLOWFULLSCREENCHANGE self.options.useFullScreen%=INTEGER(INIGET$ (DISPLAY_SECTION$,DISPLAY_SCREENFULL$,TRUE)) IF self.options.useFullScreen%<>TRUE AND self.options.useFullScreen %<>FALSE THEN self.options.useFullScreen%=TRUE ?ELSE self.options.useFullScreen%=FALSE ?ENDIF self.options.mVolume=INTEGER(INIGET$ (VOLUME_SECTION$,VOLUME_MUSIC$,"100"))/100.0 IF self.options.mVolume<=0.0 self.options.mVolume=0.0 ELSE IF self.options.mVolume>=1.0 self.options.mVolume=1.0 ENDIF ENDIF self.options.sVolume=INTEGER(INIGET$ (VOLUME_SECTION$,VOLUME_SFX$,"100"))/100.0 IF self.options.sVolume<=0.0 self.options.sVolume=0.0 ELSE IF self.options.sVolume>=1.0 self.options.sVolume=1.0 ENDIF ENDIF // Do we allow hiscore uploads? self.options.webUpload%=INTEGER(INIGET$ (WEB_SECTION$,WEB_UPLOAD$,self.default_internetAvailable%)) IF self.options.webUpload%<>FALSE AND self.options.webUpload%<>TRUE THEN self.options.webUpload%=self.default_internetAvailable% // Read in the names DIM self.options.names$[0] IF self.NUM_PLAYERS%>0 FOR loop%=0 TO self.NUM_PLAYERS%-1 DIMPUSH self.options.names$[],self.validateName$(INIGET$ (NAME_SECTION$,NAME_KEY$+(CHR$(49+loop%)),"Plyr"+CHR$(49+loop%))) DEBUG "Added names\n" NEXT

175 ENDIF // Read in flag to see if background should be displayed self.options.showBackground%=INTEGER(INIGET$ (DISPLAY_SECTION$,DISPLAY_DOBACKGROUND$,TRUE)) IF self.options.showBackground%<>TRUE AND self.options.showBackground %<>FALSE THEN self.options.showBackground%=TRUE userOptions.LoadOptions() ELSE ?IFDEF PROGRAM_ALLOWRESOLUTIONCHANGE INIPUT DISPLAY_SECTION$,DISPLAY_SCREENWIDTH$,self.options.screenWidth % INIPUT DISPLAY_SECTION$,DISPLAY_SCREENHEIGHT$,self.options.screenHeight% ?ENDIF ?IFDEF PROGRAM_ALLOWFULLSCREENCHANGE INIPUT DISPLAY_SECTION$,DISPLAY_SCREENFULL$,self.options.useFullScreen% ?ENDIF INIPUT VOLUME_SECTION$,VOLUME_MUSIC$,INTEGER(self.options.mVolume*100.0) INIPUT VOLUME_SECTION$,VOLUME_SFX$,INTEGER(self.options.sVolume*100.0) INIPUT WEB_SECTION$,WEB_UPLOAD$,self.options.webUpload% INIPUT DISPLAY_SECTION$,DISPLAY_DOBACKGROUND$,self.options.showBackground%

// Save all the names IF self.NUM_PLAYERS%>0 FOR loop%=0 TO self.NUM_PLAYERS%-1 INIPUT NAME_SECTION$,NAME_KEY$+(CHR$(49+loop %)),self.options.names$[loop%] NEXT ENDIF userOptions.SaveOptions() ENDIF INIOPEN "" ENDFUNCTION //! Read in the screen resolution data FUNCTION setScreenResolution%:userOptions AS TUserOptions self.saveLoadConfiguration(userOptions,TRUE) // If we dont allow screen resolution change, then we just check again the default resolution IF BOUNDS(self.screenResolutions[],0)>0 IF self.findResolution(self.options.screenWidth %,self.options.screenHeight%)=FALSE // Can we use the default resolution ? IF self.findResolution(self.default_screenWidth %,self.default_screenHeight%)=FALSE // No RETURN FALSE ELSE self.options.screenWidth%=self.default_screenWidth% self.options.screenHeight%=self.default_screenHeight% ENDIF ENDIF ENDIF self.changeScreen() IF self.font.Initialise()=FALSE THEN RETURN FALSE RETURN TRUE ENDFUNCTION FUNCTION loadProgramData%:progress AS TProgress LOCAL errorMessage$,usingShoeBox%,result% LOCAL iconFileNames$[]; DIMDATA iconFileNames$ [],"Sound.png","ScreenResolution.png","Names.png","Extra.png","Okay.png","Reset.png","Ca ncel.png","Hiscore_Upload.png","Web.png",""

176 LOCAL buttonID%[]; DIMDATA buttonID%[],MENU_MUSICSFX%,MENU_SCREENRESOLUTION %,MENU_NAMES%,MENU_EXTRA%,MENU_OKAY%,MENU_RESET%,MENU_CANCEL%,MENU_HISCOREUPLOAD %,MENU_WEB% LOCAL loop% LOCAL programOptions AS tProgramOptions // Make sure shoebox files are closed before we re-open them DIM self.programOptions[0] SETSHOEBOX "","" result%=TRUE IF (self.shoeBoxGraphics$<>"" AND DOESFILEEXIST(self.shoeBoxGraphics$)=FALSE) OR _ (self.shoeBoxSounds$<>"" AND DOESFILEEXIST(self.shoeBoxSounds$)=FALSE) self.seriousError(self.unableToFindFileText$+" "+self.shoeBoxGraphics$+"/"+self.shoeBoxSounds$) result%=FALSE ELSE IF self.shoeBoxGraphics$<>"" OR self.shoeBoxSounds$<>"" SETSHOEBOX self.shoeBoxGraphics$,self.shoeBoxSounds$ DEBUG "Shoebox used\n" usingShoeBox%=TRUE ELSE usingShoeBox%=FALSE ENDIF errorMessage$=self.doLoadData$(progress,usingShoeBox%) // SETSHOEBOX "","" // Music is in a seperate directory

result%=TRUE // Load in the graphics for the options SETSHOEBOX self.mediaPath$+"StandardOptions.sbx","" loop%=0 WHILE iconFileNames$[loop%]<>"" AND result%=TRUE programOptions.spriteID%=GENSPRITE() IF programOptions.spriteID%<0 result%=FALSE BREAK ELSE programOptions.buttonID$=buttonID%[loop%] programOptions.description$=self.descriptions$[loop%] LOADSPRITE self.mediaPath$+self.loadDirList$ [self.LOAD_2DOBJ%]+"/"+iconFileNames$[loop%],programOptions.spriteID% DIMPUSH self.programOptions[],programOptions INC loop% ENDIF WEND SETSHOEBOX "","" IF errorMessage$<>"" self.seriousError(errorMessage$) result%=FALSE ELSE result%=TRUE ENDIF ENDIF RETURN result% ENDFUNCTION FUNCTION changeScreen%:baseScreenRes%=320 self.Destroy() SHOWSCREEN IF BOUNDS(self.screenResolutions[],0)>0 // we are using project based settings SETSCREEN self.options.screenWidth%,self.options.screenHeight %,self.options.useFullScreen% IF self.options.useFullScreen% AND ISFULLSCREEN()=FALSE // Try it without full screen ? self.options.useFullScreen%=FALSE self.default_useFullScreen%=FALSE SETSCREEN self.options.screenWidth%,self.options.screenHeight

177 %,self.options.useFullScreen% ELSE ?IFDEF PROGRAM_ALLOWFULLSCREENCHANGE self.default_useFullScreen%=TRUE ?ELSE self.default_useFullScreen%=FALSE ?ENDIF ENDIF IF self.options.useFullScreen%=FALSE SLEEP 500 CentreWindow() ENDIF ENDIF CLEARSCREEN SHOWSCREEN LIMITFPS -1 SETTRANSPARENCY RGB(0,0,0) ?IFNDEF GLB_DEBUG ALLOWESCAPE FALSE ?ELSE ALLOWESCAPE TRUE ?ENDIF X_AUTONORMALS 2 X_MIPMAPPING TRUE AUTOPAUSE TRUE SHOWSCREEN SYSTEMPOINTER FALSE SEEDRND GETTIMERALL() // Calculate the movement step for different resolution > 320 self.moveStep=(self.options.screenWidth%/baseScreenRes%)*1.0 IF self.doLogo%=FALSE self.doLogo%=TRUE // Do logo stuff here self.logo() ENDIF ENDFUNCTION //! Display 3 logo graphics FUNCTION logo%: LOCAL logos$[]; DIMDATA logos$[],"UnmapLogo","indie640","glbasic","" LOCAL sprite%,loop% SETSHOEBOX self.mediaPath$+"Logo.sbx","" ALPHAMODE 0.0 loop%=0 WHILE logos$[loop%]<>"" sprite%=GENSPRITE() IF sprite%>=0 LOADSPRITE logos$[loop%]+".png",sprite% SMOOTHSHADING FALSE STRETCHSPRITE sprite%,0,0,self.options.screenWidth %,self.options.screenHeight% SHOWSCREEN SLEEP 1000 LOADSPRITE "",sprite% INC loop% ENDIF WEND SETSHOEBOX "","" ENDFUNCTION //! Set sprite handle FUNCTION _SetSpriteHandle%:spriteID%,handleX%,handleY% IF handleX%=HANDLE_LEFT% self.spriteLoadData[spriteID%].handleXOffset%=0 ELSEIF handleX%=HANDLE_RIGHT%

178 self.spriteLoadData[spriteID%].handleXOffset %=self.spriteLoadData[spriteID%].xSize% ELSE self.spriteLoadData[spriteID%].handleXOffset %=self.spriteLoadData[spriteID%].xSize%/2 ENDIF IF handleY%=HANDLE_TOP% self.spriteLoadData[spriteID%].handleYOffset%=0 ELSEIF handleY%=HANDLE_BOTTOM% self.spriteLoadData[spriteID%].handleYOffset %=self.spriteLoadData[spriteID%].ySize% ELSE self.spriteLoadData[spriteID%].handleYOffset %=self.spriteLoadData[spriteID%].ySize%/2 ENDIF RETURN TRUE ENDFUNCTION //! Change sprite handles FUNCTION SetSpriteHandle%:spriteID%,handleX%,handleY% LOCAL loop% IF spriteID%<0 // Do all sprites FOR loop%=0 TO BOUNDS(self.spriteLoadData[],0)-1 self._SetSpriteHandle(loop%,handleX%,handleY%) NEXT

RETURN TRUE ELSE // Do one sprite RETURN self._SetSpriteHandle(spriteID%,handleX%,handleY%) ENDIF ENDFUNCTION //! Create a window so that it fits the screen up until a resolution of 480 x 320 FUNCTION createAWindow%:title$,BYREF windowWidth%,BYREF windowHeight %,useDefinedHeight%=-1 LOCAL x%,y% SETFONT FONT_DEFAULT% windowWidth%=MIN(self.options.screenWidth%,480) IF useDefinedHeight%<0 windowHeight%=MIN(self.options.screenHeight%,320) ELSE windowHeight%=useDefinedHeight% ENDIF x%=(self.options.screenWidth%-windowWidth%)/2 y%=(self.options.screenHeight%-windowHeight%)/2 DDgui_pushdialog(x%,y%,windowWidth%,windowHeight%) DEBUG "Window height : "+windowHeight%+"\n" DDgui_set("","TEXT",title$) ENDFUNCTION //! Display a created window FUNCTION displayWindow%:speed,animateBackground%=TRUE ALPHAMODE 0.0 SMOOTHSHADING FALSE IF animateBackground%=TRUE THEN self.doBackground(speed) DDgui_show(FALSE) SHOWSCREEN ENDFUNCTION //! Load in all the files from the data statements FUNCTION doLoadData$:progress AS TProgress,usingShoeBox% LOCAL result$ LOCAL loop% FOR loop%=self.LOAD_SOUND% TO self.LOAD_3DOBJ% result$=self.doLoadFiles$(progress,usingShoeBox%,loop%) IF result$<>"" BREAK ENDIF

179 NEXT RETURN result$ ENDFUNCTION

//! If the filename has a special code in it, then process it FUNCTION checkForSpecialCode%:BYREF file$,loadType% LOCAL temp$ LOCAL dirCode$="#" LOCAL lanDirCode$="!" IF LEFT$(file$,LEN(lanDirCode$))=lanDirCode$ // ! code forces the file to be loaded from users selected language directory file$=self.languagePath$+self.mediaPath$+self.loadDirList$[loadType%] +"/"+MID$(file$,LEN(lanDirCode$)) ELSE // Anything else forces load from specificed directory file$=self.mediaPath$+self.loadDirList$[loadType%]+"/"+file$ ENDIF ENDFUNCTION FUNCTION doLoadFiles$:progress AS TProgress,usingShoeBox%,loadType% LOCAL result$,file$ LOCAL l AS tLoadDataList LOCAL bumpCode$="BUMP:" LOCAL animCode$="ANIM:" LOCAL rgbCode$="RGB:" LOCAL width%,height%,loop% LOCAL temp$[] l.isBumpMap%=0 l.isAnim%=FALSE // Is an animated sprite l.idNumber%=0 l.xSize%=0 l.ySize%=0 l.channel%=0 // Only used by the sound system l.handleXOffset%=0 l.handleYOffset%=0 l.fileName$="" SELECT loadType% CASE self.LOAD_SOUND% RESTORE soundsData CASE self.LOAD_MUSIC% RESTORE musicData CASE self.LOAD_FIXEDFONT% RESTORE fontsData // Load the default font LOADFONT self.mediaPath$ +"smalfont.png",FONT_DEFAULT% SETFONT FONT_DEFAULT% GETFONTSIZE l.xSize%,l.ySize% DIMPUSH self.fontLoadData[],l CASE self.LOAD_PROFONT% RESTORE ttFFontsData CASE self.LOAD_3DOBJ RESTORE graphics3DData CASE self.LOAD_2DOBJ RESTORE spritesData ENDSELECT result$="" READ file$ WHILE file$<>"" AND result$="" // Remove the extension if present IF loadType%=self.LOAD_MUSIC% FOR loop%=LEN(file$)-1 TO 0 STEP -1 IF MID$(file$,loop%,1)="."

180 file$=LEFT$(file$,loop%-1) BREAK ENDIF NEXT ?IF DEFINED(OSXUNI) OR DEFINED(ANDROID) OR DEFINED(LINUX) ?WARNING "Using OGG file" INC file$,".ogg" ?ELSE ?WARNING "using MP3 file" INC file$,".mp3" ?ENDIF ENDIF DEBUG "file:"+file$+"\n" IF loadType%=self.LOAD_2DOBJ l.isAnim%=FALSE IF MID$(file$,0,LEN(rgbCode$))=rgbCode$ DIM temp$[0] file$=MID$(file$,LEN(rgbCode$)) IF SPLITSTR(file$,temp$[],",")<>3 result$=self.invalidColourText$ ELSE DEBUG "Colour : "+INTEGER(temp$[0])+" "+INTEGER(temp$[1])+" "+INTEGER(temp$[2])+"\n" SETTRANSPARENCY RGB(INTEGER(temp$[0]),INTEGER(temp$[1]),INTEGER(temp$[2])) READ file$ CONTINUE ENDIF ELSE IF MID$(file$,0,LEN(bumpCode$))=bumpCode$ file$=MID$(file$,LEN(bumpCode$),LEN(file$)) l.isBumpMap%=TRUE ELSE l.isBumpMap%=FALSE IF MID$(file$,0,LEN(animCode$))=animCode$ DIM temp$[0] file$=MID$(file$,LEN(animCode$),LEN(file$)) IF SPLITSTR(file$,temp$[],",")<>3 result$=self.invalidAnimSpriteText$ ELSE width%=INTEGER(temp$[0]) height%=INTEGER(temp$[1]) file$=temp$[2] l.isAnim%=TRUE ENDIF ENDIF ENDIF ENDIF ENDIF self.checkForSpecialCode(file$,loadType%) IF (usingShoeBox%=FALSE AND DOESFILEEXIST(file$)=TRUE) OR (usingShoeBox%=TRUE) ?IFDEF GLB_DEBUG DEBUG "Loading : "+file$+"\n" STDERR "Loading : "+file$+"\n" STDOUT "Loading : "+file$+"\n" ?ENDIF SELECT loadType% CASE self.LOAD_SOUND% l.idNumber%=GENSOUND() IF l.idNumber%<0 result$=self.allocateSoundIDErrorText$ ELSE LOADSOUND file$,l.idNumber%,1 DIMPUSH self.soundLoadData[],l ENDIF CASE self.LOAD_MUSIC%

181 l.fileName$=file$ DIMPUSH self.musicLoadData[],l CASE self.LOAD_FIXEDFONT% l.idNumber%=GENFONT() IF l.idNumber%<0 result$=self.allocateFontIDErrorText$ ELSE LOADFONT file$,l.idNumber% SETFONT l.idNumber% GETFONTSIZE l.xSize %,l.ySize% DIMPUSH self.fontLoadData[],l ENDIF CASE self.LOAD_PROFONT% l.idNumber %=self.font.AddFont(file$,2) IF l.idNumber%<0 result$=self.unableToFindFileText$+file$ ELSE DIMPUSH self.ttFontLoadData[],l ENDIF CASE self.LOAD_3DOBJ l.idNumber%=GENX_OBJ() IF l.idNumber%<0 result$=self.allocate3DObjectErrorText$ ELSE LOCAL numFaces% LOCAL face[] X_LOADOBJ file$,l.idNumber% numFaces% = X_NUMFACES(l.idNumber%) IF numFaces%>0 l._xSize=-1 l._ySize=-1 l._zSize=-1 DIM face[0] FOR i = 0 TO numFaces% - 1 X_GETFACE l.idNumber% , 0 , i , face[] IF l._xSize < ABS( face[ 0 ][ 0 ] ) THEN l._xSize = ABS( face[ 0 ][ 0 ] ) IF l._xSize < ABS( face[ 1 ][ 0 ] ) THEN l._xSize = ABS( face[ 1 ][ 0 ] ) IF l._xSize < ABS( face[ 2 ][ 0 ] ) THEN l._xSize = ABS( face[ 2 ][ 0 ] ) IF l._ySize < ABS( face[ 0 ][ 1 ] ) THEN l._ySize = ABS( face[ 0 ][ 1 ] ) IF l._ySize < ABS( face[ 1 ][ 1 ] ) THEN l._ySize = ABS( face[ 1 ][ 1 ] ) IF l._ySize < ABS( face[ 2 ][ 1 ] ) THEN l._ySize = ABS( face[ 2 ][ 1 ] ) IF l._zSize < ABS( face[ 0 ][ 2 ] ) THEN l._zSize = ABS( face[ 0 ][ 2 ] ) IF l._zSize < ABS( face[ 1 ][ 2 ] ) THEN l._zSize = ABS( face[ 1 ][ 2 ] ) IF l._zSize < ABS( face[ 2 ][ 2 ] ) THEN l._zSize = ABS( face[ 2 ][ 2 ] ) NEXT l._xSize=RoundDecimal(l._xSize*2.0,2) l._ySize=RoundDecimal(l._ySize*2.0,2)

182 l._zSize=RoundDecimal(l._zSize*2.0,2) ELSE l._xSize=0.0 l._ySize=0.0 l._zSize=0.0 ENDIF DEBUG l._xSize+" "+l._ySize+" "+l._zSize+"\n" DIMPUSH self.objectLoadData[],l ENDIF CASE self.LOAD_2DOBJ IF result$="" l.idNumber%=GENSPRITE() IF l.idNumber%<0 result$=self.allocate2DObjectErrorText$ ELSE IF l.isBumpMap %=TRUE LOADBUMPTEXTURE file$,l.idNumber% ELSE IF l.isAnim%=TRUE LOADANIM file$,l.idNumber%,width%,height%

GETSPRITESIZE l.idNumber%,l.xSize%,l.ySize% IF l.xSize%=0 OR l.ySize%=0 result$=self.unableToFindFileText$+file$ ELSE l.xSize%=width% l.ySize%=height% ENDIF ELSE LOADSPRITE file$,l.idNumber% GETSPRITESIZE l.idNumber%,l.xSize%,l.ySize% IF l.xSize%=0 OR l.ySize%=0 result$=self.unableToFindFileText$+file$ ENDIF ENDIF ENDIF DEBUG "Width : "+l.xSize%+" Height : "+l.ySize%+"\n" IF result$="" // Set sprite handles IF self.handleX%=HANDLE_LEFT% l.handleXOffset%=0 ELSEIF self.handleX%=HANDLE_RIGHT% l.handleXOffset%=l.xSize% ELSE l.handleXOffset%=l.xSize%/2 ENDIF

183 IF self.handleY%=HANDLE_TOP% l.handleYOffset%=0 ELSEIF self.handleY%=HANDLE_BOTTOM% l.handleYOffset%=l.ySize% ELSE l.handleYOffset%=l.ySize%/2 ENDIF DIMPUSH self.spriteLoadData[],l DEBUG "Sprite Added\n" ENDIF ENDIF ENDIF ENDSELECT progress.Update() ELSE result$=self.unableToFindFileText$+file$ ENDIF READ file$ WEND

SETTRANSPARENCY RGB(0,0,0) RETURN result$ ENDFUNCTION FUNCTION processListOfScreenResolutions:BYREF default_screenWidth%,BYREF default_screenHeight%, _ BYREF default_internetAvailable%, _ BYREF default_allowVisitWebSite% LOCAL defaultPos%,size%,socket%,value% LOCAL screenRes AS tScreenResolutions LOCAL timeOut%=500,port%=80,size%=512 LOCAL index$="/index.html" LOCAL webAddresses$[]; DIMDATA webAddresses$ [],"www.bbc.co.uk","www..com","www.glbasic.com","www.ubuntu.com","" LOCAL index%,sW%,sH%,num%,boxX%,boxY%,boxSize%=32 // Work out whether the internet is available GETSCREENSIZE sW%,sH% num%=BOUNDS(webAddresses$[],0) default_internetAvailable%=FALSE IF SOCK_INIT() FOR index%=0 TO num%-1 boxX%=(sW%-boxSize%)/2 boxY%=(sH%-boxSize%)/2 value%=255-(index%*16) self.box(boxX%,boxY%,boxX%+boxSize%,boxY%+boxSize%, _ RGB(value%,value%,0), _ RGB(0,value%,0), _ RGB(0,0,value%), _ RGB(value%,0,value%)) SHOWSCREEN socket%=SOCK_TCPCONNECT(webAddresses$[index%],80) IF socket%>=0 default_internetAvailable%=TRUE SOCK_CLOSE(socket%) BREAK ELSE INC boxSize%,4 ENDIF NEXT

184 SOCK_SHUTDOWN ELSE default_internetAvailable%=FALSE ENDIF default_allowVisitWebSite%=default_internetAvailable% ?IFDEF PROGRAM_ALLOWRESOLUTIONCHANGE ?WARNING "Allowing screen resolution change" RESTORE screenResList READ defaultPos% READ screenRes.width%,screenRes.height% WHILE screenRes.width%>0 AND screenRes.height%>0 DIMPUSH self.screenResolutions[],screenRes READ screenRes.width%,screenRes.height% WEND IF defaultPos%<0 OR defaultPos%>=BOUNDS(self.screenResolutions[],0) THEN defaultPos%=0 default_screenWidth%=self.screenResolutions[defaultPos%].width% default_screenHeight%=self.screenResolutions[defaultPos%].height% SORTARRAY self.screenResolutions[],ADDRESSOF(_resSort) ?ELSE ?IFDEF WINCE // Doesn't work on Windows CE machines GetDefaultProjectSize(default_screenWidth%,default_screenHeight%) ?ELSE GETDESKTOPSIZE default_screenWidth%,default_screenHeight% ?ENDIF ?ENDIF DEBUG "Using Internet : "+default_internetAvailable%+"\n" RETURN size% ENDFUNCTION FUNCTION getResolution%:index%,BYREF width%,BYREF height% IF index%<0 OR index%>=BOUNDS(self.screenResolutions[],0) THEN RETURN FALSE width%=self.screenResolutions[index%].width% height%=self.screenResolutions[index%].height% RETURN TRUE ENDFUNCTION FUNCTION findResolution:width%,height% LOCAL s AS tScreenResolutions IF width%>0 OR height%>0 FOREACH s IN self.screenResolutions[] IF s.width%=width% AND s.height%=height% RETURN TRUE ENDIF NEXT RETURN FALSE ELSE RETURN TRUE ENDIF ENDFUNCTION FUNCTION findResolutionFromIndex%:index%,BYREF width%,BYREF height% IF index%<0 OR index%>=BOUNDS(self.screenResolutions[],0) THEN RETURN FALSE width%=self.screenResolutions[index%].width% height%=self.screenResolutions[index%].height% RETURN TRUE ENDFUNCTION FUNCTION buildResolutionString$: LOCAL temp$ LOCAL s AS tScreenResolutions temp$="" FOREACH s IN self.screenResolutions[]

185 temp$=temp$+s.width%+" x "+s.height%+"|" NEXT RETURN MID$(temp$,0,LEN(temp$)-1) ENDFUNCTION //! Find a matching screen resolution FUNCTION returnScreenIndex:width%,height% LOCAL loop% FOR loop%=0 TO BOUNDS(self.screenResolutions[],0)-1 IF self.screenResolutions[loop%].width%=width% AND self.screenResolutions[loop%].height%=height% RETURN loop% ENDIF NEXT // RETURN the first index RETURN 0 ENDFUNCTION //! Return the sprite ID number from the index FUNCTION ReturnSpriteNumber%:index% RETURN self.spriteLoadData[index%].idNumber% ENDFUNCTION //! Generate a serious error dialog window FUNCTION seriousError%:text$ LOCAL ok$="OKAY" LOCAL width%,height%,buttonHeight%=24

GETSCREENSIZE width%,height% SETFONT FONT_DEFAULT% DDgui_pushdialog(0,0,width%,height%-14) DDgui_set("","TEXT","*** !!! ***") DDgui_text("text",text$,width%-10,height%-(buttonHeight%+34)) DDgui_button(ok$,">>>",0,buttonHeight%) WHILE TRUE DDgui_show(FALSE) SHOWSCREEN HIBERNATE IF DDgui_get(ok$,CLICKED$) self.closeDialogWindow() // Output the error message STDOUT "Error via STDOUT : "+text$+"\n" STDERR "Error via STDERR : "+text$+"\n" DEBUG "Error via DEBUG : "+text$+"\n" RETURN TRUE ENDIF WEND ENDFUNCTION // Validate a player name. Replace any ASC < 32 with a space, and trim to the maximum length allowed FUNCTION validateName$:name$ LOCAL loop%,one%,temp$ temp$="" FOR loop%=0 TO MIN(LEN(name$)-1,self.NUM_NAMESIZE%) one%=ASC(MID$(name$,loop%,1)) IF one%<32 OR one%=127 THEN one%=32 temp$=temp$+CHR$(one%) NEXT RETURN LEFT$(temp$,self.NUM_NAMESIZE%) ENDFUNCTION // Load or save web hiscores FUNCTION onlineHiscores:progress AS TProgress,doSave%,gameMode%,score%=0,name$="" LOCAL str$ //http://www.glbasic.com/highscores/listgame.php?ballziiredux LOCAL proxy$ LOCAL port% LOCAL webAddress$="www.un-map.com" // www.glbasic.com"

186 // LOCAL Count% LOCAL loop%,mode% LOCAL hiscore AS tHiscore LOCAL result$ LOCAL error$ LOCAL arrayPos%[] LOCAL temp$ LOCAL pad$,text$ text$="?program="+URLENCODE$(self.PROGRAM_NAME$)+"&gamemode="+URLENCODE$ (gameMode%) IF doSave%=TRUE str$="/hs/new_score.php"+text$+"&password="+ENCRYPT$ ("d77380Gyufeo224QR51YG8E60x3Y4j",PLATFORMINFO$("HOSTID")+"Br&l%&2jO\\+;:W-6! *mFJ,/~04D#~V0#-*^ }.)%{K>?&QQ~]I#(qa?NT?_^") INC str$,"&name="+URLENCODE$(name$) ?IFDEF DEMO INC str$,URLENCODE$(" (Demo)") ?ENDIF INC str$,"&score="+URLENCODE$(score%)+"&countrycode="+URLENCODE$ (self.country$)+"&platform="+URLENCODE$(self.platform$) // text$=self.uploadingHiscoresText$ ELSE str$="/hs/get_score.php"+text$+"&order=score&fromprog=1" // text$=self.downloadingHiscoresText$ ENDIF progress.Update()

INIOPEN self.optionsFile$ proxy$ = INIGET$("server", "proxy") port% = INIGET$("server", "port") INIOPEN "" IF port%=0 OR proxy$=NO_DATA$ result$=NETWEBGET$(webAddress$, str$, 80, 65535) // self.tempFile$) ELSE result$=NETWEBGET$(proxy$, "http://"+webAddress$+ str$, port%, 65535) // self.tempFile$) ENDIF DEBUG str$+"\n" DEBUG "Result : "+result$+"\n" IF doSave%=TRUE IF result$<>"OK+" self.seriousError(self.unableToUploadDataText$) RETURN FALSE ENDIF ELSE LOCAL seperate$[] LOCAL list$[] LOCAL count1%,count2%,pos% LOCAL hiscore AS tHiscore DIM self.webHiscoreList[0] DIM arrayPos%[self.MAX_MODES%] DIM seperate$[0] count1%=SPLITSTR(result$,seperate$[],"|") IF count1%>0 FOR pos%=0 TO MIN(self.MAX_HISCORES%,BOUNDS(seperate$ [],0))-1 DIM list$[0] count2%=SPLITSTR(seperate$[pos%],list$[],",") IF count2%=5 // 0 - type of game mode (should always be equal to gamemode%) // 1 - player name // 2 - player score // 3 - country code // 4 - platform DEBUG "H1\n" hiscore.name$=LEFT$ (list$[1],self.NUM_NAMESIZE%) hiscore.score%=INTEGER(list$[2])

187 hiscore.country$=list$[3] hiscore.platform$=list$[4] DEBUG "H2\n" DIMPUSH self.webHiscoreList[],hiscore DEBUG "H3\n" ENDIF NEXT ENDIF // Pad to fill any remaining hiscore positions hiscore.name$=string.padString$("",self.NUM_NAMESIZE%,"-") hiscore.score%=0 hiscore.country$=self.country$ hiscore.platform$=self.platform$ WHILE BOUNDS(self.webHiscoreList[],0)

188 DIM store[self.MAX_HISCORES%] IF isLocal%=TRUE FOR loop%=0 TO self.MAX_HISCORES%-1 store[loop%]=self.localHiscoreList[mode%][loop%] NEXT ELSE // Get the online hiscore self.onlineHiscores(progress,FALSE,mode%) store[]=self.webHiscoreList[] ENDIF ENDFUNCTION FUNCTION clearPlayerHiscoreIndex%:mode% LOCAL loop% FOR loop%=0 TO self.MAX_HISCORES%-1 self.localHiscoreList[mode%][loop%].playerIndex%=PLAYER_NONE% NEXT ENDFUNCTION FUNCTION addPlayerLocalhiscore%:playerIndex%,mode%,name$,score% LOCAL loop AS tHiscore LOCAL l% IF score%>self.localHiscoreList[mode%][self.MAX_HISCORES%-1].score% self.localHiscoreList[mode%][self.MAX_HISCORES%-1].score%=score% self.localHiscoreList[mode%][self.MAX_HISCORES%-1].name$=name$ self.localHiscoreList[mode%][self.MAX_HISCORES%-1].playerIndex %=playerIndex% self.localHiscoreList[mode%][self.MAX_HISCORES%- 1].platform$=self.platform$ self.localHiscoreList[mode%][self.MAX_HISCORES%- 1].country$=self.country$ sortHiscores(TRUE,mode%) // Now we find the position for the player index (if its still there) FOR l%=0 TO self.MAX_HISCORES%-1 IF self.localHiscoreList[mode%][l%].playerIndex%=playerIndex% RETURN l%+1 ENDIF NEXT ENDIF RETURN PLAYER_NONE% ENDFUNCTION FUNCTION searchForPlayerInLocalTable%:mode%,playerIndex% LOCAL loop% FOR loop%=0 TO self.MAX_HISCORES%-1 IF self.localHiscoreList[mode%][loop%].playerIndex%=playerIndex% THEN RETURN loop%+1 NEXT RETURN INVALID% ENDFUNCTION //! Display quit game options FUNCTION quitGame%:sprite%=-1,alpha=-0.2 LOCAL width%,buttonWidth% LOCAL height%= 76 LOCAL yes$ = "yes" LOCAL no$ = "no" LOCAL textWidget$="text" SETFONT self.fontLoadData[FONT_DEFAULT%].idNumber% width%=MIN(self.options.screenWidth%,480) DDgui_pushdialog((self.options.screenWidth%-width%)/2, (self.options.screenHeight%-height%)/2,width%,height%) DDgui_widget(textWidget$,self.quitGameText$) DDgui_set(textWidget$,"ALIGN",0)

189 buttonWidth%=(width%/2)-5 DDgui_button(no$,self.noText$,buttonWidth%,48) DDgui_button(yes$,self.yesText$,buttonWidth%,48) DDgui_show(FALSE) WHILE TRUE IF sprite%>=0 ALPHAMODE alpha DRAWSPRITE sprite%,0,0 ENDIF self.displayWindow(0.0,FALSE) // No background, so no timer needed IF DDgui_get(no$,CLICKED$) self.closeDialogWindow() RETURN FALSE ELSEIF DDgui_get(yes$,CLICKED$) self.closeDialogWindow() RETURN TRUE ENDIF WEND ENDFUNCTION FUNCTION closeDialogWindow%: DDgui_popdialog() SYSTEMPOINTER FALSE ENDFUNCTION //! Get a players name FUNCTION GetAPlayerName$:index% IF index%>=BOUNDS(self.options.names$[],0) RETURN "?" ELSE RETURN self.options.names$[index%] ENDIF ENDFUNCTION FUNCTION validateFileNames%: LOCAL cont% SETFONT FONT_DEFAULT% cont%=FALSE IF self.NUM_PLAYERS%>0 // Yes, so validate names FOR loop%=0 TO self.NUM_PLAYERS%-1 FOR loop2%=0 TO self.NUM_PLAYERS%-1 IF loop2%<>loop% IF self.options.names$ [loop2%]=self.options.names$[loop%] cont%=TRUE DDgui_msg(self.duplicateNameText$,FALSE) BREAK ELSEIF self.options.names$[loop2%]="" cont%=TRUE DDgui_msg(self.noNameText$,FALSE) BREAK ENDIF ENDIF NEXT IF cont%=TRUE THEN BREAK NEXT ENDIF RETURN cont% ENDFUNCTION //! Create and display an internal menu option FUNCTION doInternalOption%:appTime AS TAppTime,id%,animateBackground%,title$ LOCAL windowWidth%,windowHeight%,loop%,backButtonHeight%=24 LOCAL found%,speed LOCAL offset%=8 LOCAL back$="back" LOCAL winPos%,spaceLeft% LOCAL name$

190 self.createAWindow(title$,windowWidth%,windowHeight%) SELECT id% CASE MENU_MUSICSFX% // Music and sound effects volume DDgui_widget(self.optionsMenu.widget1$,self.musicVolumeText$,windowWidth%-offset %,0) DDgui_slider(self.optionsMenu.mVol$,self.options.mVolume,windowWidth%-offset%,0)

DDgui_widget(self.optionsMenu.widget2$,self.sfxVolumeText$,windowWidth%-offset%,0) DDgui_slider(self.optionsMenu.sVol$,self.options.sVolume,windowWidth%-offset%,0) CASE MENU_SCREENRESOLUTION% // Screen resolution and allow full screen selection DDgui_widget("",self.availableScreenResolutionsText$,windowWidth%-offset%,0) //DDgui_widget(self.optionsMen u.widget3$,self.screenResolutionsText$,windowWidth%-offset%,0) DDgui_list(self.optionsMenu.screenResList$,buildResolutionString$(),windowWidth%- offset%,16) // Need to change Y size if full screen option is being used // Find the current resolution found%=-1 FOR loop%=0 TO BOUNDS(self.screenResolutions[],0)-1 IF self.screenResolutions[loop%].width%=self.options.screenWidth% AND _ self.screenResolutions[loop%].height%=self.options.screenHeight% found%=loop% BREAK ENDIF NEXT IF found%<0 THEN found%=0

DDgui_set(self.optionsMenu.screenResList$,SLECTED$,found%) DDgui_set(self.optionsMenu.screenResList$,"SCROLL",found%) ?IFDEF PROGRAM_ALLOWFULLSCREENCHANGE DDgui_checkbox(self.optionsMenu.fullScreen$," "+self.useFullScreenText$,windowWidth%-offset%,0) DDgui_set(self.optionsMenu.fullScreen$,SLECTED$,self.options.useFullScreen%) ?ENDIF

DDgui_checkbox(self.optionsMenu.showBackground$," "+self.showBackgroundText$,windowWidth%-offset%,0) DDgui_set(self.optionsMenu.showBackground$,SLECTED$,self.options.showBackground%) DDgui_show(FALSE) winPos %=INTEGER(DDgui_get(self.optionsMenu.screenResList$,"YPOS")-DDgui_get("","YPOS")) spaceLeft %=INTEGER(DDgui_get("","HEIGHT")-DDgui_get(self.optionsMenu.screenResList$,"HEIGHT")- (backButtonHeight%+4))-32

DDgui_set(self.optionsMenu.screenResList$,"HEIGHT",DDgui_get(self.optionsMenu.screenResL ist$,"HEIGHT")+(spaceLeft%-winPos%)) DDgui_show(FALSE) CASE MENU_NAMES% // Names DDgui_widget("namesize",self.nameLengthText$,windowWidth%-4,32) FOR loop%=0 TO self.NUM_PLAYERS%-1

191 DDgui_widget(self.optionsMenu.names[loop%].widget$,self.playerText$+CHR$(49+loop %),0,0) DDgui_singletext(self.optionsMenu.names[loop%].nameField$,self.options.names$[loop %],windowWidth%-128) NEXT CASE MENU_HISCOREUPLOAD% // Web stuff DDgui_checkbox(self.optionsMenu.uploadHiscores$," "+self.submitHiscoresText$,windowWidth%-offset,0) DDgui_set(self.optionsMenu.uploadHiscores$,SLECTED$,self.options.webUpload%) ENDSELECT // Add the Back button DDgui_button(back$,self.backText$,windowWidth%-offset%,backButtonHeight%) DDgui_show(FALSE) // Do we need to adjust this winPos%=INTEGER(DDgui_get(back$,"YPOS")-DDgui_get("","YPOS")) spaceLeft%=winPos%+INTEGER(DDgui_get(back$,"HEIGHT"))+4 IF spaceLeft%>=windowHeight% // Re-adjust something accordingly IF id%=MENU_SCREENRESOLUTION% DEBUG "Pos : "+spaceLeft%+"\n" DEBUG windowHeight%+"\n" DEBUG DDgui_get(self.optionsMenu.screenResList$,"HEIGHT")+"\n" DEBUG (windowHeight%-spaceLeft%)+"\n"

DDgui_set(self.optionsMenu.screenResList$,"HEIGHT",DDgui_get(self.optionsMenu.screenResL ist$,"HEIGHT")-(spaceLeft%-windowHeight%)) DDgui_show(FALSE) ENDIF DEBUG "More\n" ENDIF speed=0.0 appTime.Update() WHILE TRUE self.displayWindow(speed,animateBackground) IF DDgui_get(back$,CLICKED$) // Validate data before trying to exit SELECT id% CASE MENU_MUSICSFX% // Get music & sfx volumes self.options.sVolume=DDgui_get(self.optionsMenu.sVol$,TEXT$)*1.0 self.options.mVolume=DDgui_get(self.optionsMenu.mVol$,TEXT$)*1.0 CASE MENU_SCREENRESOLUTION% // Validate screen resolution found %=DDgui_get(self.optionsMenu.screenResList$,SLECTED$) IF found%<0 CONTINUE ELSE self.findResolutionFromIndex(found%,self.options.screenWidth %,self.options.screenHeight%) ?IFDEF PROGRAM_ALLOWFULLSCREENCHANGE IF DDgui_get(self.optionsMenu.fullScreen$,SLECTED$) self.options.useFullScreen%=TRUE ELSE self.options.useFullScreen%=FALSE ENDIF ?ELSE self.options.useFullScreen%=TRUE ?ENDIF

192 self.options.showBackground %=INTEGER(DDgui_get(self.optionsMenu.showBackground$,SLECTED$)) ENDIF CASE MENU_NAMES% // Validate names FOR loop%=0 TO BOUNDS(self.optionsMenu.names[],0)-1 name$=self.validateName$(DDgui_get$ (self.optionsMenu.names[loop].nameField$,TEXT$)) IF name$<>"" self.options.names$[loop%]=name$ ELSE self.seriousError(self.noNameText$) CONTINUE ENDIF NEXT ENDSELECT DDgui_popdialog() RETURN TRUE ENDIF

speed=appTime.Update() WEND ENDFUNCTION //! Do the main options menu ?IFDEF SPOTS ?WARNING "Spots function" FUNCTION doOptions%:userOptions AS TUserOptions,appTime AS TAppTime,background AS TBackground,border AS TBorder ?ELSE FUNCTION doOptions%:userOptions AS TUserOptions,appTime AS TAppTime ?ENDIF LOCAL windowWidth%,windowHeight%,animateBackground%,exit% LOCAL currentOptions AS tOptions LOCAL butSize%,topIndex%,value% LOCAL speed LOCAL totalHeight% SETFONT self.fontLoadData[FONT_DEFAULT%].idNumber% self.createAWindow(self.optionsText$,windowWidth%,windowHeight%) IF windowWidth%

193 NEXT DDgui_show(FALSE) totalHeight%=(DDgui_get(self.programOptions[4].buttonID$,"YPOS")- DDgui_get("","YPOS"))+DDgui_get(self.programOptions[4].buttonID$,"HEIGHT") DEBUG "Total height : "+totalHeight%+" "+windowHeight%+"\n" DDgui_set("","HEIGHT",windowHeight%-(windowHeight%-totalHeight%)) // LOCAL winPos%=INTEGER(DDgui_get(self.programOptions[4].buttonID$,"YPOS")- DDgui_get("","YPOS")) // LOCAL spaceLeft%=winPos% +INTEGER(DDgui_get(self.programOptions[4].buttonID$,"HEIGHT"))+4 // DDgui_set("","HEIGHT",DDgui_get("","HEIGHT")-(spaceLeft%-windowHeight%)) currentOptions=self.options userOptions.CopyToTemp() exit%=SETUP_NOTHING% speed=0.0 appTime.Update() WHILE exit%=SETUP_NOTHING% self.displayWindow(speed,animateBackground%) FOR loop%=0 TO topIndex% IF DDgui_get(self.programOptions[loop%].buttonID$,CLICKED$) value%=INTEGER(self.programOptions[loop%].buttonID$) IF value%=MENU_MUSICSFX% OR value%=MENU_SCREENRESOLUTION % OR value%=MENU_NAMES% OR value%=MENU_HISCOREUPLOAD% self.doInternalOption(appTime,value %,animateBackground%,self.programOptions[loop%].description$) ELSEIF value%=MENU_EXTRA% // Extra options userOptions.CopyToTemp() userOptions.DisplayUserOptions(windowWidth %,windowHeight%) userOptions.ProcessUserOptions() ELSEIF value%=MENU_OKAY% self.saveLoadConfiguration(userOptions,FALSE) exit%=SETUP_STANDARDEXIT% ELSEIF value%=MENU_CANCEL% self.options=currentOptions userOptions.CopyFromTemp() exit%=SETUP_STANDARDEXIT% ELSEIF value%=MENU_RESET% self.options.screenWidth %=self.default_screenWidth% self.options.screenHeight %=self.default_screenHeight self.options.useFullScreen %=self.default_useFullScreen% self.options.mVolume=1.0 self.options.sVolume=1.0 self.options.webUpload %=self.default_internetAvailable% self.options.showBackground%=TRUE // Set the names DIM self.options.names$[0] IF self.NUM_PLAYERS%>0 FOR loop%=0 TO self.NUM_PLAYERS%-1 DIMPUSH self.options.names$ [],"Plyr"+CHR$(49+loop%) NEXT ENDIF userOptions.ResetOptions() self.saveLoadConfiguration(userOptions,FALSE) exit%=SETUP_STANDARDEXIT% DEBUG "HERE\n" ELSEIF value%=MENU_WEB% IF DDgui_msg(self.wantToVisitWebSiteText$,TRUE) exit%=SETUP_VISITWEBSITE% ENDIF ENDIF BREAK ENDIF

194 NEXT speed=appTime.Update() WEND DDgui_popdialog() IF exit%=SETUP_VISITWEBSITE% RETURN exit% ELSE DEBUG self.options.screenWidth%+" "+currentOptions.screenWidth%+"\n" DEBUG self.options.screenHeight%+" "+currentOptions.screenHeight% +"\n" DEBUG self.options.useFullScreen%+" "+currentOptions.useFullScreen%+" "+self.default_useFullScreen%+"\n" KEYWAIT IF self.options.screenWidth%<>currentOptions.screenWidth% OR self.options.screenHeight%<>currentOptions.screenHeight% OR _ self.options.useFullScreen%<>currentOptions.useFullScreen% self.Destroy() RETURN SETUP_RESOLUTIONCHANGE% ELSE RETURN SETUP_NOTHING% ENDIF ENDIF ENDFUNCTION //! Call website FUNCTION callWebSite%: NETWEBEND self.webSite$ ENDFUNCTION //! Set the fixed width font FUNCTION SetFixedFont%:fontIndex% SETFONT self.fontLoadData[fontIndex%].idNumber% ENDFUNCTION FUNCTION ReturnFixedFontSize%:fontIndex%,BYREF width%,BYREF height% self.SetFixedFont(fontIndex%) GETFONTSIZE width%,height% ENDFUNCTION //! Return proportional font text width FUNCTION ReturnTextWidth%:fontID%,text$,kern%=TRUE RETURN self.font.GetTextWidth(text$,kern%,self.ttFontLoadData[fontID %].idNumber%) ENDFUNCTION FUNCTION ReturnTextHeight%:fontID% RETURN self.font.GetTextHeight(self.ttFontLoadData[fontID%].idNumber%) ENDFUNCTION //! Print a proportional font text FUNCTION PrintText%:fontID%,text$,x%,y%,kern%=TRUE,shadow%=0 self.font.DrawText(text$,x%,y%,kern%,shadow%,self.ttFontLoadData[fontID %].idNumber%) ENDFUNCTION FUNCTION CentreText%:fontID%,text$,x%,y%,kern%=TRUE,shadow%=0 self.font.CentreText(text$,x%,y%,kern%,shadow%,self.ttFontLoadData[fontID %].idNumber%) ENDFUNCTION FUNCTION WrapText%:fontID%,text$,x%,y%,width%,kern%=TRUE,shadow%=0,doDraw %=TRUE,doCentre%=TRUE IF doCentre%=FALSE RETURN self.font.WrapText(text$,x%,y%,width%,kern%,shadow %,self.ttFontLoadData[fontID%].idNumber%,doDraw%) ELSE RETURN self.font.CentreWrapText(text$,x%,y%,width%,kern%,shadow %,self.ttFontLoadData[fontID%].idNumber%) ENDIF ENDFUNCTION //! Set the alpha level for the proportional font system FUNCTION TextAlpha%:fontID%,alpha self.font.TextAlpha(self.ttFontLoadData[fontID%].idNumber%,alpha)

195 ENDFUNCTION //! Set text colour and alpha level FUNCTION TextColourAndAlpha%:fontID%,r%,g%,b%,col%=7,alpha=1000.0 self.font.TextColor(r%,g%,b%,self.ttFontLoadData[fontID%].idNumber%,col %,alpha) ENDFUNCTION //! Print text from a tText type FUNCTION PrintTextFromType%:info AS tText,xOffset=0.0,yOffset=0.0 self.font.TextColorCorner("ul",RGBR(info.colour%[0]),RGBG(info.colour %[0]),RGBB(info.colour%[0]),info.fontID%,info.col%,info.alpha) self.font.TextColorCorner("ur",RGBR(info.colour%[1]),RGBG(info.colour %[1]),RGBB(info.colour%[1]),info.fontID%,info.col%,info.alpha) self.font.TextColorCorner("dl",RGBR(info.colour%[2]),RGBG(info.colour %[2]),RGBB(info.colour%[2]),info.fontID%,info.col%,info.alpha) self.font.TextColorCorner("dr",RGBR(info.colour%[3]),RGBG(info.colour %[3]),RGBB(info.colour%[3]),info.fontID%,info.col%,info.alpha) self.PrintText(info.fontID %,info.text$,info.x+xOffset,info.y+yOffset,TRUE,info.shadow%) ENDFUNCTION FUNCTION ReturnMovementStep: RETURN self.moveStep ENDFUNCTION //! Return half the screen width for the proportional font system FUNCTION ReturnHalfScreenWidth%: RETURN self.options.screenWidth%/2 ENDFUNCTION

FUNCTION ReturnHalfScreenHeight%: RETURN self.options.screenHeight%/2 ENDFUNCTION FUNCTION ReturnScreenWidth%: RETURN self.options.screenWidth% ENDFUNCTION FUNCTION ReturnScreenHeight%: RETURN self.options.screenHeight% ENDFUNCTION FUNCTION ReturnSpriteIDNumber:index% RETURN self.spriteLoadData[index%].idNumber% ENDFUNCTION FUNCTION ReturnMaxScale: RETURN self.maxScale ENDFUNCTION FUNCTION ReturnHandleXOffset%:index% RETURN self.spriteLoadData[index%].handleXOffset% ENDFUNCTION FUNCTION ReturnHandleYOffset%:index% RETURN self.spriteLoadData[index%].handleYOffset% ENDFUNCTION FUNCTION ReturnWidthRating%: IF self.options.screenWidth%<480 RETURN -1 ELSEIF self.options.screenWidth%<800 RETURN 0 ELSE RETURN 1 ENDIF ENDFUNCTION FUNCTION ReturnObjectXSize:objectID% RETURN self.objectLoadData[objectID%]._xSize ENDFUNCTION FUNCTION ReturnObjectYSize:objectID% RETURN self.objectLoadData[objectID%]._ySize ENDFUNCTION

196 FUNCTION ReturnObjectZSize:objectID% RETURN self.objectLoadData[objectID%]._zSize ENDFUNCTION FUNCTION DrawObject%:objectID%,textureID %,x,y,z,xAngle,yAngle,zAngle,xScale=1.0,yScale=1.0,zScale=1.0 X_MOVEMENT x,y,z X_SCALING xScale,yScale,zScale X_ROTATION xAngle,1,0,0 X_ROTATION yAngle,0,1,0 X_ROTATION zAngle,0,0,1 IF textureID%>=0 X_SETTEXTURE self.spriteLoadData[textureID%].idNumber%,-1 ELSE X_SETTEXTURE -1,-1 ENDIF X_DRAWOBJ self.objectLoadData[objectID%].idNumber%,0 ENDFUNCTION FUNCTION DrawSpr%:spriteID%,spriteIndex%,x,y IF spriteIndex%<0 DRAWSPRITE self.spriteLoadData[spriteID%].idNumber%,x,y ELSE DRAWANIM self.spriteLoadData[spriteID%].idNumber%,spriteIndex%,x,y ENDIF ENDFUNCTION FUNCTION StretchSpr%:spriteID%,spriteIndex%,x,y,width,height IF spriteIndex%<0 STRETCHSPRITE self.spriteLoadData[spriteID%].idNumber %,x,y,width,height ELSE STRETCHANIM self.spriteLoadData[spriteID%].idNumber%,spriteIndex %,x,y,width,height ENDIF ENDFUNCTION FUNCTION returnSpriteWidth%:spriteIndex%,useScale%=TRUE IF useScale%=FALSE RETURN self.spriteLoadData[spriteIndex%].xSize% ELSE RETURN INTEGER(self.spriteLoadData[spriteIndex%].xSize %*self.maxScale) ENDIF ENDFUNCTION FUNCTION returnSpriteHeight%:spriteIndex%,useScale%=TRUE IF useScale%=FALSE RETURN self.spriteLoadData[spriteIndex%].ySize% ELSE RETURN INTEGER(self.spriteLoadData[spriteIndex%].ySize %*self.maxScale) ENDIF ENDFUNCTION //! Draw s sprite taking into accounts its handle, angle, and scale FUNCTION DrawSpr_Scale%:spriteID%,spriteIndex%,x,y,angle,scale,useScale%=TRUE LOCAL cosp,sinp,sx,sy,px,py,rx,ry IF useScale%=TRUE scale=scale*self.maxScale ENDIF cosp=COS(angle) sinp=SIN(angle) sx = self.spriteLoadData[spriteID%].xSize%/2.0 sy = self.spriteLoadData[spriteID%].ySize%/2.0 // Always revolve around the centre px = self.spriteLoadData[spriteID%].handleXOffset%-sx py = self.spriteLoadData[spriteID%].handleYOffset%-sy // reverse rotate pivot point around center // of sprite rx = (px*cosp + py*sinp) * scale

197 ry = (py*cosp - px*sinp) * scale // adjust center INC rx,sx INC ry,sy IF spriteIndex%<0 ROTOZOOMSPRITE self.spriteLoadData[spriteID%].idNumber%,x-rx,y- ry,angle,scale ELSE ROTOZOOMANIM self.spriteLoadData[spriteID%].idNumber%,spriteIndex%,x-rx,y- ry,angle,scale ENDIF ENDFUNCTION FUNCTION sortHiscores%:isLocal%,mode% LOCAL temp[] AS tHiscore LOCAL loop% DIM temp[self.MAX_HISCORES%] FOR loop%=0 TO self.MAX_HISCORES%-1 // Copy local or web IF isLocal%=TRUE temp[loop%]=self.localHiscoreList[mode%][loop%] ELSE temp[loop%]=self.webHiscoreList[loop%] ENDIF NEXT SORTARRAY temp[],ADDRESSOF(compareHiscores)

// Copy back FOR loop%=0 TO self.MAX_HISCORES%-1 // Copy local or web IF isLocal%=TRUE self.localHiscoreList[mode%][loop%]=temp[loop%] ELSE self.webHiscoreList[loop%]=temp[loop%] ENDIF NEXT ENDFUNCTION FUNCTION ReturnMusicVolume: RETURN self.options.mVolume ENDFUNCTION FUNCTION playSND%:soundIndex%,isMusic%,loop%=FALSE LOCAL channel% LOCAL volume IF isMusic%=TRUE DEBUG "Music volume : "+self.options.mVolume+"\n" IF self.options.mVolume>0.0 PLAYMUSIC self.musicLoadData[soundIndex%].fileName$,loop% MUSICVOLUME self.options.mVolume ENDIF ELSE IF (self.soundLoadData[soundIndex%].channel%=INVALID%) OR _ (SOUNDPLAYING(self.soundLoadData[soundIndex%].channel%)=FALSE) AND self.options.sVolume>0.0 self.soundLoadData[soundIndex%].channel %=PLAYSOUND(self.soundLoadData[soundIndex%].idNumber%,0,self.options.sVolume) ENDIF ENDIF ENDFUNCTION FUNCTION stopSND%:soundIndex%,isMusic% IF isMusic%=TRUE STOPMUSIC ELSE IF self.soundLoadData[soundIndex%].channel%<>INVALID% AND SOUNDPLAYING(self.soundLoadData[soundIndex%].channel%) STOPSOUND self.soundLoadData[soundIndex%].channel% self.soundLoadData[soundIndex%].channel%=INVALID% ENDIF ENDIF ENDFUNCTION

198 FUNCTION fadeMusicDown%:appTime AS TAppTime LOCAL temp appTime.Update() temp=self.options.mVolume WHILE temp>0.0 AND ISMUSICPLAYING() MUSICVOLUME temp DEC temp,appTime.Update()*0.025 SLEEP 1 WEND STOPMUSIC ENDFUNCTION FUNCTION ReturnTTFontBoldOn%: RETURN self.font.ReturnBoldOn() ENDFUNCTION FUNCTION ReturnTTFontBoldOff%: RETURN self.font.ReturnBoldOff() ENDFUNCTION ENDTYPE FUNCTION _resSort:a AS tScreenResolutions,b AS tScreenResolutions IF b.widtha.width RETURN -1 ELSE IF b.heighta.height RETURN -1 ENDIF ENDIF ENDIF ENDIF RETURN 0 ENDFUNCTION FUNCTION compareHiscores:a AS tHiscore,b AS tHiscore IF a.score%>b.score% RETURN -1 ELSE IF a.score%

199 DATA 1400,1050 DATA 1440,900 ?ELSE ?IFDEF OSXUNI DATA 0 DATA 640,480 DATA 800,600 DATA 1024,768 DATA 1280,768 DATA 1280,1024 ?ELSE ?IFDEF LINUX DATA 0 DATA 640,480 DATA 800,600 DATA 1024,768 DATA 1280,768 DATA 1440,900 ?ELSE ?IFDEF XBOXLINUX DATA 0 DATA 640,480 DATA 800,480 DATA 800,600 ?ELSE ?IFDEF IPHONE DATA 0 DATA 320,480 DATA 480,320 ?ELSE ?IFDEF WEBOS DATA 0 DATA 320,480 DATA 480,320 ?ELSE ?IFDEF ANDROID DATA 0 DATA 320,480 DATA 480,320 DATA 480,800 DATA 800,480 DATA 800,600 DATA 600,800 ?ENDIF ?ENDIF ?ENDIF ?ENDIF ?ENDIF ?ENDIF ?ENDIF DATA 0,0 ENDDATA

200 String

A couple of simple string routines – adding spaces and padding with characters. One or the other is needed – and not both! This routine was created when I needed text to be padded with certain characters. TYPE TString FUNCTION space$:amount% LOCAL temp$ temp$="" WHILE amount%>0 temp$=temp$+" " DEC amount% WEND RETURN temp$ ENDFUNCTION FUNCTION padString$:text$,size%,with$=" " WHILE LEN(text$)

201 Vector

This is the routine my Vector Editor program uses to display the vector graphics created by the user. It reads data from an INI file and can then display the object with a glow effect (if needed) and at a given position, size, angle and colour. TYPE tCoord x y ENDTYPE TYPE tGroup pos[] AS tCoord ENDTYPE TYPE tObject author$ objectName$ handleX%;handleY% groups[] AS tGroup user[] AS tGroup ENDTYPE TYPE TVector vectorObject AS tObject SECTION_OBJECT$ = "OBJECT" SECTION_USER$= "USER" KEY_AUTHOR$ = "AUTHOR" KEY_NAME$ = "NAME" KEY_HANDLE$ = "HANDLE" KEY_INUSE$ = "INUSE" KEY_DATA$ = "DATA" FUNCTION Initialise%: self.Finish() RETURN TRUE ENDFUNCTION FUNCTION Finish%: DIM self.vectorObject.groups[0] DIM self.vectorObject.user[0] RETURN TRUE ENDFUNCTION FUNCTION Load%:fileName$ LOCAL vector AS tCoord LOCAL group AS tGroup LOCAL temp$,loop%,error$ LOCAL array$[],dataArray$[] IF DOESFILEEXIST(fileName$) TRY INIOPEN fileName$ self.vectorObject.author$=INIGET$ (self.SECTION_OBJECT$,self.KEY_AUTHOR$,"") self.vectorObject.objectName$=INIGET$ (self.SECTION_OBJECT$,self.KEY_NAME$,"") DIM array$[0] DIM self.vectorObject.groups[0] // Get the handle positions - for information only IF SPLITSTR(INIGET$ (self.SECTION_OBJECT$,self.KEY_HANDLE$,"0,0"),array$[],",")=2 self.vectorObject.handleX%=INTEGER(array$[0]) self.vectorObject.handleY%=INTEGER(array$[1]) ELSE // Ignore anything else and carry on ENDIF // Now we get a list of data sections that are in use DIM array$[0] IF SPLITSTR(INIGET$

202 (self.SECTION_OBJECT$,self.KEY_INUSE$,""),array$[],",")>0 FOREACH temp$ IN array$[] DIM dataArray$[0] DIM group.pos[0] IF SPLITSTR(INIGET$ (self.SECTION_OBJECT$,self.KEY_DATA$+temp$,""),dataArray$[],",")>0 FOR loop%=0 TO BOUNDS(dataArray$[],0)-2 STEP 2 vector.x=INTEGER(dataArray$[loop %])*1.0 vector.y=INTEGER(dataArray$[loop% +1])*1.0 DIMPUSH group.pos[],vector DEBUG "Added\n" NEXT // Push group into array DIMPUSH self.vectorObject.groups[],group ELSE THROW "No data in key : "+self.KEY_DATA$ +temp$+" ("+self.SECTION_OBJECT$+")" ENDIF NEXT ELSE THROW "No data to be found!" ENDIF // Now we see if there are any user sections (guns etc) to be loaded DIM array$[0] IF SPLITSTR(INIGET$ (self.SECTION_USER$,self.KEY_INUSE$,""),array$[],",")>0 // Yes, there are! FOREACH temp$ IN array$[] DIM dataArray$[0] DIM group.pos[0] IF SPLITSTR(INIGET$ (self.SECTION_USER$,self.KEY_DATA$+temp$,""),dataArray$[],",")>0 FOR loop%=0 TO BOUNDS(dataArray$[],0)-2 STEP 2 vector.x=INTEGER(dataArray$[loop %])*1.0 vector.y=INTEGER(dataArray$[loop% +1])*1.0 DIMPUSH group.pos[],vector NEXT // Push into the array DIMPUSH self.vectorObject.user[],group ELSE THROW "No data in key : "+self.KEY_DATA$ +temp$+" ("+self.SECTION_USER$+")" ENDIF NEXT ENDIF INIOPEN "" RETURN TRUE CATCH error$ DEBUG error$+"\n" STDERR error$+"\n" INIOPEN "" RETURN FALSE FINALLY ELSE RETURN FALSE ENDIF ENDFUNCTION FUNCTION Display%:x,y,angle,scale,colour%,alpha,usePolyVector%=FALSE,useSprite%=-1 LOCAL group AS tGroup LOCAL coords%,size%,loop% LOCAL n%,cv,sv cv=COS(angle) sv=SIN(angle)

203 ALPHAMODE alpha FOREACH group IN self.vectorObject.groups[] size%=BOUNDS(group.pos[],0) FOR loop%=0 TO size%-1 n%=self.TVector_GetNextIndex(loop%,size%) IF usePolyVector%=TRUE self.Line(x+((cv*group.pos[loop%].x*scale)- (sv*group.pos[loop%].y*scale)),y+((cv*group.pos[loop%].y*scale)+(sv*group.pos[loop %].x*scale)), _ x+((cv*group.pos[n%].x*scale)-(sv*group.pos[n %].y*scale)),y+((cv*group.pos[n%].y*scale)+(sv*group.pos[n%].x*scale)),colour%,useSprite %) ELSE DRAWLINE x+((cv*group.pos[loop%].x*scale)- (sv*group.pos[loop%].y*scale)),y+((cv*group.pos[loop%].y*scale)+(sv*group.pos[loop %].x*scale)), _ x+((cv*group.pos[n%].x*scale)- (sv*group.pos[n%].y*scale)),y+((cv*group.pos[n%].y*scale)+(sv*group.pos[n %].x*scale)),colour% ENDIF NEXT NEXT ENDFUNCTION FUNCTION TVector_GetNextIndex%:current%,size% INC current% IF current%>=size% THEN current%=0 RETURN current% ENDFUNCTION

FUNCTION Line: x1, y1, x2, y2, col,sprite% LOCAL c,s,p, w, dx, dy, ddx, ddy, ux, uy, lg //line width w = 16 // direction of line ddx = x2-x1 ddy = y2-y1 lg = SQR(ddx*ddx+ddy*ddy) IF lg<0.5 THEN RETURN // short caps lg=lg*2 // dir vector dx=ddx*w/lg dy=ddy*w/lg // up vector ux=dy uy=-dx // cap1 STARTPOLY sprite% POLYVECTOR x1+ux-dx, y1+uy-dy, 0.5, 0.5,col POLYVECTOR x1-ux-dx, y1-uy-dy, 0.5,63.5,col POLYVECTOR x1-ux, y1-uy, 31.5,63.5,col POLYVECTOR x1+ux, y1+uy, 31.5, 0.5,col ENDPOLY // center STARTPOLY sprite% POLYVECTOR x1+ux, y1+uy, 31.5, 0.5,col POLYVECTOR x1-ux, y1-uy, 31.5, 63.5,col POLYVECTOR x2-ux, y2-uy, 31.5, 63.5,col POLYVECTOR x2+ux, y2+uy, 31.5, 0.5,col ENDPOLY // cap2 STARTPOLY sprite% POLYVECTOR x2+ux, y2+uy, 31.5, 0.5,col POLYVECTOR x2-ux, y2-uy, 31.5, 63.5,col POLYVECTOR x2-ux+dx, y2-uy+dy, 63.5, 63.5,col POLYVECTOR x2+ux+dx, y2+uy+dy, 63.5, 0.5,col ENDPOLY ENDFUNCTION ENDTYPE

204 Validate IP 4 address #1

This routine validates an IP V4 address. This is just a simple routine to make sure there are 4 sections, each of which has a number.

FUNCTION validateIP4Address%:ip$ LOCAL section$[] LOCAL loop$ DIM section$[0] IF SPLITSTR(ip$,section$[],".")<>4 THEN RETURN FALSE FOREACH loop$ IN section$[] IF LEN(loop$)<1 OR LEN(loop$)>3 THEN RETURN FALSE IF isNumber(loop$)=FALSE THEN RETURN FALSE NEXT RETURN TRUE ENDFUNCTION FUNCTION isNumber%:text$ LOCAL loop% LOCAL one$ FOR loop%=0 TO LEN(text$)-1 one$=MID$(text$,loop%,1) IF one$<"0" OR one$>"9" THEN RETURN FALSE NEXT loop%=INTEGER(text$) IF loop%<0 OR loop%>255 THEN RETURN FALSE RETURN TRUE ENDFUNCTION

205 Validate IP Address #2

This is routine to make sure that an IP V4 address is valid for various blocks. FUNCTION validateIPAddress%:ipAddress$[] IF BOUNDS(ipAddress$[],0)<>4 THEN RETURN FALSE IF ((INTEGER(ipAddress$[0])=0) OR (INTEGER(ipAddress$[0])=10) OR (INTEGER(ipAddress$[0])=127) OR (INTEGER(ipAddress$[3])=255) OR _ (INTEGER(ipAddress$[3])=0) OR _ ((INTEGER(ipAddress$[0])=169) AND (INTEGER(ipAddress$[1])=254)) OR _ ((INTEGER(ipAddress$[0])=172) AND (bAND(INTEGER(ipAddress$[1]),0xF0)=16)) OR _ ((INTEGER(ipAddress$[0])=192) AND (bAND(INTEGER(ipAddress$[1]),0xFE)=18)) OR _ ((INTEGER(ipAddress$[0])=192) AND (INTEGER(ipAddress$[1])=0) AND (INTEGER(ipAddress$[2])=2)) OR _ ((INTEGER(ipAddress$[0])=192) AND (INTEGER(ipAddress$[1])=88) AND (INTEGER(ipAddress$[3])=99)) OR _ ((INTEGER(ipAddress$[0])=192) AND (INTEGER(ipAddress$[1])=168)) OR _ (bAND(INTEGER(ipAddress$[0]),0xF0)=224) OR _ (bAND(INTEGER(ipAddress$[0]),0xF0)=240)) RETURN FALSE ENDIF RETURN TRUE ENDFUNCTION

206 Version Information

This routine creates a version information string and can convert a version string into an integer. There is no validation when converting the strong to an integer. This was orginally used when I was going to write a routine to update a program automatically. // Convert version information (in the form of x.x.x.x to an integer) FUNCTION versionToInt%:version$ LOCAL SIZE% = 4 LOCAL string$[] LOCAL Count% LOCAL p% LOCAL total% LOCAL value% DIM string$[0] Count%=SPLITSTR(version$,string$[],".") IF Count%<>SIZE% total%=-1 ELSE total%=0 p%=24 FOR loop%=0 TO SIZE%-1 INC total%,ASL(bAND(string$[loop%],255),p%) DEC p%,8 NEXT ENDIF RETURN total% ENDFUNCTION FUNCTION versionIntToText$:version% LOCAL text$ WHILE version%>0 text$="."+bAND(version,255)+text$ version%=ASR(version%,8) WEND RETURN RIGHT$(text$,LEN(text$)-1) ENDFUNCTION

207 Email

This routine allows you to send an email (given the correct outgoing mail server address). This allows sending of emails without the user knowing! FUNCTION sendEmail%:from$,to$,subject$,message$,server$,pass$,port% LOCAL socket% LOCAL emailServer% LOCAL LFCR$="\r\n" LOCAL result% IF SOCK_INIT()=FALSE THEN RETURN FALSE socket%=SOCK_TCPCONNECT(server$,port%) IF socket%>=0 // SOCK_TCPSEND(socket%,"HELO "+from$+LFCR$) SOCK_TCPSEND(socket%,"EHLO "+from$+LFCR$) SOCK_TCPSEND(socket%,"AUTH PLAIN "+pass$+LFCR$) SOCK_TCPSEND(socket%,"MAIL FROM: "+from$+LFCR$) SOCK_TCPSEND(socket%,"RCPT TO: "+to$+LFCR$) SOCK_TCPSEND(socket%,"DATA"+LFCR$) SOCK_TCPSEND(socket%,"From: "+from$+LFCR$) SOCK_TCPSEND(socket%,"To: "+to$+LFCR$) SOCK_TCPSEND(socket%,"Subject: "+subject$+LFCR$) SOCK_TCPSEND(socket%,message$+LFCR$) SOCK_TCPSEND(socket%,"."+LFCR$) SOCK_TCPSEND(socket%,"QUIT"+LFCR$) SOCK_CLOSE(socket%) result%=TRUE ELSE result%=FALSE ENDIF DEBUG "EMAIL: "+result% SOCK_SHUTDOWN RETURN result% ENDFUNCTION

208 Pseudo 3D Road

This routine was converted from Louis Gorenfeld's BASIC code to see how well GLBasic could cope with a pseudo 3D system. You will be pleased to know if coped very weill indeed. I didn't change much code – just the line drawing commands, so for the use of one character variable use, you can blame Louis! //Road Demonstration Program by Louis Gorenfeld 2010 //This program is intended to show concepts described at Lou//s Pseudo 3d Page //http://www.gorenfeld.net/lou/pseudo //It defaults to generating 80 frames during which the road curves right, //uncurves, and repeats. It lasts several seconds running under DOSBox at //12000 cycles and 0 frameskip on a Mac Mini 2.0GHz dual core //I//ve left much detail off of the road. You can draw them in around line 70! //Happy coding! :) CONSTANT RoadLines% = 120 CONSTANT ScrollSpeed = 10 CONSTANT RoadY = -1 //arbitrary CONSTANT ResX = 480 CONSTANT ResY = 320 CONSTANT PlrLine = 8 //What line is the player sprite on? CONSTANT WidthStep = 1 GLOBAL ZMap[]; DIM ZMap[RoadLines%+1] LOCAL A,b,X,DX,DDX,HalfWidth,SegY,TexOffset,C,NextStretch$,ScreenLine,GrassColor,RoadColor // Initialize ZMap FOR A = 1 TO RoadLines ZMap[A] = RoadY / (A - (ResY / 2)) NEXT // Normalize ZMap so the line with the player on it is scale=1 (or would be // If we had a player sprite :)) b = 1 / ZMap[PlrLine] b = b * 100 //in percents because QBasic//s MOD is lame FOR A = 1 TO RoadLines ZMap[A] = ZMap[A] * b NEXT // 320x200x4bpp SETSCREEN ResX,ResY,0 // Draw the road NextStretch$="Straight"

FOR A = 1 TO ResY - RoadLines DRAWLINE 0, A,ResX, A, RGB(9,9,9) NEXT TexOffset = 100 SegY = RoadLines DX = 0 DDX = .02 // This controls the steepness of the curve REPEAT // Set up the frame X = ResX / 2 DX = 0 HalfWidth = 120 ScreenLine = ResY - 1 DEBUG ScreenLine+"\n" FOR A = 1 TO RoadLines IF MOD(ZMap[A] + TexOffset,100) > 50 GrassColor = 100 RoadColor = 200 ELSE GrassColor = 200 RoadColor = 100

209 ENDIF DRAWLINE X - HalfWidth, ScreenLine,X + HalfWidth, ScreenLine,RGB(RoadColor,RoadColor,RoadColor) //DEBUG X-HalfWidth+" "+ScreenLine+"\n" DRAWLINE 0, ScreenLine,X-HalfWidth, ScreenLine, RGB(0,GrassColor,0) DRAWLINE X + HalfWidth, ScreenLine,ResX - 1, ScreenLine, RGB(0,GrassColor,0) HalfWidth = HalfWidth - WidthStep ScreenLine = ScreenLine - 1 IF NextStretch$ = "Straight" IF A > SegY DX = DX + DDX ENDIF ELSEIF NextStretch$ = "Curved" IF A < SegY DX = DX + DDX ENDIF ENDIF X = X + DX NEXT SHOWSCREEN // Wrap positions (fractional): TexOffset = TexOffset + ScrollSpeed WHILE TexOffset >= 100 TexOffset = TexOffset - 100 WEND

SegY = SegY - 1 // Decrease SegY by an arbitrary amount. Adjust to taste. WHILE SegY < 0 SegY = SegY + RoadLines IF NextStretch$ = "Curved" NextStretch$ = "Straight" ELSEIF NextStretch$ = "Straight" NextStretch$ = "Curved" ENDIF WEND UNTIL FALSE

210 Message

This is a routine to allow messages to be stored and then read later on, perhaps by another function. It could be useful for something, but as yet, I don't know what... TYPE tMessage fromHashValue% toHashValue% data$ ENDTYPE TYPE tTypeList hashValue% typeName$ ENDTYPE TYPE TMessage NOT_VALID% = -1 typeList[] AS tTypeList messages[] AS tMessage FUNCTION TMessage_Initialise%: DIM self.typeList[0] ENDFUNCTION FUNCTION TMessage_RegisterType%:typeName$ LOCAL loop AS tTypeList LOCAL found%,hashValue% found%=FALSE hashValue%=Hash(typeName$) IF TMessage_LookForHash(hashValue%)=self.NOT_VALID% // Not found, so add this hash to the list loop.typeName$=typeName$ loop.hashValue%=hashValue% DIMPUSH self.typeList[],loop SORTARRAY self.typeList[],0 RETURN hashValue% ELSE RETURN self.NOT_VALID% ENDIF ENDFUNCTION FUNCTION TMessage_SendMessage%:fromHash%,toHash%,data$ LOCAL fromIndex%,toIndex% LOCAL message AS tMessage fromIndex%=TMessage_LookForHash(fromHash%) toIndex%=TMessage_LookForHash(toHash%) IF fromIndex%>self.NOT_VALID% AND toIndex%>self.NOT_VALID% message.fromHashValue%=fromHash% message.toHashValue%=toHash% message.data$=data$ DIMPUSH self.messages[],message RETURN TRUE ELSE RETURN FALSE ENDIF ENDFUNCTION //! Send debug information to the output window FUNCTION TMessage_Debug%: LOCAL loop AS tTypeList DEBUG "Number of registered types : "+BOUNDS(self.typeList[],0)+"\n" FOREACH loop IN self.typeList[] DEBUG "("+loop.hashValue%+" - "+loop.typeName$+")\n" NEXT DEBUG "Messages Outstanding : "+BOUNDS(self.messages[],0)+"\n" ENDFUNCTION //! Send a message to the destination type, using its registered name. This could be very slow FUNCTION TMessage_SendMessage_Text%:fromHash%,toType$,data$

211 LOCAL fromIndex%,toIndex% LOCAL message AS tMessage fromIndex%=TMessage_LookForHash(fromHash%) IF fromIndex%>self.NOT_VALID% toIndex%=TMessage_LookForName(toType$) IF toIndex%>self.NOT_VALID% message.fromHashValue%=fromHash% message.toHashValue%=self.typeList[toIndex%].hashValue% message.data$=data$ DIMPUSH self.messages[],message RETURN TRUE ENDIF ENDIF RETURN FALSE ENDFUNCTION FUNCTION TMessage_SendMessage_All%:fromHash%,data$,includeFrom%=FALSE LOCAL fromIndex% LOCAL message AS tMessage LOCAL loop AS tTypeList fromIndex%=TMessage_LookForHash(fromHash%) IF fromIndex%>=0 message.fromHashValue%=fromHash% message.data$=data$ FOREACH loop IN self.typeList[] IF (loop.hashValue%=fromHash% AND includeFrom%=TRUE) OR (loop.hashValue%<>fromHash%) message.toHashValue%=loop.hashValue%

DIMPUSH self.messages[],message ENDIF NEXT RETURN TRUE ENDIF RETURN FALSE ENDFUNCTION //! Send to the first ordered type - may not be in the order added by the user FUNCTION TMessage_SendMessage_First%:fromHash%,data$ LOCAL fromIndex%,toIndex% LOCAL message AS tMessage fromIndex%=TMessage_LookForHash(fromHash%) toIndex%=0 IF fromIndex%>=0 message.fromHashValue%=fromHash% message.toHashValue%=self.typeList[toIndex%].hashValue% message.data$=data$ DIMPUSH self.messages[],message RETURN TRUE ENDIF ENDFUNCTION //! Send to the last ordered type - may not be in the order added by the user FUNCTION TMessage_SendMessage_Last%:fromHash%,data$ LOCAL fromIndex%,toIndex% LOCAL message AS tMessage fromIndex%=TMessage_LookForHash(fromHash%) toIndex%=BOUNDS(self.typeList[],0)-1 IF fromIndex%>=0 message.fromHashValue%=fromHash% message.toHashValue%=self.typeList[toIndex%].hashValue% message.data$=data$ DIMPUSH self.messages[],message RETURN TRUE ENDIF ENDFUNCTION //! Send to the next ordered type - may not be in the order added by the user FUNCTION TMessage_SendMessage_Next%:fromHash%,data$,wrap%=TRUE LOCAL fromIndex% LOCAL message AS tMessage

212 fromIndex%=TMessage_LookForHash(fromHash%) IF fromIndex%>=0 INC fromIndex% IF fromIndex%>=BOUNDS(self.typeList[],0) IF wrap%=FALSE THEN RETURN FALSE fromIndex%=0 ENDIF message.fromHashValue%=fromHash% message.toHashValue%=self.typeList[fromIndex%].hashValue% message.data$=data$ DIMPUSH self.messages[],message RETURN TRUE ENDIF RETURN FALSE ENDFUNCTION //! Send to the previous ordered type - may not be in the order added by the user FUNCTION TMessage_SendMessage_Prev%:fromHash%,data$,wrap%=TRUE LOCAL fromIndex% LOCAL message AS tMessage fromIndex%=TMessage_LookForHash(fromHash%) IF fromIndex%>=0 DEC fromIndex% IF fromIndex%<0 IF wrap%=FALSE THEN RETURN FALSE fromIndex%=BOUNDS(self.typeList[],0)-1 ENDIF message.fromHashValue%=fromHash% message.toHashValue%=self.typeList[fromIndex%].hashValue% message.data$=data$ DIMPUSH self.messages[],message RETURN TRUE ENDIF RETURN FALSE ENDFUNCTION FUNCTION TMessage_PeekMessage%:hashValue%,BYREF data$,BYREF fromHash% LOCAL index% IF BOUNDS(self.messages[],0)>0 IF self.messages[0].toHashValue%=hashValue% data$=self.messages[0].data$ fromHash%=self.messages[0].fromHashValue% DIMDEL self.messages[],0 RETURN TRUE ELSE RETURN FALSE ENDIF ELSE RETURN FALSE ENDIF ENDFUNCTION FUNCTION TMessage_GetRegisteredText$: LOCAL temp$ LOCAL loop AS tTypeList temp$="" FOREACH loop IN self.typeList[] temp$=temp$+loop.typeName$+","+loop.hashValue%+"," NEXT RETURN LEFT$(temp$,LEN(temp$)-1) ENDFUNCTION //! This is a private function and should not be called FUNCTION TMessage_LookForHash%:hashValue% LOCAL count% LOCAL low%,high%,middle%

213 count%=BOUNDS(self.typeList[],0) IF count%=0 THEN RETURN self.NOT_VALID% IF count%=1 IF self.typeList[0].hashValue%=hashValue% RETURN self.typeList[0].hashValue% ELSE RETURN self.NOT_VALID% ENDIF ENDIF low%=0 high%=count%-1 WHILE low%<=high% middle%=low%+(high%-low%)/2 IF hashValue%self.typeList[middle%].hashValue% low%=middle%+1 ELSE RETURN middle% ENDIF WEND RETURN self.NOT_VALID% ENDFUNCTION FUNCTION TMessage_LookForName%:typeName$ LOCAL loop%

FOR loop%=0 TO BOUNDS(self.typeList[],0)-1 IF self.typeList[loop%].typeName$=typeName$ THEN RETURN loop% NEXT RETURN self.NOT_VALID% ENDFUNCTION ENDTYPE

214 Flood fill routine

This flood fill routine is unfortunately rather slow. It was a conversion of the first routine I found... TYPE tPixel x% y% ENDTYPE GLOBAL pixels[] AS tPixel LOCAL sW%,sH% GETSCREENSIZE sW%,sH% CLEARSCREEN DRAWRECT 0,0,400,400,RGB(255,255,0) DRAWRECT 0,0,140,140,0 DRAWRECT 8,8,8,8,RGB(255,255,0) SHOWSCREEN DRAWRECT 0,0,400,400,RGB(255,255,0) DRAWRECT 0,0,140,140,0 DRAWRECT 8,8,8,8,RGB(255,255,0) FloodFill(0,0,RGB(0,0,0),RGB(255,255,255)) SHOWSCREEN KEYWAIT DEBUG "Finished\n" END FUNCTION FloodFill%:x%,y%,targetColour%,replacementColour% LOCAL Q AS tPixel LOCAL N AS tPixel LOCAL w%,e%,loop% LOCAL tempPixels[] AS tPixel IF GETPIXEL(x%,y%)<>targetColour% THEN RETURN FALSE DIM pixels[0] Q.x%=x% Q.y%=y% DIMPUSH pixels[],Q WHILE TRUE IF BOUNDS(pixels[],0)=0 THEN RETURN FALSE DIM tempPixels[0] FOREACH Q IN pixels[] IF GETPIXEL(Q.x%,Q.y%)=targetColour% y%=Q.y% w%=Q.x%-1 e%=Q.x%+1 WHILE w%>=0 AND GETPIXEL(w%,y%)=targetColour% DEC w% WEND WHILE e%<=400 AND GETPIXEL(e%,y%)=targetColour% INC e% WEND DRAWLINE w%+1,y%,e%-1,y%,replacementColour% FOR loop%=w%+1 TO e%-1 N.x%=loop% N.y%=y%-1 IF N.y%>-1 IF GETPIXEL(N.x%,N.y%)=targetColour% DIMPUSH tempPixels[],N ENDIF ENDIF

215 N.x%=loop% N.y%=y%+1 IF N.y%<400 IF GETPIXEL(N.x%,N.y%)=targetColour% DIMPUSH tempPixels[],N ENDIF ENDIF NEXT ENDIF NEXT pixels[]=tempPixels[] WEND ENDFUNCTION

216 Select Files

This routine allows users to select a music file. This was initially for Spots to allow the user to select their own music, but was never used. TYPE tListOfFiles selected% file$ ENDTYPE TYPE tSelectFiles listOfFiles[] AS tListOfFiles playList$[] extList$[] title$ fileList$ path$ button_back$ button_select$ button_deSelect$ button_rescan$ button_action$ button_finished$ programDir$ playListFile$ sectionName$ currentLine% ENDTYPE GLOBAL selectFiles AS tSelectFiles FUNCTION initialiseSelectFiles %:ext$,title$,programDir$,dirName$,sectionName$,actionDesc$ LOCAL path$ DIM selectFiles.extList$[0] DIM selectFiles.playList$[0] IF SPLITSTR(ext$,selectFiles.extList$[],"|")=0 RETURN FALSE ENDIF // Make sure user's directory exists selectFiles.path$=PLATFORMINFO$("DOCUMENTS")+"/"+programDir$ // Where the INI file will be kept IF DOESDIREXIST(selectFiles.path$)=FALSE CREATEDIR(selectFiles.path$) ENDIF selectFiles.programDir$=selectFiles.path$+"/"+dirName$ // Where user's media will be kept IF DOESDIREXIST(selectFiles.programDir$)=FALSE CREATEDIR(selectFiles.programDir$) ENDIF selectFiles.title$=title$ selectFiles.fileList$="fileList" selectFiles.button_back$="BACK" selectFiles.button_select$="SELECT" selectFiles.button_deSelect$="DE-SELECT" selectFiles.button_rescan$="RESCAN" selectFiles.button_action$=actionDesc$ selectFiles.button_finished$="FINISHED" selectFiles.playListFile$=selectFiles.path$+"/PLAYLIST.INI" selectFiles.sectionName$=sectionName$ selectFiles.currentLine%=-1 ENDFUNCTION FUNCTION processSelectFiles%: LOCAL screenWidth%,screenHeight% GETSCREENSIZE screenWidth%,screenHeight% DDgui_pushdialog(0,0,screenWidth%,screenHeight%) DDgui_set("", "TEXT", selectFiles.title$)

217 DDgui_widget("id1","SEL File",0,0) DDgui_spacer(1,8) // Read in the playlist readList() scanForFiles() rebuildList(screenWidth%,screenHeight%) DDgui_button(selectFiles.button_back$,selectFiles.button_back$,0,0) DDgui_button(selectFiles.button_select$,selectFiles.button_select$,0,0) DDgui_button(selectFiles.button_deSelect$,selectFiles.button_deSelect$,0,0) DDgui_button(selectFiles.button_rescan$,selectFiles.button_rescan$,0,0) DDgui_button(selectFiles.button_action$,selectFiles.button_action$,0,0) DDgui_button(selectFiles.button_finished$,selectFiles.button_finished$,0,0) WHILE TRUE DDgui_show(FALSE) SHOWSCREEN // Now we process the buttons // PLAY IF DDgui_get(selectFiles.button_action$,"CLICKED") processAction(DDgui_get(selectFiles.fileList$,"SELECT")) ELSEIF DDgui_get(selectFiles.button_select$,"CLICKED") IF selectTune(DDgui_get(selectFiles.fileList$,"SELECT"))=TRUE // Rebuild list rebuildList(screenWidth%,screenHeight%) ENDIF ELSEIF DDgui_get(selectFiles.button_deSelect$,"CLICKED") IF deSelectTune(DDgui_get(selectFiles.fileList$,"SELECT"))=TRUE // Rebuild list rebuildList(screenWidth%,screenHeight%) ENDIF ELSEIF DDgui_get(selectFiles.button_rescan$,"CLICKED") scanForFiles() rebuildList(screenWidth%,screenHeight%) ELSEIF DDgui_get(selectFiles.button_finished$,"CLICKED") saveList() RETURN TRUE ENDIF WEND ENDFUNCTION FUNCTION rebuildList:screenWidth%,screenHeight% DDgui_list(selectFiles.fileList$,createListFromFiles$(),screenWidth%-11,screenHeight%- 70) ENDFUNCTION FUNCTION scanForFiles%: LOCAL currentDir$ LOCAL count% LOCAL numFiles% LOCAL ext$,ext2$ LOCAL tempList$[] LOCAL file AS tListOfFiles DIM selectFiles.listOfFiles[0] currentDir$=GETCURRENTDIR$() SETCURRENTDIR(selectFiles.programDir$) FOREACH ext$ IN selectFiles.extList$[] DIM tempList$[0] count%=GETFILELIST(ext$,tempList$[]) numFiles%=MOD(count%,0x10000) DEBUG "Files : "+numFiles%+"\n" IF numFiles%>0 // We have a list of files, so add them to the list FOREACH ext2$ IN tempList$[] IF ext2$<>"." AND ext2$<>".." file.selected%=checkInPlayList(ext2$) file.file$=ext2$ DIMPUSH selectFiles.listOfFiles[],file ENDIF NEXT

218 ENDIF NEXT // Put directory back SETCURRENTDIR(currentDir$) SORTARRAY selectFiles.listOfFiles[],ADDRESSOF(sort) RETURN BOUNDS(selectFiles.listOfFiles[],0) ENDFUNCTION FUNCTION sort:a AS tListOfFiles,b AS tListOfFiles IF b.file$a.file$ RETURN -1 ELSE RETURN 0 ENDIF ENDIF ENDFUNCTION FUNCTION createListFromFiles$: LOCAL loop AS tListOfFiles LOCAL temp$ temp$="" FOREACH loop IN selectFiles.listOfFiles[] IF loop.selected%=TRUE temp$=temp$+"YES" ELSE temp$=temp$+"NO " ENDIF temp$=temp$+" "+loop.file$+"|" NEXT RETURN MID$(temp$,0,LEN(temp$)-1) ENDFUNCTION FUNCTION processAction:line% LOCAL file$ LOCAL play% file$=selectFiles.programDir$+"/"+selectFiles.listOfFiles[line%].file$ IF line%<>selectFiles.currentLine% // Different line, so play automatically play%=TRUE selectFiles.currentLine%=line% ELSE IF ISMUSICPLAYING()=FALSE play%=TRUE ELSE play%=FALSE ENDIF ENDIF IF play%=TRUE IF DOESFILEEXIST(file$)=TRUE PLAYMUSIC file$,FALSE ENDIF ELSE STOPMUSIC ENDIF ENDFUNCTION FUNCTION selectTune%:line% selectFiles.listOfFiles[line%].selected%=TRUE RETURN TRUE ENDFUNCTION FUNCTION deSelectTune%:line% selectFiles.listOfFiles[line%].selected%=FALSE RETURN TRUE ENDFUNCTION FUNCTION saveList%: LOCAL loop AS tListOfFiles

219 LOCAL count% KILLFILE selectFiles.playListFile$ DIM selectFiles.playList$[0] INIOPEN selectFiles.playListFile$ count%=0 FOREACH loop IN selectFiles.listOfFiles[] IF loop.selected%=TRUE INIPUT selectFiles.sectionName$,count%,loop.file$ // And add to the playlist DIMPUSH selectFiles.playList$[],loop.file$ INC count% ENDIF NEXT INIPUT selectFiles.sectionName$,"COUNT",count% INIOPEN "" ENDFUNCTION FUNCTION readList%: LOCAL count% LOCAL loop% LOCAL temp$ DIM selectFiles.playList$[0] INIOPEN selectFiles.playListFile$ count%=INIGET$(selectFiles.sectionName$,"COUNT") IF count%>0 FOR loop%=0 TO count%-1 temp$=INIGET$(selectFiles.sectionName$,loop%) DIMPUSH selectFiles.playList$[],temp$ NEXT INIOPEN "" ENDIF RETURN BOUNDS(selectFiles.playList$[],0) ENDFUNCTION FUNCTION checkInPlayList%:name$ LOCAL loop$ FOREACH loop$ IN selectFiles.playList$[] IF loop$=name$ RETURN TRUE ENDIF NEXT RETURN FALSE ENDFUNCTION

220 JNR Examples #1 - #3

The following three routines were converted from the original C code that used to be available at http://jnrdev.72dpiarmy.com (but is no longer available). It was done to see how well GLBasic would fair against the original code, and I am pleased to say there is no speed difference – plus, it's easier to read!

// ------// Project: JNRExample#1 // Start: Saturday, June 26, 2010 // IDE Version: 8.006 CONSTANT MAPWIDTH% = 32 //width of the map CONSTANT MAPHEIGHT% = 24 //height of the map CONSTANT TILESIZE% = 20 //size of the tiles, should be dynamically read CONSTANT PLAYERHEIGHT% = 37 //should be dynamically read from a player definition file CONSTANT PLAYERWIDTH% = 11 //--""--

CONSTANT WAITTIME% = 15 //delay between frames

CONSTANT GRAVITATION% = 1 //gravitation - 1 pixel per frame (too fast, will be changed somewhen (playerx,y -> float, grav: 0.xx)) CONSTANT VELMOVING% = 4 //velocity (speed) for moving left, right CONSTANT VELJUMP% = 13 //velocity for jumping TYPE tTile solid%=FALSE spr%=-1 ENDTYPE TYPE TMap tiles[] AS tTile spr_t%[] FUNCTION TMap_Initialise%:spr_t%[] DIM self.tiles[MAPWIDTH%][MAPHEIGHT%] self.spr_t%[]=spr_t%[] RETURN TRUE ENDFUNCTION FUNCTION loadMap%:file$ LOCAL handle% LOCAL t%,i%,j% IF DOESFILEEXIST(file$) handle%=GENFILE() IF handle%>=0 IF OPENFILE(handle%,file$,1) FOR j%=0 TO MAPHEIGHT%-1 FOR i%=0 TO MAPWIDTH%-1 READBYTE handle%,self.tiles[i%][j%].solid% READLONG handle%,t% IF t%<>0 THEN self.tiles[i%][j%].spr %=self.spr_t%[t%-1] NEXT NEXT RETURN TRUE ELSE RETURN FALSE ENDIF ENDIF ENDIF ENDFUNCTION FUNCTION TMap_draw%: LOCAL i%,j%

221 FOR i%=0 TO MAPWIDTH%-1 FOR j%=0 TO MAPHEIGHT%-1 IF self.tiles[i%][j%].spr%<>-1 THEN DRAWSPRITE self.tiles[i%] [j%].spr%,i%*TILESIZE%,j%*TILESIZE% NEXT NEXT ENDFUNCTION FUNCTION colmapxy%:x%,y% RETURN self.tiles[x%][y%].solid ENDFUNCTION ENDTYPE TYPE TPlayer x%;y% h%;w% velx%;vely% faceright% lockjump% spr_player%[] map AS TMap FUNCTION TPlayer_Initialise%:spr_player%[],map AS TMap self.spr_player%[]=spr_player%[] self.x%=100 self.y%=100 self.h%= PLAYERHEIGHT; //save player height and width self.w%= PLAYERWIDTH; self.velx%=0 self.vely%=0 self.lockjump%=FALSE self.faceright%=TRUE self.map=map ENDFUNCTION FUNCTION think%: LOCAL tilecoord% self.velx=0; //don't move left / right by default IF KEY(205) self.velx=VELMOVING% // Move right self.faceright%=TRUE ELSE IF KEY(203) self.velx=0-VELMOVING% // Move left self.faceright=FALSE ENDIF ENDIF IF KEY(42) AND self.lockjump=FALSE // Can the player jump ? self.vely=0-VELJUMP% self.lockjump%=TRUE ENDIF IF self.velx%>0 IF self.collision_ver(self.x+self.velx+self.w%,self.y%,tilecoord%) //collision on the right side. self.x%=tilecoord%*20-self.w%-1; //move to the edge of the tile (tile on the right -> mind the player width) ELSE INC self.x%,self.velx% ENDIF ELSE IF self.velx%<0 IF self.collision_ver(self.x+self.velx,self.y%,tilecoord%) //collision on the left side self.x%=(tilecoord%+1)*20+1; //move to the edge of the tile ELSE INC self.x%,self.velx% ENDIF ENDIF ENDIF IF self.vely%<0 IF self.collision_hor(self.x%,self.y%+self.vely%,tilecoord%)

222 self.y%=(tilecoord%+1)*20+1 self.vely%=0 ELSE INC self.y%,self.vely% INC self.vely%,GRAVITATION% ENDIF ELSE IF self.collision_hor(self.x%,self.y%+self.vely%+self.h%,tilecoord%) self.y%=tilecoord%*20-self.h%-1 self.vely%=1 // so we test against the ground again int the next frame (0 would test against the ground in the next+1 frame) IF KEY(42)=0 self.lockjump%=FALSE ENDIF ELSE INC self.y%,self.vely% INC self.vely%,GRAVITATION% IF self.vely%>=TILESIZE% self.vely%=TILESIZE% ENDIF self.lockjump%=TRUE ENDIF ENDIF ENDFUNCTION FUNCTION TPlayer_draw%: IF self.faceright% DRAWSPRITE self.spr_player%[1],self.x%-4,self.y%-2 ELSE DRAWSPRITE self.spr_player%[0],self.x%-17,self.y%-2 ENDIF ENDFUNCTION FUNCTION collision_ver%:x%,y%,BYREF tilecoordx% LOCAL tileypixels%,testend%,tilecoordy% tileypixels% = y%-MOD(y%,20) testend% = y% + self.h%; tilecoordx% = x%/20; tilecoordy% = tileypixels/20; WHILE tileypixels <= testend IF self.map.colmapxy(tilecoordx%,tilecoordy%) RETURN TRUE ENDIF INC tilecoordy% INC tileypixels%,20 WEND RETURN FALSE ENDFUNCTION FUNCTION collision_hor%:x%,y%,BYREF tilecoordy% LOCAL tilexpixels%,testend%,tilecoordx% tilexpixels = self.x%-MOD(x%,20) //calculate the x position (pixels!) of the tiles we check against testend = x% + self.w //calculate the end of testing (just to save the x+w calculation each for loop) tilecoordy% = y%/20; //calculate the y position (map coordinates!) of the tiles we want to test tilecoordx% = tilexpixels%/20; //calculate map x coordinate for first tile

//loop while the start point (pixels!) of the test tile is inside the players bounding box WHILE tilexpixels <= testend IF self.map.colmapxy(tilecoordx%, tilecoordy%) //is a solid

223 tile is found at tilecoordx, tilecoordy? RETURN TRUE; ENDIF INC tilecoordx //increase tile x map coordinate INC tilexpixels,20; //increase tile x pixel coordinate WEND RETURN FALSE ENDFUNCTION ENDTYPE LOCAL spr_t%[]; DIM spr_t%[3] LOCAL spr_player%[]; DIM spr_player%[2] LOCAL spr_background% LOCAL map AS TMap LOCAL player AS TPlayer SETTRANSPARENCY RGB(255,0,255) spr_t%[0]=GENSPRITE(); LOADSPRITE "Media/t1.bmp",spr_t%[0] spr_t%[1]=GENSPRITE(); LOADSPRITE "Media/t2.bmp",spr_t%[1] spr_t%[2]=GENSPRITE(); LOADSPRITE "Media/t3.bmp",spr_t%[2] spr_player%[0]=GENSPRITE(); LOADSPRITE "Media/left.bmp",spr_player%[0] spr_player%[1]=GENSPRITE(); LOADSPRITE "Media/right.bmp",spr_player%[1] spr_background%=GENSPRITE(); LOADSPRITE "Media/bg.bmp",spr_background% IF map.TMap_Initialise(spr_t%[])=FALSE DEBUG "Map could not be initialised" RETURN FALSE ENDIF

IF map.loadMap("Media/maps/map01.map")=FALSE DEBUG "Map could not be loaded" RETURN FALSE ENDIF player.TPlayer_Initialise(spr_player%[],map) WHILE TRUE //update objects player.think();

//draw everything DRAWSPRITE spr_background%,0,0 map.TMap_draw() player.TPlayer_draw() SHOWSCREEN WEND

224 // ------// Project: JNRExample#2 // Start: Sunday, June 27, 2010 // IDE Version: 8.006 CONSTANT MAPWIDTH% = 32 //width of the map CONSTANT MAPHEIGHT% = 24 //height of the map CONSTANT TILESIZE% = 20 //size of the tiles, should be dynamically read CONSTANT PLAYERHEIGHT% = 37 //should be dynamically read from a player definition file CONSTANT PLAYERWIDTH% = 11 //--""--

CONSTANT WAITTIME% = 16 //delay between frames

CONSTANT GRAVITATION% = 1 //gravitation - 1 pixel per frame (too fast, will be changed somewhen (playerx,y -> float, grav: 0.xx)) CONSTANT VELMOVING% = 4 //velocity (speed) for moving left, right CONSTANT VELJUMP% = 13 //velocity for jumping CONSTANT t_nonsolid% = 0 CONSTANT t_solid% = 1 CONSTANT t_slopeleft% = 2 CONSTANT t_sloperight% = 3

TYPE tTile type% spr% ENDTYPE TYPE TMap tiles[] AS tTile spr_t%[] FUNCTION TMap_Initialise%:spr_t%[] LOCAL x%,y% DIM self.tiles[MAPWIDTH%][MAPHEIGHT%] self.spr_t%[]=spr_t%[] FOR y%=0 TO MAPHEIGHT%-1 FOR x%=0 TO MAPWIDTH%-1 self.tiles[x%][y%].type%=t_nonsolid% // Need to initialise here as constants cant be assigned in TYPES self.tiles[x%][y%].spr%=-1 NEXT NEXT RETURN TRUE ENDFUNCTION FUNCTION loadMap%:file$ LOCAL handle% LOCAL t%,i%,j% IF DOESFILEEXIST(file$) handle%=GENFILE() IF handle%>=0 IF OPENFILE(handle%,file$,1) FOR j%=0 TO MAPHEIGHT%-1 FOR i%=0 TO MAPWIDTH%-1 READLONG handle%,self.tiles[i%][j%].type% READLONG handle%,t% IF t%<>128 THEN self.tiles[i%][j%].spr %=self.spr_t%[t%] NEXT NEXT RETURN TRUE ELSE RETURN FALSE ENDIF ENDIF

225 ENDIF ENDFUNCTION FUNCTION TMap_draw%: LOCAL i%,j% FOR i%=0 TO MAPWIDTH%-1 FOR j%=0 TO MAPHEIGHT%-1 IF self.tiles[i%][j%].spr%<>-1 THEN DRAWSPRITE self.tiles[i%] [j%].spr%,i%*TILESIZE%,j%*TILESIZE% NEXT NEXT ENDFUNCTION FUNCTION TMap_map%:x%,y% RETURN self.tiles[x%][y%].type% ENDFUNCTION ENDTYPE TYPE TPlayer x%;y% //x, y coordinate (top left of the player rectangle) h%;w% //height, width velx%;vely% //velocity on x, y axis faceright% //player facing right? -> graphics lockjump% //may the player jump jumping% spr_player%[] map AS TMap slope_prevtilex%//the tile at slopex, slopey in the last frame slope_prevtiley%

// Passing the players array and the TMap TYPE is a bit inefficient FUNCTION TPlayer_Initialise%:spr_players%[],map AS TMap self.spr_player%[]=spr_players%[] self.x%=100 self.y%=100 self.h%= PLAYERHEIGHT; //save player height and width self.w%= PLAYERWIDTH; self.velx%=0 self.vely%=0 self.lockjump%=FALSE self.faceright%=TRUE self.jumping%=FALSE self.map=map self.slope_prevtilex% = (self.x + ASR(self.w,1))/ TILESIZE; self.slope_prevtiley% = (self.y + self.h) / TILESIZE; ENDFUNCTION FUNCTION unlockjump%: //this function is called if the player hits the ground // this if is quite tricky: // the player may jump again: // a) if he fell of an edge (!jumping) - without releasing the jump key on the ground // b) if he jumped - only when he releases the jump key on the ground IF NOT(self.jumping%) OR NOT(KEY(42)) self.lockjump%=FALSE self.jumping%=FALSE ENDIF ENDFUNCTION FUNCTION think%: LOCAL tilecoord% self.velx%=0;//don't move left / right by default IF KEY(205) self.velx%=VELMOVING% //move right self.faceright%=TRUE ELSE IF KEY(203) self.velx%=0-VELMOVING% //move right self.faceright%=FALSE ENDIF ENDIF IF KEY(42) AND self.lockjump%=FALSE //if the player isn't jumping already

226 self.vely%=0-VELJUMP% //jump! self.lockjump%=TRUE //player is not allowed to jump anymore ENDIF self.collision_detection_map() ENDFUNCTION FUNCTION TPlayer_draw%: IF self.faceright% DRAWSPRITE self.spr_player%[1],self.x%-4,self.y%-2 ELSE DRAWSPRITE self.spr_player%[0],self.x%-17,self.y%-2 ENDIF ENDFUNCTION FUNCTION collision_hor_up%:x%,y%,BYREF tilecoordy% LOCAL tilexpixels%,testend%,tilecoordx% tilexpixels% = x%-MOD(x%,TILESIZE%) testend% = x% + self.w tilecoordy% = y%/TILESIZE% tilecoordx% = tilexpixels%/TILESIZE WHILE tilexpixels% <= testend% IF self.map.TMap_map(tilecoordx%, tilecoordy%)<>t_nonsolid% //only this changed: when jumping (moving up) we don't want to go through slopes RETURN TRUE; ENDIF

INC tilecoordx% INC tilexpixels%,TILESIZE% WEND RETURN FALSE ENDFUNCTION FUNCTION collision_hor_down%:x%,y%,BYREF tilecoordy% LOCAL tilexpixels%,testend%,tilecoordx% tilexpixels% = x%-MOD(x%,TILESIZE%) //calculate the x position (pixels!) of the tiles we check against testend% = x% + self.w% //calculate the end of testing (just to save the x+w calculation each for loop) tilecoordy% = y/TILESIZE //calculate the y position (map coordinates!) of the tiles we want to test tilecoordx% = tilexpixels%/TILESIZE //calculate map x coordinate for first tile

//loop while the start point (pixels!) of the test tile is inside the players bounding box WHILE tilexpixels% <= testend% IF self.map.TMap_map(tilecoordx%, tilecoordy%)=t_solid //is a solid tile is found at tilecoordx, tilecoordy? RETURN TRUE ENDIF INC tilecoordx% //increase tile x map coordinate INC tilexpixels%,TILESIZE%//increase tile x pixel coordinate WEND RETURN FALSE ENDFUNCTION // FOR explanation see CPlayer::collision_hor() FUNCTION collision_ver%:x%,y%,BYREF tilecoordx% LOCAL tileypixels%,testend%,tilecoordy% tileypixels = y%-MOD(y%,TILESIZE%) testend = y% + self.h%

227 tilecoordx% = x/TILESIZE tilecoordy% = tileypixels/TILESIZE WHILE tileypixels <= testend IF self.map.TMap_map(tilecoordx%, tilecoordy%) = t_solid% RETURN TRUE ENDIF INC tilecoordy% INC tileypixels%,TILESIZE WEND RETURN FALSE ENDFUNCTION FUNCTION collision_slope%:sx%,sy%,BYREF tsx%,BYREF tsy% LOCAL t% tsx = sx% / TILESIZE //map coordinates of the tile we check against tsy = sy% / TILESIZE t% = self.map.TMap_map(tsx, tsy) //if we found a slope we set align y to the slope. //take a look at jnrdev #2, why it's calculated this way IF t% = t_sloperight% //sloperight -> \ self.y = (tsy%+1)*TILESIZE - (TILESIZE - MOD(sx%,TILESIZE)) - self.h% - 1; RETURN TRUE; ELSEIF t% = t_slopeleft% //slopeleft -> / self.y = (tsy+1)*TILESIZE - MOD(sx%,TILESIZE%) - self.h% - 1 ; RETURN TRUE; ENDIF RETURN FALSE; ENDFUNCTION FUNCTION collision_detection_map%: LOCAL tsx%, tsy%,sx%,sy%,what%,tilecoord% //slope tile coordinates //check for slopes (only if moving down) IF self.vely > 0 sx = self.x% + ASR(self.w%,1) + self.velx //slope chechpoint x coordinate IF self.collision_slope(sx%, (self.y + self.h), tsx%, tsy%) //we entered a slope (y is set by collision_slope) INC self.x%,self.velx% //move on //y has been set by collision_slope self.unlockjump(); //we hit the ground - the player may jump again self.vely = 1 //test against the ground again in the next frame self.slope_prevtilex% = tsx% //save slope koordinate self.slope_prevtiley% = tsy% RETURN TRUE ELSE //we're not on a slope this frame - check if we left a slope //-1 ... we didn't move from slope to slope //0 ... we left a slope after moving down //1 ... we left a slope after moving up what% = -1 IF self.map.TMap_map(self.slope_prevtilex%, self.slope_prevtiley%) = t_sloperight% IF self.velx > 0 //sloperight + velx > 0 = we left a slope after moving up the slope what = 0; ELSE

228 what = 1; //we left after moving down the slope ENDIF ELSEIF self.map.TMap_map(self.slope_prevtilex, self.slope_prevtiley) = t_slopeleft% IF self.velx < 0 //sloperight + velx > 0 = we left a slope after moving up the slope what = 0; ELSE what = 1; //we left after moving down the slope ENDIF ENDIF IF what%<>-1 //if we left a slope and now are on a slope IF what% = 1 self.y% = tsy*TILESIZE% - self.h -1; //move y to top of the slope tile sy% = self.y + self.h% ELSE self.y% = (tsy+1)*TILESIZE - self.h -1; //move y to the bottom of the slope tile sy% = self.y + self.h% + TILESIZE% //test one tile lower than the bottom of the slope (to test if we move down a long slope) //it's physically incorrect, but looks a lot smoother ingame ENDIF //check for slopes on new position IF self.collision_slope(sx, sy, tsx, tsy) //slope on new pos (entered a slope after we left a slope) INC self.x%,self.velx% //- > we moved from slope to slope self.unlockjump(); self.vely% = 1; self.slope_prevtilex% = tsx% self.slope_prevtiley% = tsy% RETURN TRUE ENDIF ENDIF ENDIF ENDIF //no slope collisions were found -> check for collisions with the map //x axis first (--) IF self.velx > 0 //moving right IF self.collision_ver(self.x%+self.velx%+self.w, self.y, tilecoord%) //collision on the right side. self.x = tilecoord%*TILESIZE% -self.w-1; //move to the edge of the tile (tile on the right -> mind the player width) ELSE //no collision INC self.x%,self.velx ENDIF ELSEIF self.velx < 0 //moving left IF self.collision_ver(self.x%+self.velx%, self.y%, tilecoord%) //collision on the left side self.x% = (tilecoord%+1)*TILESIZE% +1; //move to the edge of the tile ELSE INC self.x%,self.velx% ENDIF ENDIF //THEN y axis (|) IF self.vely%<0 IF self.collision_hor_up(self.x%,self.y%+self.vely%,tilecoord%) self.y% = (tilecoord%+1)*TILESIZE% +1; self.vely% = 0 ELSE INC self.y%,self.vely% INC self.vely%,GRAVITATION% ENDIF

229 ELSE //moving down / on ground IF self.collision_hor_down(self.x%, self.y%+self.vely%+self.h%, tilecoord%) //on ground self.y% = tilecoord%*TILESIZE% -self.h%-1; self.vely% = 1; //1 so we test against the ground again int the next frame (0 would test against the ground in the next+1 frame) self.unlockjump() ELSE //falling (in air) INC self.y,self.vely% INC self.vely%,GRAVITATION% IF self.vely%>= TILESIZE% //if the speed is higher than this we might fall through a tile self.vely% = TILESIZE% ENDIF self.lockjump = TRUE; //don't allow jumping after falling of an edge ENDIF ENDIF self.slope_prevtilex = (self.x% + ASR(self.w%,1)) / TILESIZE% self.slope_prevtiley = (self.y% + self.h%) / TILESIZE% RETURN FALSE ENDFUNCTION ENDTYPE

LOCAL spr_t%[]; DIM spr_t%[7] LOCAL spr_player%[]; DIM spr_player%[2] LOCAL spr_background% LOCAL map AS TMap LOCAL player AS TPlayer LIMITFPS 30 SETTRANSPARENCY RGB(255,0,255) spr_t%[0]=GENSPRITE(); LOADSPRITE "Media/gfx/t1.bmp",spr_t%[0] spr_t%[1]=GENSPRITE(); LOADSPRITE "Media/gfx/t2.bmp",spr_t%[1] spr_t%[2]=GENSPRITE(); LOADSPRITE "Media/gfx/t3.bmp",spr_t%[2] spr_t%[3]=GENSPRITE(); LOADSPRITE "Media/gfx/tsloper.bmp",spr_t%[3] spr_t%[4]=GENSPRITE(); LOADSPRITE "Media/gfx/tslopel.bmp",spr_t%[4] spr_t%[5]=GENSPRITE(); LOADSPRITE "Media/gfx/t6.bmp",spr_t%[5] spr_t%[6]=GENSPRITE(); LOADSPRITE "Media/gfx/t7.bmp",spr_t%[6] spr_player%[0]=GENSPRITE(); LOADSPRITE "Media/gfx/left.bmp",spr_player%[0] spr_player%[1]=GENSPRITE(); LOADSPRITE "Media/gfx/right.bmp",spr_player%[1] spr_background%=GENSPRITE(); LOADSPRITE "Media/gfx/bg.bmp",spr_background% IF map.TMap_Initialise(spr_t%[])=FALSE DEBUG "Map could not be initialised" RETURN FALSE ENDIF IF map.loadMap("Media/maps/map01.map")=FALSE DEBUG "Map could not be loaded" RETURN FALSE ENDIF player.TPlayer_Initialise(spr_player%[],map) WHILE TRUE //update objects player.think()

//draw everything DRAWSPRITE spr_background%,0,0 map.TMap_draw() player.TPlayer_draw() SHOWSCREEN WEND

230 // ------// // Project: JNRExample#3 // Start: Thursday, July 01, 2010 // IDE Version: 8.006 CONSTANT MAPWIDTH% = 32 //width of the map CONSTANT MAPHEIGHT% = 24 //height of the map CONSTANT TILESIZE% = 20 //size of the tiles, should be dynamically read

CONSTANT PLAYERHEIGHT% = 37 //should be dynamically read from a player definition file CONSTANT PLAYERWIDTH% = 11 //--""-- CONSTANT VELMOVING% = 4 //velocity (speed) for moving left, right CONSTANT VELJUMP% = 13 //velocity for jumping CONSTANT GRAVITATION% = 1 //gravitation - 1 pixel per frame (too fast, will be changed somewhen (playerx,y -> float, grav: 0.xx))

CONSTANT WAITTIME% = 18 //delay between frames to keep a constant framerate ( fps = 1000/WAITTIME ) CONSTANT t_nonsolid% = 0 CONSTANT t_solid% = 1 CONSTANT t_slopeleft% = 2 CONSTANT t_sloperight% = 3 GLOBAL scroll_x%,scroll_y% GLOBAL spr_player%[] GLOBAL spr_t%[] TYPE tTile tileType% spr% ENDTYPE GLOBAL tileset[] AS tTile;DIM tileset[9] TYPE TMap tiles%[] tiles2d%[] width%;height% FUNCTION TMap_Initialise%: DIM self.tiles%[0] DIM self.tiles2d%[0] ENDFUNCTION FUNCTION loadMap%:fileName$ LOCAL handle%,i%,j%,t% handle%=GENFILE() IF handle%>=0 IF DOESFILEEXIST(fileName$) IF OPENFILE(handle%,fileName$,1) self.freetiles() READLONG handle%,self.width% READLONG handle%,self.height% DIM self.tiles%[self.width%*self.height] t%=0 FOR j%=0 TO self.width%-1 FOR i%=0 TO self.height%-1 READLONG handle%,self.tiles%[t%] INC t% NEXT NEXT CLOSEFILE handle% // In the C version, this would be a pointer to a row. However, I dont use it and has only been left in for completeness DIM self.tiles2d%[self.height%]

231 FOR i%=0 TO self.height%-1 self.tiles2d%[i%]=i%*self.width% NEXT RETURN TRUE ENDIF ENDIF ENDIF RETURN FALSE ENDFUNCTION FUNCTION freetiles%: DIM self.tiles%[0] ENDFUNCTION FUNCTION TMap_map%:x%,y% LOCAL index% RETURN tileset[self.tiles[x+y*self.width]].tileType; ENDFUNCTION FUNCTION limit_scroll%: IF scroll_x%<0 scroll_x%=0 ELSEIF scroll_x+640 > self.width*TILESIZE scroll_x = self.width*TILESIZE-640; ENDIF

IF scroll_y%<0 scroll_y%=0 ELSEIF scroll_y + 480 > self.height*TILESIZE scroll_y%=self.height*TILESIZE-480 ENDIF ENDFUNCTION FUNCTION TMap_draw%: LOCAL x%,y%,mx%,my%,sx%,smx%,index% sx% = 0-MOD(scroll_x%,TILESIZE) smx% = (sx+scroll_x)/TILESIZE my = (y+scroll_y)/TILESIZE FOR y = 0-MOD(scroll_y%,TILESIZE) TO 480 STEP TILESIZE% //y draw coordinate, the offset is for smooth scrolling mx = smx FOR x = sx TO 640 STEP TILESIZE //x draw coordinate index%=(my%*self.width%)+mx% IF index%>=0 AND index%-1 DRAWSPRITE tileset[self.tiles%[index%]].spr%,x%,y % ENDIF ENDIF INC mx% NEXT INC my% NEXT ENDFUNCTION ENDTYPE GLOBAL map AS TMap TYPE TPlayer x%;y% h%;w% velx%;vely% lockjump% faceright% jumping% slope_prevtilex% slope_prevtiley%

232 FUNCTION TPlayer_Initialise%: self.x = 100; //start coordinates self.y = 100; self.h = PLAYERHEIGHT; //save player height and width self.w = PLAYERWIDTH; self.velx = 0; //speed self.vely = 0;

self.lockjump = TRUE; //player may jump self.faceright = TRUE; //player looks right self.jumping = FALSE; self.slope_prevtilex = (self.x + ASR(self.w,1)) / TILESIZE; self.slope_prevtiley = (self.y + self.h) / TILESIZE; RETURN TRUE ENDFUNCTION FUNCTION unlockjump%: //this function is called if the player hits the ground //this if is quite tricky: //the player may jump again: //a) if he fell of an edge (!jumping) - without releasing the jump key on the ground //b) if he jumped - only when he releases the jump key on the ground IF (NOT(self.jumping) OR NOT(KEY(42))) self.lockjump = FALSE; self.jumping = FALSE; ENDIF ENDFUNCTION FUNCTION TPlayer_draw%: //crap, change planned in jnrdev #4 IF self.faceright DRAWSPRITE spr_player[1],(self.x-4)-scroll_x,(self.y-2)-scroll_y ELSE DRAWSPRITE spr_player[0],(self.x-17)-scroll_x,(self.y-2)-scroll_y ENDIF ENDFUNCTION FUNCTION collision_hor_down%:x%,y%,BYREF tilecoordy% LOCAL tilexpixels% = x-MOD(x%,TILESIZE); //calculate the x position (pixels!) of the tiles we check against LOCAL testend% = x + self.w; //calculate the end of testing (just to save the x+w calculation each for loop) tilecoordy = y/TILESIZE; //calculate the y position (map coordinates!) of the tiles we want to test LOCAL tilecoordx% = tilexpixels/TILESIZE; //calculate map x coordinate for first tile //loop while the start point (pixels!) of the test tile is inside the players bounding box WHILE tilexpixels <= testend IF map.TMap_map(tilecoordx, tilecoordy) = t_solid% //is a solid tile is found at tilecoordx, tilecoordy? RETURN TRUE ENDIF INC tilecoordx //increase tile x map coordinate INC tilexpixels,TILESIZE //increase tile x pixel coordinate WEND RETURN FALSE ENDFUNCTION FUNCTION collision_hor_up%:x%,y%,BYREF tilecoordy% LOCAL tilexpixels% = x-MOD(x%,TILESIZE); LOCAL testend% = x + self.w; tilecoordy = y/TILESIZE; LOCAL tilecoordx% = tilexpixels/TILESIZE;

233 WHILE tilexpixels <= testend IF map.TMap_map(tilecoordx, tilecoordy) <> t_nonsolid //only this changed: when jumping (moving up) we don't want to go through slopes RETURN TRUE ENDIF INC tilecoordx INC tilexpixels,TILESIZE WEND RETURN FALSE ENDFUNCTION //for explanation see CPlayer::collision_hor() FUNCTION collision_ver%:x%,y%,BYREF tilecoordx% LOCAL tileypixels% = y-MOD(y%,TILESIZE) LOCAL testend% = y + self.h% tilecoordx = x/TILESIZE; LOCAL tilecoordy% = tileypixels/TILESIZE; WHILE tileypixels <= testend IF map.TMap_map(tilecoordx, tilecoordy) = t_solid RETURN TRUE ENDIF INC tilecoordy INC tileypixels,TILESIZE WEND RETURN FALSE ENDFUNCTION FUNCTION collision_slope%:sx%,sy%,BYREF tsx%, BYREF tsy% LOCAL t% tsx = sx / TILESIZE; //map coordinates of the tile we check against tsy = sy / TILESIZE; t = map.TMap_map(tsx, tsy); //if we found a slope we set align y to the slope. //take a look at jnrdev #2, why it's calculated this way IF (t = t_sloperight) //sloperight -> \ self.y = (tsy+1)*TILESIZE - (TILESIZE - MOD(sx%,TILESIZE)) - self.h - 1; RETURN TRUE ELSEIF t = t_slopeleft //slopeleft -> / self.y = (tsy+1)*TILESIZE - MOD(sx%,TILESIZE) - self.h - 1 ; RETURN TRUE ENDIF RETURN FALSE ENDFUNCTION FUNCTION collision_detection_map%: //check for slopes (only if moving down) IF self.vely > 0 LOCAL tsx%, tsy% //slope tile coordinates LOCAL sx% = self.x + ASR(self.w,1) + self.velx; //slope chechpoint x coordinate

IF self.collision_slope(sx, (self.y + self.h), tsx, tsy) //we entered a slope (y is set by collision_slope) INC self.x,self.velx; //move on //y has been set by collision_slope self.unlockjump(); //we hit the ground - the player may jump again self.vely = 1; //test against the ground again in the next frame

234 self.slope_prevtilex = tsx; //save slope koordinate self.slope_prevtiley = tsy; RETURN TRUE ELSE //we're not on a slope this frame - check if we left a slope //-1 ... we didn't move from slope to slope //0 ... we left a slope after moving down //1 ... we left a slope after moving up LOCAL what% = -1 IF map.TMap_map(self.slope_prevtilex, self.slope_prevtiley) = t_sloperight IF(self.velx > 0) //sloperight + velx > 0 = we left a slope after moving up the slope what = 0; ELSE what = 1; //we left after moving down the slope ENDIF ELSEIF map.TMap_map(self.slope_prevtilex, self.slope_prevtiley) = t_slopeleft IF(self.velx < 0) //sloperight + velx > 0 = we left a slope after moving up the slope what = 0; ELSE what = 1; //we left after moving down the slope ENDIF ENDIF IF(what <> -1) //if we left a slope and now are on a slope LOCAL sy% IF(what = 1) self.y = tsy*TILESIZE - self.h -1; //move y to top of the slope tile sy = self.y + self.h ELSE self.y = (tsy+1)*TILESIZE - self.h -1; //move y to the bottom of the slope tile sy = self.y + self.h + TILESIZE; //test one tile lower than the bottom of the slope (to test if we move down a long slope) //i t's physically incorrect, but looks a lot smoother ingame ENDIF //check for slopes on new position IF (self.collision_slope(sx, sy, tsx, tsy) ) //slope on new pos (entered a slope after we left a slope) //-> we moved from slope to slope INC self.x,self.velx self.unlockjump(); self.vely = 1; self.slope_prevtilex = tsx; self.slope_prevtiley = tsy; RETURN TRUE ENDIF ENDIF ENDIF ENDIF //no slope collisions were found -> check for collisions with the map LOCAL tilecoord% //x axis first (--) IF(self.velx > 0) //moving right IF(self.collision_ver(self.x+self.velx+self.w, self.y, tilecoord))

235 //collision on the right side. self.x = tilecoord*TILESIZE -self.w-1; //move to the edge of the tile (tile on the right -> mind the player width) ELSE //no collision INC self.x,self.velx ENDIF ELSEIF (self.velx < 0) //moving left IF(self.collision_ver(self.x+self.velx, self.y, tilecoord)) //collision on the left side self.x = (tilecoord+1)*TILESIZE +1; //move to the edge of the tile ELSE INC self.x,self.velx ENDIF ENDIF //then y axis (|) IF(self.vely < 0) //moving up IF(self.collision_hor_up(self.x, self.y+self.vely, tilecoord)) self.y = (tilecoord+1)*TILESIZE +1 self.vely = 0 ELSE INC self.y,self.vely; INC self.vely,GRAVITATION ENDIF ELSE //moving down / on ground //printf("test: down, vely:%d\n", vely); IF(self.collision_hor_down(self.x, self.y+self.vely+self.h, tilecoord)) //on ground self.y = tilecoord*TILESIZE -self.h-1; self.vely = 1; //1 so we test against the ground again int the next frame (0 would test against the ground in the next+1 frame) self.unlockjump(); ELSE //falling (in air) INC self.y,self.vely INC self.vely,GRAVITATION IF (self.vely >= TILESIZE) //if the speed is higher than this we might fall through a tile self.vely = TILESIZE ENDIF self.lockjump = TRUE; //don't allow jumping after falling of an edge ENDIF ENDIF self.slope_prevtilex = (self.x + ASR(self.w,1)) / TILESIZE; self.slope_prevtiley = (self.y + self.h) / TILESIZE; ENDFUNCTION FUNCTION think%: self.velx=0; //don't move left / right by default IF KEY(205) self.velx = VELMOVING; //move right self.faceright = TRUE; //player graphic is facing right ELSEIF KEY(203) self.velx = -VELMOVING; //move left self.faceright = FALSE; //player graphic is facing left ENDIF IF KEY(42) AND NOT(self.lockjump) //if the player isn't jumping already self.vely = -VELJUMP; //jump! self.lockjump = TRUE; //player is not allowed to jump anymore self.jumping = TRUE; ENDIF self.collision_detection_map(); //now that we have our new position it's time to update the scrolling position scroll_x = self.x - 270;

236 scroll_y = self.y - 260; map.limit_scroll(); ENDFUNCTION ENDTYPE scroll_x% = 0; scroll_y% = 0; DIM spr_t%[7] DIM spr_player%[2] LOCAL spr_background%,backWidth%,backHeight% LOCAL player AS TPlayer IF player.TPlayer_Initialise()=FALSE DEBUG "Error\n" RETURN FALSE ENDIF LIMITFPS -1 SETTRANSPARENCY RGB(255,0,255) spr_t[0]=GENSPRITE(); LOADSPRITE "Media/gfx/t1.bmp",spr_t[0] //tile graphics spr_t[1]=GENSPRITE(); LOADSPRITE "Media/gfx/t2.bmp",spr_t[1] spr_t[2]=GENSPRITE(); LOADSPRITE "Media/gfx/t3.bmp",spr_t[2] spr_t[3]=GENSPRITE(); LOADSPRITE "Media/gfx/tsloper.bmp",spr_t[3] spr_t[4]=GENSPRITE(); LOADSPRITE "Media/gfx/tslopel.bmp",spr_t[4] spr_t[5]=GENSPRITE(); LOADSPRITE "Media/gfx/t6.bmp",spr_t[5] spr_t[6]=GENSPRITE(); LOADSPRITE "Media/gfx/t7.bmp",spr_t[6] spr_player[0]=GENSPRITE();LOADSPRITE "Media/gfx/left.bmp",spr_player[0] //player graphics spr_player[1]=GENSPRITE();LOADSPRITE "Media/gfx/right.bmp", spr_player[1] spr_background=GENSPRITE(); LOADSPRITE "Media/gfx/bg.bmp",spr_background% //background GETSPRITESIZE spr_background%,backWidth%,backHeight% tileset[0].tileType = t_nonsolid; tileset[0].spr = -1 tileset[1].tileType = t_solid; tileset[1].spr = -1 tileset[2].tileType = t_solid; tileset[2].spr = spr_t[0] tileset[3].tileType = t_solid; tileset[3].spr = spr_t[1] tileset[4].tileType = t_nonsolid; tileset[4].spr = spr_t[2] tileset[5].tileType = t_sloperight; tileset[5].spr = spr_t[3] tileset[6].tileType = t_slopeleft; tileset[6].spr = spr_t[4] tileset[7].tileType = t_solid; tileset[7].spr = spr_t[5] tileset[8].tileType = t_solid; tileset[8].spr = spr_t[6] scroll_x%=0 scroll_y%=0 IF map.loadMap("Media/maps/map01.map")=FALSE DEBUG "Error loading map\n" RETURN FALSE ENDIF WHILE TRUE player.think() //------draw everything (render the scene) ------//ugly, stupid, slow background tiling LOCAL sdx% = MOD(scroll_x,backWidth%); LOCAL sdy% = MOD(scroll_y,backHeight); DRAWSPRITE spr_background%,-sdx%,-sdy% DRAWSPRITE spr_background%,backWidth%-sdx,-sdy% DRAWSPRITE spr_background%,-sdx%,backHeight%-sdy DRAWSPRITE spr_background%,backWidth%-sdx,backHeight%-sdy map.TMap_draw(); player.TPlayer_draw(); SHOWSCREEN WEND

237 Clock Graphic Demonstration

This routine was written when I was bordered, and felt it was time to write a clock based graphical demo. // ------// // Project: Graphic // Start: Saturday, July 03, 2010 // IDE Version: 8.006 CONSTANT STEPSIZE% = 4 TYPE tPixel x% y% colour% ENDTYPE TYPE tClock size angle speed alpha ENDTYPE LOCAL pixel[] AS tPixel LOCAL clock[] AS tClock LOCAL mx%,sW%,sH% LOCAL alpha GETSCREENSIZE sW%,sH% LIMITFPS -1 mx%=sW%/4 DIM pixel[mx%] DIM clock[9] SEEDRND GETTIMERALL() FOR loop%=0 TO mx%-1 pixel[loop%].x%=loop%*4 pixel[loop%].y%=sH%/2 pixel[loop%].colour%=RGB(128,128,128) NEXT alpha=-0.1 FOR loop%=0 TO 8 clock[loop%].size=RND(MIN(sW%,sH%)/2)+8; clock[loop%].angle=RND(359); clock[loop%].alpha=alpha clock[loop%].speed=0.0 WHILE clock[loop%].speed=0.0 clock[loop%].speed=(RND(100)-50)/20.0 WEND DEC alpha,0.1 NEXT WHILE TRUE FOR loop%=0 TO 8 ALPHAMODE clock[loop%].alpha DRAWLINE (sW%/2),(sH%/2), _ (sW%/2)+(clock[loop%].size%*SIN(clock[loop%].angle)), _ (sH%/2)+(clock[loop%].size%*COS(clock[loop%].angle)), _ RGB(255,255,0) INC clock[loop%].angle,clock[loop%].speed IF clock[loop%].angle>=360.0 THEN DEC clock[loop%].angle,360.0 NEXT FOR loop%=0 TO mx%-2 ALPHAMODE 0.0 DRAWLINE pixel[loop%].x%,pixel[loop%].y%,pixel[loop%+1].x%,pixel[loop%+1].y %,pixel[loop%].colour% ALPHAMODE -0.8 DRAWLINE sW%-pixel[loop%].x%,sH%-pixel[loop%].y%,sW%-pixel[loop%+1].x%,sH%-

238 pixel[loop%+1].y%,RGB(128,128,128) NEXT SHOWSCREEN FOR loop%=1 TO mx%-1 pixel[loop%-1].y%=pixel[loop%].y% pixel[loop%-1].colour%=pixel[loop%].colour% NEXT INC pixel[mx%-1].y%,(RND(10)-5)*STEPSIZE% IF pixel[mx%-1].y%<0 THEN pixel[mx%-1].y%=0 IF pixel[mx%-1].y%>sH% THEN pixel[mx%-1].y%=sH% pixel[mx%-1].colour%=RGB(RND(255),RND(255),RND(255)) WEND

239 Basic INCBIN routine

This is a very simple INCBIN example for GLBasic, where data that would normally be stored in a file would instead be compiled into the program. Not terribly useful.

REQUIRE "ExampleC.c" INLINE }; extern int dataV[]; namespace __GLBASIC__ { ENDINLINE FUNCTION GetValue%:index% INLINE return (DGNat) dataV[index]; ENDINLINE ENDFUNCTION ExampleC.c file : int dataV[]={0,12,25,13,74,105,996,17,48,9};

240 BallBlazer/Trailblazer-type Track Editor

When I was thinking of doing a BallBlazer type game, I wrote this program to allow easy level creation. Unfortunately, nothing came of the game. It uses DDgui // ------// // Project: SpaceRace_Editor // Start: Monday, June 07, 2010 // IDE Version: 8.002 // Things to add later : // Level of tile so that they can be level, or angled upwards or downwards // Status : Started implementation, and can be written when saving blocks // Action script - where specific codes could execute routines depending on whether player touches a tile or a tile with the script has been dsiplayed // Status : Not started // Tunnels // Status : Done. A tunnel will cover all the columns and can only be placed on a level surface // Tilting maps (maybe ?) // Status : Think about CONSTANT ROW_PAD$ = "0000" CONSTANT BLOCK_PAD$ = "000" CONSTANT MIN_ROWS% = 8 CONSTANT MAX_ROWS% = 9999 CONSTANT MAX_BLOCKS% = 999 CONSTANT MIN_CHECKPOINTTIME% = 5 CONSTANT MAX_CHECKPOINTTIME% = 60 CONSTANT FONT_PATH$ = "Media/Fonts/" CONSTANT PROGRAM_VERSION$="1.0.0.0" CONSTANT SHAPE_WIDTH% = 16 CONSTANT SHAPE_HEIGHT% = 16 CONSTANT EXT_LEVELDATA$ = "*.LVD" CONSTANT EXT_BLOCKDATA$ = "*.BLK" CONSTANT SEPERATOR$ = "," CONSTANT HEADER_SECTION$= "HEADER" CONSTANT VERSION_KEY$ = "VERSION" CONSTANT DATA_KEY$ = "DATA" CONSTANT LEVELDATA_DATA$= "LEVELDATA" CONSTANT BLOCKDATA_DATA$= "BLOCKDATA" CONSTANT INFO_SECTION$ = "INFORMATION"// For level details CONSTANT NUMCOLUMN_KEY$ = "NUMCOLUMNS" CONSTANT NUMBLOCK_KEY$ = "NUMBLOCKS" CONSTANT BLOCKNAME_KEY$ = "BLOCKNAME" CONSTANT AUTHOR_KEY$ = "AUTHOR" CONSTANT NAME_KEY$ = "NAME" CONSTANT TILE_SECTION$ = "TILES" CONSTANT NUMROWS_KEY$ = "ROWS" CONSTANT LINE_KEY$ = "LINE" CONSTANT COLUMN_ODD% = -1 CONSTANT COLUMN_EVEN% = 0 CONSTANT COLUMN_0% = 1 CONSTANT FONT_DEFAULT% = 0 CONSTANT FONT_WHITEFONT%= 1 CONSTANT FONT_DARKFONT% = 2 CONSTANT ADDTYPE_ADDREMOVE_END% = 1 // Add tiles to end of current block of rows CONSTANT OddColumnText$="Odd Column Graphics :" CONSTANT OddColumnList$="oddList" CONSTANT OddColumnSelect$="oddSelect" CONSTANT EvenColumnText$="Even Column Graphics :"

241 CONSTANT EvenColumnList$="evenList" CONSTANT EvenColumnSelect$="evenSelect" CONSTANT SelectionColumnText$="Selection Column graphics :" CONSTANT SelectionColumnList$="selectionList" CONSTANT SelectionColumnListColumn$="selectionListColumn" CONSTANT SelectionColumnSelectButton$="Select" CONSTANT HeightText$="Row Height :" CONSTANT HeightValue$="blockValue" CONSTANT HeightHigherButton$="H" CONSTANT HeightLevelButton$="-" CONSTANT HeightLowerButton$="L" CONSTANT TunnelText$="Is a tunnel" // Movement tab CONSTANT AddRow$="Add Row" CONSTANT DeleteRow$="Delete Row" CONSTANT InsertRow$="Insert Row" CONSTANT TopRow$="Top Row" CONSTANT UpRow$="Up Row" CONSTANT DownRow$="Down Row" CONSTANT BottomRow$="Bottom Row" // File tab CONSTANT MainMenu$="Main Menu" // Checkpoint tab CONSTANT CheckpointText$="Checkpoint Time :" CONSTANT CheckpointValue$="checkpointValue" CONSTANT CheckpointAccept$="Update"

TYPE tTileInfo image% description$ ENDTYPE CONSTANT IMAGE_CHECKPOINT% = 0 CONSTANT IMAGE_BLANK% = 1 CONSTANT IMAGE_STANDARD% = 2 CONSTANT IMAGE_BOUNCE% = 3 CONSTANT IMAGE_SPEEDUP% = 4 CONSTANT IMAGE_SLOWDOWN% = 5 CONSTANT IMAGE_REVERSECONTROLS% = 6 CONSTANT IMAGE_SWAPPOSITIONS% = 7 CONSTANT IMAGE_THROWBACKWARDS% = 8 CONSTANT IMAGE_TELEPORTBACK% = 9 CONSTANT IMAGE_WARP% = 10 // This defines one block TYPE tBlock index% // x% // X position (0 - MAX_COLUMNS% - 1) // y% // Y (in 2D), Z (in 3D) position extraInfo% // Extra info ENDTYPE // Checkpoint type TYPE tTime fromY% endY% time% ENDTYPE //------//! This defines 5 columns of a row //------TYPE TColumn column[] AS tBlock // Block of 5 tiles height% // Height of this tile. Can only be between -1 to 1 to stop big jumps. The main game calculates the total height checkpointTime% // Only used if this row is a checkpoint - this is the time allowed between this checkpoint and the next

242 isTunnel% MAX_COLUMNS% = 9 FUNCTION TColumn_Initialise%: LOCAL loop AS tBlock DIM self.column[self.MAX_COLUMNS%] FOREACH loop IN self.column[] loop.index%=IMAGE_STANDARD% NEXT self.height%=0 self.isTunnel%=FALSE ENDFUNCTION FUNCTION TColumn_IsColumnACheckpoint%: LOCAL loop% FOR loop%=0 TO BOUNDS(self.column[],0)-1 IF self.column[loop%].index%=IMAGE_CHECKPOINT% RETURN TRUE ENDIF NEXT RETURN FALSE ENDFUNCTION FUNCTION TColumn_DrawColumn%:x%,y% LOCAL loop% IF BOUNDS(self.column[],0)=0 DDgui_msg("Column has not been initialised",FALSE,"* Fatal Error *") ELSE FOR loop%=0 TO BOUNDS(self.column[],0)-1 DRAWSPRITE IMAGE_CHECKPOINT%+self.column[loop%].index%,x%+ (loop%*SHAPE_WIDTH%),y% NEXT ENDIF ENDFUNCTION // Change one or more columns to the specific index FUNCTION TColumn_ChangeRow%:changeType%,value% LOCAL loop%,start% SELECT changeType% CASE COLUMN_ODD% start%=1 CASE COLUMN_EVEN% start%=0 DEFAULT start%=changeType%-COLUMN_0% ENDSELECT IF start%<0 OR start%>=self.MAX_COLUMNS% DDgui_msg("Invalid column position",FALSE,"* Column Error *") RETURN FALSE ELSE IF value%=IMAGE_CHECKPOINT% FOR loop%=0 TO BOUNDS(self.column[],0)-1 self.column[loop%].index%=value% NEXT self.checkpointTime%=60 ELSE IF changeType%=COLUMN_ODD% OR changeType%=COLUMN_EVEN% FOR loop%=start% TO BOUNDS(self.column[],0)-1 STEP 2 self.column[loop%].index%=value% NEXT ELSE self.column[start%].index%=value% ENDIF self.checkpointTime%=0 ENDIF RETURN TRUE

243 ENDIF ENDFUNCTION // Get all the columns as a string // Data is returned in the format : // c0,c1,c2,c3,c4,c5,c6,c7,c8,height,tunnel [,checkpoint] FUNCTION TColumn_GetTileData$: LOCAL temp$ LOCAL loop% temp$="" FOR loop%=0 TO BOUNDS(self.column[],0)-1 temp$=temp$+self.column[loop%].index%+SEPERATOR$ NEXT // Now to add the height temp$=temp$+self.height%+SEPERATOR$ // Add tunnel flag temp$=temp$+self.isTunnel%+SEPERATOR$ // And, if this row is a checkpoint, add the time IF TColumn_IsColumnACheckpoint() temp$=temp$+self.checkpointTime% ENDIF IF RIGHT$(temp$,1)=SEPERATOR$ temp$=LEFT$(temp$,LEN(temp$)-1) ENDIF

RETURN temp$ ENDFUNCTION FUNCTION TColumn_SetRowHeight%:height% self.height%=height% ENDFUNCTION FUNCTION TColumn_GetRowHeight%: RETURN self.height% ENDFUNCTION FUNCTION TColumn_ReturnColumnWidth%: RETURN self.MAX_COLUMNS% ENDFUNCTION FUNCTION TColumn_ReturnCheckpointTime%: RETURN self.checkpointTime% ENDFUNCTION // Set the checkpoint time value FUNCTION TColumn_SetCheckpointTime%:value% self.checkpointTime%=value% ENDFUNCTION FUNCTION TColumn_IsTunnel%: RETURN self.isTunnel% ENDFUNCTION FUNCTION TColumn_SetTunnel%:isTunnel% IF isTunnel% self.isTunnel%=TRUE ELSE self.isTunnel%=FALSE ENDIF ENDFUNCTION ENDTYPE //------//! Display displays the block border and title text //------TYPE TShapeDisplay // X,Y, width and height of border around the shapes x%;y% width%;height%

244 // Maximum shapes in the border that can be displayed maxShapeLines% // Font sizes titleText$="Space Race Editor" titleFont% titleFontWidth%;titleFontHeight% // Title X & Y positions titleX%;titleY% // The selection line halfY% FUNCTION TShapeDisplay_Initialise%:MAX_COLUMNS% LOCAL sW%,sH% LOCAL temp%,titleYSize% SETFONT FONT_WHITEFONT% GETFONTSIZE self.titleFontWidth%,self.titleFontHeight% GETSCREENSIZE sW%,sH% self.titleX%=(sW%-(LEN(self.titleText$)*self.titleFontWidth%))/2 self.titleY%=1 titleYSize%=self.titleY%+self.titleFontHeight% temp%=sH%-titleYSize%-8 self.maxShapeLines%=temp%/SHAPE_HEIGHT% IF bAND(self.maxShapeLines%,1)=0 THEN DEC self.maxShapeLines% // Make sure its an odd number

self.width%=(SHAPE_WIDTH%*MAX_COLUMNS%)+2 self.height%=self.maxShapeLines%*SHAPE_HEIGHT% self.x%=4 self.y%=((temp%-self.height%)/2)+titleYSize% DEBUG self.height%+" "+self.y%+" "+temp%+"\n" self.halfY%=self.maxShapeLines%/2 RETURN TRUE ENDFUNCTION // Display border and title texts FUNCTION TShapeDisplay_DisplayBorder%: SMOOTHSHADING FALSE ALPHAMODE 0.0 SETFONT FONT_WHITEFONT% PRINT self.titleText$,self.titleX%,self.titleY% // Draw the border DRAWLINE self.x%-1,self.y%-1,self.x%+self.width%,self.y%-1,RGB(255,255,255) DRAWLINE self.x%+self.width%,self.y%-1,self.x%+self.width%,self.y% +self.height%+2,RGB(255,255,255) DRAWLINE self.x%-1,self.y%+self.height%+2,self.x%+self.width%,self.y% +self.height%+2,RGB(255,255,255) DRAWLINE self.x%-1,self.y%-1,self.x%-1,self.y%+self.height% +2,RGB(255,255,255) ENDFUNCTION FUNCTION TShapeDisplay_DisplaySelectionArea%: LOCAL y%,col%=RGB(255,0,0) y%=(self.y%+(self.halfY%*SHAPE_HEIGHT))-1 // Draw the current line selection area DRAWLINE self.x%-1,y%,self.width%+2,y%,col% INC y%,SHAPE_HEIGHT%+1 DRAWLINE self.x%-1,y%,self.width%+2,y%,col% ENDFUNCTION FUNCTION TShapeDisplay_DisplayTiles%:row%,dat[] AS TColumn LOCAL loop%,size%,y% size%=BOUNDS(dat[],0) IF size%=0 THEN RETURN FALSE y%=(self.y%+self.height%)-SHAPE_HEIGHT% FOR loop%=row%-self.halfY% TO row+(self.halfY%)

245 IF loop%>=0 AND loop%orgSize% // Initialise the new set of rows FOR loop%=orgSize% TO (orgSize+amount%)-1 self.row[loop%].TColumn_Initialise() NEXT ENDIF ENDSELECT ENDIF ENDIF

246 RETURN TRUE ENDFUNCTION // Delete one or more lines from the current cursor position FUNCTION TRowEditor_DeleteAtRowPos%:rowPos%,amount% LOCAL size% WHILE BOUNDS(self.row[],0)>0 AND rowPos%0 DEBUG "R:"+rowPos%+" "+BOUNDS(self.row[],0)+" "+amount%+"\n" DEC amount% DIMDEL self.row[],rowPos% WEND size%=BOUNDS(self.row[],0) IF size%>0 IF rowPos%>=size% rowPos%=size%-1 ENDIF ELSE rowPos%=0 ENDIF DEBUG "X:"+rowPos%+"\n" RETURN rowPos% ENDFUNCTION FUNCTION TRowEditor_ReturnNumberOfRows%: RETURN BOUNDS(self.row[],0) ENDFUNCTION

FUNCTION TRowEditor_isValidRow%:row% IF BOUNDS(self.row[],0)=0 OR row%>=BOUNDS(self.row[],0) OR row%<0 THEN RETURN FALSE RETURN TRUE ENDFUNCTION // Check to see if given row is a checkpoint FUNCTION TRowEditor_isRowACheckpoint%:row% IF TRowEditor_isValidRow(row%)=TRUE RETURN self.row[row%].TColumn_IsColumnACheckpoint() ELSE RETURN FALSE ENDIF ENDFUNCTION // Return the row data FUNCTION TRowEditor_GetRowData$:row% RETURN self.row[row%].TColumn_GetTileData$() ENDFUNCTION // Change odd or even tiles with the index value from the selected list // If its a checkpoint, then all columns are set to checkpoint. // A checkpoint must not appear on row 0 or the last row // Nor should it appear less than 2 rows below FUNCTION TRowEditor_ChangeTiles%:changeType%,row%,value% IF BOUNDS(self.row[],0)>0 SELECT value% CASE IMAGE_CHECKPOINT% IF row%=0 OR row %>=TRowEditor_ReturnNumberOfRows()-1 DDgui_msg("A checkpoint MUST not be on the first or last line",FALSE,"* Checkpoint Error *") RETURN FALSE ELSE IF TRowEditor_isRowACheckpoint(row%-1) OR TRowEditor_isRowACheckpoint(row%-2) DDgui_msg("A space of at least two rows must be present between checkpoints",FALSE,"* Checkpoint Error *") RETURN FALSE ENDIF ENDIF

247 ENDSELECT RETURN self.row[row%].TColumn_ChangeRow(changeType%,value%) ENDIF RETURN FALSE ENDFUNCTION // Add one or more rows after the current line FUNCTION addRow%: //TRowEditor_SetTitle() ENDFUNCTION // Delete one or more rows after the current line FUNCTION deleteRow%: //TRowEditor_SetTitle() ENDFUNCTION // Return the row name FUNCTION TRowEditor_ReturnRowName$: RETURN self.rowName$ ENDFUNCTION // Set a row name FUNCTION TRowEditor_SetRowName%:name$ self.rowName$=name$ ENDFUNCTION // Get row information as text FUNCTION TRowEditor_GetTileData$:row% IF TRowEditor_isValidRow(row%) RETURN self.row[row%].TColumn_GetTileData$() ELSE RETURN "" ENDIF ENDFUNCTION FUNCTION TRowEditor_SetTileData%:data$ LOCAL size%,loop%,width% LOCAL info$[] LOCAL row AS TColumn DIM info$[0] row.TColumn_Initialise() width%=row.TColumn_ReturnColumnWidth() size%=SPLITSTR(data$,info$[],SEPERATOR$) IF size%>=width% FOR loop%=0 TO width%-1 row.column[loop%].index%=INTEGER(info$[loop%]) NEXT ENDIF IF size%>=width%+1 row.height%=INTEGER(info$[width%]) ENDIF IF size%>=width%+2 row.isTunnel%=INTEGER(info$[width%+1]) ENDIF IF size%>=width%+3 row.checkpointTime%=INTEGER(info$[width%+2]) ENDIF DIMPUSH self.row[],row ENDFUNCTION // Change the height of the row offset FUNCTION TRowEditor_SetRowHeight%:row%,height% IF TRowEditor_isValidRow(row%) self.row[row%].TColumn_SetRowHeight(height%) ENDIF ENDFUNCTION // Return the height of the row FUNCTION TRowEditor_GetRowHeight%:row%

248 IF TRowEditor_isValidRow(row%) RETURN self.row[row%].TColumn_GetRowHeight() ENDIF RETURN 0 ENDFUNCTION // Get number of columns in arow FUNCTION TRowEditor_ReturnColumnWidth%: IF BOUNDS(self.row[],0)>0 THEN RETURN self.row[0].TColumn_ReturnColumnWidth() RETURN 0 ENDFUNCTION FUNCTION TRowEditor_GetCheckpointTime%:row% IF TRowEditor_isValidRow(row%) RETURN self.row[row%].TColumn_ReturnCheckpointTime() ENDIF RETURN 0 ENDFUNCTION FUNCTION TRowEditor_SetCheckpointTime%:row%,value% IF TRowEditor_isValidRow(row%) RETURN self.row[row%].TColumn_SetCheckpointTime(value%) ENDIF RETURN 0 ENDFUNCTION

FUNCTION TRowEditor_IsTunnel%:row% IF TRowEditor_isValidRow(row%) RETURN self.row[row%].TColumn_IsTunnel() ENDIF RETURN FALSE ENDFUNCTION FUNCTION TRowEditor_SetTunnel%:row%,isTunnel% IF TRowEditor_isValidRow(row%) THEN self.row[row %].TColumn_SetTunnel(isTunnel%) ENDFUNCTION ENDTYPE //------//! This allows the creation and modification of a block of rows (sequence editing) //------TYPE TBlockEditor block[] AS TRowEditor blockIndex% // Which block we are editing tileList$ // List of tiles rowPos% // Position in current block // Editor window sizes and position windowX%;windowY% windowWidth%=310 windowHeight%=289 blockTab$ // To create a tab blockTitle$="Block Editor" // Tile column blockAvailText$="Aviliable Blocks :" blockAvailList$="availList" blockSelectionColumnSelect$ blockNameEntry$="blockName" blockAddButton$="Add Block" blockDeleteButton$="Delete Block" blockSaveData$="Save Blocks" // Only block editor blockLoadData$="Load Blocks" // Only block editor blockNewBlock$="New Block" // Only block editor timeForNextMovement

249 lastMove% FUNCTION TBlock_Initialise%:tileList$,MAX_COLUMNS% LOCAL sW%,sH%,loop% GETSCREENSIZE sW%,sH% TBlock_DeleteAllBlocks() self.blockSelectionColumnSelect$="" FOR loop%=0 TO MAX_COLUMNS%-1 self.blockSelectionColumnSelect$=self.blockSelectionColumnSelect$ +"Col #"+loop%+"|" NEXT self.blockSelectionColumnSelect$=LEFT$ (self.blockSelectionColumnSelect$,LEN(self.blockSelectionColumnSelect$)-1) self.windowX%=(sW%-self.windowWidth%) self.windowY%=(sH%-self.windowHeight%)/2 self.tileList$=tileList$ // Tile tab - this is for modifing the current selection row self.blockTab$="Tile,"+self.blockAvailText$+","+self.blockAvailList$ +","+self.blockNameEntry$+","+ _ self.blockAddButton$+","+self.blockDeleteButton$ +","+OddColumnText$+","+OddColumnList$+","+OddColumnSelect$+","+EvenColumnText$ +","+EvenColumnList$+","+EvenColumnSelect$+","+ _ SelectionColumnText$+","+SelectionColumnList$ +","+SelectionColumnListColumn$+","+SelectionColumnSelectButton$+","+ _ HeightText$+","+HeightValue$+","+HeightHigherButton$ +","+ _ HeightLevelButton$+","+HeightLowerButton$ +","+TunnelText$ // Movement tab - this is for moving up and down the rows self.blockTab$=self.blockTab$+"|Movement,"+TopRow$+","+BottomRow$ +","+UpRow$+","+DownRow$ // Size tab - adjust the number of rows self.blockTab$=self.blockTab$+"|Size,"+AddRow$+","+DeleteRow$ // Checkpoint tab self.blockTab$=self.blockTab$+"|Data,"+CheckpointText$+","+CheckpointValue$ +","+CheckpointAccept$ // File tab - saving, loading and new block self.blockTab$=self.blockTab$+"|File,"+self.blockSaveData$ +","+self.blockLoadData$+","+self.blockNewBlock$+","+MainMenu$ RETURN TRUE ENDFUNCTION FUNCTION TBlock_CreateWindow%: LOCAL listSub%=28 LOCAL buttonSub%=10 LOCAL comboSub%=10 LOCAL halfX% LOCAL loop% halfX%=(self.windowWidth%/2)-6 self.rowPos%=0 SETFONT FONT_DARKFONT% DDgui_pushdialog(self.windowX%,self.windowY%,self.windowWidth %,self.windowHeight%) DDgui_tab("tab",self.blockTab$) // Tile tab DDgui_widget(self.blockAvailText$,self.blockAvailText$,halfX%,0) DDgui_combo(self.blockAvailList$,TBlock_GetListOfBlockNames$(),halfX%,24) // Cant select first block name as it would continually generate a "selected" code DDgui_text(self.blockNameEntry$,"",self.windowWidth%-4,0) DDgui_button(self.blockAddButton$,self.blockAddButton$,halfX%,0)

250 DDgui_button(self.blockDeleteButton$,self.blockDeleteButton$,halfX%,0) DDgui_widget(OddColumnText$,OddColumnText$,self.windowWidth%-4,0) DDgui_combo(OddColumnList$,self.tileList$,halfX%,24) DDgui_button(OddColumnSelect$,"Select",halfX%,0) DDgui_widget(EvenColumnText$,EvenColumnText$,self.windowWidth%-4,0) DDgui_combo(EvenColumnList$,self.tileList$,halfX%,24) DDgui_button(EvenColumnSelect$,"Select",halfX%,0) DDgui_widget(SelectionColumnText$,SelectionColumnText$,self.windowWidth%- 4,0) DDgui_combo(SelectionColumnList$,self.tileList$,halfX%,24) DDgui_combo(SelectionColumnListColumn$,self.blockSelectionColumnSelect$,halfX%,0)

DDgui_button(SelectionColumnSelectButton$,SelectionColumnSelectButton$,self.windowWidth %-buttonSub%,0) DDgui_widget(HeightText$,HeightText$,halfX%,0) IF BOUNDS(self.block[],0)>0 DDgui_widget(HeightValue$,self.block[self.blockIndex %].TRowEditor_GetRowHeight(self.rowPos%),16,0) ELSE DDgui_widget(HeightValue$,"0",16,0) ENDIF DDgui_button(HeightHigherButton$,HeightHigherButton$,18,0) DDgui_button(HeightLevelButton$,HeightLevelButton$,18,0) DDgui_button(HeightLowerButton$,HeightLowerButton$,18,0) DDgui_checkbox(TunnelText$,TunnelText$,0,0) IF BOUNDS(self.block[],0)>0 IF self.block[self.blockIndex%].TRowEditor_IsTunnel(self.rowPos%) DDgui_set(TunnelText$,"CLICKED",TRUE) ELSE DDgui_set(TunnelText$,"CLICKED",FALSE) ENDIF ELSE DDgui_set(TunnelText$,"CLICKED",TRUE) ENDIF // Movement tab DDgui_button(TopRow$,TopRow$,self.windowWidth%-comboSub%,0) DDgui_button(BottomRow$,BottomRow$,self.windowWidth%-comboSub%,0) DDgui_button(UpRow$,UpRow$,self.windowWidth%-comboSub%,0) DDgui_button(DownRow$,DownRow$,self.windowWidth%-comboSub%,0) // Size tab DDgui_button(AddRow$,AddRow$,halfX%,0) DDgui_button(DeleteRow$,DeleteRow$,halfX%,0) // Data tab DDgui_widget(CheckpointText$,CheckpointText$,140,0) DDgui_text(CheckpointValue$,"",32,0) DDgui_button(CheckpointAccept$,CheckpointAccept$,100,0) // File tab DDgui_button(self.blockSaveData$,self.blockSaveData$,self.windowWidth%- buttonSub%,0) DDgui_button(self.blockLoadData$,self.blockLoadData$,self.windowWidth%- buttonSub%,0) DDgui_button(self.blockNewBlock$,self.blockNewBlock$,self.windowWidth%- buttonSub%,0) DDgui_button(MainMenu$,MainMenu$,self.windowWidth%-buttonSub%,0) ENDFUNCTION FUNCTION TBlock_DeleteAllBlocks%: DIM self.block[0] self.blockIndex%=-1 self.rowPos%=0 ENDFUNCTION FUNCTION TBlock_SetTitle%: LOCAL temp$ LOCAL size%

251 temp$="" size%=BOUNDS(self.block[],0) IF size%>0 temp$="("+RIGHT$(ROW_PAD$+self.rowPos%,LEN(ROW_PAD$))+"/"+RIGHT$ (ROW_PAD$+self.block[self.blockIndex%].TRowEditor_ReturnNumberOfRows(),LEN(ROW_PAD$)) +"/"+RIGHT$(BLOCK_PAD$+size%,LEN(BLOCK_PAD$))+")" ENDIF DDgui_set("","TEXT",self.blockTitle$+" "+temp$) ENDFUNCTION FUNCTION TBlock_MainLoop%:shapeDisplay AS TShapeDisplay LOCAL index%,num% TBlock_CreateWindow() TBlock_SetTitle() self.timeForNextMovement=GETTIMERALL() self.lastMove%=0 WHILE TRUE shapeDisplay.TShapeDisplay_DisplayBorder() IF BOUNDS(self.block[],0)>0 shapeDisplay.TShapeDisplay_DisplayTiles(self.rowPos %,self.block[self.blockIndex%].row[]) // Display the row height offset DDgui_set(HeightValue$,"TEXT",self.block[self.blockIndex %].TRowEditor_GetRowHeight(self.rowPos%))

// Display the Tunnel value IF self.block[self.blockIndex %].TRowEditor_IsTunnel(self.rowPos%) DDgui_set(TunnelText$,"SELECT",TRUE) ELSE DDgui_set(TunnelText$,"SELECT",FALSE) ENDIF // Disable the checkpoint text if this row is not a checkpoint IF self.block[self.blockIndex %].TRowEditor_isRowACheckpoint(self.rowPos%)=FALSE IF DDgui_get(CheckpointValue$,"READONLY")=FALSE DDgui_set(CheckpointValue$,"READONLY",TRUE) DDgui_set(CheckpointAccept$,"READONLY",TRUE) DDgui_set(CheckpointValue$,"TEXT","N/A") ENDIF ELSE // This is a checkpoint line, so put the initial value in IF DDgui_get(CheckpointValue$,"READONLY")=TRUE DDgui_set(CheckpointValue$,"READONLY",FALSE) DDgui_set(CheckpointAccept$,"READONLY",FALSE) DDgui_set(CheckpointValue$,"TEXT",self.block[self.blockIndex %].TRowEditor_GetCheckpointTime(self.rowPos%)) ENDIF ENDIF ENDIF shapeDisplay.TShapeDisplay_DisplaySelectionArea() SMOOTHSHADING FALSE ALPHAMODE 0.0 SETFONT FONT_DARKFONT% DDgui_show(FALSE) SHOWSCREEN HIBERNATE IF DDgui_get(self.blockAddButton$,"CLICKED") TBlock_AddRemoveName(TRUE) ELSEIF DDgui_get(self.blockDeleteButton$,"CLICKED") TBlock_AddRemoveName(FALSE) ELSEIF DDgui_get(AddRow$,"CLICKED") TBlock_AddRows() ELSEIF DDgui_get(self.blockAvailList$,"CLICKED")

252 index%=DDgui_get(self.blockAvailList$,"SELECT") IF index%>=0 THEN TBlock_SelectBlock(index%) ELSEIF DDgui_get(OddColumnSelect$,"CLICKED") TBlock_ChangeTileData(COLUMN_ODD %,DDgui_get(OddColumnList$,"SELECT")) ELSEIF DDgui_get(EvenColumnSelect$,"CLICKED") TBlock_ChangeTileData(COLUMN_EVEN %,DDgui_get(EvenColumnList$,"SELECT")) ELSEIF DDgui_get(SelectionColumnSelectButton$,"CLICKED") // Modify a single columns TBlock_ModifySingleColumn() ELSEIF DDgui_get(AddRow$,"CLICKED") TBlock_AddRows() ELSEIF DDgui_get(DeleteRow$,"CLICKED") TBlock_DeleteRows() ELSEIF DDgui_get(TopRow$,"CLICKED") TBlock_MoveStartOrEnd(FALSE) ELSEIF DDgui_get(BottomRow$,"CLICKED") TBlock_MoveStartOrEnd(TRUE) ELSEIF DDgui_get(UpRow$,"CLICKED") TBlock_MoveUpOrDown(1) ELSEIF DDgui_get(DownRow$,"CLICKED") TBlock_MoveUpOrDown(-1) ELSEIF DDgui_get(HeightHigherButton$,"CLICKED") TBlock_ChangeHeight(1) ELSEIF DDgui_get(HeightLevelButton$,"CLICKED") TBlock_ChangeHeight(0) ELSEIF DDgui_get(HeightLowerButton$,"CLICKED") TBlock_ChangeHeight(-1) ELSEIF DDgui_get(self.blockSaveData$,"CLICKED") TBlock_SaveBlockData() ELSEIF DDgui_get(self.blockLoadData$,"CLICKED") TBlock_LoadBlockData() ELSEIF DDgui_get(CheckpointAccept$,"CLICKED") TBlock_UpdateCheckpointTime() ELSEIF DDgui_get(self.blockNewBlock$,"CLICKED") IF BOUNDS(self.block[],0)>0 IF DDgui_msg("This will delete all blocks from memory. Are you sure that you want to continue ?",TRUE,"* Delete Blocks ? *") TBlock_DeleteAllBlocks() DDgui_set(self.blockAvailList$,"TEXT","") DDgui_set(self.blockNameEntry$,"TEXT","") TBlock_SetTitle() ENDIF ENDIF ELSEIF DDgui_get(TunnelText$,"CLICKED") TBlock_ToggleTunnel() ELSEIF DDgui_get(MainMenu$,"CLICKED") DDgui_popdialog() RETURN TRUE ENDIF // Use mousewheen to scroll up and down num%=TBlock_ReturnNumberOfRows(self.blockIndex%) IF num%>0 INC self.rowPos%,MOUSEAXIS(2) IF self.rowPos%<0 self.rowPos%=0 ELSE IF self.rowPos%>=num% self.rowPos%=num%-1 ENDIF ENDIF ENDIF // Now for the key controls IF ABS(GETTIMERALL()-self.timeForNextMovement)>100.0 OR self.lastMove %=FALSE IF KEY(200) TBlock_MoveUpOrDown(1) self.lastMove%=TRUE ELSEIF KEY(208) TBlock_MoveUpOrDown(-1) self.lastMove%=TRUE ELSEIF KEY(199) // Top of row TBlock_MoveStartOrEnd(FALSE) self.lastMove%=TRUE

253 ELSEIF KEY(207) TBlock_MoveStartOrEnd(TRUE) self.lastMove%=TRUE ELSEIF KEY(201) // Move up x many lines TBlock_MoveUpOrDown(shapeDisplay.TShapeDisplay_ReturnHalfY()) self.lastMove%=TRUE ELSEIF KEY(209) // Move down x many lines TBlock_MoveUpOrDown(0- shapeDisplay.TShapeDisplay_ReturnHalfY()) self.lastMove%=TRUE ELSEIF KEY(210) // Insert 1 line TBlock_AddRows() self.lastMove%=TRUE ELSEIF KEY(211) TBlock_DeleteRows() self.lastMove%=TRUE ENDIF self.timeForNextMovement=GETTIMERALL()+100.0 ELSE self.lastMove%=FALSE ENDIF WEND ENDFUNCTION // Modify a single column FUNCTION TBlock_ModifySingleColumn%: LOCAL which%,index%

// Find which combo was selected index%=DDgui_get(SelectionColumnListColumn$,"SELECT") IF index%>=0 which%=index%+COLUMN_0% TBlock_ChangeTileData(which %,DDgui_get(SelectionColumnList$,"SELECT")) ELSE DDgui_msg("Invalid column selected",FALSE,"* Invalid Column *") ENDIF ENDFUNCTION // Change odd, even or single tiles FUNCTION TBlock_ChangeTileData%:changeType%,value% IF value%<0 DDgui_msg("Invalid tile type selected",FALSE,"* Invalid Tile *") ELSE IF BOUNDS(self.block[],0)>0 self.block[self.blockIndex%].TRowEditor_ChangeTiles% (changeType%,self.rowPos%,value%) ENDIF ENDIF ENDFUNCTION // Return the list of all the block names FUNCTION TBlock_GetListOfBlockNames$: LOCAL loop AS TRowEditor LOCAL temp$ temp$="" FOREACH loop IN self.block[] temp$=temp$+loop.TRowEditor_ReturnRowName$()+"|" NEXT RETURN LEFT$(temp$,LEN(temp$)-1) ENDFUNCTION // Return block name FUNCTION TBlock_ReturnBlockName$:index% IF BOUNDS(self.block[],0)>0 RETURN self.block[index%].TRowEditor_ReturnRowName$() ENDIF RETURN "" ENDFUNCTION // Return number of rows for the block FUNCTION TBlock_ReturnNumberOfRows%:index%

254 IF BOUNDS(self.block[],0)>0 RETURN self.block[index%].TRowEditor_ReturnNumberOfRows() ENDIF RETURN 0 ENDFUNCTION // Return the tile data FUNCTION TBlock_GetRowData$:index%,row% IF BOUNDS(self.block[],0)>0 RETURN self.block[index%].TRowEditor_GetRowData$(row%) ENDIF RETURN "" ENDFUNCTION // Add or remove a block name FUNCTION TBlock_AddRemoveName%:doAdd% LOCAL name$ LOCAL found% LOCAL temp AS TRowEditor name$=DDgui_get$(self.blockNameEntry$,"TEXT") IF name$="" DDgui_msg("Please select or enter a valid name to add or delete",FALSE,"* Name Entry Error *") RETURN FALSE ENDIF found%=TBlock_FindName(name$) IF doAdd%=TRUE IF BOUNDS(self.block[],0)>=MAX_BLOCKS% DDgui_msg("The maximum number of blocks ("+MAX_BLOCKS%+") has been reached",FALSE,"* Limit Reached *") RETURN FALSE ENDIF IF found%>=0 DDgui_msg("The name "+name$+" already exists",FALSE,"* Name Already Present Error *") RETURN FALSE ENDIF temp.TRowEditor_Initialise(name$,1) DIMPUSH self.block[],temp self.blockIndex%=BOUNDS(self.block[],0)-1 ELSE IF TBlock_FindName(name$)<0 DDgui_msg("The name "+name$+" could not be found",FALSE,"* Name Not Found Error *") RETURN FALSE ENDIF DIMDEL self.block[],found% // Revert to the last one if the block index is invalid found%=BOUNDS(self.block[],0) IF found%>0 IF self.blockIndex%>=found% self.blockIndex%=found%-1 ENDIF ELSE self.blockIndex%=-1 ENDIF ENDIF // Update the list if block names DDgui_set(self.blockAvailList$,"TEXT",TBlock_GetListOfBlockNames$()) IF doAdd%=TRUE // Automatically select this list DDgui_set(self.blockAvailList$,"SELECT",TBlock_FindName(name$)) ELSE // Clear the name entry section DDgui_set(self.blockNameEntry$,"TEXT","") ENDIF

255 self.rowPos%=0 TBlock_SetTitle() ENDFUNCTION // User has selected a new block FUNCTION TBlock_SelectBlock%:index% // Get the name for this entry DDgui_set(self.blockNameEntry$,"TEXT",self.block[index %].TRowEditor_ReturnRowName$()) self.blockIndex%=index% ENDFUNCTION // Find a block name FUNCTION TBlock_FindName%:name$ LOCAL loop% IF BOUNDS(self.block[],0)>0 FOR loop%=0 TO BOUNDS(self.block[],0)-1 IF self.block[loop%].TRowEditor_ReturnRowName$()=name$ THEN RETURN loop% NEXT ENDIF RETURN -1 ENDFUNCTION // Return number of blocks FUNCTION TBlock_ReturnNumberOfBlocks%: RETURN BOUNDS(self.block[],0) ENDFUNCTION

// Move the editing column up or down FUNCTION TBlock_MoveUpOrDown%:dir% LOCAL size% size%=self.block[self.blockIndex%].TRowEditor_ReturnNumberOfRows() IF size%>=0 INC self.rowPos%,dir% IF self.rowPos%<=0 self.rowPos%=0 ELSE IF self.rowPos%>=size% self.rowPos%=size%-1 ENDIF ENDIF ENDIF TBlock_SetTitle() ENDFUNCTION // Move the editing column to the first or last line FUNCTION TBlock_MoveStartOrEnd%:isStart% LOCAL size% size%=self.block[self.blockIndex%].TRowEditor_ReturnNumberOfRows() IF size%>=0 IF isStart%=TRUE self.rowPos%=0 ELSE self.rowPos%=size%-1 ENDIF ENDIF TBlock_SetTitle() ENDFUNCTION // Add one or more rows at the end of the list FUNCTION TBlock_AddRows%:amount%=1 LOCAL size% IF self.blockIndex%>=0 self.block[self.blockIndex %].TRowEditor_ResizeNumberOfRows(ADDTYPE_ADDREMOVE_END%,amount%) TBlock_SetTitle() ELSE DDgui_msg("You need to create a block before adding a row",FALSE,"* Add Row Error *")

256 ENDIF ENDFUNCTION // Delete 1 or more rows (later on), from the current position FUNCTION TBlock_DeleteRows%:amount%=1 LOCAL size%,orgSize% IF self.blockIndex%>=0 self.rowPos%=self.block[self.blockIndex %].TRowEditor_DeleteAtRowPos(self.rowPos%,amount%) TBlock_SetTitle() ELSE DDgui_msg("You need to create a block before deleting a row",FALSE,"* Add Row Error *") ENDIF ENDFUNCTION // Change the height for the row FUNCTION TBlock_ChangeHeight%:height% IF self.blockIndex%>=0 IF self.block[self.blockIndex%].TRowEditor_IsTunnel(self.rowPos%) DDgui_msg("cant change slopes with a tunnel activated",FALSE,"* Tunnel Present *") ELSE self.block[self.blockIndex %].TRowEditor_SetRowHeight(self.rowPos%,height%) ENDIF ENDIF ENDFUNCTION

// Return columns width FUNCTION TBlock_ReturnColumnWidth%: IF self.blockIndex%>=0 RETURN self.block[self.blockIndex%].TRowEditor_ReturnColumnWidth() ENDIF RETURN 0 ENDFUNCTION // Update the checkpoint for the current row FUNCTION TBlock_UpdateCheckpointTime%: LOCAL value% value%=INTEGER(DDgui_get$(CheckpointValue$,"TEXT")) IF value%MAX_CHECKPOINTTIME% DDgui_msg("The checkpoint time should be between "+MIN_CHECKPOINTTIME %+" and "+MAX_CHECKPOINTTIME%,FALSE,"* Time Error *") RETURN FALSE ELSE self.block[self.blockIndex%].TRowEditor_SetCheckpointTime(self.rowPos %,value%) ENDIF ENDFUNCTION // Toggle tunnel option. Tunnels cant be on a slope FUNCTION TBlock_ToggleTunnel%: LOCAL tunnel% IF self.blockIndex%>=0 IF self.block[self.blockIndex%].TRowEditor_IsTunnel(self.rowPos %)=FALSE tunnel%=TRUE ELSE tunnel%=FALSE ENDIF IF tunnel% IF self.block[self.blockIndex %].TRowEditor_GetRowHeight(self.rowPos%)<>0 DDgui_set(TunnelText$,"SELECT",FALSE) DDgui_msg("Tunnels cant be placed on a slope",FALSE,"* Tunnel Error *") RETURN FALSE ENDIF ENDIF self.block[self.blockIndex%].TRowEditor_SetTunnel(self.rowPos%,tunnel %)

257 RETURN TRUE ENDIF RETURN FALSE ENDFUNCTION // Save block data : X number of blocks with X number of rows FUNCTION TBlock_SaveBlockData%: LOCAL fileName$ LOCAL temp$ LOCAL loop%,count%,numRows%,loop2% SETFONT FONT_DARKFONT% count%=TBlock_ReturnNumberOfBlocks() IF count%=0 DDgui_msg("There is nothing to save!",FALSE,"* Nothing To Save *") RETURN FALSE ENDIF fileName$=DDgui_FileDialog$(FALSE,EXT_BLOCKDATA$) IF fileName$<>"" IF DOESFILEEXIST(fileName$) IF DDgui_msg("Are you sure that you want to overwrite this file ?",TRUE,"* Overwrite File ? *") KILLFILE fileName$ IF DOESFILEEXIST(fileName$) DDgui_msg("The file appears to be in use by another program, and cannot be deleted",FALSE,"* File In Use *") RETURN FALSE ENDIF INIOPEN fileName$ INIPUT HEADER_SECTION$,DATA_KEY$,BLOCKDATA_DATA$ INIPUT HEADER_SECTION$,VERSION_KEY$,PROGRAM_VERSION$ INIPUT HEADER_SECTION$,NUMCOLUMN_KEY$,TBlock_ReturnColumnWidth() INIPUT HEADER_SECTION$,NUMBLOCK_KEY$,count% FOR loop%=0 TO count%-1 DEBUG "Block name : "+TBlock_ReturnBlockName$ (loop%)+"\n" INIPUT HEADER_SECTION$,BLOCKNAME_KEY$+RIGHT$ (BLOCK_PAD$+loop%,LEN(BLOCK_PAD$)),TBlock_ReturnBlockName$(loop%) NEXT // Now we write the data data for each block FOR loop%=0 TO count%-1 temp$=TBlock_ReturnBlockName$(loop%) numRows%=TBlock_ReturnNumberOfRows(loop%) INIPUT temp$,NUMROWS_KEY$,numRows% FOR loop2%=0 TO numRows%-1 INIPUT temp$,LINE_KEY$+RIGHT$(ROW_PAD$ +loop2%,LEN(ROW_PAD$)),TBlock_GetRowData$(loop%,loop2%) NEXT NEXT INIOPEN "" RETURN TRUE ELSE RETURN FALSE ENDIF ENDIF ENDIF RETURN FALSE ENDFUNCTION FUNCTION TBlock_LoadBlockData%: LOCAL fileName$ LOCAL temp$,data$,totalBlockNames$ LOCAL loop%,count%,numRows%,loop2%,blockIndex% LOCAL rowEditor AS TRowEditor SETFONT FONT_DARKFONT%

258 count%=TBlock_ReturnNumberOfBlocks() IF count%>0 IF DDgui_msg("Are you sure that you want to overwrite all current data ?",TRUE,"* Overwrite ? *")=FALSE RETURN FALSE ENDIF ENDIF fileName$=DDgui_FileDialog$(FALSE,EXT_BLOCKDATA$) IF fileName$<>"" IF DOESFILEEXIST(fileName$)=FALSE DDgui_msg("Unable to file the file "+fileName$,FALSE,"* File Not Present Error *") RETURN FALSE ELSE INIOPEN fileName$ // Clear everything TBlock_DeleteAllBlocks() DDgui_set(self.blockAvailList$,"TEXT","") DDgui_set(self.blockNameEntry$,"TEXT","") TBlock_SetTitle() // Validate versions and other stuff later on count%=INTEGER(INIGET$(HEADER_SECTION$,NUMBLOCK_KEY$,"0")) DEBUG "Number of blocks : "+count%+"\n" IF count%<=0 DDgui_msg("There are no blocks to load",FALSE,"* No Blocks Error *") ELSE // Get all the section names totalBlockNames$="" FOR loop%=0 TO count%-1 temp$=INIGET$(HEADER_SECTION$,BLOCKNAME_KEY$ +RIGHT$(BLOCK_PAD$+loop%,LEN(BLOCK_PAD$)),"") IF temp$<>"" rowEditor.TRowEditor_Initialise(temp$,0) // Dont want to allocate at the moment DIMPUSH self.block[],rowEditor blockIndex%=BOUNDS(self.block[],0)-1 // Now we load in all the blocks for this name totalBlockNames$=totalBlockNames$+temp$+"|" // Add this to what will be the block name list numRows%=INTEGER(INIGET$ (temp$,NUMROWS_KEY$,0)) IF numRows%>=0 FOR loop2%=0 TO numRows%-1 data$=INIGET$(temp$,LINE_KEY$ +RIGHT$(ROW_PAD$+loop2%,LEN(ROW_PAD$)),"") IF data$<>"" self.block[blockIndex %].TRowEditor_SetTileData(data$) ELSE DDgui_msg("No data present for row",FALSE,"* No Data Error *") ENDIF NEXT ELSE DDgui_msg("A block has now rows present",FALSE,"* Rows Missing Error *") ENDIF ELSE DDgui_msg("A block name is empty",FALSE,"* Empty Block Name *") ENDIF NEXT ENDIF INIOPEN "" DDgui_set(self.blockAvailList$,"TEXT",LEFT$ (totalBlockNames$,LEN(totalBlockNames$)-1)) // Put in all the names IF BOUNDS(self.block[],0)>0

259 self.blockIndex%=0 DDgui_set(self.blockAvailList$,"SELECT",0) // Select the first one ELSE self.blockIndex%=-1 ENDIF self.rowPos%=0 RETURN TRUE ENDIF ENDIF RETURN FALSE ENDFUNCTION ENDTYPE //------//! This allows the creation and modification of a level //------TYPE TLevelEditor level AS TRowEditor levelName$ // Level name. levelDescription$ // Level description author$ // Level author. Blank when creating blocks rowPos% // Current row index

tileList$ // List of tiles windowX%;windowY% windowWidth%=310 windowHeight%=270 windowTitle$="Enter Level Details :" windowLevelName_Static$="levelNameStatic" windowLevelName_Entry$="levelNameEntry" windowDescription_Static$="levelDescriptionStatic" windowDescription_Entry$="levelDescriptionEntry" windowAuthorName_Static$="authorNameStatic" windowAuthorName_Entry$="authorNameEntry" windowDefaultSize_Static$="defaultSizeStatic" windowDefaultSize_Entry$="defaultSizeEntry" windowOk$="okay" windowCancel$="cancel" editorX%;editorY% editorWidth%=310 editorHeight%=264 levelEditorTitle$="Level Editor" levelTab$ levelSelectionColumnSelect$ // Block tab levelBlockNameText$="Block Name :" levelBlockNameList$="blockNameEntry" levelBlockNameInsertDown$="Insert Down" levelBlockNameInsertUp$="Insert Up" // File tab levelSaveData$="Save Level" levelLoadData$="Load Level" levelNewLevel$="New Level" lastMove% timeForNextMovement FUNCTION TLevelEditor_Initialise%:tileList$,MAX_COLUMNS% LOCAL sW%,sH%,loop% GETSCREENSIZE sW%,sH% self.levelSelectionColumnSelect$="" FOR loop%=0 TO MAX_COLUMNS%-1 self.levelSelectionColumnSelect$=self.levelSelectionColumnSelect$ +"Col #"+loop%+"|"

260 NEXT self.levelSelectionColumnSelect$=LEFT$ (self.levelSelectionColumnSelect$,LEN(self.levelSelectionColumnSelect$)-1) TLevelEditor_InitialiseRows() self.windowX%=(sW%-self.windowWidth%)/2 self.windowY%=(sH%-self.windowHeight%)/2 self.editorX%=sW%-self.editorWidth% // Change later self.editorY%=(sH%-self.editorHeight%)/2 self.tileList$=tileList$ self.levelTab$="Tile,"+OddColumnText$+","+OddColumnList$ +","+OddColumnSelect$+","+EvenColumnText$+","+EvenColumnList$+","+EvenColumnSelect$+","+ _ SelectionColumnText$+","+SelectionColumnListColumn$ +","+SelectionColumnSelectButton$+","+ _ HeightText$+","+HeightValue$+","+HeightHigherButton$ +","+ _ HeightLevelButton$+","+HeightLowerButton$ +","+TunnelText$ // Movement tab - this is for moving up and down the rows self.levelTab$=self.levelTab$+"|Movement,"+TopRow$+","+BottomRow$ +","+UpRow$+","+DownRow$ // Size tab - adjust the number of rows self.levelTab$=self.levelTab$+"|Size,"+AddRow$+","+DeleteRow$

// Checkpoint tab self.levelTab$=self.levelTab$+"|Data,"+CheckpointText$+","+CheckpointValue$ +","+CheckpointAccept$+"," // Size tab - adjust the number of rows self.levelTab$=self.levelTab$+"|Blocks,"+self.levelBlockNameText$ +","+self.levelBlockNameList$+","+self.levelBlockNameInsertDown$ +","+self.levelBlockNameInsertUp$ // File tab - saving, loading and new block self.levelTab$=self.levelTab$+"|File,"+self.levelSaveData$ +","+self.levelLoadData$+","+self.levelNewLevel$+","+MainMenu$ RETURN TRUE ENDFUNCTION // Initialise row FUNCTION TLevelEditor_InitialiseRows%: DIM self.level.row[0] self.rowPos%=-1 self.levelName$="" self.levelDescription$="" self.author$="" ENDFUNCTION // Create level information window FUNCTION TLevelEditor_CreateLevelDetailWindow%:BYREF numRows% LOCAL temp$ LOCAL error%,size% LOCAL listSub%=28 LOCAL buttonSub%=10 LOCAL comboSub%=10 LOCAL textSub%=10 SETFONT FONT_DARKFONT% DDgui_pushdialog(self.windowX%,self.windowY%,self.windowWidth %,self.windowHeight%) DDgui_set("","TEXT",self.windowTitle$) DDgui_widget(self.windowLevelName_Static$,"Enter Level Name :",self.windowWidth%-4,0); DDgui_text(self.windowLevelName_Entry$,"",self.windowWidth%-textSub%,0) DDgui_widget(self.windowDescription_Static$,"Enter Level Description :",self.windowWidth%-4,0); DDgui_text(self.windowDescription_Entry$,"",self.windowWidth%-textSub%,0) DDgui_widget(self.windowAuthorName_Static$,"Enter Level Author :",self.windowWidth%-4,0); DDgui_text(self.windowAuthorName_Entry$,"",self.windowWidth%-textSub%,0)

261 DDgui_widget(self.windowDefaultSize_Static$,"Enter Default Size :",self.windowWidth%-4,0); DDgui_text(self.windowDefaultSize_Entry$,"",self.windowWidth%-textSub%,0) DDgui_widget("a","Enter Default Start Time :",self.windowWidth%-4,0); DDgui_text("b","",self.windowWidth%-textSub %,0) DDgui_widget("c","Enter Default No. Jumps :",self.windowWidth%-4,0); DDgui_text("d","",self.windowWidth%-textSub %,0) DDgui_button(self.windowOk$,"OKAY",(self.windowWidth%/2)-14,0) DDgui_button(self.windowCancel$,"CANCEL",(self.windowWidth%/2)-14,0) WHILE TRUE DDgui_show(FALSE) SHOWSCREEN IF DDgui_get(self.windowCancel$,"CLICKED") DDgui_popdialog() RETURN FALSE ELSE IF DDgui_get(self.windowOk$,"CLICKED") // Validate entry error%=FALSE // Validate level name temp$=DDgui_get$(self.windowLevelName_Entry$,"TEXT") IF temp$="" DDgui_msg("Please enter a name for this level",FALSE,"* Entry Error *") error%=TRUE ELSE self.levelName$=temp$ ENDIF // Validate level description IF error%=FALSE temp$=DDgui_get$ (self.windowDescription_Entry$,"TEXT") IF temp$="" DDgui_msg("Please enter a description for this level",FALSE,"* Entry Error *") error%=TRUE ELSE self.levelDescription$=temp$ ENDIF IF error%=FALSE // Validate author temp$=DDgui_get$ (self.windowAuthorName_Entry$,"TEXT") IF temp$="" self.author$="N/A" ELSE self.author$=temp$ ENDIF // Validate default size size%=INTEGER(DDgui_get$ (self.windowDefaultSize_Entry$,"TEXT")) IF size%MAX_ROWS% DDgui_msg("Invalid row size. Should be between "+MIN_ROWS%+" and "+MAX_ROWS%,FALSE,"* Entry Error *") error%=TRUE ELSE numRows%=size% ENDIF ENDIF ENDIF IF error%=FALSE DDgui_popdialog() RETURN TRUE ENDIF ENDIF ENDIF WEND

262 RETURN TRUE ENDFUNCTION // Create level details FUNCTION TLevelEditor_CreateEditorWindow%:blockEditor AS TBlockEditor LOCAL listSub%=28 LOCAL buttonSub%=10 LOCAL comboSub%=10 LOCAL halfX% halfX%=(self.windowWidth%/2)-6 self.rowPos%=0 SETFONT FONT_DARKFONT% DDgui_pushdialog(self.editorX%,self.editorY%,self.editorWidth %,self.editorHeight%) DDgui_tab("tab",self.levelTab$) DDgui_widget(OddColumnText$,OddColumnText$,self.windowWidth%-4,0) DDgui_combo(OddColumnList$,self.tileList$,halfX%,24) DDgui_button(OddColumnSelect$,"Select",halfX%,0) DDgui_widget(EvenColumnText$,EvenColumnText$,self.windowWidth%-4,0) DDgui_combo(EvenColumnList$,self.tileList$,halfX%,24) DDgui_button(EvenColumnSelect$,"Select",halfX%,0) DDgui_widget(SelectionColumnText$,SelectionColumnText$,self.windowWidth%- 4,0) DDgui_combo(SelectionColumnListColumn$,self.levelSelectionColumnSelect$,halfX%,24)

DDgui_button(SelectionColumnSelectButton$,SelectionColumnSelectButton$,self.windowWidth %-buttonSub%,0) DDgui_widget(HeightText$,HeightText$,halfX%,0) IF self.level.TRowEditor_ReturnNumberOfRows()>0 DDgui_widget(HeightValue$,self.level.TRowEditor_GetRowHeight(self.rowPos%),16,0) ELSE DDgui_widget(HeightValue$,"0",16,0) ENDIF DDgui_button(HeightHigherButton$,HeightHigherButton$,18,0) DDgui_button(HeightLevelButton$,HeightLevelButton$,18,0) DDgui_button(HeightLowerButton$,HeightLowerButton$,18,0) DDgui_checkbox(TunnelText$,TunnelText$,0,0) // Movement tab DDgui_button(TopRow$,TopRow$,self.windowWidth%-comboSub%,0) DDgui_button(BottomRow$,BottomRow$,self.windowWidth%-comboSub%,0) DDgui_button(UpRow$,UpRow$,self.windowWidth%-comboSub%,0) DDgui_button(DownRow$,DownRow$,self.windowWidth%-comboSub%,0) // Size tab DDgui_button(AddRow$,AddRow$,halfX%,0) DDgui_button(DeleteRow$,DeleteRow$,halfX%,0) // Data tab DDgui_widget(CheckpointText$,CheckpointText$,140,0) DDgui_text(CheckpointValue$,"",32,0) DDgui_button(CheckpointAccept$,CheckpointAccept$,100,0) DDgui_widget(self.levelBlockNameText$,self.levelBlockNameText$,halfX%,0) DDgui_combo(self.levelBlockNameList$,blockEditor.TBlock_GetListOfBlockNames$ (),self.windowWidth%-4,24) DDgui_button(self.levelBlockNameInsertDown$,self.levelBlockNameInsertDown$,halfX %,0) DDgui_button(self.levelBlockNameInsertUp$,self.levelBlockNameInsertUp$,halfX%,0) // Cant select first block name as it would continually generate a "selected" code // DDgui_text(self.blockNameEntry$,"",self.windowWidth%-4,0)

263 // File tab DDgui_button(self.levelSaveData$,self.levelSaveData$,self.windowWidth%- buttonSub%,0) DDgui_button(self.levelLoadData$,self.levelLoadData$,self.windowWidth%- buttonSub%,0) DDgui_button(self.levelNewLevel$,self.levelNewLevel$,self.windowWidth%- buttonSub%,0) DDgui_button(MainMenu$,MainMenu$,self.windowWidth%-buttonSub%,0) ENDFUNCTION // Display the title (which shows the number of rows FUNCTION TLevelEditor_SetTitle%: LOCAL text$ IF self.level.TRowEditor_ReturnNumberOfRows()>0 text$="("+RIGHT$(ROW_PAD$+self.rowPos%,LEN(ROW_PAD$))+"/"+RIGHT$ (ROW_PAD$+self.level.TRowEditor_ReturnNumberOfRows(),LEN(ROW_PAD$))+")" ELSE text$="" ENDIF DDgui_set("","TEXT",self.levelEditorTitle$+" "+text$) ENDFUNCTION // Set the level name FUNCTION TLevelEditor_SetLevelName%:name$ self.levelName$=name$ ENDFUNCTION

// Return author name FUNCTION TLevelEditor_ReturnAuthorName$: RETURN self.author$ ENDFUNCTION // Return level name FUNCTION TLevelEditor_ReturnLevelName$: RETURN self.levelName$ ENDFUNCTION // Return level description FUNCTION TLevelEditor_ReturnLevelDescription$: RETURN self.levelDescription$ ENDFUNCTION // Set author name FUNCTION TLevelEditor_SetAuthorName%:name$ self.author$=name$ ENDFUNCTION // Main level editing loop FUNCTION TLevelEditor_MainLoop%:shapeDisplay AS TShapeDisplay,blockEditor AS TBlockEditor LOCAL loop AS TRowEditor LOCAL numRows%,num% IF self.level.TRowEditor_ReturnNumberOfRows()=0 IF TLevelEditor_CreateLevelDetailWindow(numRows%)=FALSE RETURN FALSE ENDIF IF self.level.TRowEditor_Initialise("",numRows%)=FALSE RETURN FALSE ENDIF ENDIF TLevelEditor_CreateEditorWindow(blockEditor) TLevelEditor_SetTitle() self.lastMove%=FALSE self.timeForNextMovement=GETTIMERALL() WHILE TRUE shapeDisplay.TShapeDisplay_DisplayBorder() IF self.level.TRowEditor_ReturnNumberOfRows()>0 shapeDisplay.TShapeDisplay_DisplayTiles(self.rowPos %,self.level.row[]) ENDIF

264 shapeDisplay.TShapeDisplay_DisplaySelectionArea() SMOOTHSHADING FALSE ALPHAMODE 0.0 SETFONT FONT_DARKFONT% DDgui_show(FALSE) SHOWSCREEN IF DDgui_get(self.levelSaveData$,"CLICKED") TLevelEditor_SaveLevel() ELSEIF DDgui_get(self.levelLoadData$,"CLICKED") TLevelEditor_LoadLevel() ELSEIF DDgui_get(MainMenu$,"CLICKED") DDgui_popdialog() RETURN TRUE ELSEIF DDgui_get(UpRow$,"CLICKED") TLevelEditor_MoveUpOrDown(1) ELSEIF DDgui_get(DownRow$,"CLICKED") TLevelEditor_MoveUpOrDown(-1) ELSEIF DDgui_get(TopRow$,"CLICKED") TLevelEditor_MoveStartOrEnd(FALSE) ELSEIF DDgui_get(BottomRow$,"CLICKED") TLevelEditor_MoveStartOrEnd(TRUE) ELSEIF DDgui_get(self.levelBlockNameInsertDown$,"CLICKED") TLevelEditor_InsertBlock(-1,blockEditor) ELSEIF DDgui_get(self.levelBlockNameInsertUp$,"CLICKED") TLevelEditor_InsertBlock(1,blockEditor) ELSEIF DDgui_get(AddRow$,"CLICKED") TLevelEditor_CreateRows(1) ELSEIF DDgui_get(DeleteRow$,"CLICKED") TLevelEditor_DeleteRows(self.rowPos%,1) ELSEIF DDgui_get(HeightHigherButton$,"CLICKED") TLevelEditor_ChangeHeight(1) ELSEIF DDgui_get(HeightLevelButton$,"CLICKED") TLevelEditor_ChangeHeight(0) ELSEIF DDgui_get(HeightLowerButton$,"CLICKED") TLevelEditor_ChangeHeight(-1) ELSEIF DDgui_get(CheckpointAccept$,"CLICKED") TLevelEditor_UpdateCheckpointTime() ELSEIF DDgui_get(self.levelNewLevel$,"CLICKED") IF self.level.TRowEditor_ReturnNumberOfRows()>0 IF DDgui_msg("This will delete the curret level from memory. Are you sure that you want to continue ?",TRUE,"* Delete Level ? *") TLevelEditor_InitialiseRows() TLevelEditor_SetTitle() ENDIF ENDIF ELSEIF DDgui_get(TunnelText$,"CLICKED") TLevelEditor_ToggleTunnel() ENDIF // Use mousewheen to scroll up and down num%=self.level.TRowEditor_ReturnNumberOfRows() IF num%>0 INC self.rowPos%,MOUSEAXIS(2) IF self.rowPos%<0 self.rowPos%=0 ELSE IF self.rowPos%>=num% self.rowPos%=num%-1 ENDIF ENDIF ENDIF // Now for the key controls IF ABS(GETTIMERALL()-self.timeForNextMovement)>100.0 OR self.lastMove %=FALSE IF KEY(200) TLevelEditor_MoveUpOrDown(1) self.lastMove%=TRUE ELSEIF KEY(208) TLevelEditor_MoveUpOrDown(-1) self.lastMove%=TRUE ELSEIF KEY(199) // Top of row TLevelEditor_MoveStartOrEnd(FALSE) self.lastMove%=TRUE

265 ELSEIF KEY(207) TLevelEditor_MoveStartOrEnd(TRUE) self.lastMove%=TRUE ELSEIF KEY(201) // Move up x many lines TLevelEditor_MoveUpOrDown(shapeDisplay.TShapeDisplay_ReturnHalfY()) self.lastMove%=TRUE ELSEIF KEY(209) // Move down x many lines TLevelEditor_MoveUpOrDown(0- shapeDisplay.TShapeDisplay_ReturnHalfY()) self.lastMove%=TRUE ELSEIF KEY(210) // Insert 1 line TLevelEditor_CreateRows(1) self.lastMove%=TRUE ELSEIF KEY(211) TLevelEditor_DeleteRows(self.rowPos%,1) self.lastMove%=TRUE ENDIF self.timeForNextMovement=GETTIMERALL()+100.0 ELSE self.lastMove%=FALSE ENDIF WEND ENDFUNCTION // Toggle tunnel option. Tunnels cant be on a slope FUNCTION TLevelEditor_ToggleTunnel%: LOCAL tunnel%

IF self.level.TRowEditor_ReturnNumberOfRows()>0 IF self.level.TRowEditor_IsTunnel(self.rowPos%)=FALSE tunnel%=TRUE ELSE tunnel%=FALSE ENDIF IF tunnel% IF self.level.TRowEditor_GetRowHeight(self.rowPos%)<>0 DDgui_set(TunnelText$,"SELECT",FALSE) DDgui_msg("Tunnels cant be placed on a slope",FALSE,"* Tunnel Error *") RETURN FALSE ENDIF ENDIF self.level.TRowEditor_SetTunnel(self.rowPos%,tunnel%) RETURN TRUE ENDIF RETURN FALSE ENDFUNCTION // Insert a block down or up // If its down, then you start from the top // If its up, then you start from the bottom FUNCTION TLevelEditor_InsertBlock:dir%,blockEditor AS TBlockEditor LOCAL startRow%,size%,tempRow%,maxRows% LOCAL index% index%=DDgui_get(self.levelBlockNameList$,"SELECT") IF index%<0 DDgui_msg("There are no block lists to select",FALSE,"* Block List Error *") RETURN FALSE ELSE size%=blockEditor.TBlock_ReturnNumberOfRows(index%) IF size%<=0 DDgui_msg("There are no rows in this block",FALSE,"* Block Size Error *") RETURN FALSE ELSE IF dir%<0 startRow%=size%-1 ELSE startRow%=0

266 ENDIF maxRows%=self.level.TRowEditor_ReturnNumberOfRows() tempRow%=self.rowPos% WHILE ((dir%<0 AND startRow%>=0) OR (dir%>0 AND startRow%=0 AND tempRow%0 value%=INTEGER(DDgui_get$(CheckpointValue$,"TEXT")) IF value%MAX_CHECKPOINTTIME% DDgui_msg("The checkpoint time should be between "+MIN_CHECKPOINTTIME%+" and "+MAX_CHECKPOINTTIME%,FALSE,"* Time Error *") RETURN FALSE ELSE self.level.TRowEditor_SetCheckpointTime(self.rowPos%,value%) ENDIF ENDIF ENDFUNCTION // Change height of a row FUNCTION TLevelEditor_ChangeHeight%:level% IF self.level.TRowEditor_ReturnNumberOfRows()>0 IF self.level.TRowEditor_IsTunnel(self.rowPos%) DDgui_msg("Cant change slopes with a tunnel activated",FALSE,"* Tunnel Present *") ELSE self.level.TRowEditor_SetRowHeight(self.rowPos%,level%) ENDIF ENDIF ENDFUNCTION // Move the editing column up or down FUNCTION TLevelEditor_MoveUpOrDown%:dir% LOCAL size% size%=self.level.TRowEditor_ReturnNumberOfRows() IF size%>=0 INC self.rowPos%,dir IF self.rowPos%<=0 self.rowPos%=0 ELSE IF self.rowPos%>=size% self.rowPos%=size%-1 ENDIF ENDIF ENDIF ENDFUNCTION // Move the editing column to the first or last line FUNCTION TLevelEditor_MoveStartOrEnd%:isStart% LOCAL size% size%=self.level.TRowEditor_ReturnNumberOfRows() IF size%>=0 IF isStart%=TRUE RETURN 0 ELSE RETURN size%-1 ENDIF ENDIF

267 RETURN 0 ENDFUNCTION // Define a number of rows FUNCTION TLevelEditor_CreateRows%:amount% self.level.TRowEditor_Initialise("",amount%) ENDFUNCTION // Delete one or more rows FUNCTION TLevelEditor_DeleteRows%:row%,amount% IF self.level.TRowEditor_ReturnNumberOfRows()>0 self.level.TRowEditor_DeleteAtRowPos(row%,amount%) ENDIF ENDFUNCTION // Get row data FUNCTION TLevelEditor_GetRowData$:row% RETURN self.level.TRowEditor_GetRowData$(row%) ENDFUNCTION // Set tile data for a row FUNCTION TLevelEditor_SetTileData%:data$ self.level.TRowEditor_SetTileData(data$) ENDFUNCTION FUNCTION TLevelEditor_ReturnColumnWidth%: RETURN self.level.TRowEditor_ReturnColumnWidth() ENDFUNCTION

// Save level data FUNCTION TLevelEditor_SaveLevel%: LOCAL fileName$ LOCAL loop%,size% SETFONT FONT_DARKFONT% size%=self.level.TRowEditor_ReturnNumberOfRows() IF size%=0 DDgui_msg("There is nothing to save!",FALSE,"* Nothing To Save *") RETURN FALSE ENDIF fileName$=DDgui_FileDialog$(FALSE,EXT_LEVELDATA$) IF fileName$<>"" IF DOESFILEEXIST(fileName$) IF DDgui_msg("Are you sure that you want to overwrite this file ?",TRUE,"* Overwrite File ? *") KILLFILE fileName$ IF DOESFILEEXIST(fileName$) DDgui_msg("The file appears to be in use by another program, and cannot be deleted",FALSE,"* File In Use *") RETURN FALSE ENDIF INIOPEN fileName$ INIPUT HEADER_SECTION$,DATA_KEY$,LEVELDATA_DATA$ INIPUT HEADER_SECTION$,VERSION_KEY$,PROGRAM_VERSION$ INIPUT INFO_SECTION$,AUTHOR_KEY$,TLevelEditor_ReturnAuthorName$() INIPUT HEADER_SECTION$,NUMCOLUMN_KEY$,TLevelEditor_ReturnColumnWidth() INIPUT INFO_SECTION$,NAME_KEY$,TLevelEditor_ReturnLevelName$() // Now we write the tile data INIPUT TILE_SECTION$,NUMROWS_KEY$,size% FOR loop%=0 TO size%-1 INIPUT TILE_SECTION$,LINE_KEY$+RIGHT$(ROW_PAD$ +loop%,LEN(ROW_PAD$)),TLevelEditor_GetRowData$(loop%) NEXT INIOPEN "" RETURN TRUE ELSE RETURN FALSE

268 ENDIF ENDIF ENDIF RETURN FALSE ENDFUNCTION

// Load level data back into the LevelEditor structure FUNCTION TLevelEditor_LoadLevel%: LOCAL fileName$ LOCAL temp$ LOCAL loop%,size% SETFONT FONT_DARKFONT% size%=self.level.TRowEditor_ReturnNumberOfRows() IF size%<>0 IF DDgui_msg("You have a level being designed. Are you sure that you want to overwrite it ?",TRUE,"* Overwrite Level ? *")=FALSE RETURN FALSE ENDIF ENDIF fileName$=DDgui_FileDialog$(TRUE,EXT_LEVELDATA$) IF fileName$<>"" IF DOESFILEEXIST(fileName$) INIOPEN fileName$ // INIPUT HEADER_SECTION$,DATA_KEY$,LEVELDATA_DATA$ // INIPUT HEADER_SECTION$,VERSION_KEY$,PROGRAM_VERSION$ TLevelEditor_SetAuthorName(INIGET$ (INFO_SECTION$,AUTHOR_KEY$,"")) TLevelEditor_SetLevelName(INIGET$(INFO_SECTION$,NAME_KEY$,"")) size%=INTEGER(INIGET$(TILE_SECTION$,NUMROWS_KEY$,"0")) DEBUG "Size : "+size%+"\n" IF size%>0 TLevelEditor_CreateRows(0) FOR loop%=0 TO size%-1 temp$=INIGET$(TILE_SECTION$,LINE_KEY$+RIGHT$ (ROW_PAD$+loop%,LEN(ROW_PAD$)),"") IF temp$<>"" TLevelEditor_SetTileData(temp$) ELSE DDgui_msg("A row line was invalid",FALSE,"* Fatal Loading Error *") BREAK ENDIF NEXT INIOPEN "" //TLevelEditor_SetRowSize() RETURN TRUE ELSE INIOPEN "" DDgui_msg("There is no data to load",FALSE,"* Fatal Loading Error *") RETURN FALSE ENDIF ENDIF ENDIF RETURN FALSE ENDFUNCTION // Set row size to the amount loaded in // FUNCTION TLevelEditor_SetRowSize%: // self.numRows%=self.level.TRowEditor_ReturnNumberOfRows() // ENDFUNCTION ENDTYPE //------// This allows the creation and modification of time between checkpoints (if any)

269 //------TYPE TCheckpointTime time[] AS tTime FUNCTION TCheckpointTime_Initialise%: DIM self.time[0] ENDFUNCTION FUNCTION TCheckpointTime_MainProgram%:level AS TLevelEditor ENDFUNCTION ENDTYPE

// Main menu - all sections are called from this TYPE TMainMenu levelEditor AS TLevelEditor blockEditor AS TBlockEditor shapeDisplay AS TShapeDisplay column AS TColumn menu$[] windowTitle$="Road Puzzle Editor - Main Menu" windowWidth%;windowHeight%;windowX%;windowY% FUNCTION TMainMenu_Initialise%:tileList$ LOCAL loop$ LOCAL fW%,fH% LOCAL sW%,sH% LOCAL fontFile$="Media/darkfont.png" LOCAL maxColumns% maxColumns%=self.column.TColumn_ReturnColumnWidth() IF self.shapeDisplay.TShapeDisplay_Initialise(maxColumns%)=TRUE GETSCREENSIZE sW%,sH% SETFONT FONT_DARKFONT% GETFONTSIZE fW%,fH% DIM self.menu$[0] DIMPUSH self.menu$[],"LEVEL EDITOR" DIMPUSH self.menu$[],"BLOCK EDITOR" DIMPUSH self.menu$[],"TEST LEVEL" DIMPUSH self.menu$[],"HELP" DIMPUSH self.menu$[],"QUIT PROGRAM" self.windowWidth%=LEN(self.windowTitle$) FOREACH loop$ IN self.menu$[] self.windowWidth%=MAX(self.windowWidth%,LEN(loop$)) NEXT self.windowWidth%=(self.windowWidth%*fW%)+16 self.windowHeight%=48+(BOUNDS(self.menu$[],0)*(fH%+3)) self.windowX%=(sW%-self.windowWidth%)/2 self.windowY%=(sH%-self.windowHeight%)/2 DDgui_UpdateFont(FALSE) IF self.levelEditor.TLevelEditor_Initialise(tileList$,maxColumns%) IF self.blockEditor.TBlock_Initialise(tileList$,maxColumns%) RETURN TRUE ENDIF ENDIF ENDIF RETURN FALSE ENDFUNCTION FUNCTION TMainMenu_CreateWindow%: LOCAL loop$ DDgui_pushdialog(self.windowX%,self.windowY%,self.windowWidth %,self.windowHeight%) DDgui_set("","TEXT",self.windowTitle$) FOREACH loop$ IN self.menu$[] DDgui_button(loop$,loop$,self.windowWidth%-6,24) NEXT

270 ENDFUNCTION FUNCTION TMainMenu_MainLoop%: LOCAL vv WHILE TRUE SMOOTHSHADING FALSE SETFONT FONT_DARKFONT% ALPHAMODE 0.0 DDgui_show(FALSE) SHOWSCREEN IF DDgui_get(self.menu$[0],"CLICKED") // Create a new level DDgui_popdialog() self.levelEditor.TLevelEditor_MainLoop(self.shapeDisplay,self.blockEditor) TMainMenu_CreateWindow() // New level ELSEIF DDgui_get(self.menu$[1],"CLICKED") // Block editor DDgui_popdialog() self.blockEditor.TBlock_MainLoop(self.shapeDisplay) TMainMenu_CreateWindow() ELSEIF DDgui_get(self.menu$[2],"CLICKED") SHELLCMD("C:/Users/Nicholas/GLBasic/Programming/GLBasic/My Programs/Space_Race/Space_Race.app/Space_Race.exe TEST "+CHR$(34)+"C:/Users/Nicholas/GLBasic/Programming/GLBasic/My Programs/Space_Race/Space_Race.app/Levels/Level.LVD"+CHR$(34),TRUE,TRUE,vv) ELSEIF DDgui_get(self.menu$[3],"CLICKED") // Help ELSE IF DDgui_get(self.menu$[4],"CLICKED") IF DDgui_msg("Are you sure that you want to quit ?",TRUE,"Quit Program ?")=TRUE DDgui_popdialog() RETURN TRUE ENDIF ENDIF ENDIF WEND ENDFUNCTION ENDTYPE GLOBAL tileInfo[] AS tTileInfo LOCAL mainMenu AS TMainMenu LOCAL tileList$,error% //LOCAL vv // error%=FALSE IF DOESFILEEXIST(FONT_PATH$+"/smalfont.png") LOADFONT FONT_PATH$+"smalfont.png",FONT_DEFAULT% IF DOESFILEEXIST(FONT_PATH$+"whitefont.png") LOADFONT FONT_PATH$+"whitefont.png",FONT_WHITEFONT% IF DOESFILEEXIST(FONT_PATH$+"darkfont.png") LOADFONT FONT_PATH$+"darkfont.png",FONT_DARKFONT% ELSE error%=TRUE ENDIF ELSE error%=TRUE ENDIF ELSE error%=TRUE ENDIF IF error% SETFONT 0 // Has to be 0 DDgui_msg("One or more fonts could not be found",FALSE,"* Font Error *") END ENDIF tileList$=loadGraphics$()

271 DEBUG "Tile : "+tileList$+"\n" IF tileList$<>"" IF mainMenu.TMainMenu_Initialise(tileList$)=TRUE mainMenu.TMainMenu_CreateWindow() mainMenu.TMainMenu_MainLoop() ELSE SETFONT FONT_DEFAULT% DDgui_msg("There was a problem initialising TMainMenu_Initialise",FALSE,"* Fatal Error *") ENDIF ENDIF END FUNCTION loadGraphics$: LOCAL path$="Media/Tiles/" LOCAL loop%,line$ LOCAL text$[] LOCAL tileFile$="Tiles.INI" LOCAL tile AS tTileInfo LOCAL tileList$ SETFONT FONT_DEFAULT% tileList$="" DIM tileInfo[0] IF DOESFILEEXIST(tileFile$) INIOPEN tileFile$ loop%=0 line$=INIGET$("TILES",loop%,"") WHILE line$<>"" DIM text$[0] IF SPLITSTR(line$,text$[],",")=2 tile.image%=GENSPRITE() IF tile.image%<0 DDgui_msg("Unable to allocate sprite for tile",FALSE,"* Sprite Error *") ELSE IF DOESFILEEXIST(path$+text$[1])=FALSE DDgui_msg(path$+text$[1]+" could not be found",FALSE,"* File Not Found *") ELSE LOADSPRITE path$+text$[1],tile.image% tile.description$=text$[0] tileList$=tileList$+tile.description$+"|" DEBUG tileList$+"\n" DIMPUSH tileInfo[],tile ENDIF ENDIF ELSE DDgui_msg("Invalid number of sections on key "+loop%,FALSE,"* Line Error *") ENDIF INC loop% line$=INIGET$("TILES",loop%,"") WEND INIOPEN "" ELSE DDgui_msg("Tile INI file could not be found",FALSE,"* Tile.INI Error *") RETURN "" ENDIF IF BOUNDS(tileInfo[],0)=0 DDgui_msg("No tiles added",FALSE,"* No Tiles Error *") RETURN "" ELSE RETURN LEFT$(tileList$,LEN(tileList$)-1) ENDIF ENDFUNCTION

272 Bouncing Lines IV

Bouncing Lines is a program I usually write as soon as I get a new – the last being Bouncing Lines IV, mainly because it is easy to write and a good test of 2D graphics. This is version 4, although it doesn't contain anything extra over the other versions. // ------// // Project: BouncingLinesIV // Start: Monday, December 29, 2008 // IDE Version: 6.111 LOCAL MAX_LINES% = 30 LOCAL MESS_SPEED = 10.0 LOCAL TEXT_SPEED = 8

LOCAL c1,screenWidth%,screenHeight% LOCAL c2 LOCAL c3 LOCAL c4 LOCAL d1 LOCAL d2 LOCAL d3 LOCAL d4 TYPE __lines x y dx dy colour[3] ENDTYPE LOCAL lines[] AS __lines; DIM lines[MAX_LINES%] LOCAL move LOCAL loop% LOCAL one$ LOCAL finished% LOCAL messageText$="Welcome to the fourth iteration of Bouncing Lines, written by Nicholas Kingsley using GLBasic this time. The music that is playing was also written by me, and is called Beat It. This is an almost direct conversion of Bouncing Lines III, written using DarkSDK. There is nothing exciting here except bouncing lines...... " LOCAL textY LOCAL textSpeed LOCAL sSpeed GETSCREENSIZE screenWidth%,screenHeight% LIMITFPS 0 c1=RND(255) c2=RND(255) c3=RND(255) c4=RND(255) d1=20.0+(RND(511)/10.0) d2=-(30.0+(RND(511)/10.0)) d3=10.0+(RND(511)/10.0) d4=-(5.0+(RND(511)/10.0)) textY=screenHeight%/2 textSpeed=0.0-MESS_SPEED sSpeed=TEXT_SPEED LOADFONT "smalfont.png",0 // Setup the lines FOR loop%=0 TO MAX_LINES%-1 finished%=FALSE WHILE finished%=FALSE finished%=TRUE lines[loop%].x=RND(screenWidth%)

273 lines[loop%].dx=RND(255)+70.0 IF lines[loop%].x<32 OR lines[loop].x>screenWidth%-32.0 finished%=FALSE ENDIF WEND finished%=FALSE WHILE finished%=FALSE finished%=TRUE lines[loop%].y=RND(screenHeight%) lines[loop%].dy=RND(255)+70.0 IF lines[loop%].y<32 OR lines[loop].y>screenHeight%-32.0 finished%=FALSE ENDIF WEND lines[loop%].colour[0]=RND(255) lines[loop%].colour[1]=RND(255) lines[loop%].colour[2]=RND(255) NEXT ?IFDEF WIN32 PLAYMUSIC "Media/Beat_It.mp3",0 ?ELSE ?IFDEF OSXUNI PLAYMUSIC "Media/Beat_It.ogg",0 ?ELSE ?IFDEF LINUX PLAYMUSIC "Media/Beat_It.mp3",0 ?ELSE ?IFDEF WINCE PLAYMUSIC "Media/Beat_It.mp3",0 ?ENDIF ?ENDIF ?ENDIF ?ENDIF MUSICVOLUME 1.0 moveSpeed(screenWidth%,screenHeight%) WHILE TRUE // Do the background box(0,0,screenWidth%,screenHeight %,RGB(c1,0,0),RGB(c2,c2,c2),RGB(c3,0,c3),RGB(c4,c4,0)) // Draw the lines FOR loop%=0 TO MAX_LINES%-1 IF loop%=screenWidth%*1.0 lines[loop].dx=0.0-lines[loop].dx IF lines[loop].x<=0.0 lines[loop].x=0.0 ELSE IF lines[loop].x>=screenWidth%*1.0 lines[loop].x=screenWidth%*1.0 ENDIF ENDIF ENDIF IF lines[loop].y<=0.0 OR lines[loop].y>=screenHeight%*1.0 lines[loop].dy=0.0-lines[loop].dy IF lines[loop].y<=0.0 lines[loop].y=0.0 ELSE IF lines[loop].y>=screenHeight%*1.0 lines[loop].y=screenHeight%*1.0 ENDIF

274 ENDIF ENDIF INC lines[loop].x,lines[loop].dx*move*0.03 INC lines[loop].y,lines[loop].dy*move*0.03 NEXT DEBUG textY+"\n" PRINT messageText$,0,textY INC textY,textSpeed*move*0.1 IF textY<=48.0 OR textY>=screenHeight%-48.0 textSpeed=0.0-textSpeed IF textY<=48.0 textY=48.0 ELSE IF textY>=screenHeight%-48.0 textY=screenHeight%-48.0 ENDIF ENDIF ENDIF DEC sSpeed,move IF sSpeed<=0.0 sSpeed=TEXT_SPEED one$=LEFT$(messageText$,1) messageText$=MID$(messageText$,1,LEN(messageText$)-1)+one$ ENDIF SHOWSCREEN

c1=changeValue(c1,d1,move) c2=changeValue(c2,d2,move) c3=changeValue(c3,d3,move) c4=changeValue(c4,d4,move) move=moveSpeed(screenWidth%,screenHeight%)*0.085 WEND FUNCTION box:x%,y%,width%,height%,c1%,c2%,c3%,c4% STARTPOLY -1 POLYVECTOR width%,y%,0,0,c1% POLYVECTOR x%,y%,0,0,c2% POLYVECTOR x%,height%,0,0,c3% POLYVECTOR width%,height%,0,0,c4% ENDPOLY ENDFUNCTION FUNCTION changeValue:start,BYREF change,move LOCAL r LOCAL t r=start+(change*move*0.025) IF r<=0.0 OR r>=255.0 IF change<0 r=1 ELSE r=-1 ENDIF change=20.0+(RND(511)/10.0)*r ENDIF IF r<=0.0 RETURN 0.0 ELSE IF r>=255.0 RETURN 255.0 ELSE RETURN r ENDIF ENDIF ENDFUNCTION

275 Javascript

276 Export Javascript

This routine creates a Javascript file, allowing the exporting of tile data into Javascript format. -- Export Javascript -- Nicholas Kingsley function main () if mappy.msgBox ("Export Javascript Code", "This will export all layers as a Javascript file", mappy.MMB_OKCANCEL, mappy.MMB_ICONQUESTION) == mappy.MMB_OK then local w = mappy.getValue(mappy.MAPWIDTH) local h = mappy.getValue(mappy.MAPHEIGHT) local l = mappy.getValue(mappy.NUMLAYERS) if (w == 0) then mappy.msgBox ("Export Javascript code", "You need to load or create a map first", mappy.MMB_OK, mappy.MMB_ICONINFO) else local isok,asname = mappy.fileRequester (".", "Javascript (*.jas)", "*.jas", mappy.MMB_SAVE) if isok == mappy.MMB_OK then if (not (string.sub (string.lower (asname), -4) == ".jas")) then asname = asname .. ".jas" end outas = io.open (asname, "w")

outas:write("var mapWidth=") outas:write(tostring(w)) outas:write(";\n") outas:write("var mapHeight=") outas:write(tostring(h)); outas:write(";\n\n"); -- Now we write out all the layers -- It goes Level_mapx:int[]=[ local layer=0 local x=0 local y=0 while layer

outas:write("\n") y=y+1 end outas:write("];\n\n") layer=layer+1 end outas:close () end end end end

277 test, errormsg = pcall( main ) if not test then mappy.msgBox("Error ...", errormsg, mappy.MMB_OK, mappy.MMB_ICONEXCLAMATION) end -- Shift contents of a layer up -- Nicholas Kingsley function main () local w = mappy.getValue(mappy.MAPWIDTH) local h = mappy.getValue(mappy.MAPHEIGHT) local x = 1 local y = 0 local block = 0 y=h-2 while y >= 0 do x=1 while x < w do block = mappy.getBlock(x,y) mappy.setBlock(x,y+1,block) x = x + 1 end y = y - 1 end x=0 while x < w do mappy.setBlock(x,0,0) x = x + 1 end mappy.updateScreen() end test, errormsg = pcall( main ) if not test then mappy.msgBox("Error ...", errormsg, mappy.MMB_OK, mappy.MMB_ICONEXCLAMATION) end

278 Shift Tiles #1 - #4

These routines shift the positions of tiles on a specific Mappy layer around. -- Shift contents of a layer left -- Nicholas Kingsley function main () local w = mappy.getValue(mappy.MAPWIDTH) local h = mappy.getValue(mappy.MAPHEIGHT) local x = 1 local y = 0 local block = 0 while y < h do x=1 while x < w do block = mappy.getBlock(x,y) mappy.setBlock(x-1,y,block) x = x + 1 end y = y + 1 end y=0 while y < h do mappy.setBlock(w-1,y,0) y = y + 1 end mappy.updateScreen() end test, errormsg = pcall( main ) if not test then mappy.msgBox("Error ...", errormsg, mappy.MMB_OK, mappy.MMB_ICONEXCLAMATION) end

-- Shift contents of a layer right -- Nicholas Kingsley function main () local w = mappy.getValue(mappy.MAPWIDTH) local h = mappy.getValue(mappy.MAPHEIGHT) local x = 1 local y = 0 local block = 0 while y < h do x=w-2 while x>=0 do block = mappy.getBlock(x,y) mappy.setBlock(x+1,y,block) x = x - 1 end y = y + 1 end y=0 while y < h do mappy.setBlock(0,y,0) y = y + 1 end mappy.updateScreen() end test, errormsg = pcall( main ) if not test then mappy.msgBox("Error ...", errormsg, mappy.MMB_OK, mappy.MMB_ICONEXCLAMATION) end

279 -- Shift contents of a layer up -- Nicholas Kingsley function main () local w = mappy.getValue(mappy.MAPWIDTH) local h = mappy.getValue(mappy.MAPHEIGHT) local x = 1 local y = 0 local block = 0 y=1 while y < h do x=1 while x < w do block = mappy.getBlock(x,y) mappy.setBlock(x,y-1,block) x = x + 1 end y = y + 1 end x=0 while x < w do mappy.setBlock(x,h-1,0) x = x + 1 end mappy.updateScreen() end test, errormsg = pcall( main ) if not test then mappy.msgBox("Error ...", errormsg, mappy.MMB_OK, mappy.MMB_ICONEXCLAMATION) end

-- Shift contents of a layer up -- Nicholas Kingsley function main () local w = mappy.getValue(mappy.MAPWIDTH) local h = mappy.getValue(mappy.MAPHEIGHT) local x = 1 local y = 0 local block = 0 y=h-2 while y >= 0 do x=1 while x < w do block = mappy.getBlock(x,y) mappy.setBlock(x,y+1,block) x = x + 1 end y = y - 1 end x=0 while x < w do mappy.setBlock(x,0,0) x = x + 1 end mappy.updateScreen() end test, errormsg = pcall( main ) if not test then mappy.msgBox("Error ...", errormsg, mappy.MMB_OK, mappy.MMB_ICONEXCLAMATION) end

280 PureBasic

281 Mappy Test Program

This is the PureBasic test program for my old Mappy DLL. Mappy being the previously mentioned Tile Map editor. ImportC "Mappy_DBPro.lib" loadMappyFile.l(FileName$,extraBytes.l=0) As "?loadMappyFile@@YAKPADK@Z" getMappyVersion.l() As "?getMappyVersion@@YAKXZ" getMapFormat.l() As "?getMapFormat@@YAKXZ" getAuthor.s(pOldString.l=0) As "?getAuthor@@YAKK@Z" getMapWidth.l() As "?getMapWidth@@YAKXZ" getMapHeight.l() As "?getMapHeight@@YAKXZ" getBlockWidth.l() As "?getBlockWidth@@YAKXZ" getBlockHeight.l() As "?getBlockHeight@@YAKXZ" getNumBlocks.l() As "?getNumBlocks@@YAKXZ" getNumberOfLayers.l() As "?getNumberOfLayers@@YAKXZ" getMappyIndex.l(x.l,y.l) As "?getMappyIndex@@YAKKK@Z" getTileAtPosition.l(layer.l,x.l,y.l) As "?getTileAtPosition@@YAKKKK@Z" wrietTileAtPosition.l(layer.l,x.l,y.l,tile.l) As "?writeTileAtPosition@@YAKKKKK@Z" getBlockDataSide.l() As "?getBlockDataSize@@YAKXZ" updateAnimation() As "?updateAnimations@@YAXXZ" getBackgroundOffset.l(block.l) As "?getBackgroundOffset@@YAKK@Z" getForegroundOffset.l(block.l,which.l) As "?getForgroundOffset@@YAKKK@Z" getCurrentAnimationBlock.l(block.l) As "?getCurrentAnimationBlock@@YAKK@Z" EndImport Declare loadMappyGraphics() Declare displayMap(x.l, y.l,layer.l,sx.l,sy.l)

Global MAPPYGRAPHICDIR$="MAPPYGRAPHICS" Global MAPPYFILENAMEBASE$="000000" OpenConsole() status=LoadMappyFile("test.fma",0) Print ("Load Status:") PrintN (Str(status)) If status=0 PrintN ("Loaded OK") mapWidth=getMapWidth() mapHeight=getMapHeight() mapFormat=getMapFormat() mapVersion=getMappyVersion() numBlocks=getNumBlocks()

Print ("Map Width :") PrintN (Str(mapWidth)) Print ("Map Height :") PrintN (Str(mapHeight)) Print ("Map Format :") PrintN (Str(mapFormat)) Print ("Mappy Version :") PrintN (Str(mapVersion)) Print ("Number Of Blocks :") PrintN (Str(numBlocks))

EndIf Repeat Until Inkey()<>"" CloseConsole() If OpenWindow(0, 100, 100, 600, 600, "PureBasic - Image") loadMappyGraphics() x=20 y=20 displayMap(0,0,0,x,y)

Repeat EventID = WindowEvent() If EventID Else displayMap(0,0,0,x,y) updateAnimation() Delay(1) EndIf

282 Until EventID = #PB_Event_CloseWindow ; If the user has pressed on the close button CloseWindow(0) EndIf

End Procedure displayMap(x.l, y.l,layer.l,sx.l,sy.l) lx.l=0 ly.l=0 tile.w=0 px.l=0 py.l=0 image.l=0 mapWidth=getMapWidth() mapHeight=getMapHeight() blockWidth=getBlockWidth() blockHeight=getBlockHeight()

If StartDrawing(WindowOutput(0)) py=y For ly=sy To mapHeight-1 px=x For lx=sx To mapWidth-1 tile=getTileAtPosition(lx,ly,layer) If tile<>0 If tile & (1<<15) ; Check To see If the tile number is negative, And thus an animated tile. tile=getCurrentAnimationBlock(tile) EndIf image=getBackgroundOffset(tile)

If image>0 DrawImage(ImageID(image),px*blockWidth,py*blockHeight) EndIf

image=getForegroundOffset(tile,0)

If image>0 DrawImage(ImageID(image),px*blockWidth,py*blockHeight) EndIf Else DrawImage(ImageID(image),px*blockWidth,py*blockHeight) EndIf INC px Next lx INC py Next ly StopDrawing() EndIf EndProcedure

Procedure loadMappyGraphics() l.l=0 name$="" num$="" numA$=""

For l=0 To getNumBlocks()-1 numA$=Str(l) num$=Right(MAPPYFILENAMEBASE$,Len(MAPPYFILENAMEBASE$)-Len(numA$))+numA$+".BMP" name$=MAPPYGRAPHICDIR$+"\G"+num$ If LoadImage(l,name$)=0 MessageRequester("*","File "+name$+" Not Loaded") EndIf Next l EndProcedure

283 DispBit

This was one of my commerical programs which was used to display a graphic. ; Written by Nicholas Kingsley Define.s COMPOSITE_CODE = "*" Define.s COMPOSITE_CODE_TO_FILE_CODE = "[" UseJPEGImageDecoder() UsePNGImageDecoder() UseTIFFImageDecoder() UseTGAImageDecoder() inFile.s="" actualFile.s="" picturePath.s="" fileName.s="" l.l=0 found.b=0 Dim ext.s(5) ext(0)=".BMP" ext(1)=".JPG" ext(2)=".PNG" ext(3)=".TIFF" ext(4)=".TGA" ext(5)="" IncludeFile "Common.pb" ; Get the passed filename numParams=CountProgramParameters() If numParams>=1 inFile=ProgramParameter()

; Now we need to replace any composite starts with the appropriate code actualFile="" For l=1 To Len(inFile) If Mid(inFile,l,1)=COMPOSITE_CODE actualFile=actualFile+COMPOSITE_CODE_TO_FILE_CODE Else actualFile=actualFile+Mid(inFile,l,1) EndIf Next l Else MessageRequester("* No Parameters Passed *","* No Picture Name Passed *",#PB_MessageRequester_Ok) End EndIf ; Now we get the picture path from the UNC.INI file If OpenPreferences("UNC.INI") PreferenceGroup("PATHS") picturePath=ReadPreferenceString("PLASWAREPICTURES","") ClosePreferences() If picturePath="" MessageRequester("* Picture Path Not Setup *","Cant Find Picture Path Value",#PB_MessageRequester_Ok) End Else ; Now we go through looking for a picture picturePath=picturePath+"\" l=0 found=0 While ext(l)<>"" And found=0 fileName=picturePath+inFile+ext(l) If LoadImage(1,fileName) found=1 Break Else

284 l=l+1 EndIf Wend

If found=0 MessageRequester("* No Picture Found *","No Picture Found",#PB_MessageRequester_Ok) End EndIf EndIf Else MessageRequester("* UNC File Not Found *","") End EndIf Open_Window_0(inFile) Repeat ; Start of the event loop

Event = WaitWindowEvent() ; This line waits until an event is received from Windows

WindowID = EventWindow() ; The Window where the event is generated, can be used in the gadget procedures

GadgetID = EventGadget() ; Is it a gadget event?

EventType = EventType() ; The event type

;You can place code here, and use the result as parameters for the procedures

If Event = #PB_Event_Gadget

If GadgetID = #Image_0

EndIf

EndIf

Until Event = #PB_Event_CloseWindow ; End of the event loop End ;

285 Graphics Converter

Another commercial program which I used to convert graphics to BMP format ; Convert JPEG, TGA, PNG or TIFF files to BMP format ; Import "c:\plasware\vstudio\ps_utils\release\PS_Utils.lib" deleteGlobalAtom($) EndImport UseJPEGImageDecoder() UseTGAImageDecoder() UsePNGImageDecoder() UseTIFFImageDecoder() ; Dont need to initialise any encoders as we will always write to a bitmap inFile.s="" tempFile.s="" outFile.s="" globalAtom$="CONVERTING GRAPHICS" silent.b=1 numParams.l=0 pos.l=0 l.l=0 atom.l=0

Dim ext.s(6) ext(1)=".JPG" ext(2)=".TGA" ext(3)=".PNG" ext(4)=".TIFF" ext(5)=".BMP" ext(6)="" numParams=CountProgramParameters() If numParams>=2 inFile=ProgramParameter() outFile=ProgramParameter() If numParams=3 If ProgramParameter()="/NOTSILENT" silent=0 EndIf EndIf ; Remove any extensions from the filename pos=FindString(inFile,".",1) If pos>0 inFile=Left(inFile,pos-1) EndIf

If silent=0 MessageRequester("* Passed Values *","In:"+inFile+" Out:"+outFile) EndIf

; Now we go through all the different possible extensions, looking to see if the file exist l=1 While ext(l)<>"" tempFile=inFile+ext(l) If LoadImage(1,tempFile)<>0 ; Found, so we save it If SaveImage(1,outFile,#PB_ImagePlugin_BMP )<>0 If silent=0 MessageRequester("* Finished *","* Conversion Has Completed *") EndIf Else If silent=0 MessageRequester("* Failed Save *","Unable to save the bitmap "+outFile) EndIf EndIf

286 Break Else INC l EndIf Wend

If silent=0 MessageRequester("* No Graphic Found *","No valid graphic file was found") EndIf Else If silent=0 MessageRequester("* Invalid Number Of Parameters *","Invalid Number Of Parameters : GRPXCONV.EXE []") EndIf EndIf deleteGlobalAtom(globalAtom$) End

287 Statistical Analysis Program

Yet another commercial program, this one was used to create a statistical analysis graph for a given product. The data was saved by my MFC program, and then this program called. Like the rest of what I was working on, it never got released. ; PureBasic Visual Designer v3.95 build 1485 (PB4Code) ImportC "c:\plasware\vstudio\SPCWin\Release\SPCWin.lib" setSPCDimensionRange_PB(upperTol.d,upperCTL.d,nomDim.d,lowerCTL.d,lowerTol.d) As "? setSPCDimensionRange_PB@@YAXNNNNN@Z" where_in_range.l(value.d) As "?where_in_range@@YAHN@Z" EndImport

Structure _SPCHeader productCode.s productDescription.s partNumber.s worksOrderNumber.s stroke.l productionBatchNumber.s upperTolerance.d lowerTolerance.d upperControlLimit.d lowerControlLimit.d nominalDimension.d EndStructure

Structure _SPCList spcData.d date.s time.s EndStructure Structure _startXY x.l y.l EndStructure Declare displayBars() Declare.l chart(first.b,xStep.l,x.l,maxDimension.l) Declare firstSPCList(xStep.l,maxDimension.l) Declare nextSPCList(xStep.l,maxDimension.l) Global fileName.s Global HANDLE.l = 0 Global spcHeader._SPCHeader Global numLines.l Global MIDDLE_LINE.l Global off_top.l Global calc.l Global MAXWIDTH.l Global BOT_LINE.l Global startXY._startXY first.b xPos.l xStep.l maxDimension.l #STARTRECTANGLEX = 84 #STARTRECTANGLEY = 4 #EXCEED_NOTHING = 0 #EXCEED_UPTOL = 4 #EXCEED_UPCLIMIT = 3 #EXCEED_LOTOL = 1 #EXCEED_LOCLIMIT = 2 #EXCEED_UPNOM = 5 #EXCEED_LONOM = 6 #MIN_STEP = 4 #TEXT_STARTX = 4 #DISPDP = 5 #TEXT_YOFFSET = 4 #TEXT_INFOYPOS = 140

288 IncludeFile "Common.pb" numLines=128 MIDDLE_LINE=numLines/2 off_top=8 calc=MIDDLE_LINE-off_top-4 BOT_LINE=#STARTRECTANGLEY+numLines Global NewList spcList._SPCList() ClearList(spcList()) If CountProgramParameters()<1 MessageRequester("* Invalid Number Of Parameters *","The number of passed parameters is incorrect") End EndIf fileName.s=ProgramParameter() If fileName="" MessageRequester("* Invalid FileName *","No filename has been passed") End EndIf ; Read in the header bits first If ReadFile(HANDLE,fileName) spcHeader\productCode=ReadString(HANDLE) spcHeader\productDescription=ReadString(HANDLE) spcHeader\partNumber=ReadString(HANDLE) spcHeader\worksOrderNumber=ReadString(HANDLE) spcHeader\stroke=Val(ReadString(HANDLE)) spcHeader\productionBatchNumber=ReadString(HANDLE)

; Now to read in the range values spcHeader\upperTolerance=ValD(ReadString(HANDLE)) spcHeader\upperControlLimit=ValD(ReadString(HANDLE)) spcHeader\nominalDimension=ValD(ReadString(HANDLE)) spcHeader\lowerControlLimit=ValD(ReadString(HANDLE)) spcHeader\lowerTolerance=ValD(ReadString(HANDLE))

; Now we get all the dimensions

While Eof(HANDLE)=0 AddElement(spcList()) spcList()\spcData=ValD(ReadString(HANDLE)) spcList()\date=ReadString(HANDLE) spcList()\time=ReadString(HANDLE) Wend

CloseFile(HANDLE)

If FirstElement(spcList())=0 MessageRequester("* No Data *","There is nothing to display") End EndIf Else MessageRequester("* File Not Found *","Unable to open the file "+fileName+" for reading.") End EndIf Open_Window_0() MAXWIDTH=WindowWidth(#Window_0)-8-#STARTRECTANGLEX maxDimension=MAXWIDTH/#MIN_STEP xStep=MAXWIDTH/CountList(spcList()) If xStep<#MIN_STEP xStep=#MIN_STEP EndIf setSPCDimensionRange_PB(spcHeader\upperTolerance,spcHeader\upperControlLimit,spcHeader\n ominalDimension,spcHeader\lowerControlLimit,spcHeader\lowerTolerance)

Repeat ; Start of the event loop

Event = WaitWindowEvent() ; This line waits until an event is received from Windows

289 WindowID = EventWindow() ; The Window where the event is generated, can be used in the gadget procedures

GadgetID = EventGadget() ; Is it a gadget event?

EventType = EventType() ; The event type

;You can place code here, and use the result as parameters for the procedures

Select Event Case #PB_Event_Gadget Select GadgetID Case #Button_0 ; Rewind the graph firstSPCList(xStep,maxDimension) Case #Button_1 ; Continue nextSPCList(xStep,maxDimension) EndSelect Case #PB_Event_ActivateWindow Case #PB_Event_MoveWindow firstSPCList(xStep,maxDimension) EndSelect Until Event = #PB_Event_CloseWindow ; End of the event loop End Procedure displayBars() scale.d pos.l temp.s

; The middle line If (StartDrawing(WindowOutput(#Window_0))) DrawingMode(#PB_2DDrawing_Default)

Box(#STARTRECTANGLEX,#STARTRECTANGLEY,MAXWIDTH,#STARTRECTANGLEY+numLines,0)

DrawingMode(#PB_2DDrawing_Transparent) LineXY(#STARTRECTANGLEX+1,MIDDLE_LINE+#STARTRECTANGLEY,#STARTRECTANGLEX+MAXWIDTH,MID DLE_LINE+#STARTRECTANGLEY,RGB(255,255,255)) DrawText(#TEXT_STARTX,MIDDLE_LINE- #TEXT_YOFFSET,StrD(spcHeader\nominalDimension,#DISPDP))

; Upper & lower tolerance LineXY(#STARTRECTANGLEX+1,#STARTRECTANGLEY+off_top,#STARTRECTANGLEX+MAXWIDTH,#STARTR ECTANGLEY+off_top,RGB(255,255,0)) LineXY(#STARTRECTANGLEX+1,BOT_LINE-off_top,#STARTRECTANGLEX+MAXWIDTH,BOT_LINE- off_top,RGB(255,255,0)) DrawText(#TEXT_STARTX,(#STARTRECTANGLEY+off_top)- #TEXT_YOFFSET,StrD(spcHeader\upperTolerance,#DISPDP)) DrawText(#TEXT_STARTX,(BOT_LINE-off_top)- #TEXT_YOFFSET,StrD(spcHeader\lowerTolerance,#DISPDP))

; Upper control limit scale=(calc/(spcHeader\upperTolerance-spcHeader\nominalDimension)) pos=(spcHeader\upperControlLimit-spcHeader\nominalDimension)*scale If pos<=0 pos=1 EndIf

LineXY(#STARTRECTANGLEX+1,(MIDDLE_LINE-pos) +#STARTRECTANGLEY,#STARTRECTANGLEX+MAXWIDTH,(MIDDLE_LINE-pos) +#STARTRECTANGLEY,RGB(0,255,0)) DrawText(#TEXT_STARTX,(MIDDLE_LINE-pos-#TEXT_YOFFSET) +#STARTRECTANGLEY,StrD(spcHeader\upperControlLimit,#DISPDP))

; Lower control limit scale=(calc/(spcHeader\nominalDimension-spcHeader\lowerTolerance)) pos=(spcHeader\nominalDimension-spcHeader\lowerControlLimit)*scale If pos<=0 pos=1 EndIf

LineXY(#STARTRECTANGLEX+1,(MIDDLE_LINE+pos) +#STARTRECTANGLEY,#STARTRECTANGLEX+MAXWIDTH,(MIDDLE_LINE+pos) +#STARTRECTANGLEY,RGB(0,255,0)) DrawText(#TEXT_STARTX,((MIDDLE_LINE+pos)+#STARTRECTANGLEY)- #TEXT_YOFFSET,StrD(spcHeader\lowerControlLimit,#DISPDP))

290 DrawText(#TEXT_STARTX,#TEXT_INFOYPOS,"Description : "+spcHeader\productDescription) StopDrawing() EndIf EndProcedure Procedure.l chart(first.b,xStep.l,x.l,maxDimension.l) scale.d line.l colour.l count.l

If first If FirstElement(spcList())=0 ProcedureReturn 0 Else x=1 EndIf Else If NextElement(spcList())=0 ProcedureReturn 0 EndIf EndIf displayBars() count=0 While countspcHeader\upperControlLimit scale=(MIDDLE_LINE-off_top)/(spcHeader\upperTolerance-spcHeader\nominalDimension) Else If _data>spcHeader\nominalDimension scale=(calc/(spcHeader\upperTolerance-spcHeader\nominalDimension)) Else If _data

line=((MIDDLE_LINE-((_data-spcHeader\nominalDimension)*scale))+#STARTRECTANGLEY)

If line<=#STARTRECTANGLEY line=#STARTRECTANGLEY Else If line>=BOT_LINE line=BOT_LINE EndIf EndIf

; Colour is based on difference between the ND, UCL/LCL & UT/LT Select where_in_range(_data) Case #EXCEED_LOTOL colour=RGB(255,0,0) Case #EXCEED_UPTOL colour=RGB(255,0,0) Case #EXCEED_UPCLIMIT colour=RGB(255,0,255) Case #EXCEED_LOCLIMIT colour=RGB(255,0,255) Default colour=RGB(255,255,255) EndSelect If first And count=0 startXY\x=#STARTRECTANGLEX+1 startXY\y=line EndIf

291 If (StartDrawing(WindowOutput(#Window_0))) LineXY(startXY\x,startXY\y,startXY\x+xStep,line,colour) StopDrawing() EndIf

startXY\x=startXY\x+xStep startXY\y=line

x=x+1 count=count+1

If NextElement(spcList())=0 Break EndIf Wend If (StartDrawing(WindowOutput(#Window_0))) DrawingMode(#PB_2DDrawing_Transparent) DrawText(#TEXT_STARTX,#TEXT_INFOYPOS+16,"Samples Displayed : "+Str(count) +"/"+Str(CountList(spcList()))) EndIf

ProcedureReturn x EndProcedure Procedure firstSPCList(xStep.l,maxDimension.l) xPos=chart(1,xStep,1,maxDimension) EndProcedure Procedure nextSPCList(xStep.l,maxDimension.l) xPos=chart(0,xStep,xPos,maxDimension) EndProcedure

292 DarkBasic Professional

293 Screen Wipe Routines

These are screen wiping routines, and were one of the first things I wrote after buying it... remstart This is a collection of screen wipes Written by N. Kingsley NOTE : The effect you get with backdrop off is different to that with backdrop on! remend function verticalWipe(x as integer, y as integer, r as integer, g as integer, b as integer) xloop as integer ink rgb(r,g,b),rgb(r,g,b) for xloop=0 to y step 2 line 0,xloop,x,xloop line 0,y-xloop-1,x,y-xloop-1 sync next loop endfunction function horizontalWipe(x as integer, y as integer, r as integer, g as integer, b as integer) xloop as integer ink rgb(r,g,b),rgb(r,g,b) for xloop=0 to x step 2 line xloop,0,xloop,y line x-xloop-1,0,x-xloop-1,y sync next loop endfunction function boxWipe(x as integer, y as integer, r as integer, g as integer, b as integer) halfx as integer halfy as integer xloop as integer yloop as integer halfX=x>>1 halfY=y>>1 xloop=0 ink rgb(r,g,b),rgb(r,g,b) for yloop=0 to y box halfX-xloop,halfY-yloop,halfX+xloop,halfY+yloop sync inc xloop if xloop>halfX then exitfunction next yloop endfunction function diagonalWipeL(x as integer,y as integer,r as integer,g as integer,b as integer) xloop as integer ink rgb(r,g,b),rgb(r,g,b) for xloop=0 to x<<1 step 2 line xloop,0,0,xloop line x-xloop-1,y,x,y-xloop-1 sync next xloop endfunction function diagonalWipeR(x as integer,y as integer,r as integer,g as integer,b as integer) xloop as integer ink rgb(r,g,b),rgb(r,g,b) for xloop=0 to x<<1 step 2 line x-xloop,0,x,xloop line xloop+1,y,0,(y-xloop)-1

294 sync next xloop endfunction function verticalSplitWipe(x as integer,y as integer,r as integer,g as integer,b as integer) yloop as integer ink rgb(r,g,b),rgb(r,g,b) for yloop=0 to y step 2 line 0,yloop,(x>>1)+1,yloop line x,y-yloop-1,x>>1,y-yloop-1 sync next yloop for yloop=0 to y step 2 line 0,y-yloop-1,(x>>1)+1,y-yloop-1 line x,yloop,x>>1,yloop sync next yloop endfunction function horizontalSplitWipe(x as integer,y as integer,r as integer,g as integer,b as integer) xloop as integer ink rgb(r,g,b),rgb(r,g,b) for xloop=0 to x step 2 line xloop,0,xloop,(y>>1)+1 line x-xloop-1,y,x-xloop-1,y>>1 sync next xloop for xloop=0 to x step 2 line xloop,y,xloop,y>>1 line x-xloop-1,0,x-xloop-1,(y>>1)+1 sync next xloop endfunction function horizontalBlindWipe(x as integer,y as integer,r as integer,g as integer,b as integer) stp as integer xloop as integer pos as integer stp=16 ink rgb(r,g,b),rgb(r,g,b) for xloop=0 to stp for pos=stp to x step stp line pos-xloop,0,pos-xloop,y next pos sync next xloop endfunction function verticalBlindWipe(x as integer,y as integer,r as integer,g as integer,b as integer) stp as integer yloop as integer pos as integer stp=16 ink rgb(r,g,b),rgb(r,g,b) for yloop=0 to stp for pos=stp to y step stp line 0,pos-yloop,x,pos-yloop next pos sync next yloop endfunction function circleWipe(r as integer,g as integer,b as integer)

295 xloop as integer width as integer ink rgb(r,g,b),rgb(r,g,b) width=screen width()>>1 for xloop=0 to width+(width>>1) circle width,screen height()>>1,xloop sync next xloop endfunction

296 Multiple Cameras

This routine is to enable easy use of split screen cameras. It was going to be used for something, but I never actually used it. Rem *** Include File: C:Program FilesDark Basic SoftwareDark Basic ProfessionalRoutinesMultiCamera.dba *** Rem Created: 19/10/2003 13:43:16 Rem Included in Project: C:Program FilesDark Basic SoftwareDark Basic ProfessionalProjectsTestMultiCameraTestMultiCamera.dbpro type _SCREENINFO screenWidth as DWORD screenHeight as DWORD halfX as DWORD halfY as DWORD thirdX as DWORD thirdY as DWORD endtype type _PLAYERCAMERA camNum as DWORD viewX as DWORD viewY as DWORD viewDX as DWORD viewDY as DWORD endtype #constant MAXPLAYERS 4 #constant PLAYER1CAMERA 1 #constant PLAYER2CAMERA 2 #constant PLAYER3CAMERA 3 #constant PLAYER4CAMERA 4 #constant PLAYER1 1 #constant PLAYER2 2 #constant PLAYER3 3 #constant PLAYER4 4 #constant NOCAMERA 0 #constant ONEPLAYER_FULLSCREEN 1<<0 #constant ONEPLAYER_HALFSCREEN 1<<1 #constant ONEPLAYER_169RATIO 1<<2 #constant TWOPLAYER_HORZSPLIT 1<<3 #constant TWOPLAYER_VERTSPLIT 1<<4 #constant THREEPLAYER_HORZSPLIT 1<<5 #constant THREEPLAYER_VERTSPLIT 1<<6 #constant THREEPLAYER_GROUP 1<<7 function getScreenSize screenInfo.screenWidth=screen width() screenInfo.screenHeight=screen height() screenInfo.halfX=screenInfo.screenWidth>>1 screenInfo.halfY=screenInfo.screenHeight>>1 screenInfo.thirdX=screenInfo.screenWidth/3 screenInfo.thirdY=screenInfo.screenHeight/3 endfunction function resetAllCameras local l as DWORD for l=PLAYER1 to PLAYER1+(MAXPLAYERS-1):playerCamera(l).camNum=NOCAMERA:next l endfunction function clearAllCameras local l as DWORD for l=PLAYER1 to PLAYER1+(MAXPLAYERS-1) if playerCamera(l).camNum<>NOCAMERA delete camera playerCamera(l).camNum playerCamera(l).camNum=NOCAMERA endif next l

297 endfunction function createViewPort(numPlayers as DWORD,flags as DWORD) local l as DWORD playerCamera(PLAYER1).camNum=PLAYER1CAMERA if numPlayers>=2 then playerCamera(PLAYER2).camNum=PLAYER2CAMERA if numPlayers>=3 then playerCamera(PLAYER3).camNum=PLAYER3CAMERA if numPlayers=4 then playerCamera(PLAYER4).camNum=PLAYER4CAMERA select numPlayers case 1 : REM One player if flags && ONEPLAYER_FULLSCREEN playerCamera(PLAYER1).viewX=0 playerCamera(PLAYER1).viewY=0 playerCamera(PLAYER1).viewDX=screenInfo.screenWidth playerCamera(PLAYER1).viewDY=screenInfo.screenHeight else if flags && ONEPLAYER_HALFSCREEN playerCamera(PLAYER1).viewX=screenInfo.halfX>>1 playerCamera(PLAYER1).viewY=screenInfo.halfY>>1 playerCamera(PLAYER1).viewDX=playerCamera(PLAYER1).viewX+screenI nfo.halfX playerCamera(PLAYER1).viewDY=playerCamera(PLAYER1).viewY+screenI nfo.halfY else playerCamera(PLAYER1).viewX=0 playerCamera(PLAYER1).viewY=screenInfo.halfY>>1 playerCamera(PLAYER1).viewDX=screenInfo.screenWidth playerCamera(PLAYER1).viewDY=playerCamera(PLAYER1).viewY+screenI nfo.halfY endif endif endcase case 2 : REM Two players if flags && TWOPLAYER_HORZSPLIT playerCamera(PLAYER1).viewX=0 playerCamera(PLAYER1).viewY=0 playerCamera(PLAYER1).viewDX=screenInfo.halfX-1 playerCamera(PLAYER1).viewDY=screenInfo.screenHeight playerCamera(PLAYER2).viewX=screenInfo.halfX+1 playerCamera(PLAYER2).viewY=0 playerCamera(PLAYER2).viewDX=screenInfo.screenWidth playerCamera(PLAYER2).viewDY=screenInfo.screenHeight else if flags && TWOPLAYER_VERTSPLIT playerCamera(PLAYER1).viewX=0 playerCamera(PLAYER1).viewY=0 playerCamera(PLAYER1).viewDX=screenInfo.screenWidth playerCamera(PLAYER1).viewDY=screenInfo.halfY-1 playerCamera(PLAYER2).viewX=0 playerCamera(PLAYER2).viewY=screenInfo.halfY+1 playerCamera(PLAYER2).viewDX=screenInfo.screenWidth playerCamera(PLAYER2).viewDY=screenInfo.screenHeight endif endif endcase case 3 : REM Three players if flags && THREEPLAYER_HORZSPLIT playerCamera(PLAYER1).viewX=0 playerCamera(PLAYER1).viewY=0 playerCamera(PLAYER1).viewDX=screenInfo.thirdX playerCamera(PLAYER1).viewDY=screenInfo.screenHeight playerCamera(PLAYER2).viewX=screenInfo.thirdX+1 playerCamera(PLAYER2).viewY=0 playerCamera(PLAYER2).viewDX=(screenInfo.thirdX*2)-1 playerCamera(PLAYER2).viewDY=screenInfo.screenHeight playerCamera(PLAYER3).viewX=(screenInfo.thirdX*2)+1 playerCamera(PLAYER3).viewY=0 playerCamera(PLAYER3).viewDX=screenInfo.screenWidth playerCamera(PLAYER3).viewDY=screeninfo.screenHeight else if flags && THREEPLAYER_VERTSPLIT

298 playerCamera(PLAYER1).viewX=0 playerCamera(PLAYER1).viewY=0 playerCamera(PLAYER1).viewDX=screenInfo.screenWidth playerCamera(PLAYER1).viewDY=screenInfo.thirdY-1 playerCamera(PLAYER2).viewX=0 playerCamera(PLAYER2).viewY=screenInfo.thirdY+1 playerCamera(PLAYER2).viewDX=screenInfo.screenWidth playerCamera(PLAYER2).viewDY=(screenInfo.thirdY*2)-1 playerCamera(PLAYER3).viewX=0 playerCamera(PLAYER3).viewY=(screenInfo.thirdY*2)+1 playerCamera(PLAYER3).viewDX=screenInfo.screenWidth playerCamera(PLAYER3).viewDY=screeninfo.screenHeight else if flags && THREEPLAYER_GROUP playerCamera(PLAYER1).viewX=0 playerCamera(PLAYER1).viewY=0 playerCamera(PLAYER1).viewDX=screenInfo.halfX-1 playerCamera(PLAYER1).viewDY=screenInfo.halfY-1 playerCamera(PLAYER2).viewX=screenInfo.halfX+1 playerCamera(PLAYER2).viewY=0 playerCamera(PLAYER2).viewDX=screenInfo.screenWidth playerCamera(PLAYER2).viewDY=screenInfo.halfY-1 playerCamera(PLAYER3).viewX=0 playerCamera(PLAYER3).viewY=screenInfo.halfY+1 playerCamera(PLAYER3).viewDX=screenInfo.screenWidth playerCamera(PLAYER3).viewDY=screeninfo.screenHeight endif endif endif endcase case 4 : REM 4 players playerCamera(PLAYER1).viewX=0 playerCamera(PLAYER1).viewY=0 playerCamera(PLAYER1).viewDX=screenInfo.halfX-1 playerCamera(PLAYER1).viewDY=screenInfo.halfY-1 playerCamera(PLAYER2).viewX=screenInfo.halfX+1 playerCamera(PLAYER2).viewY=0 playerCamera(PLAYER2).viewDX=screenInfo.screenWidth playerCamera(PLAYER2).viewDY=screenInfo.halfY-1 playerCamera(PLAYER3).viewX=0 playerCamera(PLAYER3).viewY=screenInfo.halfY+1 playerCamera(PLAYER3).viewDX=screenInfo.halfX-1 playerCamera(PLAYER3).viewDY=screenInfo.screenHeight playerCamera(PLAYER4).viewX=screenInfo.halfX+1 playerCamera(PLAYER4).viewY=screenInfo.halfY+1 playerCamera(PLAYER4).viewDX=screenInfo.screenWidth playerCamera(PLAYER4).viewDY=screenInfo.screenHeight endcase endselect for l=PLAYER1 to PLAYER1+(numPlayers-1) if playerCamera(l).camNum<>0 make camera playerCamera(l).camNum set camera view playerCamera(l).camNum,_ playerCamera(l).viewX,playerCamera(l).viewY,_ playerCamera(l).viewDX,playerCamera(l).viewDY set camera range playerCamera(l).camNum,10,100000 endif next l endfunction function scaleToSize(obj as integer,_ resX# as float,_ resY# as float,_ resZ# as float) local x# as float local y# as float local z# as float x#=(resX#/object size x(obj))*100.0

299 y#=(resY#/object size y(obj))*100.0 z#=(resZ#/object size z(obj))*100.0 scale object obj,x#,y#,z# endfunction

300 LoadRoutine

This is a routine designed to allow easier loading of music, graphics, 3D objects etc. These routines make it easier to load lots of files... Rem Project: LoadDLL Rem Created: 04/11/2002 20:29:04 Rem ***** Main Source File ***** REM REM Version 1.0 #constant ERROR_FILEMISSING -1 #constant ERROR_NUMMISSING -2 #constant UNLOAD_FILE 0 #constant LOAD_FILE 1 #constant PROCESS_DLL 1 #constant PROCESS_IMAGE 2 #constant PROCESS_XFILE 3 #constant PROCESS_MP3 4 #constant SEPERATOR "-" #constant SECTIONSEP "," #constant FILEEXT "." function processMusicFile(fileName as string,musicNum as DWORD,load as DWORD) select load case LOAD_FILE : Rem Load if music exist (musicNum)=0 and file exist(fileName) load music fileName,musicNum endif endcase case default : Rem Unload if music exist (musicNum)<>0 stop music musicNum delete music musicNum endif endcase endselect endfunction function processDLLFile(fileName as string,dllNum as DWORD,load as DWORD) select load case LOAD_FILE : Rem Load if dll exist (dllNum)=0 and file exist(fileName) load dll fileName,dllNum endif endcase case default : Rem Unload if dll exist (dllNum)<>0 delete dll dllNum endif endcase endselect endfunction function processImageFile(fileName as string,imgNum as DWORD,load as DWORD) select load case LOAD_FILE : if image exist (imgNum)=0 and file exist(fileName) load image fileName,imgNum,1 endif endcase case default : if image exist (imgNum)<>0 delete image imgNum endif endcase endselect endfunction function process3DFile(fileName as string,xnum as DWORD,load as DWORD) select load case LOAD_FILE : if object exist(xnum)=0 and file exist(fileName)

301 load object fileName,xnum endif endcase case default : if object exist(xnum)<>0 delete object xnum endif endcase endselect endfunction function processSoundFile(fileName as string,soundNum as DWORD,load as DWORD) select load case LOAD_FILE : Rem Load if sound exist (soundNum)=0 and file exist(fileName) load sound fileName,soundNum endif endcase case default : Rem Unload if sound exist (soundNum)<>0 stop sound soundNum delete sound soundNum endif endcase endselect endfunction function loadData(str as string,load as DWORD) local pos as DWORD local cpos as DWORD local full$ as string local filename$ as string local objnum as DWORD local done as DWORD local extPos as DWORD local extension$ as string local temp as DWORD local tl as DWORD local sH as DWORD local sW as DWORD local st# as float local cl as DWORD tl=0 sH=screen height() sW=screen width() st#=0.25 done=0 while str<>"" pos=strpos$(str,SEPERATOR) if pos=0 pos=len(str)+1 endif full$=left$(str,pos-1) cpos=strpos$(full$,SECTIONSEP) if cpos<>0 filename$=left$(full$,cpos-1) objnum=val(right$(full$,len(full$)-cpos)) REM Look for the extension on the filename extPos=strpos$(filename$,FILEEXT) if extPos<>0 extension$=mid$(filename$,extPos+1,len(filename$)-extPos) REM We now have the extension, so we decide what it is select extension$ case "X" : process3DFile(filename$,objnum,load) endcase case "3DS": process3DFile(filename$,objnum,load) endcase case "DBO": process3DFile(filename$,objnum,load) endcase case "WAV": processSoundFile(filename$,objnum,load) endcase case "MP3": processMusicFile(filename$,objnum,load) endcase case "MID": processMusicFile(filename$,objnum,load) endcase case "DLL": processDLLFile(filename$,objnum,load)

302 endcase case "BMP": processImageFile(filename$,objnum,load) endcase case "JPG": processImageFile(filename$,objnum,load) endcase case "PNG": processImageFile(filename$,objnum,load) endcase endselect endif str=right$(str,(len(str)-pos)) inc done if tl255 then tl=0 endif endif sync else exitfunction ERROR_NUMMISSING endif endwhile endfunction done

303 DrawLine

A routine to allow plotting of a line from the start to end coordinates. It is a quick example of how line drawing works. drawLine(100,100,110,150,rgb(255,0,0)) sync wait key function drawLine(Ax as float,Ay as float,Bx as float,By as float,c as integer) local Dx as float local Dy as float local NumSteps as float Dx=Bx-Ax Dy=By-Ay Bx=abs(Dx) By=abs(Dy) if Bx>By NumSteps=Bx else NumSteps=By endif Bx=Dx/NumSteps By=Dy/NumSteps

while (NumSteps>=0.0) dot Ax,Ay,c inc Ax,Bx inc Ay,By dec NumSteps endwhile endfunction

304 Gradient Lines

Creates horizontal and vertical 3D matrix gradients. Another early routine which probably was going to be used for something... function gradYTiles(mat as DWORD,x as DWORD,startY as DWORD,_ endY as DWORD,start# as float,incA# as float) local l as DWORD local st as integer local d as integer st=startY-endY if st<0 then d=1 else d=-1 for l=startY to endY step d set matrix height mat,x,l,start# set matrix height mat,x+1,l,start# inc start#,incA# set matrix height mat,x,l+d,start# set matrix height mat,x+1,l+d,start# inc start#,incA# next l update matrix mat endfunction function gradXTiles(mat as DWORD,y as DWORD,startX as DWORD,_ endX as DWORD,start# as float,incA# as float) local l as DWORD local st as integer local d as integer st=startX-endX if st<0 then d=1 else d=-1 for l=startX to endX step d set matrix height mat,l,y,start# set matrix height mat,l,y+1,start# inc start#,incA# set matrix height mat,l+1,y,start# set matrix height mat,l+1,y+1,start# inc start#,incA# next l update matrix mat endfunction

305 Fade Screen

A simple full screen fade routine. Can either fade up or down. remstart ScreenFade routine by Nicholas Kingsley remend #constant FADE_DOWN 1 #constant FADE_UP 0 function screenFade(dirx as integer,spr as integer,bmp as integer,_ background as integer) l as integer sprite spr,0,0,bmp:sync select dirx case FADE_UP : for l=0 to 255 cls background set sprite alpha spr,l sync next l endcase case FADE_DOWN : for l=255 to 0 step -1 cls background set sprite alpha spr,l sync next l endcase endselect endfunction function textFade(text$ as string,x as integer,y as integer,dirx as integer,_ tr as integer,tb as integer,tg as integer,_ br as integer,bb as integer,bg as integer) l as integer select dirx case FADE_DOWN : Rem fade down for l=255 to 0 step -1 ink rgb(l,l,l),rgb(br,bb,bg) text x,y,text$ sync next l endcase case FADE_UP : Rem fade up for l=0 to 255 ink rgb(l,l,l),rgb(br,bb,bg) text x,y,text$ sync next l ink rgb(tr,tg,tb),rgb(br,bb,bg) text x,y,text$ sync endcase endselect endfunction

306 Sprite Print

Use a sprite as a character to print. It would have used a UDG set from an Acorn Archimedies for its font. Rem Project: GrabFont Rem Created: 21/10/2002 22:30:20 Rem ***** Main Source File ***** Rem Version 1.0.0.2 - Bug found by DarthPuff prevent the normal screen to be displayed Rem if a line had fewer characters than the given size Rem Version 1.0.0.3 - Allow buttons to be created #constant ERROR_OK 0 #constant ERROR_FILENOTFOUND 1 #constant ERROR_FEWCHARS 2 #constant ERROR_COLOURSIZE 3 #constant ERROR_NOCHARS 4 rem Invalid Width OR height #constant ERROR_INVALIDWIDTH 5 #constant ERROR_IMAGEEXIST 6 #constant ERROR_IMAGEMISSING 7 #constant CHAR_BANK 256 function ASCIIToImage(one$ as string,bank as DWORD) local value as DWORD

value=(bank*CHAR_BANK)+asc(mid$(one$,1)) endfunction value function putCharIntoBitmap(img as DWORD,_ bm as DWORD,_ width as DWORD,_ string$ as string,_ bank as DWORD,_ tran as DWORD) if image exist(img)=1 then exitfunction ERROR_IMAGEEXIST if string$<>"" create bitmap bm, len(string$)*width, width set current bitmap bm cls 0 writeFontChars(string$,0,0,width,bank,tran) get image img,0,0,len(string$)*width,width,1 set current bitmap 0 delete bitmap bm else exitfunction ERROR_NOCHARS endif endfunction ERROR_OK function writeFontCharsCentre(str as string,_ y as DWORD,_ width as DWORD,_ bank as DWORD,_ tran as DWORD,_ areaWidth as DWORD) local x as DWORD local a as DWORD local one as DWORD local img as DWORD if str<>"" if areaWidth=0 areaWidth=screen width() endif if width*len(str)>areaWidth then exitfunction ERROR_INVALIDWIDTH x=(areaWidth-(len(str)*width))>>1 for one=1 to len(str) a=asc(mid$(str,one)) img=a+(bank*CHAR_BANK) if a<>32 and image exist(img)=1

307 paste image img,x+((one-1)*width),y,tran endif next one endif endfunction ERROR_OK function writeFontCharsRightAligned(str as string,y as DWORD,width as DWORD,_ bank as DWORD,tran as DWORD,areaWidth as DWORD) local one as DWORD local a as DWORD local img as DWORD local pos as DWORD if str<>"" pos=areaWidth-(len(str)*width) a=writeFontChars(str,pos,y,width,bank,tran) else a=ERROR_NOCHARS endif endfunction a function writeFontChars(str as string,_ x as DWORD,_ y as DWORD,_ width as DWORD,_ bank as DWORD,_ tran as DWORD) local one as DWORD local a as DWORD local img as DWORD

if str<>"" for one=1 to len(str) a=asc(mid$(str,one)) img=a+(bank*CHAR_BANK) if a<>32 and image exist(img)=1 paste image img,x+((one-1)*width),y,tran endif next one else exitfunction ERROR_NOCHARS endif endfunction ERROR_OK remstart Hex is 4 lots of 32bit RGB colours in hex "FFFFFFFF.." The first 4 are the top left colours The next 4 are the bottom right colours The next 4 are the middle colours The last 4 group of 4 is the background colour AltImage allows an image to be pasted in the main button area remend function createButton(imb as DWORD,_ bmp as DWORD,_ str as string,_ width as DWORD,_ height as DWORD,_ bank as DWORD,_ hex as string,_ lWidth as DWORD,_ lHeight as DWORD,_ altImage as DWORD) local backCol as DWORD local boxCol as DWORD local bWidth as DWORD if str="" then exitfunction ERROR_NOCHARS if len(hex)<>8*4 then exitfunction ERROR_COLOURSIZE if image exist(imb)=1 then exitfunction ERROR_IMAGEEXIST if altImage<>0 if image exist(altImage)=0 then exitfunction ERROR_IMAGEMISSING endif bWidth=len(str)*width if lWidth=0 and lHeight=0

308 lWidth=bWidth+4 lHeight=width+4 else if lWidth0 paste image altImage,0,0 endif ink hexToInt(mid$(hex,1,8)),backCol line 0,0,lWidth,0 line 0,0,0,lHeight ink hexToInt(mid$(hex,9,8)),backcol line 0,lHeight,lWidth,lHeight line lWidth,0,lWidth,lHeight+1:Rem because the line command always finishes 1 pixel short, we need to add one here rem Now paste the text on writeFontCharsCentre(str,(lHeight-height)>>1,width,bank,1,lWidth) get image imb,0,0,lWidth+1,lHeight+1,1:Rem always finishes 1 pixel short set current bitmap 0 delete bitmap bmp endfunction ERROR_OK function createFont(width as DWORD,_ height as DWORD,_ charXWidth as DWORD,_ charYWidth as DWORD,_ charXOffset as DWORD,_ charYOffset as DWORD,_ charXStep as DWORD,_ charYStep as DWORD,_ filename as string,_ chars as string,_ bank as DWORD,_ tempBitmap as DWORD) local xLoop as DWORD local yLoop as DWORD local pos as DWORD local one as DWORD if file exist(filename)=0 then exitfunction ERROR_FILENOTFOUND if bitmap exist(tempBitmap) then delete bitmap tempBitmap load bitmap filename,tempBitmap set current bitmap tempBitmap pos=1 for y=0 to height-1 for x=0 to width-1 if pos>len(chars) set current bitmap 0 delete bitmap tempBitmap exitfunction ERROR_FEWCHARS endif one=asc(mid$(chars,pos))+(bank*CHAR_BANK) if image exist(one)=0 get image one,_ (x*charXStep)+charXOffset,_ (y*charYStep)+charYOffset,_ (x*charXStep)+charXOffset+(charXWidth),_ (y*charYStep)+charYOffset+(charYWidth),_ 1 endif inc pos next x

309 next y set current bitmap 0 delete bitmap tempBitmap endfunction ERROR_OK

310 Bouncing Lines I

This is the first version of my Bouncing Lines routine. Rem Project: Lines Rem Created: 09/09/2003 19:36:22 Rem ***** Main Source File ***** SYNC On SYNC RATE 0 #constant MAXLINES 4*4 #constant MAXDOTS 128 screenWidth=screen width() screenHeight=screen height() type lines x as float y as float dx as float dy as float r as integer g as integer b as integer endtype type dots x as float y as float dy as float dx as float endtype dim l(MAXLINES) as lines dim d(MAXDOTS) as dots for x=1 to MAXDOTS d(x).x=0.0 d(x).y=0.0 d(x).dy=0.0 next x randomize timer() for x=1 to MAXLINES l(x).x=rnd(screenWidth>>1)*1.0 l(x).y=rnd(screenHeight>>1)*1.0 l(x).dx=(rnd(4)+1)*0.5 l(x).dy=(rnd(4)+1)*0.5 l(x).r=rnd(254)+1 l(x).g=rnd(254)+1 l(x).b=rnd(254)+1 next x cls 0 print "Please Wait" sync sync bmSize=screenWidth*2 create bitmap 1,bmSize,bmSize set current bitmap 1 for x=0 to bmSize step 8 for y=0 to bmSize step 8 box x,y,x+8,y+8,_ rgb(rnd(64),rnd(64),rnd(64)),_ rgb(rnd(64),rnd(64),rnd(64)),_ rgb(rnd(64),rnd(64),rnd(64)),_ rgb(rnd(64),rnd(64),rnd(64)) next y next x get image 1,0,0,bmSize-1,bmSize-1,1 set current bitmap 0

311 draw sprites first sprite 1,screenWidth>>1,screenHeight>>1,1 offset sprite 1,bmSize>>1,bmSize>>1 angle#=0.0 dotColour=rgb(rnd(256-32)+32,rnd(256-32)+32,rnd(256-32)+32) count=0 dir#=0.5 loopc=0 sa#=0.0 cls 0 sync do sprite 1,(screenWidth>>1)+(256*sin(sa#)),(screenHeight>>1)+(256*cos(sa#)),1 rotate sprite 1,angle# sa#=wrapvalue(sa#+1.0) angle#=wrapvalue(angle#+dir#) for x=1 to MAXLINES ink rgb(l(x).r,l(x).g,l(x).b),0 if xscreenWidth-1 l(x).x=screenWidth-1 l(x).dx=-l(x).dx if rnd(100)>75 l(x).dx=(rnd(4)+1)*0.5 endif l(x).r=(l(x).r+1) && 255 l(x).g=(l(x).g+1) && 255 l(x).b=(l(x).b+1) && 255 endif endif inc l(x).y,l(x).dy if l(x).y<0 l(x).y=0 l(x).dy=-l(x).dy if rnd(100)>40 l(x).dx=(rnd(4)+1)*0.5 endif l(x).r=(l(x).r+1) && 255 l(x).g=(l(x).g+1) && 255 l(x).b=(l(x).b+1) && 255 else if l(x).y>screenHeight-1 l(x).y=screenHeight-1 l(x).dy=-l(x).dy if rnd(100)<30 l(x).dy=(rnd(4)+1)*0.5 endif endif l(x).r=(l(x).r+1) && 255 l(x).g=(l(x).g+1) && 255 l(x).b=(l(x).b+1) && 255 endif next x for index=1 to MAXDOTS if d(index).x>0.0 ink dotColour,0 box d(index).x,d(index).y,d(index).x+2,d(index).y+2 inc d(index).y,d(index).dy inc d(index).x,d(index).dx

312 if d(index).y>screenHeight or d(index).x>screenWidth d(index).x=0.0 d(index).y=0.0 endif else if rnd(10)<3 d(index).x=rnd(screenWidth)*1.0 d(index).y=0.0 d(index).dy=(rnd(5)+1)*1.0 d(index).dx=((rnd(5)-1)*1.0)+0.5 endif endif next index repeat until spacekey()=0 inc count if count>screenHeight*4 dotColour=rgb(rnd(256-32)+32,rnd(256-32)+32,rnd(256-32)+32) count=0 endif if loopc>2000 loopc=0 dir#=0.0-dir# if dir#>0.0 inc dir#,0.5 if dir#>5.0 dir#=0.5 endif endif else inc loopc endif text 0,0,"FPS:"+str$(screen fps())+" Speed:"+str$(dir#)+" Angle #2:"+str$(sa#) sync loop

313 Dark Game SDK

314 A Game

C code for a game that I wrote ages ago. It might have been some version of Spots #include "Windows.h" #include "DarkSDK.h" #include "Player.hpp" #include "LoadGraphics.h" #include "GetDisplay.h" #include "FindFree.h" #include "Deletes.h" #include "stdio.h" #include "stdlib.h" #include "CBFont.hpp" #include "globstruct.h" #include "..\Resource\Resource.h" #include "Winver.h" #include "CResource.h" #include "CPointer.h" #include "CFade.h" struct __GRAPHICS graphics[]={ {TILES_PATH, "Shape1.BMP", IMAGE_SHAPE1, TYPE_IMAGE}, {TILES_PATH, "Shape2.BMP", IMAGE_SHAPE2, TYPE_IMAGE}, {TILES_PATH, "Shape3.BMP", IMAGE_SHAPE3, TYPE_IMAGE}, {TILES_PATH, "Shape4.BMP", IMAGE_SHAPE4, TYPE_IMAGE}, {TILES_PATH, "Shape5.BMP", IMAGE_SHAPE5, TYPE_IMAGE}, {TILES_PATH, "Shape6.BMP", IMAGE_SHAPE6, TYPE_IMAGE}, {TILES_PATH, "Shape7.BMP", IMAGE_SHAPE7, TYPE_IMAGE}, {TILES_PATH, "Shape8.BMP", IMAGE_SHAPE8, TYPE_IMAGE}, {FONT_PATH, "P1GameFont.BMP", IMAGE_P1GAMEFONT, TYPE_IMAGE}, {FONT_PATH, "P2GameFont.BMP", IMAGE_P2GAMEFONT, TYPE_IMAGE}, {GRAPHICS_PATH, "Selector.BMP", IMAGE_SELECTOR, TYPE_IMAGE}, {GRAPHICS_PATH, "TimeLeft.BMP", IMAGE_TIMELEFTEXT, TYPE_IMAGE}, {GRAPHICS_PATH, "Colon.BMP", IMAGE_TIMELEFTC, TYPE_IMAGE}, {GRAPHICS_PATH, "0.BMP", IMAGE_TIMELEFT0, TYPE_IMAGE}, {GRAPHICS_PATH, "1.BMP", IMAGE_TIMELEFT1, TYPE_IMAGE}, {GRAPHICS_PATH, "2.BMP", IMAGE_TIMELEFT2, TYPE_IMAGE}, {GRAPHICS_PATH, "3.BMP", IMAGE_TIMELEFT3, TYPE_IMAGE}, {GRAPHICS_PATH, "4.BMP", IMAGE_TIMELEFT4, TYPE_IMAGE}, {GRAPHICS_PATH, "5.BMP", IMAGE_TIMELEFT5, TYPE_IMAGE}, {GRAPHICS_PATH, "6.BMP", IMAGE_TIMELEFT6, TYPE_IMAGE}, {GRAPHICS_PATH, "7.BMP", IMAGE_TIMELEFT7, TYPE_IMAGE}, {GRAPHICS_PATH, "8.BMP", IMAGE_TIMELEFT8, TYPE_IMAGE}, {GRAPHICS_PATH, "9.BMP", IMAGE_TIMELEFT9, TYPE_IMAGE}, {GRAPHICS_PATH, "Star.BMP", IMAGE_STAR, TYPE_IMAGE}, {GRAPHICS_PATH, "Border.BMP", IMAGE_BORDER, TYPE_IMAGE}, {MENU_PATH, "SinglePlayer.BMP", MENU_SINGLEPLAYER, TYPE_IMAGE}, {MENU_PATH, "MultiPlayer.BMP", MENU_MULTIPLAYER, TYPE_IMAGE}, {MENU_PATH, "HiscoreTable.BMP", MENU_HISCORETABLE, TYPE_IMAGE}, {MENU_PATH, "Help.BMP", MENU_HELP, TYPE_IMAGE}, {MENU_PATH, "Options.BMP", MENU_OPTIONS, TYPE_IMAGE}, {MENU_PATH, "QuitGame.BMP",

315 MENU_QUITGAME, TYPE_IMAGE}, {MENU_PATH, "Continue.BMP", IMAGE_CONTINUE, TYPE_IMAGE}, {GRAPHICS_PATH, "Shapies.X", OBJECT_TITLE, TYPE_X}, {SINGLEPLAYER_PATH, "SinglePlayer_Standard.BMP", MENU_STANDARD, TYPE_IMAGE}, {SINGLEPLAYER_PATH, "SinglePlayer_Timed.BMP", MENU_TIMED, TYPE_IMAGE}, {SINGLEPLAYER_PATH, "SinglePlayer_VS.BMP", MENU_VS, TYPE_IMAGE}, {MULTIPLAYER_PATH, "MultiPlayer_Deathmatch.BMP", MENU_DEATHMATCH, TYPE_IMAGE}, {MULTIPLAYER_PATH, "MultiPlayer_Cooperative.BMP", MENU_COOPERATIVE, TYPE_IMAGE}, {MULTIPLAYER_PATH, "MultiPlayer_Timed.BMP", MENU_MTIMED, TYPE_IMAGE}, {GRAPHICS_PATH, "Arrow.BMP", IMAGE_ARROW, TYPE_IMAGE}, {GRAPHICS_PATH, "Ripple.BMP", IMAGE_RIPPLE, TYPE_IMAGE}, {GRAPHICS_PATH, "Time.BMP", IMAGE_TIMESUP, TYPE_IMAGE}, {GRAPHICS_PATH, "YouLose.BMP", IMAGE_YOULOSE, TYPE_IMAGE}, {GRAPHICS_PATH, "YouWin.BMP", IMAGE_YOUWIN, TYPE_IMAGE}, {GRAPHICS_PATH, "FinalScore.BMP", IMAGE_FINALSCORE, TYPE_IMAGE}, {SFX_PATH, "Pop.WAV", SFX_POP, TYPE_SFX}, {SFX_PATH, "LevelUp.WAV", SFX_LEVELUP, TYPE_SFX}, {NULL, NULL, 0, TYPE_END}, }; struct __BORDERINFO borderInfo; DWORD m_gameShapeWidth; // These are global DWORD m_halfSelectorWidth,m_halfSelectorHeight; struct __CONTROLMETHOD controlMethod[MAX_PLAYERS][3]; struct __MENUITEMS menuItems[MAX_MENUITEMS]; struct __BALL balls[MAX_BALLS]; CPlayer *player[MAX_PLAYERS]; CPointer mousePointer; CFade fade; void DarkSDK(void) { struct __DISPLAY display; struct __GAMESPEED gameSpeed; struct __TIMER timer; bool fileNotPresent,cont; CResource resource; register double move; DWORD middle,timeSprite; DWORD gameMode,xPos,yPos,gameState,menuResult; DWORD borderX1,borderX2,borderY1,borderY2,timerPositionX,timerPositionY,loop,level; DWORD timeLeftSprites[TIME_WIDTH+1]; DWORD playerControls[MAX_PLAYERS]; CBFont programVersion; char programVersionText[256]; char thisProgram[MAX_PATH+1]; SecureZeroMemory(&thisProgram,sizeof(thisProgram)); SecureZeroMemory(&programVersionText,sizeof(programVersionText)); GetModuleFileName(NULL,(char *) &thisProgram,sizeof(thisProgram)-1);

316 if (resource.loadLibrary(DLL_PATH "RESOURCE.DLL")==false) { MessageBox(NULL,"resource not loaded","*",MB_OK); return; } resource.getStringResource(IDS_PROGRAMVERSION,(char *) &programVersionText,sizeof(programVersionText)-1); getProgramVersion((char *) &thisProgram,(char *) &programVersionText[strlen(programVersionText)]); dbSyncOn(); dbSyncRate(0); dbDrawSpritesLast(); dbDrawToFront(); dbHideMouse(); dbAutoCamOff(); dbSetCameraRange(0,(float) 0.2,(float) 15000.0); if (isHFTimerPresent((struct __GAMESPEED *) &gameSpeed)==false) { MessageBox(NULL,"no HT timer","*",MB_OK); return; } if (setDisplayMode((struct __DISPLAY *) &display,&fileNotPresent)!=DISPLAY_OK) { MessageBox(NULL,"Display Error","*",MB_OK); return; } // Setup control methods for both players for (middle=PLAYER1; middle<=PLAYER2; middle++) { setupControlMethods((struct __CONTROLMETHOD *) &controlMethod[middle- PLAYER1],middle); if (fileNotPresent) { // The initial settings file is not present, so we can save the setup data and set the initial control methods to // keyboard writeControlMethods((struct __CONTROLMETHOD *) &controlMethod,middle- PLAYER1); playerControls[middle-PLAYER1]=CONTROL_KEYBOARD; } else { playerControls[middle-PLAYER1]=CONTROL_KEYBOARD; } } if (loadGraphics(true,(struct __GRAPHICS *) &graphics)!=LOADERROR_OK) { MessageBox(NULL,"loading error","*",MB_OK); return; } // Load the title font if (programVersion.loadFontFromConfig(FONT_PATH "TITLE.INI",FONT_PATH)==false) { MessageBox(NULL,"Load error","*",MB_OK); return; } // Create sprites------dbSprite(SPRITE_CONTINUE,display.x-dbGetImageWidth(IMAGE_CONTINUE)-1,display.y- dbGetImageHeight(IMAGE_CONTINUE),IMAGE_CONTINUE); dbSetSpritePriority(SPRITE_CONTINUE,4); dbHideSprite(SPRITE_CONTINUE); // End of sprite creation------

317 // Initialise mouse pointer------mousePointer.initialise(); // End of initialuse mouse pointer------// Create fade sprite------fade.initialise(display.x,display.y); // End of create fade------programVersion.setCharSet(0.5,0.5); programVersion.createText((char *) &programVersionText,0.0,0.0,false); // Get image sizes m_gameShapeWidth=dbGetImageWidth(IMAGE_SHAPE1); // Check to make sure all shapes are the same size borderInfo.width=dbGetImageWidth(IMAGE_BORDER); borderInfo.height=dbGetImageHeight(IMAGE_BORDER); borderInfo.scoreWidth=SCORE_WIDTH*(BLOCK_STEP+1); m_halfSelectorWidth=dbGetImageWidth(IMAGE_SELECTOR)>>1; m_halfSelectorHeight=dbGetImageHeight(IMAGE_SELECTOR)>>1;

// Position menu items SecureZeroMemory(&menuItems,sizeof(menuItems)); positionMenuItems((struct __MENUITEMS *) &menuItems,(struct __DISPLAY *) &display); // Position the title dbPositionObject(OBJECT_TITLE,0.0,0.0,0.0); dbHideObject(OBJECT_TITLE); // Position the backdrop dbMakeObjectBox(OBJECT_BACKDROP,300.0,300.0,1.0); //-5800.0); dbPositionObject(OBJECT_BACKDROP,0.0,0.0,120.0); dbTextureObject(OBJECT_BACKDROP,IMAGE_RIPPLE); dbSetObjectFilter(OBJECT_BACKDROP,2); //dbHideObject(OBJECT_BACKDROP); // Setup the title balls for (middle=0; middlehWnd,IDOK); return; } else if (menuResult==menuItems[MENU_INDEX_SINGLEPLAYER].subMenuItems[MENU_INDEX_STANDARD].sprite ) { gameMode=MODE_SINGLEPLAYER_STANDARD;

318 MessageBox(NULL,"Single","*",MB_OK); } else if (menuResult==menuItems[MENU_INDEX_SINGLEPLAYER].subMenuItems[MENU_INDEX_TIMED].sprite) { gameMode=MODE_SINGLEPLAYER_TIMED; } else if (menuResult==menuItems[MENU_INDEX_SINGLEPLAYER].subMenuItems[MENU_INDEX_VS].sprite) { MessageBox(NULL,"Not implemented","*",MB_OK); return; } else if (menuResult==menuItems[MENU_INDEX_MULTIPLAYER].subMenuItems[MENU_INDEX_DEATHMATCH].sprit e) { gameMode=MODE_MULTIPLAYER_DEATHMATCH; } else if (menuResult==menuItems[MENU_INDEX_MULTIPLAYER].subMenuItems[MENU_INDEX_COOPERATIVE].spri te) { gameMode=MODE_MULTIPLAYER_COOP; } else if (menuResult==menuItems[MENU_INDEX_MULTIPLAYER].subMenuItems[MENU_INDEX_MTIMED].sprite) { gameMode=MODE_MULTIPLAYER_TIMED; } dbFlushVideoMemory(); dbCLS(0); player[PLAYER1]=(CPlayer *) new CPlayer; if (player[PLAYER1]==NULL) { } else { } if (gameMode==MODE_MULTIPLAYER_DEATHMATCH || gameMode==MODE_MULTIPLAYER_TIMED || gameMode==MODE_MULTIPLAYER_COOP) { player[PLAYER2]=(CPlayer *) new CPlayer; if (player[PLAYER2]==NULL) { } } else { player[PLAYER2]=NULL; } switch (gameMode) { case MODE_SINGLEPLAYER_STANDARD: // Setup the single player standard mode level=1; borderX1=(display.x-borderInfo.width)>>1; borderY1=(display.y-borderInfo.height)>>1;

timerPositionX=(borderX1-dbGetImageWidth(IMAGE_TIMELEFTEXT))>>1; timerPositionY=(display.y-dbGetImageHeight(IMAGE_TIMELEFTEXT))>>1;

319 xPos=(borderX1-(TIME_WIDTH*(TIME_STEPVALUE+1)))>>1;

player[PLAYER1]->setOffset(borderX1,borderY1, IMAGE_P1FONTTEXTS, playerControls[PLAYER1], PLAYER1,true); player[PLAYER1]->clearGrid(level); player[PLAYER1]->ShowBorder(); player[PLAYER1]->setupGrid();

break; case MODE_MULTIPLAYER_DEATHMATCH : // Deathmatch game case MODE_SINGLEPLAYER_TIMED : case MODE_MULTIPLAYER_TIMED : // Setup time demo level=(gameMode==MODE_MULTIPLAYER_DEATHMATCH ? 1 : 10); if (gameMode==MODE_SINGLEPLAYER_TIMED) {

borderX1=(display.x-borderInfo.width)>>1; borderY1=(display.y-borderInfo.height)>>1;

timerPositionX=(borderX1-dbGetImageWidth(IMAGE_TIMELEFTEXT))>>1; timerPositionY=(display.y-dbGetImageHeight(IMAGE_TIMELEFTEXT))>>1;

xPos=(borderX1-(TIME_WIDTH*(TIME_STEPVALUE+1)))>>1; } else { borderX1=8; borderY1=(display.y-borderInfo.height)>>1;

borderX2=display.x-borderInfo.width-8; borderY2=borderY1;

if (gameMode==MODE_MULTIPLAYER_TIMED) { timerPositionX=(display.x-dbGetImageWidth(IMAGE_TIMELEFTEXT))>>1; // (borderX2-borderX1)>>1; timerPositionY=(display.y-dbGetImageHeight(IMAGE_TIMELEFTEXT))>>1;

xPos=(display.x-(TIME_WIDTH*(TIME_STEPVALUE+1)))>>1; } } for (loop=PLAYER1; loop<=(DWORD) (gameMode==MODE_MULTIPLAYER_TIMED ||

gameMode==MODE_MULTIPLAYER_DEATHMATCH ? PLAYER2 : PLAYER1); loop++)

320 { player[loop-PLAYER1]->setOffset((loop==PLAYER1 ? borderX1 : borderX2), (loop==PLAYER1 ? borderY1 : borderY2), (loop==PLAYER1 ? IMAGE_P1FONTTEXTS : IMAGE_P2FONTTEXTS), playerControls[loop-PLAYER1], loop, (loop==PLAYER1 ? true : false));

player[loop-PLAYER1]->clearGrid(level); player[loop-PLAYER1]->ShowBorder();

player[loop-PLAYER1]->setupGrid(); } // Now to set the timer positions if (gameMode!=MODE_MULTIPLAYER_DEATHMATCH) { timer.timer=(double) 5.0*60.0; timeSprite=findFreeSprite(); dbSprite(timeSprite,timerPositionX,timerPositionY,IMAGE_TIMELEFTEXT); dbSetSprite(timeSprite,0,1);

yPos=timerPositionY+dbSpriteHeight(timeSprite)+8; for (loop=0; loop

dbSprite(timeLeftSprites[loop], xPos+(loop*(TIME_STEPVALUE+1)), yPos, IMAGE_TIMELEFT0); dbSetSprite(timeLeftSprites[loop],0,1); } } break; }; for (middle=0;middle<10; middle++) { move=updateTimer((struct __GAMESPEED *) &gameSpeed); } gameState=STATE_NONE; timer.timer=10.0; while (LoopSDK() && gameState==STATE_NONE) {

321 if (dbEscapeKey()) return; dbScrollObjectTexture(OBJECT_BACKDROP,(float) (0.01*move),(float) (0.01*move)); dbText(0,0,dbStr(dbScreenFPS())); dbText(200,0,dbStr((float) move)); if (gameMode==MODE_SINGLEPLAYER_TIMED || gameMode==MODE_MULTIPLAYER_TIMED) { displayTimer(timer.timer,(DWORD *) &timeLeftSprites); timer.timer-=0.1*move; if (timer.timer<=0.0) { timer.timer=0.0; gameState=STATE_TIMEISUP; player[PLAYER1]->createEndOfGameGraphic(IMAGE_TIMESUP); if (player[PLAYER2]) { player[PLAYER2]- >createEndOfGameGraphic(IMAGE_TIMESUP); } } } for (middle=0; middle<(DWORD) (gameMode==MODE_SINGLEPLAYER_STANDARD ||

gameMode==MODE_SINGLEPLAYER_TIMED ? 1 : MAX_PLAYERS); middle++) { // Process player 1 if (player[middle]->returnPlayerState()==STATE_NONE) { player[middle]->movePlayer(move); player[middle]->Update(move,player[(middle==0 ? MAX_PLAYERS-1 : 0)]); } else { gameState=STATE_GAMEOVER; } } dbSync(); move=updateTimer((struct __GAMESPEED *) &gameSpeed); }; fade.fade(true,(struct __GAMESPEED *) &gameSpeed,moveEndOfGameSprites); //End of game------// Make sure the sprite reaches the top do { cont=player[PLAYER1]->moveEndOfGameGraphicUp(move); if (cont==false && player[PLAYER2]) { cont=player[PLAYER2]->moveEndOfGameGraphicUp(move); } dbSync(); move=updateTimer((struct __GAMESPEED *) &gameSpeed); } while (cont==false); // Display final score------player[PLAYER1]->displayFinalScore(); if (player[PLAYER2]) { player[PLAYER2]->displayFinalScore(); } displayContinueButton((struct __GAMESPEED *) &gameSpeed);

322 for (middle=0; middlemoveEndOfGameGraphicUp(speed); if (player[PLAYER2]) { player[PLAYER2]->moveEndOfGameGraphicUp(speed); } } void displayTimer(double time,DWORD *sprites) { char temp[TIME_WIDTH+1]; register DWORD loop; register DWORD mins,secs;

SecureZeroMemory(&temp,sizeof(temp)); mins=(DWORD) (time/60.0); secs=(DWORD) ((double) (time-(double) (mins*60.0))); sprintf((char *) &temp,"%02ld:%02ld",mins,secs); for (loop=0; loop

dbLine(0,0,borderInfo->width-borderInfo->scoreWidth-8,0); dbLine(0,1,borderInfo->width-borderInfo->scoreWidth-8,1); dbLine(width-1,0,width-4,0); dbLine(width-2,1,width-4,1); dbLine(width-1,0,width-1,height-1); dbLine(width-2,0,width-2,height-1); dbLine(0,height-1,width-1,height-1); dbLine(0,height-2,width-2,height-2); dbLine(0,0,0,height-1); dbLine(1,0,1,height-1); dbGetImage(IMAGE_BORDER,0,0,width-1,height-1,1); dbSetCurrentBitmap(0);

323 DELETE_BITMAP(tempBitmap); *(middle)=((display->y-height)>>1); dbSaveImage("Border.BMP",IMAGE_BORDER); } else { DELETE_IMAGE(IMAGE_BORDER); } } void positionMenuItems(struct __MENUITEMS *menu,struct __DISPLAY *display) { DWORD menuItems[]={MENU_QUITGAME, MENU_OPTIONS, MENU_HELP, MENU_HISCORETABLE, MENU_MULTIPLAYER, MENU_SINGLEPLAYER, 0 }; DWORD single_subMenu[]={MENU_STANDARD,MENU_TIMED,MENU_VS,0}; DWORD multiplayer_subMenu[]={MENU_DEATHMATCH,MENU_COOPERATIVE,MENU_MTIMED,0}; register DWORD x,y,yPos,temp; register DWORD *ptr;

y=display->y-MENU_HEIGHT-1; x=0; while (menuItems[x]) { menu[x].menuItem.image=menuItems[x]; menu[x].menuItem.sprite=findFreeSprite(); dbSprite(menu[x].menuItem.sprite,4,y,menu[x].menuItem.image); dbSetSprite(menu[x].menuItem.sprite,0,1); dbHideSprite(menu[x].menuItem.sprite); switch (menuItems[x]) { case MENU_SINGLEPLAYER : // Process single/multi player options case MENU_MULTIPLAYER : ptr=(menuItems[x]==MENU_SINGLEPLAYER ? (DWORD *) &single_subMenu : (DWORD *) &multiplayer_subMenu); temp=0; yPos=y; while ((ptr) && (*(ptr)!=0)) //for (temp=0; temp<3; temp++) { menu[x].subMenuItems[temp].image=*(ptr); menu[x].subMenuItems[temp].sprite=findFreeSprite(); dbSprite(menu[x].subMenuItems[temp].sprite, 4+MENU_WIDTH+4, yPos, menu[x].subMenuItems[temp].image); dbSetSprite(menu[x].subMenuItems[temp].sprite,0,1); dbOffsetSprite(menu[x].subMenuItems[temp].sprite,0,0); dbHideSprite(menu[x].subMenuItems[temp].sprite); yPos+=MENU_HEIGHT+1; ptr++; temp++;

324 } break; }; y-=MENU_HEIGHT-1; x++; } } void randomBall(struct __BALL *balls) { balls->x=(double) (-10.0+(dbRnd(500)/10.0)); balls->y=(double) (200.0+(dbRnd(500)/10.0)); balls->z=(double) (-50.0+(dbRnd(1000)/10.0)); balls->speed=(double) (2.0+(dbRnd(100)/10.0)); } bool getProgramVersion(char *fileName,char *version) { VS_FIXEDFILEINFO *pVersion=NULL; register unsigned int cb; DWORD dwHandle; char FileVersionBuffer[1024]; // TODO: Add extra initialization here cb=GetFileVersionInfoSize(fileName,&dwHandle); if (cb>0) { if (cb>sizeof(FileVersionBuffer)) { cb=sizeof(FileVersionBuffer); } if (GetFileVersionInfo(fileName,0,cb,&FileVersionBuffer)) { pVersion=NULL; if (VerQueryValue(&FileVersionBuffer,"\\",(VOID **) &pVersion,&cb) && pVersion!=NULL) { sprintf(version,"%d.%d.%d.%d", HIWORD(pVersion->dwProductVersionMS), LOWORD(pVersion->dwProductVersionMS), HIWORD(pVersion->dwProductVersionLS), LOWORD(pVersion->dwProductVersionLS)); return (true); } } } return (false); } void displayContinueButton(struct __GAMESPEED *gameSpeed) { register double speed; mousePointer.show(); dbShowSprite(SPRITE_CONTINUE); speed=updateTimer(gameSpeed); while (LoopSDK() && mousePointer.getContinueCollision()==false) { mousePointer.moveMousePointer(speed); speed=updateTimer(gameSpeed); dbSync(); }; dbHideSprite(SPRITE_CONTINUE); }

325 Shadow of the Beast

C conversion of Richard Davey's DBPro routine, which was originally written in DBPro. This was one of many routines that I converted as soon as the GSDK came out. /* Project: Shadow of the Beast Parallax Scrolling Demo Authors: Frederic Cordier ([email protected]) Richard Davey ([email protected] Nicholas Kingsley URL: http://cordierfr.free.fr/html/index_english.html http://www.darkbasicpro.com Date: 18th Feb. 2003 Version: v2.0 - DarkBASIC Professional (Patch 3.1) Version : 3.0 - Upgraded for use in DarkSDK */ #define INITGUID #include #include "DarkSDK.h" #include "Bass.h" #include "stdio.h" #define WIDTH800 #define HEIGHT 600 float xspeed; float xscroll,xscroll1,xscrollb,xscroll2,xscroll3,xscroll4,xscroll5,xscroll5b,xscroll6; char *files[]={ "media\\bgd1_ciel.png", "media\\bgd2_montagnes.png", "media\\bgd3_sol1.png", "media\\bgd4_sol2.png", "media\\bgd5_sol3.png", "media\\sprite_nuages1.bmp", "media\\sprite_nuages2.bmp", "media\\sprite_nuages3.bmp", "media\\sprite_nuages4.bmp", "media\\sprite_barriere.bmp", "media\\fireworks.png", "media\\sprite_arbre.bmp", "media\\scrolltext.png" }; #define SPEEDSTEP (double) 1000.0 typedef struct __GAMESPEED { LARGE_INTEGER t1; LARGE_INTEGER t2; LARGE_INTEGER t3; LARGE_INTEGER t4; LARGE_INTEGER t9; } __GAMESPEED; struct __GAMESPEED gameSpeed; void inline pasteImage(int img,int x,int y,int transp); double updateTimer(void); void DarkGDK(void) { register int loop,x,y,dx,dy; HMUSIC music; RECT rect; char buffer[32]={0}; double speed; dbSetDisplayMode(640,480,16); dbSetWindowTitle("Shadow Of The Beast - Frederic Cordier & Richard Davey. C DarkSDK Conversion by Nicholas Kingsley"); dbSetWindowSize(WIDTH,HEIGHT);

326 GetWindowRect(GetDesktopWindow(),&rect); dx=rect.right-rect.left; dy=rect.bottom-rect.top; // Re-centre x=(dx-WIDTH)>>1; y=(dy-HEIGHT)>>1; dbSetWindowPosition(x,y); dbSyncRate(0); dbSyncOn(); dbHideMouse(); for (loop=1; loop<=13; loop++) { dbLoadImage(files[loop-1],loop,1); } xspeed = 2.0; xscroll5 = (float) (dbRnd(640)+640); xscroll5b = (float) (dbRnd(640)+640); xscroll=xscroll1=xscrollb=xscroll2=xscroll3=xscroll4=xscroll6=0.0; dbInk(dbRGB(255,255,255),0); music=NULL; if (BASS_Init(1,44000,BASS_DEVICE_SPEAKERS | BASS_DEVICE_3D,0,NULL)) { if ((music=BASS_MusicLoad(0,"media\\b-title.mod",0,0, BASS_SAMPLE_LOOP | BASS_SAMPLE_3D,0))!=NULL) { BASS_ChannelPlay(music,true); } } gameSpeed.t2.QuadPart=0; gameSpeed.t3.QuadPart=0; gameSpeed.t4.QuadPart=0; gameSpeed.t9.QuadPart=(__int64) SPEEDSTEP; speed=updateTimer(); while (LoopGDK()) { if (dbEscapeKey()) { if (music) { BASS_MusicFree(music); BASS_Free(); } return; } xscroll+=(float) (xspeed*speed*50.0); xspeed=(xscroll>=320.0 ? (float) -2.0 : \ xscroll<=-960.0 ? (float) 2.0 : xspeed); xscrollb=(float) (xscroll*2.0); xscrollb=(xscrollb<-640.0 ? (float) -640.0 : \ xscrollb>0.0 ? (float) 0.0 : xscrollb); xscroll1-=(float) (speed*20.0); if (xscroll1<=-640.0) { xscroll1=0.0; } xscroll2-=(float) (2*speed*30.0); if (xscroll2<=-640.0) { xscroll2=0.0; } xscroll3-=(float) (3*speed*30.0); if (xscroll3<=-640.0)

327 { xscroll3+=640.0; } xscroll4-=(float) (4*speed*30.0); if (xscroll4<=-640.0) { xscroll4+=640.0; } xscroll5-=(float) (5*speed*30.0); if (xscroll5<=-640.0) { xscroll5+=1280.0; } xscroll5b-=(float) (2*speed*30.0); if (xscroll5b<=-640.0) { xscroll5b+=1280.0; } xscroll6-=(float) (6*speed*30.0); if (xscroll6<-640.0) { xscroll6+=640.0; } dbPasteImage(1,0,0,0);

pasteImage(2,(int) xscroll1,200,0); pasteImage(3,(int) xscroll2,420,0); pasteImage(4,(int) xscroll3,430,0); pasteImage(5,(int) xscroll4,450,0); dbPasteImage(10,(int) xscroll5,440,1); pasteImage(6,(int) xscroll6,0,1); pasteImage(7,(int) xscroll4,82,1); pasteImage(8,(int) xscroll3,120,1); pasteImage(9,(int) xscroll2,138,1); dbPasteImage(12,(int) xscroll5b,140,1); dbPasteImage(11,(int) xscrollb,0,1); dbPasteImage(13,(int) (xscrollb+640),0,1); sprintf(buffer,"FPS:%d Rate:%4.4f",dbScreenFPS(),speed); dbText(0,0,buffer); dbSync(); speed=updateTimer(); } } void inline pasteImage(int img,int x,int y,int transp) { dbPasteImage(img,x,y,transp); dbPasteImage(img,x+640,y,transp); } double updateTimer(void) { register double move; register __int64 diff; QueryPerformanceCounter(&gameSpeed.t2); gameSpeed.t3.QuadPart++; diff=gameSpeed.t2.QuadPart-gameSpeed.t1.QuadPart; if (diff>=gameSpeed.t9.QuadPart) { move=(double) ((gameSpeed.t2.QuadPart-gameSpeed.t1.QuadPart)/(double) SPEEDSTEP)/gameSpeed.t3.QuadPart; gameSpeed.t1.QuadPart=gameSpeed.t2.QuadPart; gameSpeed.t3.QuadPart=0; gameSpeed.t4.QuadPart=1; } else {

328 if (gameSpeed.t4.QuadPart==0) { move=(double) ((diff/(double) SPEEDSTEP)/gameSpeed.t3.QuadPart); } } move/=2000.0; return (move>1.0 ? 1.0 : move); }

329 Lots of 3D Objects

C conversion of the Lots of 3D objects DBPro code. #define INITGUID #include #include "DarkSDK.h" #include "stdio.h" typedef struct __xyztype { float x; float y; float z; float a; float az; } __xyztype; struct __xyztype blob[10]; #define SPEEDSTEP (double) 1000.0 typedef struct __GAMESPEED { LARGE_INTEGER t1; LARGE_INTEGER t2; LARGE_INTEGER t3; LARGE_INTEGER t4; LARGE_INTEGER t9; } __GAMESPEED; struct __GAMESPEED gameSpeed; double updateTimer(void); void DarkGDK(void) { register int obj,fps,stat,x,s,o; register float xx,yy,zz,a,az,t; register float glide=0.0; register float spin=0.0; char buffer[256]; char *one; bool tog=false; register double speed; dbSetWindowTitle("Lots of fast 3D Objects - Converted to DarkSDK C by Nicholas Kingsley"); gameSpeed.t2.QuadPart=0; gameSpeed.t3.QuadPart=0; gameSpeed.t4.QuadPart=0; gameSpeed.t9.QuadPart=(__int64) SPEEDSTEP; stat=0; dbSyncOn(); dbSyncRate(0); dbSetImageColorKey(0,0,0); dbLoadImage("media\\backdrop.jpg",1,1); dbLoadImage("media\\sphereball.jpg",2,1); dbLoadImage("media\\logo.jpg",3,1); dbMakeObjectSphere(123,2000); dbScaleObject(12,100,100,100); dbPositionObject(123,0.0,0.0,1000.0); dbTextureObject(123,1); dbScaleObjectTexture(123,3,3); //300,300); // Make some objects for (o=1; o<=100; o++) { if ((o-((int) (o/10)*10))==1) { dbMakeObjectCube(o,128);

330 dbGhostObjectOn(o,2); dbTextureObject(o,3); } else { dbMakeObjectSphere(o,128,6,6); dbGhostObjectOn(o,2); dbSetObjectCull(o,0); dbTextureObject(o,2); } } // Set up some 'blobbies' for (s=0; s<=9; s++) { register int sc; blob[s].x=(float) (dbRnd(400)-200); blob[s].y=(float) (dbRnd(400)-200); blob[s].z=(float) (300+dbRnd(300)); blob[s].a=(float) dbRnd(359); blob[s].az=(float) dbRnd(359); obj=1+(s*10); sc=50+dbRnd(100); for (o=0; o<=9; o++) { dbScaleObject(obj+o,(float) sc,(float) sc,(float) sc); } } dbSetTextFont("Courier"); dbSetTextSize(18); dbInk(dbRGB(255,255,255),0); // Load music and loop it dbLoadMusic("media\\a.mp3",1); dbLoopMusic(1); dbSetMusicSpeed(1,10); dbLoadMusic("media\\b.mp3",1); dbLoopMusic(2); dbSetMusicSpeed(1,20); dbLoadMusic("media\\c.mp3",1); dbLoopMusic(3); dbSetMusicSpeed(1,30); dbLoadMusic("media\\d.mp3",1); dbLoopMusic(4); dbSetMusicSpeed(1,40); dbPositionCamera(0,0,0); dbRotateCamera(0,0,0); // The main loop speed=updateTimer(); speed=updateTimer(); s=0; o=0; while (LoopGDK() && !dbEscapeKey()) { // Soft glide glide=dbWrapValue(glide+(float) (0.25*speed)); dbScaleObject(123, (float) 130.0+cos(glide)*(float) 30.0, (float) 130.0+sin(glide)*(float) 30.0, (float) 10.0); // Spin spin=dbWrapValue(spin+(float) (0.25*speed)); // Object display

t=(float) s*(float) 20.0; obj=1+(s*10); xx=blob[s].x;blob[s].x+=cos(spin+(t*((float) speed*(float) 0.25))); yy=blob[s].y;blob[s].y+=cos(spin-(t*((float) speed*(float) 0.25))); zz=blob[s].z; a=blob[s].a; blob[s].a+=(float) 0.25*(float) speed; //.0; az=blob[s].az; blob[s].az+=(float) 0.25*(float) speed; //1.0; for (o=0; o<=9; o++) { register float r;

331 r=(o*(float) 36.0)+spin; dbPositionObject(obj+o, xx+(cos(r)*(float) 200.0), yy-(cos(r+a)*(float) 50.0), zz+(sin(r)*(float) 200.0)); dbRotateObject(obj+o,0,o*(float) 20.0,az); } // Description of demo fps=dbScreenFPS(); sprintf(buffer,"%ld POLYGONS PER SECOND (W=TOGGLE)",fps*7200); dbText(16,8,(char *) &buffer); sprintf(buffer,"SCREEN FPS : %d SYNC SPEED : %4.4lf",fps,speed); x=dbTextWidth(buffer); dbText(dbScreenWidth()-x-24,dbScreenHeight()-24,(char *) &buffer); one=dbInKey(); if (*(one)==(char) 'w') { if (!tog) { tog=true; stat=1-stat; if (stat==0) { for (register int o=1; o<=100; o++) { dbSetObjectWireframe(o,0); } dbSetObjectWireframe(123,0); dbBackdropOff(); } else { for (register int o=1; o<=100; o++) { dbSetObjectWireframe(o,1); } dbSetObjectWireframe(123,1); dbBackdropOn(); } } } else { tog=false; } dbSync(); speed=updateTimer(); s++; if (s>9) s=0; } } double updateTimer(void) { register double move; register __int64 diff; QueryPerformanceCounter(&gameSpeed.t2); gameSpeed.t3.QuadPart++; diff=gameSpeed.t2.QuadPart-gameSpeed.t1.QuadPart; if (diff>=gameSpeed.t9.QuadPart) { move=(double) ((gameSpeed.t2.QuadPart-gameSpeed.t1.QuadPart)/(double) SPEEDSTEP)/gameSpeed.t3.QuadPart; gameSpeed.t1.QuadPart=gameSpeed.t2.QuadPart; gameSpeed.t3.QuadPart=0; gameSpeed.t4.QuadPart=1; } else if (gameSpeed.t4.QuadPart==0) {

332 move=(double) ((diff/(double) SPEEDSTEP)/gameSpeed.t3.QuadPart); } move/=1000.0; return (move>1.0 ? 1.0 : move); }

333 Bouncing Lines III

This was the third incarnation of my Bouncing Lines program. No idea what happened to Bouncing Lines II (or even if it was ever written). #include "DarkSDK.h" #include "stdio.h" #include "stdlib.h" #define MAX_LINES 30 #define TEXT_SPEED 45.0 #define MESS_SPEED 70.0 bool loaded=false; typedef struct __lines { double x; double y; double dx; double dy; DWORD colour; } __lines; struct __lines lines[MAX_LINES]; #define SPEEDSTEP (double) 1000.0 typedef struct __GAMESPEED { LARGE_INTEGER t1; LARGE_INTEGER t2; LARGE_INTEGER t3; LARGE_INTEGER t4; LARGE_INTEGER t9; } __GAMESPEED; struct __GAMESPEED gameSpeed; double updateTimer(void); char message[4096]; double changeValue(double start,double *change,double move); double screenWidth,screenHeight; void DarkGDK ( void ) { register int loop; register double c1,c2,c3,c4,d1,d2,d3,d4,textSpeed,mY,mS; register double move; char t[256]; char *one; dbSyncOn(); dbSyncRate(0); dbSetDisplayMode(1024,768,32); dbSetWindowOn(); dbSetWindowTitle("Bouncing Lines III"); dbSetWindowPosition(0,0); dbLoadMusic("Beat_It.wma",1); dbPlayMusic(1); dbSetTextFont("Arial"); dbSetTextSize(20); dbSetTextToBold(); screenWidth=dbScreenWidth()*1.0; screenHeight=dbScreenHeight()*1.0; srand(GetTickCount()); if (QueryPerformanceCounter(&gameSpeed.t1)==false) { MessageBox(NULL,"Sorry, your computer doesn't have a high-frequency timer", "* Error *",MB_OK); return; }

334 ZeroMemory(&message,sizeof(message)); strcpy(message, " " "Welcome to the third iteration of my Bouncing Lines demo. This version has been" " written in DarkSDK by Nicholas Kingsley. The music thats playing was also" " written by me..... The code is just a small (and badly written) example of what" " the SDK can do.... Press 'L' to load a new iteration of this program...... "); gameSpeed.t2.QuadPart=0; gameSpeed.t3.QuadPart=0; gameSpeed.t4.QuadPart=0; gameSpeed.t9.QuadPart=(__int64) SPEEDSTEP; c1=rand() & 255; c2=rand() & 255; c3=rand() & 255; c4=rand() & 255; d1=20.0+((double) (rand() & 511)/10.0); d2=-(30.0+((double) (rand() & 511)/10.0)); d3=10.0+((double) (rand() & 511)/10.0); d4=-(5.0+((double) (rand() & 511)/10.0)); textSpeed=TEXT_SPEED; mY=dbScreenHeight()-48; mS=-MESS_SPEED;

for (loop=0; loopscreenWidth-32.0) { lines[loop].x=rand()*1.0; lines[loop].dx=((rand() & 255)+70)*1.0; } lines[loop].y=rand()*1.0; lines[loop].dy=((rand() & 255)+70)*1.0; while (lines[loop].y<32.0 || lines[loop].y>screenHeight-32.0) { lines[loop].y=rand()*1.0; lines[loop].dy=((rand() & 255)+50)*1.0; } lines[loop].colour=(rand() & 255) | ((rand() & 255)<<8) | ((rand() & 255)<<16); } move=updateTimer(); move=updateTimer(); // loop until the escape key is pressed while (LoopGDK() && !dbEscapeKey()) { c1=changeValue(c1,&d1,move); c2=changeValue(c2,&d2,move); c3=changeValue(c3,&d3,move); c4=changeValue(c4,&d4,move); // Display the background dbBox(0,0,(int) screenWidth,(int) screenHeight, dbRGB((int) c1,0,0),dbRGB((int) c2,(int) c2,(int) c2), dbRGB((int) c3,0,(int) c3), dbRGB((int) c4,(int) c4,0)); // Update all the lines for (loop=0; loop=screenWidth) { lines[loop].dx=-lines[loop].dx; lines[loop].x=(lines[loop].x<=0.0 ? 0.0 : \ lines[loop].x>=screenWidth ?

335 screenWidth : lines[loop].x); } if (lines[loop].y<=0.0 || lines[loop].y>=screenHeight) { lines[loop].dy=-lines[loop].dy; lines[loop].y=(lines[loop].y<=0.0 ? 0.0 : \ lines[loop].y>=screenHeight ? screenHeight : lines[loop].y); } lines[loop].x+=(lines[loop].dx*move); lines[loop].y+=(lines[loop].dy*move); dbInk(lines[loop].colour,0); if (loop=screenHeight-48.0) { mS=-mS; mY=(mY<=48.0 ? 48.0 : \ mY>=screenHeight-48 ? dbScreenHeight()-48.0 : mY); } one=dbInKey(); if ((*(one)==(char) 'l' || *(one)==(char) 'L') && loaded==false) { char fileName[MAX_PATH+1]; loaded=true; ZeroMemory(&fileName,sizeof(fileName)); GetModuleFileName(NULL,(char *) &fileName,sizeof(fileName)-1); WinExec((char *) &fileName,SW_SHOWNORMAL); move=updateTimer(); move=updateTimer(); } move=updateTimer(); dbSync (); } }

336 double updateTimer(void) { register double move; register __int64 diff; QueryPerformanceCounter(&gameSpeed.t2); gameSpeed.t3.QuadPart++; diff=gameSpeed.t2.QuadPart-gameSpeed.t1.QuadPart; if (diff>=gameSpeed.t9.QuadPart) { move=(double) ((gameSpeed.t2.QuadPart-gameSpeed.t1.QuadPart)/(double) SPEEDSTEP)/gameSpeed.t3.QuadPart; gameSpeed.t1.QuadPart=gameSpeed.t2.QuadPart; gameSpeed.t3.QuadPart=0; gameSpeed.t4.QuadPart=1; } else { if (gameSpeed.t4.QuadPart==0) { move=(double) ((diff/(double) SPEEDSTEP)/gameSpeed.t3.QuadPart); } } move/=2000.0; return (move>1.0 ? 1.0 : move); } double changeValue(double start,double *change,double move) { register double r; r=start+(*(change)*move); if (r<=0.0 || r>=255.0) { //double newV; *(change)=20.0+((rand() & 511)/10.0)*(*(change)<0.0 ? 1.0 : -1.0); //*(change)=newV; } return ((r<=0.0 ? 0.0 : \ r>=255.0 ? 255.0 : r)); }

337 C

338 LinkList Routine

C Doubly-linked list routine. This was used by the manufacturing system I used to work on, but was written (or at least a version of it) a long time before it was used there. One version I did (not this one) had a sorting routine... /* * LinkList.c * LinkList * * Created by Nicholas Kingsley on 23/11/2006. * Copyright 2006 __MyCompanyName__. All rights reserved. * */ #include "WinToMac.h" #include "LinkList.hpp" BOOL Validate_Header(struct __HEADER *header) { return ((header) && (header->header==LINKLIST_HEADER) ? TRUE : FALSE); } BOOL Validate_Node(struct __NODE *node) { return ((node) && (node->header==LINKLIST_NODE) ? TRUE : FALSE); } extern "C" struct __HEADER *Create_Header(void) { struct __HEADER *temp; temp=(struct __HEADER *) calloc(1,sizeof(struct __HEADER)); if (temp!=NULL) { temp->header=LINKLIST_HEADER; temp->numNodes=0; temp->firstNode=NULL; temp->lastNode=NULL; } return (temp); }

/* Delete the header and all further nodes */ DWORD Delete_AllNodes(struct __HEADER *header) { register struct __NODE*nextNode,*prevNode; register DWORD loop; register DWORD count; register struct __NODE *temp; if (Count(header)==0) { return (0); } count=0; nextNode=Get_FirstNode(header); prevNode=Get_LastNode(header); for (loop=0; loop<(DWORD) (header->numNodes>>1)+1; loop++) { if (nextNode && nextNode!=prevNode) { temp=Get_NextNode(nextNode); if (nextNode->data) { free(nextNode->data); } free(nextNode);

339 nextNode=temp; count++; } if (prevNode) { temp=Get_PrevNode(prevNode); if (prevNode->data) { free(prevNode->data); } free(prevNode); count++; if (nextNode==prevNode) { break; } else { prevNode=temp; } } if (nextNode->prevNode==prevNode || prevNode->nextNode==nextNode || prevNode==nextNode) { break; } }

// Clear everything header->numNodes=0; header->firstNode=NULL; header->lastNode=NULL; return (count); } struct __HEADER *Free_Header(struct __HEADER *header) { if (Validate_Header(header)) { Delete_AllNodes(header); free(header); } return NULL; } /* Get the first node */ struct __NODE *Get_FirstNode(struct __HEADER *header) { return (Validate_Header(header) ? header->firstNode : NULL); } /* Get the last node */ struct __NODE *Get_LastNode(struct __HEADER *header) { return (Validate_Header(header) ? header->lastNode : NULL); } /* Get the next node */ struct __NODE *Get_NextNode(struct __NODE *node) { return (Validate_Node(node) ? node->nextNode : NULL); } /* Get the previous node

340 */ struct __NODE *Get_PrevNode(struct __NODE *node) { return (Validate_Node(node) ? node->prevNode : NULL); } /* Get the middle node (if odd number of nodes), or next lowest node */ struct __NODE *Get_MiddleNode(struct __HEADER *header) { if (Validate_Header(header)) { if (Count(header)==0) { return (NULL); } else { register int loop; struct __NODE *node; node=Get_FirstNode(header); for (loop=0; loop<(int) (Count(header)>>1) && node; loop++) { node=Get_NextNode(node); }

return (node); } } return (NULL); } /* Add a node */ struct __NODE *Add_Node(struct __HEADER *header,char *data,DWORD dataSize,DWORD dir) { struct __NODE *node; if ((data==NULL) || (dataSize==0)) return NULL; // Nothing to do, so return node=NULL; if (Validate_Header(header)) { /* Is this the first node ? */ if ((node=(struct __NODE *) calloc(1,sizeof(struct __NODE)))!=NULL) { /* Set up this node */ node->header=LINKLIST_NODE; node->dataSize=dataSize; if (header->numNodes==0) { /* Yes, direction is ignored */ /* Allocate memory for node + dataSize */ header->firstNode=node; header->lastNode=node; } else { register struct __NODE *nextNode,*prevNode; switch (dir) { case NODE_ADDFIRST: case NODE_ADDDONTCARE: nextNode=header->firstNode; node- >nextNode=nextNode; nextNode- >prevNode=node; header- >firstNode=node;

341 break; case NODE_ADDLAST : prevNode=header->lastNode; node- >prevNode=prevNode; prevNode- >nextNode=node; header- >lastNode=node; break; }; } if (dataSize) { if ((node->data=(char *) calloc(1,dataSize+1))!=NULL) { memcpy(node->data,data,dataSize); } } header->numNodes++; } } return (node); } /* Delete a node */ struct __NODE*Delete_Node(struct __HEADER *header, struct __NODE *node) { if (Validate_Header(header) && Validate_Node(node)) { if (header->numNodes==0) { return (NULL); } else { register struct __NODE *nextNode,*prevNode; nextNode=node->nextNode; prevNode=node->prevNode; if (header->firstNode==node) { header->firstNode=nextNode; }

if (header->lastNode==node) { header->lastNode=prevNode; } if (nextNode) { nextNode->prevNode=prevNode; } if (prevNode) { prevNode->nextNode=nextNode; } if (node->data) { free(node->data); } free(node); header->numNodes--; } return (node);

342 } return (NULL); } /* Count the number of nodes */ DWORD Count(struct __HEADER *header) { return (Validate_Header(header) ? header->numNodes : 0); } DWORD Data_Size(struct __NODE *node) { return (Validate_Node(node) ? node->dataSize : 0); } void CopyData(struct __NODE *node,char *store) { if (Validate_Node(node) && store) { memcpy(store,node->data,node->dataSize); } } void WriteData(struct __NODE *node,char *read) { if (Validate_Node(node) && read) { memcpy(node->data,read,node->dataSize); } } BOOL Diag(struct __HEADER *header,char *fileName) { FILE *stream=NULL; struct __NODE*node; register DWORD count; stream=fopen(fileName,"wt"); if (stream!=NULL) { if (header==NULL) { fputs("* NO HEADER *",stream); fclose(stream); return (TRUE); } else { count=0; fputs("LINK LIST DIAGNOSTIC DATA\n",stream); fputs("------\n\n",stream); fprintf(stream,"Header : %ld (%p)\n",header- >header,header); fprintf(stream,(Validate_Header(header) ? "Header is valid.\n" : "Header is invalid.\n")); fprintf(stream,"Number of nodes : %ld\n",Count(header)); fprintf(stream,"Address of F.Node : %p\n",Get_FirstNode(header)); fprintf(stream,"Address of L.Node : %p\n\n",Get_LastNode(header)); node=Get_FirstNode(header); while (node) { register int loop,pos; char data[33]; count++; fprintf(stream,"Node : %ld\n",count); fprintf(stream,"Node header : %x (%p)\n",node- >header,node); fprintf(stream,(Validate_Node(node) ? "Node is valid.\n" :

343 "Node is invalid.\n")); fprintf(stream,"Data size : %ld\n",Data_Size(node)); fprintf(stream,"Address of N.Node : %p\n",Get_NextNode(node)); fprintf(stream,"Address of P.Node : %p\n",Get_PrevNode(node)); loop=Data_Size(node); loop=(loop>sizeof(data)-1 ? sizeof(data)-1 : loop); memcpy((char *) &data,node->data,min(node- >dataSize,sizeof(data))); fprintf(stream,"First %d bytes of data : ",loop); for (pos=0; pos

return (TRUE); } } else { } return (FALSE); }

344 Mappy Routine

This is my C Mappy loading and display routine. This version has a bug with regard to sprite display and can't cope with more than 8 layers. // CMappy.cpp : Defines the entry point for the DLL application. // #include "CMappy.hpp" #include "stdlib.h" #include "stdio.h" #include "memory.h" #include "fcntl.h" #ifdef MACOS #include "unistd.h" #endif CMappy::CMappy() { register int loop; for (loop=0; loop

345 { if (memcmp((char *) &buffer,MAPPY_HEADER2,sizeof(buffer))!=0) return (false); return (true); } } } return (false); } int CMappy::swapByteOrder(int Thislong) { int hWord,ThisLong; hWord=((Thislong & 0xFFFF0000)/65536) & 65535; ThisLong=((Thislong & 255)*16777216)+((Thislong & 65280)*256)+((hWord & 255)*256)+ ((hWord & 65280)/256); return (ThisLong); } int CMappy::getMappySize(FILE *handle,char *store) { char header[4]; int value; fread((char *) &header,1,sizeof(header),handle); if (store) { memcpy(store,(char *) &header,sizeof(header)); } fread((char *) &value,1,sizeof(value),handle); return (swapByteOrder(value)); } void CMappy::destroyMap(void) { register int loop; if (m_cpAuthor) { free((void *) m_cpAuthor); m_cpAuthor=NULL; }

for (loop=0; loop

346 { free(animationSeq); animationSeq=NULL; } if (m_cpGraphics) { free(m_cpGraphics); m_cpGraphics=NULL; } } int CMappy::loadMappyFile(char *fileName,int extraBytes) { FILE *handle; int FilePosition; bool DecodeFlag; char ChunkHeader[4]; int Mappy_FileSize,ChunkSize; if ((fileName==NULL) || strlen(fileName)==0) return (ERROR_NOFILENAME); if (access(fileName,00)==-1) return (ERROR_FILENOTFOUND); m_dExtraBytesSize=extraBytes; handle=fopen(fileName,"rb"); if (handle!=NULL) { if (readHeader(handle,&Mappy_FileSize)==false) { fflush(handle); fclose(handle); return (ERROR_INVALIDHEADER); } FilePosition=12; do { DecodeFlag=false; ChunkSize=getMappySize(handle,(char *) &ChunkHeader); FilePosition+=8; if (memcmp(ChunkHeader,HEADER_AUTHOR,strlen(HEADER_AUTHOR))==0) { // Strings of author information // Allocate memory for this chunk if ((m_cpAuthor=(char *) calloc(1,ChunkSize+1))!=NULL) { fread((char *) m_cpAuthor,1,ChunkSize,handle); } else { fclose(handle); return (ERROR_OUTOFMEM); } DecodeFlag=true; } else if (memcmp(ChunkHeader,HEADER_MAP,strlen(HEADER_MAP))==0) { processMapHeader(handle,ChunkSize); DecodeFlag=true; } else if (memcmp(ChunkHeader,HEADER_PALETTE,strlen(HEADER_PALETTE))==0) { processPalette(handle,ChunkSize); DecodeFlag=true; } else if (memcmp(ChunkHeader,HEADER_BLOCKGRFX,strlen(HEADER_BLOCKGRFX))==0) { int status;

347 if ((status=processGraphics(handle,ChunkSize))!=ERROR_OK) { fclose(handle); return (status); } DecodeFlag=true; } else if (memcmp(ChunkHeader,HEADER_BODY,strlen(HEADER_BODY))==0) { int status; if ((status=getTileMapLayer(handle,ChunkSize,0,m_dExtraBytesSize))!=ERROR_OK) { fclose(handle); return (status); } DecodeFlag=true; } else if (memcmp((char *) &ChunkHeader,HEADER_LAYER,strlen(HEADER_LAYER))==0) { int layer; int status; layer=ChunkHeader[strlen(HEADER_LAYER)]-(char) '0'; if (layer>=0 && layer

348 loadMappySkipSection(handle,ChunkSize); DecodeFlag=true; } if (DecodeFlag) { FilePosition+=ChunkSize; } } while (FilePosition=0 && blockbgoff,1,sizeof(temp->bgoff),handle); fread((char *) &temp->fgoff,1,sizeof(temp->fgoff),handle); fread((char *) &temp->fgoff2,1,sizeof(temp->fgoff2),handle); fread((char *) &temp->fgoff3,1,sizeof(temp->fgoff3),handle); fread((char *) &temp->user1,1,sizeof(temp->user1),handle); fread((char *) &temp->user2,1,sizeof(temp->user2),handle); fread((char *) &temp->user3,1,sizeof(temp->user3),handle); fread((char *) &temp->user4,1,sizeof(temp->user4),handle); fread((char *) &temp->user5,1,sizeof(temp->user5),handle); fread((char *) &temp->user6,1,sizeof(temp->user6),handle); fread((char *) &temp->user7,1,sizeof(temp->user7),handle); fread((char *) &one,1,sizeof(one),handle); temp->tl=(one & 1 ? true : false); temp->tr=(one & 2 ? true : false); temp->bl=(one & 4 ? true : false); temp->br=(one & 8 ? true : false); temp->trigger=(one & 16 ? true : false); temp->unused1=(one & 32 ? true : false); temp->unused2=(one & 64 ? true : false); temp->unused3=(one & 128 ? true : false); temp++; i+=sizeof(BLKSTR); } } else { return (ERROR_OUTOFMEM); } loadMappySkipSection(handle,ChunkSize-i); return (ERROR_OK); }

349 int CMappy::processAnimation(FILE *handle,int ChunkSize) { char *tempBuffer,*temp; ANISTR *tempAnim; register int loop; register long sequenceSize; long *ptr; if ((tempBuffer=(char *) calloc(1,ChunkSize))==NULL) { return (ERROR_OUTOFMEM); } fread(tempBuffer,1,ChunkSize,handle); // Now to count backwards to get the number of animations m_dNumAnimations=0; sequenceSize=ChunkSize; temp=(char *) (tempBuffer+ChunkSize); while (1) { temp-=sizeof(ANISTR); sequenceSize-=sizeof(ANISTR); m_dNumAnimations++; if (*(temp)==AN_END) { break; } } sequenceSize/=4;

if ((animations=(ANISTR *) calloc(m_dNumAnimations,sizeof(ANISTR)))==NULL) { return (ERROR_OUTOFMEM); } tempAnim=(ANISTR *) temp; //(tempBuffer+ChunkSize); //(ChunkSize-sizeof(ANISTR))); for (loop=0; loopantype; animations[loop].anuser=tempAnim->anuser; animations[loop].andelay=tempAnim->andelay; animations[loop].ancount=tempAnim->ancount; animations[loop].ancuroff=(tempAnim->ancuroff+ChunkSize)/(m_dMapType==FMP05 ? 4 : 1); animations[loop].anstartoff=(tempAnim->anstartoff+ChunkSize)/ (m_dMapType==FMP05 ? 4 : 1); animations[loop].anendoff=(tempAnim->anendoff+ChunkSize)/(m_dMapType==FMP05 ? 4 : 1); tempAnim++; } if ((animationSeq=(long *) calloc(sizeof(long),sequenceSize))==NULL) { return (ERROR_OUTOFMEM); } ptr=(long *) tempBuffer; for (loop=0; loop<(int) sequenceSize; loop++) { animationSeq[loop]=*(ptr); if (m_dMapType==FMP05) { animationSeq[loop]/=m_dBlockDepth; } ptr++; } // Now we've done that, we get the animation free(tempBuffer); // Initilise the animations MapInitAnims(); return (ERROR_OK);

350 } void CMappy::MapInitAnims(void) { int loop; if (m_dNumAnimations>0) { for (loop=0; loop=0 && index

351 m_dMapWidth=(int) loadMappyWord(handle,false); // 4 m_dMapHeight=(int) loadMappyWord(handle,false); loadMappyWord(handle,false); // Reserved 1 loadMappyWord(handle,false); // Reserved 2 i+=8; m_dBlockWidth=(int) loadMappyWord(handle,false); m_dBlockHeight=(int) loadMappyWord(handle,false); m_dMapDepth=(int) loadMappyWord(handle,false); m_dBlockDepth=(int) loadMappyWord(handle,false); i+=8; m_dNumBlockStructs=(int) loadMappyWord(handle,false); m_dNumBlockGFX=(int) loadMappyWord(handle,false); i+=4; m_dTileSizeInBytes=m_dMapWidth*m_dMapHeight; switch (m_dBlockDepth) { case 15: case 16 : m_dTileSizeInBytes*=2; break; case 24 : m_dTileSizeInBytes*=3; break; case 32 : m_dTileSizeInBytes*=4; break; }; if (size>24) { // Read next 4 bytes m_dMapTrans8=(int) fgetc(handle); m_dMapTransRed=(int) fgetc(handle); m_dMapTransGreen=(int) fgetc(handle); m_dMapTransBlue=(int) fgetc(handle); i+=4; } else { m_dMapTrans8=(int) 0; m_dMapTransRed=(int) 0xFF; m_dMapTransGreen=(int) 0; m_dMapTransBlue=(int) 0xFF; } if (size>28) { m_dBlockGapX=(int) loadMappyWord(handle,false); m_dBlockGapY=(int) loadMappyWord(handle,false); m_dBlockStaggerX=(int) loadMappyWord(handle,false); m_dBlockStaggerY=(int) loadMappyWord(handle,false); i+=8; } else { m_dBlockGapX=m_dBlockWidth; m_dBlockGapY=m_dBlockHeight; m_dBlockStaggerX=0; m_dBlockStaggerY=0; } if (size>36) { m_dClipMask=(int) loadMappyWord(handle,false); i+=2; } else { m_dClipMask=0; } if (i!=size) { loadMappySkipSection(handle,size-i); } return (ERROR_OK); } struct __RGB *CMappy::GetPaletteColour(int index)

352 { if (index>=0 && index<(int) (1<=0 && y>=0 && x=0 && layerLevel

353 return (0); } signed short CMappy::WriteTileAtPosition(int x,int y,int layerLevel,signed short value) { if (x>=0 && y>=0 && x=0 && layerLevel0) { if ((extraBytesLayer[layerLevel]=(char *) calloc(extraBytes,m_dMapWidth*m_dMapHeight))==NULL) { return (ERROR_OUTOFMEM); } } return ERROR_OK; } signed short CMappy::getTileMapLayer(FILE *handle,int sectionLen,int layerLevel,int extraBytesSize) { register int lp; signed short data,rleCount; signed short *ptr; int status; if ((status=allocateLayer(layerLevel,sectionLen,extraBytesSize))==ERROR_OK) { ptr=layer[layerLevel]; switch (m_dMapType) { case FMP05 : // FMP 0.5, I think case FMP10 : // FMP 1.0 for (lp=0; lp

354 *(ptr)=(signed short) (data/m_dBlockDepth); } } else { *(ptr)=data; } ptr++; } break; case FMP10RLE: // FMP 1.0 compressed lp=0; while (lp0) { while (rleCount) { fread((char *) &data,1,sizeof(data),handle); *(ptr)=data; ptr++; rleCount--; lp+=sizeof(data);

} } else if (rleCount<0) { fread((char *) &data,1,sizeof(data),handle); while (rleCount) { *(ptr)=data; ptr++; rleCount++; lp+=sizeof(data); } } } break; }; } return (status); } int CMappy::CalculateExtraPosition(int x,int y) { return ((x*m_dExtraBytesSize)+(y*m_dMapWidth*m_dExtraBytesSize)); } char *CMappy::GetExtraDataPointer(int x,int y,int layer) { if (layer>=0 && layer

355 if (layerNum>=0 && layerNumbgoff/returnDiv(); } return 0; } int CMappy::returnForegroundOffset(int blockNum,short which) { BLKSTR *block; register int index; if ((block=GetBlockData(blockNum))!=NULL) { switch (which) { case FOREGROUND1 : index=block->fgoff; break; case FOREGROUND2 : index=block->fgoff2; break; case FOREGROUND3 : index=block->fgoff3; break; default : index=0; break; }; return index/returnDiv(); } return 0; }

356 int CMappy::returnCurrentAnimationBlock(int block) { ANISTR *a; if (block<0) { block=0-block; } if ((a=GetAnimationData(m_dNumAnimations-block))!=NULL) { return animationSeq[a->ancuroff]; } return 0; } int CMappy::addLayer(int layers) { if (layers>=0 && layers0 && m_dMapHeight>0) { return allocateLayer(layers,m_dMapWidth*m_dMapHeight, m_dExtraBytesSize); } else { return ERROR_MAPNOTLOADED; } } return ERROR_INVALIDLAYER; } int CMappy::deleteLayer(int layers) { if (layers>=0 && layers=0 && fromLayer=0 && toLayer

357 memcpy(layer[toLayer],layer[fromLayer],m_dMapWidth*m_dMapHeight*sizeof(signed short)); return ERROR_OK; } } return ERROR_INVALIDLAYER; } int CMappy::clearLayer(int layers) { if (layers>=0 && layers=0 && fromLayer=0 && toLayer

358 (animations[loop].anstartoff!=animations[loop].anendoff) { animations[loop].ancuroff++; if (animations[loop].ancuroff==animations[loop].anendoff) { animations[loop].ancuroff=animations[loop].anstartoff; } } break; case AN_LOOPR : if (animations[loop].anstartoff!=animations[loop].anendoff) { animations[loop].ancuroff--; if (animations[loop].ancuroff==animations[loop].anstartoff-1) { animations[loop].ancuroff=animations[loop].anendoff; } } break; case AN_ONCE : if (animations[loop].anstartoff!=animations[loop].anendoff) {

animations[loop].ancuroff++; if (animations[loop].ancuroff==animations[loop].anendoff) { animations[loop].antype=AN_ONCES; animations[loop].ancuroff=animations[loop].anendoff; } } break; case AN_ONCEH : if (animations[loop].anstartoff!=animations[loop].anendoff) { if (animations[loop].ancuroff!=animations[loop].anendoff-1) { animations[loop].ancuroff++; } } break; case AN_PPFF : if (animations[loop].anstartoff!=animations[loop].anendoff) { animations[loop].ancuroff++; if (animations[loop].ancuroff==animations[loop].anendoff) { animations[loop].ancuroff-=2; animations[loop].antype=AN_PPFR; if (animations[loop].ancuroff

359 { animations[loop].ancuroff--; if (animations[loop].ancuroff==animations[loop].anstartoff-1) { animations[loop].ancuroff+=2; animations[loop].antype=AN_PPFF; if (animations[loop].ancuroff>animations[loop].anendoff) { animations[loop].ancuroff--; } } } break; case AN_PPRR : if (animations[loop].anstartoff!=animations[loop].anendoff) { animations[loop].ancuroff--; if (animations[loop].ancuroff==animations[loop].anstartoff-1) {

animations[loop].ancuroff+=2; animations[loop].antype=AN_PPRF; if (animations[loop].ancuroff>animations[loop].anendoff) { animations[loop].ancuroff--; } } } break; case AN_PPRF : if (animations[loop].anstartoff!=animations[loop].anendoff) { animations[loop].ancuroff--; if (animations[loop].ancuroff==animations[loop].anendoff) { animations[loop].ancuroff-=2; animations[loop].antype=AN_PPRR; if (animations[loop].ancuroff

360 BlitzMax

361 Character Counter

Routine to display the number of each ASCII character that appears in a file. This was written when someone on the forums needed a character count – don't think they used it though. ' file drag & drop example Import MaxGui.Drivers Strict Const MENU_EXIT=105 Const MENU_ABOUT=109 Global count:Int[256] Global listbox:TGadget ' ' A window Local win:TGadget = CreateWindow("Drag & Drop A File Into The Main Window",100,100,400,400,Null,WINDOW_TITLEBAR|WINDOW_RESIZABLE|WINDOW_MENU|WINDOW_STATUS| WINDOW_CLIENTCOORDS|WINDOW_ACCEPTFILES) ' ' A simple menu Local filemenu:TGadget = CreateMenu("&File",0,WindowMenu(win)) CreateMenu"E&xit",MENU_EXIT,filemenu Local helpmenu:TGadget = CreateMenu("&Help",0,WindowMenu(win)) CreateMenu "&About",MENU_ABOUT,helpmenu UpdateWindowMenu win ' listbox=CreateListBox(4,4,390,390,win) ' A few bits and pieces Local fileName:String Local one:Byte Local file:String = "Drag a text file onto window" SetStatusText win,file

' ' Main loop While WaitEvent() Select EventID() Case EVENT_GADGETPAINT Case EVENT_WINDOWCLOSE ' ' Quit End Case EVENT_WINDOWACCEPT ' ' A file has been dragged and dropped on the window For Local loop:Int=0 To 255 count[loop]=0 Next fileName = EventExtra().tostring() ' ' Try loading the file as an image Local stream:TStream = OpenFile(fileName,True,False) If stream = Null fileName = "File CANNOT be opened" Else ClearGadgetItems listbox SetStatusText win,"Reading file '"+fileName+"'" one=ReadByte(stream) While Eof(stream)=False count[one]:+1

362 one=ReadByte(stream) EndWhile CloseStream(stream) SetStatusText win,"Now counting the contents" ' Now we go through all characters, adding them to the list For Local loop:Int=0 To 255 If count[loop]>0 If loop<31 Or loop>127 AddGadgetItem listbox,"["+loop+"] = "+count[loop] Else AddGadgetItem listbox,Chr$(loop)+" = "+count[loop] EndIf EndIf Next SetStatusText win,"Finished processing" EndIf ' RedrawGadget can Case EVENT_MENUACTION ' ' Menu stuff Select EventData() Case MENU_EXIT End Case MENU_ABOUT Notify "Count characters in a file." End Select Default ' ' Uncomment this to show what other events occur ' Print CurrentEvent.toString() EndSelect Wend

363 Config Data List

Routine for saving and loading configuration data. Was used with the LoadData routine to read in configuration data. Type TConfigDataList Field key:String Field data:String EndType Type TConfig Const SEPERATOR:String = "=" Const MAX_LENGTH:Byte = 255 Field lastFileName:String Field dataList:TList = Null Function Create:TConfig() Local t:TConfig t=New TConfig If t<>Null t.dataList=CreateList() If t.dataList<>Null t.lastFileName="" Else t=Null EndIf EndIf Return t EndFunction Method open:Byte(fileName:String) Local temp:String Local dList:TConfigDataList Local pos:Int Local stream:TStream = Null If fileName="" Return False EndIf ' Are we opening a different file ? If fileName<>lastFileName ' Is a file open ? If lastFileName<>"" ' Store everything and close the file Flush() EndIf EndIf ' Its new (or everything has been closed), so we check to see if the file exists ' Open the file for reading ClearList dataList stream=OpenStream(fileName,True,False) If stream<>Null ' NULL if the file doesn't exist ' Read in everything temp=ReadLine(stream) While Eof(stream)=0 dList=New TConfigDataList If dList<>Null ' Is there a seperator ? pos=temp.Find(SEPERATOR) If pos<0 dList.key=temp dList.data="" Else dList.key=Left$(temp,pos) dList.data=Mid$(temp,pos+2) EndIf

364 ListAddLast dataList,dList dList=Null EndIf temp=ReadLine(stream) EndWhile CloseStream stream EndIf lastFileName=fileName Return True EndMethod Method addKey:Byte(key:String,data:String) Local loop:TConfigDataList For loop=EachIn dataList If loop.key=key ' Found - so if data <> NULL then replace otherwise delete If data="" ListRemove dataList,loop Return True Else loop.data=data Return True EndIf EndIf Next

' Hasn't been found, so we add loop=New TConfigDataList If loop<>Null loop.key=key loop.data=data ListAddLast dataList,loop loop=Null Return True EndIf Return False EndMethod Method getValue:String(key:String,cs:Byte=True,defaultText:String="") Local loop:TConfigDataList Local found:Byte found=False For loop=EachIn dataList If cs=True If loop.key=key found=True EndIf Else If Upper$(loop.key)=Upper$(key) found=True EndIf EndIf If found=True Return loop.data EndIf Next Return defaultText EndMethod Method Flush:Byte() Local stream:TStream Local loop:TConfigDataList Local exist:Byte If FileSize(lastFileName)=-1 exist=True

365 Else exist=DeleteFile(lastFileName) EndIf If exist=True stream=OpenStream(lastFileName,False,True) If stream<>Null For loop=EachIn dataList WriteString(stream,loop.key) WriteString(stream,SEPERATOR) WriteString(stream,loop.data) WriteString(stream,Chr$(10)) Next CloseStream(stream) Return True EndIf EndIf Return False EndMethod EndType

366 LoadData

BlitzMax version of the GLBasic/DBPro/GSDK loading system for music and graphics. It also sets up the screen display.

Include "../Constants/Constants.bmx" 'Import BaH.volumes must be used Incbin "title.PNG" Type TDisplay Const MOVE_SPEED:Float = 2.5 Const INCBIN_TEXT:String = "INCBIN::" Field screenWidth:Int Field screenHeight:Int Field screenBPP:Int Field hertz:Int Field useWindow:Byte Field sfxVolume:Int Field musicVolume:Int Field config:TConfig = Null Field controlNames:TList = Null Field listOfRes:TList = Null Field posInListOfRes:TLink= Null Field _graphics:TGraphics = Null Field fade:Float Field fadeDir:Float Field allowJoystick:Byte Field allowMouse:Byte Field numMice:Byte ' Number of mice present Field numJoypads:Byte ' Number of joypads present Field numKeyboard:Byte ' Number of players of keyboard allowed Field filesCount:Int Field CONFIG_PATH:String = "" Field CONFIG_NAME:String = "" Field errorMessage:String="" Field unstableMatrixTextImage:TImage = Null Field unstableMatrixTextX:Int Field unstableMatrixTextY:Int Function Create:TDisplay(applicationName:String,maxKeyboard:Byte=4,aJoystick:Byte=True,aMouse:Byt e=True,..

DEFAULT_SCREENWIDTH:Int=_DEFAULT_SCREENWIDTH,DEFAULT_SCREENHEIGHT:Int=_DEFAULT_SCREENHEI GHT,..

DEFAULT_SCREENBPP:Int=_DEFAULT_SCREENBPP,DEFAULT_SCREENHERTZ:Int=_DEFAULT_SCREENHERTZ) Local t:TDisplay AppTitle$=applicationName+" ©Nicholas Kingsley" t=New TDisplay If t<>Null t.fade=1.0 t.fadeDir=-1.0 t._graphics=Null t.errorMessage="" t.filesCount=0 t.config=TConfig.Create() t.controlNames=CreateList() t.listOfRes=CreateList() If t.controlNames=Null Or t.listofRes=Null Or t.config=Null t=Null Else

367 t.numKeyboard=maxKeyboard t.allowJoystick=aJoystick t.allowMouse=aMouse t.screenWidth=DEFAULT_SCREENWIDTH t.screenHeight=DEFAULT_SCREENHEIGHT t.screenBPP=DEFAULT_SCREENBPP ? MacOS t.hertz=0 ? Not MacOS t.hertz=DEFAULT_SCREENHERTZ ? t.useWindow=False t.sfxVolume=MAX_SFXVOLUME t.musicVolume=MAX_MUSICVOLUME ' Get the config path, which is local to this user t.CONFIG_PATH=GetUserHomeDir()+"/"+applicationName t.CONFIG_NAME=t.CONFIG_PATH+"/CONFIG.INF" ' Make the directory, if its not present CreateDir(t.CONFIG_PATH) t.unstableMatrixTextImage=LoadImage("incbin::title.PNG") If t.unstableMatrixTextImage<>Null t.unstableMatrixTextX=(t.screenWidth- ImageWidth(t.unstableMatrixTextImage)) Shr 1 t.unstableMatrixTextY=(t.screenHeight- ImageHeight(t.unstableMatrixTextImage)) Shr 1 EndIf EndIf EndIf Return t EndFunction Method returnErrorMessage:String() Return errorMessage EndMethod Method setup:Byte(loadData:TLoadData[]) Local BPP:Int[]=[16,24,32,0,16,24,32,-1] Local loop:Int Local found:Byte Local screenRes:TScreenRes Local altSR:TScreenRes Local add:Byte Local result:Byte ?Win32 Local desk:Int[4] Local window:Int[4] Local hWnd:Int ? ' First, check to see if ManyMouse failed for some reason If allowJoystick=True numJoypads=JoyCount() Else numJoypads=0 EndIf If allowMouse=True numMice=ManyMouse_Init() If numMice<0 errorMessage="ManyMouse failed to initialise."+Chr$(10)+"You need a later operating system." Return False endif Else numMice=0 EndIf ClearList controlNames ClearList listOfRes ' Get a list of screen resolutions

368 If CountGraphicsModes()=0 errorMessage="No screen resolutions found." Return False EndIf For loop=0 To CountGraphicsModes()-1 screenRes=New TScreenRes If screenRes<>Null

GetGraphicsMode(loop,screenRes.width,screenRes.height,screenRes.bpp,screenRes.hertz) If screenRes.width>=_DEFAULT_SCREENWIDTH And screenRes.height>=_DEFAULT_SCREENHEIGHT And .. screenRes.bpp>=_DEFAULT_SCREENBPP add=True For altSR=EachIn listOfRes If altSR.width=screenRes.width And altSR.height=screenRes.height And .. altSR.bpp=screenRes.bpp And altSR.hertz=screenRes.hertz add=False EndIf Next If add=True ListAddLast listOfRes,screenRes EndIf EndIf EndIf

screenRes=Null Next ' Sort it now SortList (listOfRes,True,MySortFunction) ' Now, we read in the default screen settings If config.open(CONFIG_NAME) screenWidth=Int(config.getValue(CONFIG_WIDTHSECTION,True,screenWidth)) screenHeight=Int(config.getValue(CONFIG_HEIGHTSECTION,True,screenHeight)) screenBPP=Int(config.getValue(CONFIG_BPPSECTION,True,screenBPP)) ? MacOS hertz=0 ? Not MacOS hertz=Int(config.getValue(CONFIG_HERTZSECTION,True,hertz)) If hertz=<0 hertz=60 EndIf ? useWindow=Byte(config.getValue(CONFIG_USEWINDOWSECTION,True,useWindow)) sfxVolume=Int(config.getValue(CONFIG_SFXVOLUME,True,sfxVolume)) musicVolume=Int(config.getValue(CONFIG_MUSICVOLUME,True,musicVolume)) Else errorMessage="Unable to open the config file :"+Chr$(10)+CONFIG_NAME Return False EndIf ' Check to see if the screen mode exists If useWindow result=modeExists(screenWidth,screenHeight,0,0) Else result=modeExists(screenWidth,screenHeight,screenBPP,hertz) EndIf If result=False ' Find the first list of graphics modes at least equal to the default values For altSR=EachIn listOfRes Print altSr.width+" "+altSR.height+" "+altSR.bpp+" "+altSR.hertz If altSR.width>=_DEFAULT_SCREENWIDTH And altSR.height>=_DEFAULT_SCREENHEIGHT And .. altSR.bpp>=_DEFAULT_SCREENBPP

369 If useWindow result=modeExists(altSR.width,altSR.height,0,0) Else ? Not MacOS If altSR.hertz>=_DEFAULT_SCREENHERTZ ? result=modeExists(altSR.width,altSR.height,altSR.bPP,altSR.hertz) ? Not MacOS EndIf ? EndIf If result=True found=True screenWidth=altSR.width screenHeight=altSR.height screenBPP=altSR.bPP hertz=altSR.hertz Exit EndIf EndIf Next If found=False errorMessage="Unable to find a screen resolution of either "+Chr$(10)+screenWidth+" x "+screenHeight+" x "+screenBPP+.. Chr$(10)+"OR"+Chr$(10)+.. "the default resolution of "+Chr$(10)+_DEFAULT_SCREENWIDTH+" x "+_DEFAULT_SCREENHEIGHT+" x "+_DEFAULT_SCREENBPP+"." Return False EndIf EndIf ' Now we add all the controls If numKeyboard>0 addKeyboardControl(numKeyboard) EndIf If numJoypads>0 addJoypadControl(numJoypads) EndIf If numMice>0 addMouseControl(numMice) EndIf If useWindow=False _graphics=Graphics(screenWidth,screenHeight,screenBPP,hertz) Else _graphics=Graphics(screenWidth,screenHeight) ?Win32 hWnd=GetActiveWindow() GetWindowRect(GetDesktopWindow(), desk) ' GetWindowRect(hWnd, window) 'Centre Window SetWindowPos(hWnd, HWND_NOTOPMOST, (desk[2] - (window[2] - window[1])) / 2, (desk[3] - (window[3] - window[0])) / 2, 0, 0, SWP_NOSIZE) ? EndIf fade=1.0 fadeDir=-1.0 SeedRnd MilliSecs() SetClsColor 0,0,0 SetMaskColor 0,0,0 SetOrigin 0.0,0.0 SetViewport 0,0,screenWidth,screenHeight HideMouse() FlushMouse() FlushKeys() Cls

370 Flip If unstableMatrixTextImage<>Null And loadData<>Null animateLogo(loadData) EndIf Return True EndMethod Function MySortFunction:Int(o1:Object, o2:Object) Return TScreenRes(o1).width-TScreenRes(o2).width End Function Function progress(fileName:String,screenWidth:Int,screenHeight:Int,image:TImage,imageX:Int=0,imag eY:Int=0) Local fileT:String Cls SetImageFont Null SetDrawingCommands() If image<>Null DrawImage image,imageX,imageY EndIf fileT=fileName If Left$(Upper$(fileT),Len(INCBIN_TEXT))=INCBIN_TEXT fileT=Right$(fileT,Len(fileT)-Len(INCBIN_TEXT)) EndIf

DrawText fileT,0,screenHeight-TextHeight(fileName) Flip EndFunction Method animateLogo:Byte(loadData:TLoadData[]) Local speed:Float Local alpha:Float Local wait:Float Local x:Float Local y:Float Local startTime:Int ' Display the logo, and load all the data Cls SetDrawingCommands() DrawImage unstableMatrixTextImage,unstableMatrixTextX,unstableMatrixTextY Flip startTime=MilliSecs() If loadRemoveFiles(True,loadData,progress)=False Return False EndIf UpdateAppTime() speed=AppSpeed() alpha=1.0 x=Float(unstableMatrixTextX) y=Float(unstableMatrixTextY) SetDrawingCommands() Repeat Cls DrawImage unstableMatrixTextImage,unstableMatrixTextX,unstableMatrixTextY Flip Delay 1 Until Abs(MilliSecs()-startTime)>2000 UpdateAppTime() speed=AppSpeed() UpdateAppTime() speed=AppSpeed() ?Win32

371 While alpha>=0.0 Cls SetDrawingCommands(alpha) DrawImage unstableMatrixTextImage,unstableMatrixTextX,unstableMatrixTextY Flip alpha:-0.01*speed UpdateAppTime() speed=AppSpeed() EndWhile ?MacOSX86 While x+Float(ImageWidth(unstableMatrixTextImage))>=0.0 Cls SetDrawingCommands(alpha) DrawImage unstableMatrixTextImage,x,unstableMatrixTextY Flip x:-MOVE_SPEED*speed UpdateAppTime() speed=AppSpeed() EndWhile ?MacOSPPC While y+Float(ImageHeight(unstableMatrixTextImage))>=0.0 Cls SetDrawingCommands(alpha) DrawImage unstableMatrixTextImage,x,y

Flip y:-MOVE_SPEED*speed UpdateAppTime() speed=AppSpeed() EndWhile ?Linux While x<=Float(screenWidth) Cls SetDrawingCommands(alpha) DrawImage unstableMatrixTextImage,x,unstableMatrixTextY Flip x:+MOVE_SPEED*speed UpdateAppTime() speed=AppSpeed() EndWhile ? SetDrawingCommands() EndMethod Method returnNumMice:Byte() Return numMice EndMethod Method returnNumJoyPads:Byte() Return numJoypads EndMethod ' Add keyboard controls Method addKeyboardControl(amount:Byte) Local control:String Local c:TControlInfo Local loop:Byte If config.open(CONFIG_NAME)=True For loop=1 To amount c=New TControlInfo If c<>Null c.controlCode=CONTROL_KEYBOARD c.controlIndex=Byte(loop) c.controlText=config.getValue(CONFIG_KEYBOARDSECTION+loop,True,"KEYBOARD "+loop) ListAddLast controlNames,c c=Null

372 EndIf Next EndIf EndMethod ' Add mouse control Method addMouseControl(amount:Byte) Local c:TControlInfo Local loop:Byte For loop=0 To amount-1 c=New TControlInfo If c<>Null c.controlCode=CONTROL_MOUSE c.controlIndex=loop c.controlText=ManyMouse_DeviceName(loop) ListAddLast controlNames,c c=Null EndIf Next EndMethod ' Add joypad control Method addJoypadControl(amount:Byte) Local loop:Byte Local c:TControlInfo For loop=0 To amount-1 c=New TControlInfo If c<>Null c.controlCode=CONTROL_JOYPAD c.controlIndex=loop c.controlText=JoyName$(loop) ListAddLast controlNames,c c=Null EndIf Next EndMethod Method getCurrentControlItem:TLink(controlCode:Byte) Local loop:TLink Local controlInfo:TControlInfo loop=controlNames.FirstLink() While loop<>Null controlInfo=TControlInfo(loop.Value()) If controlInfo<>Null If controlInfo.controlCode=controlCode Return loop EndIf EndIf loop=loop.NextLink() EndWhile Return Null EndMethod Method returnCurrentScreenValues(which:TLink,screenWidth:Int Var,screenHeight:Int Var,screenBPP:Int Var,hertz:Int Var) Local screenRes:TScreenRes If which<>Null screenRes=TScreenRes(which.Value()) If screenRes<>Null screenWidth=screenRes.width screenHeight=screenRes.height screenBPP=screenRes.bpp hertz=screenRes.hertz EndIf EndIf EndMethod

373 Method getCurrentDisplayItem:TLink() Local loop:TLink Local screenRes:TScreenRes loop=listOfRes.FirstLink() While loop<>Null screenRes=TScreenRes(loop.Value()) If screenRes<>Null If screenRes.width=screenWidth And screenRes.height=screenHeight And screenRes.bpp=screenBPP Return loop EndIf EndIf loop=loop.NextLink() EndWhile Return Null EndMethod Method controlCodeToText:String(link:TLink) Local controlInfo:TControlInfo Local text:String text="???" If link<>Null controlInfo=TControlInfo(link.Value()) If controlInfo<>Null text=controlInfo.controlText EndIf EndIf Return text EndMethod Method screenResToText:String(link:TLink) Local screenRes:TScreenRes Local text:String text="???" If link<>Null screenRes=TScreenRes(link.Value()) If screenRes<>Null text=screenRes.width+" x "+screenRes.height+" x "+screenRes.bpp+" ("+screenRes.hertz+")" EndIf EndIf Return text EndMethod Function modeExists:Byte(width:Int,height:Int,bpp:Int,hertz:Int) If bpp=0 Return GraphicsModeExists(width,height) Else Return GraphicsModeExists(width,height,bpp,hertz) EndIf EndFunction Method returnDisplayWidth:Int() Return screenWidth EndMethod Method returnDisplayHeight:Int() Return screenHeight EndMethod Method returnDisplayBPP:Int() Return screenBPP EndMethod Method returnGraphics:TGraphics() Return _graphics EndMethod Method returnSfxVolume:Int() Return sfxVolume

374 EndMethod Method returnMusicVolume:Int() Return musicVolume EndMethod Method returnUseWindow:Byte() Return useWindow EndMethod Method shutdownGraphics(restart:Byte=False) ManyMouse_Quit() ShowMouse() If restart=False If controlNames<>Null ClearList controlNames controlNames=Null EndIf If listOfRes<>Null ClearList listofRes listOfRes=Null EndIf posInListOfRes=Null EndIf If _graphics<>Null CloseGraphics(_graphics) _graphics=Null EndIf EndMethod Method saveOptions(graphicLink:TLink,tSfxVolume:Int,tMusicVolume:Int,tUseWindow:Byte) Local screenRes:TScreenRes If config.open(CONFIG_NAME) If graphicLink<>Null screenRes=TScreenRes(graphicLink.Value()) If screenRes<>Null config.addKey(CONFIG_WIDTHSECTION,screenRes.width) config.addKey(CONFIG_HEIGHTSECTION,screenRes.height) config.addKey(CONFIG_BPPSECTION,screenRes.bpp) config.addKey(CONFIG_HERTZSECTION,screenRes.hertz) EndIf EndIf config.addKey(CONFIG_USEWINDOWSECTION,tUseWindow) config.addKey(CONFIG_SFXVOLUME,tSfxVolume) config.addKey(CONFIG_MUSICVOLUME,tMusicVolume) config.Flush() EndIf sfxVolume=tSfxVolume musicVolume=tMusicVolume EndMethod Method PlaySnd:TChannel(sound:TSound,isSFX:Byte) Local c:TChannel Local vol:Float If sound<>Null If isSFX=True vol=Float(sfxVolume)/100.0 Else vol=Float(musicVolume)/100.0 EndIf If vol>0.0 c=PlaySound(sound) If c<>Null SetChannelVolume(c,vol) EndIf EndIf Else

375 c=Null EndIf Return c EndMethod Method loadRemoveFiles:Byte(_load:Byte,loadData:TLoadData[],progress(fileName:String,screenWidt h:Int,screenHeight:Int,image:TImage,imageX:Int,imageY:Int)) Local loop:Byte Local readType:Byte Local readFileName:String Local readCellWidth:Int Local readCellHeight:Int Local readCellFrames:Int Local readFontSize:Int Local value:Int If loadData=Null Return False EndIf If _Load=True RestoreData filesToLoad loop=0 Repeat ReadData readType loadData[loop]=New TLoadData If loadData[loop]=Null errorMessage="Unable to allocate memory for TLoadData ("+loop+")" Return False Else loadData[loop].image=Null loadData[loop].sample=Null loadData[loop].channel=Null loadData[loop].font=Null loadData[loop].dataType=readType If readType<>TYPE_END ReadData readFileName If readFileName<>"" If progress<>Null progress(readFileName,screenWidth,screenHeight,unstableMatrixTextImage,unstableMatrixTex tX,unstableMatrixTextY) EndIf If readType & (TYPE_GRAPHIC | TYPE_ANIMGRAPHIC) value=MASKEDIMAGE If readType & TYPE_DYNAMICIMAGE value:|DYNAMICIMAGE EndIf If readType & TYPE_GRAPHIC loadData[loop].image=LoadImage(readfileName,value) Else ReadData readCellWidth ReadData readCellHeight ReadData readCellFrames loadData[loop].image=LoadAnimImage(readfileName,readCellWidth,readCellHeight,0,readCellF rames) EndIf If loadData[loop].image=Null errorMessage="Unable to load the graphic file :"+Chr$(10)+readFileName Return False Else If readType & TYPE_GRAPHIC If readType &

376 TYPE_AUTOMIDHANDLE MidHandleImage(loadData[loop].image) Else SetImageHandle(loadData[loop].image,0.0,0.0) EndIf EndIf EndIf Else If readType & (TYPE_SFX | TYPE_MUSIC) loadData[loop].sample=LoadSound(readFileName) If loadData[loop].sample=Null errorMessage="Unable to load the sound file :"+Chr$(10)+readFileName Return False EndIf Else If readType & TYPE_FONT ReadData readFontSize loadData[loop].font=LoadImageFont(readFileName,readFontSize) If loadData[loop].font=Null errorMessage="Unable to load the font file :"+Chr$(10)+readFileName Return False EndIf EndIf EndIf EndIf EndIf EndIf EndIf loop:+1 filesCount:+1 Until readType=TYPE_END Else If filesCount>0 For loop=0 To filesCount-1 loadData[loop].image=Null loadData[loop].sample=Null loadData[loop].channel=Null Next EndIf EndIf Return True EndMethod Method resetFade() fade=1.0 fadeDir=-1.0 EndMethod Method fadeTextInOut(text:String,x:Int,y:Int,speed:Float) Local xPos:Int Local yPos:Int SetDrawingCommands(fade) If x=-1 xPos=(screenWidth-TextWidth(text)) Shr 1 Else xPos=x EndIf If y=-1 yPos=screenHeight-TextHeight(text)-8 Else yPos=y EndIf DrawText text,xPos,yPos

377 fade:+fadeDir*speed If fade<=0.0 fade=0.0 fadeDir=0.0-fadeDir Else If fade>=1.0 fade=1.0 fadeDir=0.0-fadeDir EndIf EndIf EndMethod EndType

378 Mappy

BlitzMax version of the Mappy tile map loading and display routine. It also has the sprite display bug and cant cope with more than 8 layers. Const MAPPYERROR_INVALIDCOORD = 1 Const MAPPYERROR_OK = 0 Const MAPPYERROR_NOFILENAME = 2 Const MAPPYERROR_FILENOTFOUND = 3 Const MAPPYERROR_FILENOTOPENED = 4 Const MAPPYERROR_INVALIDHEADER = 5 Const MAPPYERROR_OUTOFMEM = 6 Const MAPPYERROR_INVALIDLAYER = 7 Const MAPPYERROR_UNKNOWNVERSION = 8 Const MAPPYERROR_TILENOTFOUND = 9 Const MAPPYERROR_LAYERNOTINUSE = 10 Const MAPPYERROR_MAPNOTLOADED = 11 Const MAPPYERROR_LAYERINUSE = 12 Const MAPPYERROR_UNKNOWNHEADER = 13

Const FMP05:Byte = 0 Const FMP10:Byte = 1 Const FMP10RLE:Byte = 2 Const FOREGROUND1 = 0 Const FOREGROUND2 = 1 Const FOREGROUND3 = 2 Type RGB Field r:Byte Field g:Byte Field b:Byte EndType Type ANISTR Field antype:Byte ' Type of anim, AN_? Field andelay:Byte ' Frames To go before Next frame Field ancount:Byte ' Counter, decs each frame, till 0, Then resets To andelay Field anuser:Byte ' User info Field ancuroff:Int ' Points To current offset in list Field anstartoff:Int ' Points To start of blkstr offsets list, AFTER ref. blkstr offset */ Field anendoff:Int ' Points To End of blkstr offsets list EndType Type BLKSTR Field bgoff:Int,fgoff:Int Field fgoff2:Int,fgoff3:Int Field user1:Int,user2:Int Field user3:Short,user4:Short Field user5:Byte Field user6:Byte Field user7:Byte Field flags:Byte EndType Type TMappy Const MAX_LAYERS = 8 Const HEADER_SIZE = 4 Const HEADER_AUTHOR$ = "ATHR" Const HEADER_MAP$ = "MPHD" Const HEADER_PALETTE$ = "CMAP" Const HEADER_BLOCKGRFX$ = "BGFX" Const HEADER_BODY$ = "BODY" Const HEADER_LAYER$ = "LYR" Const HEADER_ANIMATION$ = "ANDT" Const HEADER_BLOCKDATA$ = "BKDT" Const HEADER_EDITOR$ = "EDHD" Const HEADER_EPHD$ = "EPHD" Const MAPPY_HEADER1$ = "FORM" Const MAPPY_HEADER2$ = "FMAP" Const AN_END = 255 '

379 Animation types, AN_END = End of anims Const AN_NONE = 0 ' No anim defined Const AN_LOOPF = 1 ' Loops from start To End, Then jumps To start etc Const AN_LOOPR = 2 ' As above, but from End To start Const AN_ONCE = 3 ' Only plays once Const AN_ONCEH = 4 ' Only plays once, but holds End frame Const AN_PPFF = 5 ' Ping Pong start-End-start-End-start etc Const AN_PPRR = 6 ' Ping Pong End-start-End-start-End etc Const AN_PPRF = 7 ' Used internally by playback Const AN_PPFR = 8 ' Used internally by playback Const AN_ONCES = 9 ' * Used internally by playback Const TILE_SIZE = 2 Field m_dMapType:Byte,m_dMapVersion:Short,m_dLSB:Byte Field m_dMapWidth:Short,m_dMapHeight:Short,m_dMapDepth:Byte Field m_dBlockWidth:Short,m_dBlockHeight:Short,m_dBlockSize:Short Field m_dNumBlockStructs:Short,m_dNumBlockGFX:Short Field m_dBlockGapX:Short,m_dBlockGapY:Short,m_dBlockStaggerX:Short,m_dBlockStaggerY:Short Field m_dClipMask:Short Field m_dTrans8:Byte,m_dTransRed:Byte,m_dTransGreen:Byte,m_dTransBlue:Byte Field m_dNumAnimations:Int Field m_dExtraByteSize:Int Field m_cpGraphicSize:Int,m_cpGraphics:TBank Field m_dAuthorSize:Int Field m_dTileSizeInBytes:Int,Mappy_FileSize:Int Field m_dAuthor$ Global palette:RGB[256] Global blockStructs:BLKSTR[] Global layer:TBank[MAX_LAYERS] Global extraBytesLayer:TBank[MAX_LAYERS] Global animations:ANISTR[] Field animationSeq:TBank Function create:TMappy() Local l:Int Local m:TMappy m=New TMappy If m<>Null m.m_dMapType=0 m.m_dMapVersion=0 m.m_dLSB=0 m.m_dMapWidth=0 m.m_dMapHeight=0 m.m_dMapDepth=0 m.m_dBlockWidth=0 m.m_dBlockHeight=0 m.m_dBlockSize=0 m.m_dNumBlockStructs=0 m.m_dNumBlockGFX=0 m.m_dBlockGapX=0 m.m_dBlockGapY=0 m.m_dBlockStaggerX=0 m.m_dBlockStaggerY=0 m.m_dClipMask=0 m.m_dTrans8=0 m.m_dTransRed=0 m.m_dTransGreen=0 m.m_dTransBlue=0 m.m_dNumAnimations=0 m.m_dExtraByteSize=0 m.m_dTileSizeInBytes=0 m.Mappy_FileSize=0 m.m_dAuthorSize=0 m.m_dAuthor$=""

380 m.animations=Null m.animationSeq=Null For l=0 To 255 m.palette[l]=Null Next For l=0 To MAX_LAYERS-1 m.layer[l]=Null m.extraBytesLayer[l]=Null Next m.blockStructs=Null m.animations=Null m.animationSeq=Null EndIf Return m EndFunction Method loadMappyWord:Short(handle:TStream,order:Byte) Local value:Short Local temp:Byte[2] temp[0]=ReadByte(handle) temp[1]=ReadByte(handle) If order value=(Short(temp[1]) Shl 8)+Short(temp[0]) Else value=(Short(temp[0]) Shl 8)+Short(temp[1]) EndIf Return value EndMethod Method loadMappyHeader(handle:TStream,header:TBank) PokeInt(header,0,ReadInt(handle)) EndMethod Method swapByteOrder:Int(ThisLong:Int) Local Hword:Int Hword=((Thislong & 4294901760)/65536) & 65535 Thislong=((Thislong & 255)*16777216)+((Thislong & 65280)*256)+((Hword & 255)*256)+((Hword & 65280)/256) Return Thislong EndMethod Method getMappySize:Int(handle:TStream,header:TBank) Local temp:TBank Local loop:Int Local value:Int ' Read in the header PokeInt(header,0,ReadInt(handle)) ' Now we read in the size for this header value=swapByteOrder(ReadInt(handle)) Return value EndMethod Method headerToString$(header:TBank) Local loop:Int Local text$ text$="" For loop=0 To HEADER_SIZE-1 text$:+Chr$(PeekByte(header,loop)) Next Return text$ EndMethod Method mappySkipSection(handle:TStream,amount:Int) SeekStream(handle,StreamPos(handle)+amount) EndMethod

381 Method processPalette:Int(handle:TStream,ChunkSize:Int) Local l:Int Local i:Int i=0 If m_dMapDepth<=8 For l=0 To (1 Shl m_dMapDepth)-1 palette[l]=New RGB If palette[l]=Null Return MAPPYERROR_OUTOFMEM EndIf palette[l].r=ReadByte(handle) palette[l].g=ReadByte(handle) palette[l].b=ReadByte(handle) i:+3 Next EndIf If i<>ChunkSize mappySkipSection(handle,ChunkSize-l) EndIf Return MAPPYERROR_OK EndMethod Method MapHighTo8(handle:TStream,ChunkSize:Int) mappySkipSection(handle,ChunkSize) EndMethod

Method processBlockGraphics:Short(handle:TStream,ChunkSize:Int) m_cpGraphics=CreateBank(ChunkSize) If m_cpGraphics=Null Then Return MAPPYERROR_OUTOFMEM m_cpGraphicSize=ChunkSize Return MapHighTo8(handle,ChunkSize) EndMethod Method getMapHeader(handle:TStream,ChunkSize:Int) Local temp:Short Local i:Int m_dMapVersion=(ReadByte(handle) Shl 8)+ReadByte(handle) m_dLSB=ReadByte(handle) m_dMapType=ReadByte(handle) If m_dMapType<>FMP05 And m_dMapType<>FMP10 And m_dMapType<>FMP10RLE Return MAPPYERROR_UNKNOWNVERSION EndIf i=4 m_dMapWidth=loadMappyWord(handle,m_dLSB) m_dMapHeight=loadMappyWord(handle,m_dLSB) temp=loadMappyWord(handle,m_dLSB) temp=loadMappyWord(handle,m_dLSB) i:+8 m_dBlockWidth=loadMappyWord(handle,m_dLSB) m_dBlockHeight=loadMappyWord(handle,m_dLSB) m_dMapDepth=loadMappyWord(handle,m_dLSB) m_dBlockSize=loadMappyWord(handle,m_dLSB) i:+8 m_dNumBlockStructs=loadMappyWord(handle,m_dLSB) m_dNumBlockGFX=loadMappyWord(handle,m_dLSB) i:+4 m_dTileSizeInBytes=m_dMapWidth*m_dMapHeight Select m_dMapDepth Case 15,16 m_dTileSizeInBytes:*2 Case 24 m_dTileSizeInBytes:*3 Case 32 m_dTileSizeInBytes:*4 EndSelect

382 If ChunkSize>24 m_dTrans8=ReadByte(handle) m_dTransRed=ReadByte(handle) m_dTransGreen=ReadByte(handle) m_dTransBlue=ReadByte(handle) i:+4 Else m_dTrans8=0 m_dTransRed=255 m_dTransGreen=0 m_dTransBlue=255 EndIf If ChunkSize>28 m_dBlockGapX=loadMappyWord(handle,m_dLSB) m_dBlockGapY=loadMappyWord(handle,m_dLSB) m_dBlockStaggerX=loadMappyWord(handle,m_dLSB) m_dBlockStaggerY=loadMappyWord(handle,m_dLSB) i:+8 Else m_dBlockGapX=m_dBlockWidth m_dBlockGapY=m_dBlockHeight m_dBlockStaggerX=0 m_dBlockStaggerY=0 EndIf If ChunkSize>36 m_dClipMask=loadMappyWord(handle,m_dLSB) i:+2 Else m_dClipMask=0 EndIf mappySkipSection(handle,Chunksize-i) Return MAPPYERROR_OK EndMethod Method allocateLayer:Int(layerLevel:Int,ChunkSize:Int,extraBytes:Int) Local l:Int Local size:Int size=ChunkSize*TILE_SIZE layer[layerLevel]=CreateBank(size) If layer[layerLevel]=Null Then Return MAPPYERROR_OUTOFMEM For l=0 To size-1 PokeByte(layer[layerLevel],l,0) Next If extraBytes>0 size=ChunkSize*extraBytes extraBytesLayer[layerLevel]=CreateBank(size) If extraBytesLayer[layerLevel]=Null Then Return MAPPYERROR_OUTOFMEM For l=0 To size-1 PokeByte(extraBytesLayer[layerLevel],l,0) Next EndIf Return MAPPYERROR_OK EndMethod Method getTileMapLayer:Int(handle:TStream,ChunkSize:Int,layerLevel:Int,extraBytes:Int) Local status:Int Local lp:Int Local data:Short Local rleCount:Int status=allocateLayer(layerLevel,m_dMapWidth*m_dMapHeight,extraBytes) If status=MAPPYERROR_OK 'Select doesn't seem to want to work with Byte variables If m_dMapType=FMP05 Or m_dMapType=FMP10 lp=0 While lp

383 data=ReadShort(handle) If m_dMapType=FMP05 If data & 32768 data=((65536-data)/16) | 32768 Else data:/m_dBlockSize EndIf EndIf PokeShort(layer[layerLevel],lp,data) lp:+SizeOf data EndWhile Else While lp0 While rleCount>0 data=ReadShort(handle) PokeShort(layer[layerLevel],lp,data) lp:+SizeOf data rleCount:-1 EndWhile Else If rleCount>32767 rleCount:-32767 data=ReadShort(handle) While rleCount>0 PokeShort(layer[layerLevel],lp,data) lp:+SizeOf data rleCount:-1 EndWhile EndIf EndIf EndWhile EndIf EndIf Return status EndMethod Method processBlockData:Int(handle:TStream,ChunkSize:Int) Local loop:Int blockStructs=New BLKSTR[m_dNumBlockStructs] If blockStructs=Null Then Return MAPPYERROR_OUTOFMEM For loop=0 To m_dNumBlockStructs-1 blockStructs[loop]=New BLKSTR If blockStructs[loop]=Null Then Return MAPPYERROR_OUTOFMEM blockStructs[loop].bgoff=ReadInt(handle) blockStructs[loop].fgoff=ReadInt(handle) blockStructs[loop].fgoff2=ReadInt(handle) blockStructs[loop].fgoff3=ReadInt(handle) blockStructs[loop].user1=ReadInt(handle) blockStructs[loop].user2=ReadInt(handle) blockStructs[loop].user3=ReadShort(handle) blockStructs[loop].user4=ReadShort(handle) blockStructs[loop].user5=ReadByte(handle) blockStructs[loop].user6=ReadByte(handle) blockstructs[loop].user7=ReadByte(handle) blockStructs[loop].flags=ReadByte(handle) Next Return MAPPYERROR_OK EndMethod Method processAnimationData:Int(tempBuffer:TBank,ChunkSize:Int) Local sequenceSize:Int Local temp:Int Local loop:Int Local offset:Int Local currentPos:Long Local value:Int 'Count backwards to get the number of animations

384 m_dNumAnimations=0 sequenceSize=ChunkSize temp=ChunkSize Repeat temp:-SizeOf ANISTR sequenceSize:-SizeOf ANISTR m_dNumAnimations:+1 Until PeekByte(tempBuffer,temp)=AN_END animations=New ANISTR[m_dNumAnimations] If animations=Null Then Return MAPPYERROR_OUTOFMEM sequenceSize:/4 animationSeq=CreateBank(sequenceSize*SizeOf sequenceSize) If animationSeq=Null Then Return MAPPYERROR_OUTOFMEM offset=temp For loop=0 To m_dNumAnimations-1 animations[loop]=New ANISTR If animations[loop]=Null Then Return MAPPYERROR_OUTOFMEM animations[loop].antype=PeekByte(tempBuffer,offset) animations[loop].andelay=PeekByte(tempBuffer,offset+1) animations[loop].ancount=PeekByte(tempBuffer,offset+2) animations[loop].anuser=PeekByte(tempBuffer,offset+3) animations[loop].ancuroff=PeekInt(tempBuffer,offset+4) animations[loop].anstartoff=PeekInt(tempBuffer,offset+8) animations[loop].anendoff=PeekInt(tempBuffer,offset+12)

If m_dMapType=FMP05 animations[loop].ancuroff:+ChunkSize animations[loop].ancuroff:/4 animations[loop].anstartoff:+ChunkSize animations[loop].anstartoff:/4 animations[loop].anendoff:+ChunkSize animations[loop].anendoff:/4 EndIf offset:+SizeOf ANISTR Next For loop=0 To sequenceSize-1 value=PeekInt(tempBuffer,loop*SizeOf sequenceSize) If m_dMapType=FMP05 value:/m_dBlockSize EndIf PokeInt(animationSeq,loop*SizeOf sequenceSize,value) Next MapInitAnims() Return MAPPYERROR_OK EndMethod Method MapInitAnims() Local loop:Int If m_dNumAnimations=0 Then Return For loop=0 To m_dNumAnimations-1 Select animations[loop].antype Case AN_PPFR animations[loop].antype=AN_PPFF Case AN_ONCES animations[loop].antype=AN_ONCE EndSelect If animations[loop].antype=AN_LOOPR Or .. animations[loop].antype=AN_PPRF Or .. animations[loop].antype=AN_PPRR If animations[loop].antype=AN_PPRF animations[loop].antype=AN_PPRR EndIf

385 animations[loop].ancuroff=animations[loop].anstartoff If animations[loop].anstartoff<>animations[loop].anendoff animations[loop].ancuroff=animations[loop].anendoff-1 EndIf Else animations[loop].ancuroff=animations[loop].anstartoff EndIf animations[loop].ancount=animations[loop].andelay Next EndMethod Method processAnimation:Int(handle:TStream,ChunkSize:Int) Local tempBuffer:TBank Local loop:Int tempBuffer=CreateBank(ChunkSize) If tempBuffer=Null Then Return MAPPYERROR_OUTOFMEM For loop=0 To ChunkSize-1 PokeByte(tempBuffer,loop,ReadByte(handle)) Next loop=processAnimationData(tempBuffer,ChunkSize) Return loop EndMethod

Method loadMappyFile:Int(fileName$,extraBytes:Int=0) Local handle:TStream Local header1:TBank Local header2:TBank Local ChunkHeader:TBank Local head1$ Local head2$ Local Chunk$ Local FilePosition:Int Local DecodeFlag:Byte Local ChunkSize:Int Local result:Int Local loop:Int Local layer:Int handle=OpenStream(fileName,True,False) If handle=Null Then Return MAPPYERROR_FILENOTFOUND header1=CreateBank(HEADER_SIZE) header2=CreateBank(HEADER_SIZE) ChunkHeader=CreateBank(HEADER_SIZE) If header1=Null Or header2=Null Or ChunkHeader=Null Return MAPPYERROR_OUTOFMEM EndIf m_dExtraByteSize=extraBytes Mappy_FileSize=getMappySize(handle,header1) loadMappyHeader(handle,header2) head1$=headerToString(header1) head2$=headerToString(header2) If head1$=MAPPY_HEADER1$ And head2$=MAPPY_HEADER2$ FilePosition=12 Repeat DecodeFlag=False ChunkSize=getMappySize(handle,ChunkHeader) FilePosition:+8 Chunk$=headerToString(ChunkHeader) result=MAPPYERROR_OK Select Chunk$ Case HEADER_AUTHOR$ m_dAuthorSize=ChunkSize

386 m_dAuthor$="" For loop=1 To ChunkSize m_dAuthor$:+Chr$(ReadByte(handle)) Next Case HEADER_MAP$ getMapHeader(handle,ChunkSize) Case HEADER_PALETTE$ processPalette(handle,ChunkSize) Case HEADER_BLOCKGRFX$ processBlockGraphics(handle,ChunkSize) Case HEADER_BODY$ result=getTileMapLayer(handle,ChunkSize,0, m_dExtraByteSize) Case HEADER_LAYER$ Case HEADER_ANIMATION$ result=processAnimation(handle,ChunkSize) Case HEADER_BLOCKDATA$ result=processBlockData(handle,ChunkSize) Case HEADER_EDITOR$ mappySkipSection(handle,ChunkSize) Case HEADER_EPHD$ mappySkipSection(handle,ChunkSize) Default If Left$ (Chunk$,Len(HEADER_LAYER$))=HEADER_LAYER$ layer=Asc(Right$(Chunk$,1))-Asc("0") If layer>=0 And layer=Mappy_FileSize Or result<>MAPPYERROR_OK Else result=MAPPYERROR_INVALIDHEADER EndIf CloseStream(handle) Return result EndMethod Method returnMappyFileSize:Int() Return Mappy_FileSize EndMethod Method returnAuthorName$() Return m_dAuthor$ EndMethod Method returnMappyVersion:Short() Return m_dMapVersion EndMethod Method returnMapWidth:Short() Return m_dMapWidth EndMethod Method returnMapHeight:Short() Return m_dMapHeight EndMethod Method returnMapDepth:Byte() Return m_dMapDepth EndMethod Method returnMapType:Byte() Return m_dMapType EndMethod Method returnBlockWidth:Short()

387 Return m_dBlockWidth EndMethod Method returnBlockHeight:Short() Return m_dBlockHeight EndMethod Method returnBlockSize:Short() Return m_dBlockSize EndMethod Method getPositionInLayer:Int(x:Short,y:Short) Return (x*TILE_SIZE)+(y*m_dMapWidth*TILE_SIZE) EndMethod Method tileAtPosition:Int(layers:Short,x:Int,y:Int) If (layers>=0 And layers=0 And x=0 And y=0 And layers=0 And x=0 And y=0 And which

388 Local div:Int b=New BLKSTR returnBlockStructInfo(block,b) Return b.bgoff/returnDiv() EndMethod Method returnForegroundOffset:Int(block:Int,which:Short) Local b:BLKSTR Local index:Short b=New BLKSTR returnBlockStructInfo(block,b) Select which Case FOREGROUND1 index=b.fgoff Case FOREGROUND2 index=b.fgoff2 Case FOREGROUND3 index=b.fgoff3 EndSelect Return index/returnDiv() EndMethod Method returnCurrentAnimationBlock:Int(block:Int) Local a:ANISTR Local temp:Int a=New ANISTR block=block & 32767 If getAnimation(m_dNumAnimations-block,a) temp=PeekInt(animationSeq,a.ancuroff*SizeOf temp) Return temp Else Return 0 EndIf EndMethod Method getAnimation:Short(block:Short,store:Byte Ptr) If block>=0 And block=0 And layers0 And m_dMapHeight>0 Return allocateLayer(layers,m_dMapWidth*m_dMapheight, m_dExtraByteSize) Else Return MAPPYERROR_MAPNOTLOADED EndIf Else Return MAPPYERROR_INVALIDLAYER EndIf EndMethod Method deleteLayer:Int(layers:Int) If layers>=0 And layers

389 Local status:Int If fromlayer>=0 And fromLayer=0 And toLayerMAPPYERROR_OK Return status EndIf EndIf CopyBank( layer[fromLayer],0,layer[toLayer],0,BankSize(layer[fromlayer])) Return MAPPYERROR_OK Else Return MAPPYERROR_INVALIDLAYER EndIf EndMethod Method clearLayer:Int(layers:Int) Local l:Int If layers>=0 And layers

For l=0 To BankSize(layer[layers])-1 PokeByte(layer[layers],l,0) Next Return MAPPYERROR_OK Else Return MAPPYERROR_INVALIDLAYER EndIf EndMethod Method moveLayer:Int(fromLayer:Int,toLayer:Int) Local status:Int If fromlayer>=0 And fromLayer=0 And toLayerMAPPYERROR_OK Return status EndIf EndIf CopyBank( layer[fromLayer],0,layer[toLayer],0,BankSize(layer[fromlayer])) Return clearLayer(fromLayer) Else Return MAPPYERROR_INVALIDLAYER EndIf EndMethod Method returnRGB(colour:Byte,store:Byte Ptr) MemCopy(store,palette[colour],SizeOf RGB) EndMethod Method updateAnimations() Local loop:Int If m_dNumAnimations=0 Then Return For loop=0 To m_dNumAnimations-1 If animations[loop].antype<>AN_NONE animations[loop].ancount:-1

390 If animations[loop].ancount & 128 animations[loop].ancount=animations[loop].andelay Select animations[loop].antype Case AN_LOOPF If animations[loop].anstartoff<>animations[loop].anendoff animations[loop].ancuroff:+1 If animations[loop].ancuroff=animations[loop].anendoff animations[loop].ancuroff=animations[loop].anstartoff EndIf EndIf Case AN_LOOPR If animations[loop].anstartoff<>animations[loop].anendoff animations[loop].ancuroff:-1 If animations[loop].ancuroff=animations[loop].anstartoff-1 animations[loop].ancuroff=animations[loop].anendoff EndIf EndIf Case AN_ONCE If animations[loop].anstartoff<>animations[loop].anendoff animations[loop].ancuroff:+1 If animations[loop].ancuroff=animations[loop].anendoff

animations[loop].antype=AN_ONCES animations[loop].ancuroff=animations[loop].anendoff EndIf EndIf Case AN_ONCEH If animations[loop].anstartoff<>animations[loop].anendoff If animations[loop].ancuroff<>animations[loop].anendoff-1 animations[loop].ancuroff:+1 EndIf EndIf Case AN_PPFF If animations[loop].anstartoff<>animations[loop].anendoff animations[loop].ancuroff:+1 If animations[loop].ancuroff=animations[loop].anendoff animations[loop].ancuroff:-2 animations[loop].antype=AN_PPFR If animations[loop].ancuroffanimations[loop].anendoff animations[loop].ancuroff:-1 If animations[loop].ancuroff=animations[loop].anstartoff-1 animations[loop].ancuroff:+2 animations[loop].antype=AN_PPFF If animations[loop].ancuroff>animations[loop].anendoff animations[loop].ancuroff:-1 EndIf EndIf

391 EndIf Case AN_PPRR If animations[loop].anstartoff<>animations[loop].anendoff animations[loop].ancuroff:-1 If animations[loop].ancuroff=animations[loop].anstartoff-1 animations[loop].ancuroff:+2 animations[loop].antype=AN_PPRF If animations[loop].ancuroff>animations[loop].anendoff animations[loop].ancuroff:-1 EndIf EndIf EndIf Case AN_PPRF If animations[loop].anstartoff<>animations[loop].anendoff animations[loop].ancuroff:-1 If animations[loop].ancuroff=animations[loop].anendoff animations[loop].ancuroff:-2 animations[loop].antype=AN_PPRR If animations[loop].ancuroff

animations[loop].ancuroff:+1 EndIf EndIf EndIf EndSelect EndIf EndIf Next EndMethod Method deleteMap() Local l:Int m_dMapType=0 m_dMapVersion=0 m_dLSB=0 m_dMapWidth=0 m_dMapHeight=0 m_dMapDepth=0 m_dBlockWidth=0 m_dBlockHeight=0 m_dBlockSize=0 m_dNumBlockStructs=0 m_dNumBlockGFX=0 m_dBlockGapX=0 m_dBlockGapY=0 m_dBlockStaggerX=0 m_dBlockStaggerY=0 m_dClipMask=0 m_dTrans8=0 m_dTransRed=0 m_dTransGreen=0 m_dTransBlue=0 m_dNumAnimations=0 m_dExtraByteSize=0 m_dTileSizeInBytes=0 Mappy_FileSize=0 m_dAuthorSize=0 m_dAuthor$="" animations=Null animationSeq=Null For l=0 To 255 palette[l]=Null

392 Next For l=0 To MAX_LAYERS-1 layer[l]=Null extraBytesLayer[l]=Null Next blockStructs=Null animations=Null animationSeq=Null EndMethod EndType

393 PHP

394 Online Hiscore Routine

This was originally written for my BlitzMax BallZ II game and processes data sent to it, after which it is displayed in a table. Online Scoreboard For BallZ II - Nicholas Kingsley

0)) { $query="INSERT INTO BallZIITable VALUES (null, '$name', '$score',CURRENT_TIMESTAMP,'$typeOfGame','$computerType')"; $result=mysql_query($query); echo("Score submitted! Name: $name, Score: $score"); } $query = "SELECT * FROM BallZIITable ORDER BY score DESC"; $result = mysql_query($query); $num = mysql_numrows($result); ?>

\n");

$loopindex++; } ?>

PosNameScoreType Of GameDate SubmittedOperating System

$thispos

$thisname

$thisscore

$thisTypeOfGame

$thisdate

$thisComputerType

395 396 This book was distributed courtesy of:

For your own Unlimited Reading and FREE eBooks today, visit: http://www.Free-eBooks.net

Share this eBook with anyone and everyone automatically by selecting any of the options below:

To show your appreciation to the author and help others have wonderful reading experiences and find helpful information too, we'd be very grateful if you'd kindly post your comments for this book here.

COPYRIGHT INFORMATION

Free-eBooks.net respects the intellectual property of others. When a book's copyright owner submits their work to Free-eBooks.net, they are granting us permission to distribute such material. Unless otherwise stated in this book, this permission is not passed onto others. As such, redistributing this book without the copyright owner's permission can constitute copyright infringement. If you believe that your work has been used in a manner that constitutes copyright infringement, please follow our Notice and Procedure for Making Claims of Copyright Infringement as seen in our Terms of Service here: http://www.free-ebooks.net/tos.html