From 9192d614d24e9dc913c30f9aca5b2a8efda199df Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Sat, 28 Jun 2014 23:49:09 +0100 Subject: [PATCH] Initial commit --- Makefile | 21 + audio.c | 200 +++++ audio.h | 17 + bing.wav | Bin 0 -> 88228 bytes bip.wav | Bin 0 -> 4460 bytes bop.wav | Bin 0 -> 4460 bytes cars.c | 119 +++ cars.h | 31 + cars.txt | 45 + config.c | 182 ++++ config.h | 15 + gauge.c | 237 +++++ gauge.h | 73 ++ gauges.txt | 13 + gettime.h | 81 ++ insim.c | 439 +++++++++ insim.h | 2237 ++++++++++++++++++++++++++++++++++++++++++++++ lfsdash.c | 994 ++++++++++++++++++++ list.h | 72 ++ network_worker.c | 194 ++++ network_worker.h | 12 + off.wav | Bin 0 -> 6564 bytes on.wav | Bin 0 -> 7100 bytes outgauge.c | 105 +++ outgauge.h | 43 + queue.c | 98 ++ queue.h | 11 + socket.c | 261 ++++++ socket.h | 26 + symbols512.tga | Bin 0 -> 306160 bytes text.c | 32 + text.h | 16 + timer.c | 101 +++ timer.h | 15 + worker.c | 133 +++ worker.h | 27 + 36 files changed, 5850 insertions(+) create mode 100644 Makefile create mode 100644 audio.c create mode 100644 audio.h create mode 100644 bing.wav create mode 100644 bip.wav create mode 100644 bop.wav create mode 100644 cars.c create mode 100644 cars.h create mode 100644 cars.txt create mode 100644 config.c create mode 100644 config.h create mode 100644 gauge.c create mode 100644 gauge.h create mode 100644 gauges.txt create mode 100644 gettime.h create mode 100644 insim.c create mode 100644 insim.h create mode 100644 lfsdash.c create mode 100644 list.h create mode 100644 network_worker.c create mode 100644 network_worker.h create mode 100644 off.wav create mode 100644 on.wav create mode 100644 outgauge.c create mode 100644 outgauge.h create mode 100644 queue.c create mode 100644 queue.h create mode 100644 socket.c create mode 100644 socket.h create mode 100644 symbols512.tga create mode 100644 text.c create mode 100644 text.h create mode 100644 timer.c create mode 100644 timer.h create mode 100644 worker.c create mode 100644 worker.h diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..78d6e80 --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +SRCS := lfsdash.c +SRCS += audio.c +SRCS += cars.c +SRCS += config.c +SRCS += gauge.c +SRCS += insim.c +SRCS += network_worker.c +SRCS += outgauge.c +SRCS += queue.c +SRCS += socket.c +SRCS += text.c +SRCS += timer.c +SRCS += worker.c + +OBJS := $(SRCS:.c=.o) + +CFLAGS += `pkg-config libglfw ftgl openal --cflags` -g +LDFLAGS += `pkg-config libglfw ftgl openal --libs` -g + +lfsdash: $(OBJS) + $(CC) $(LDFLAGS) $(OBJS) -o $@ diff --git a/audio.c b/audio.c new file mode 100644 index 0000000..c13b087 --- /dev/null +++ b/audio.c @@ -0,0 +1,200 @@ +#include +#include +//#include +#include +#include +#include +#include +#include "audio.h" + +#define NUM_BUFFERS 5 +#define NUM_SOURCES 6 +#define NUM_ENVIRONMENTS 1 + +ALCdevice *dev; +ALCcontext *ctx; + +//There is only one listener, and these arrays define the initial set up of the listener for its position, direction, and velocity. The listener is not moving at the start, but can move around any other sound source. +ALfloat listenerPos[]={0.0,0.0,4.0}; +ALfloat listenerVel[]={0.0,0.0,0.0}; +ALfloat listenerOri[]={0.0,0.0,1.0, 0.0,1.0,0.0}; + +//Each sound source has similar properties that the listener has. The source0Pos and source0Vel arrays show the position and velocity of the sound source. +ALfloat source0Pos[]={ -2.0, 0.0, 0.0}; +ALfloat source0Vel[]={ 0.0, 0.0, 0.0}; + +//Sounds need to be stored in an array, similar to textures. Several buffers are needed to contain the sounds and other necessary information. The size, freq, format, and data variables are used when loading the sound files. +ALuint buffer[NUM_BUFFERS]; +ALuint source[NUM_SOURCES]; +ALuint environment[NUM_ENVIRONMENTS]; + +int file_read_int32_le(char *buffer, FILE *file) +{ + int r = fread(buffer, sizeof(char), 4, file); + uint32_t ret = (uint8_t)buffer[0]; + ret |= (uint8_t)buffer[1] << 8; + ret |= (uint8_t)buffer[2] << 16; + ret |= (uint8_t)buffer[3] << 24; + return ret; +} + +int file_read_int16_le(char *buffer, FILE *file) +{ + int r = fread(buffer, sizeof(char), 2, file); + uint16_t ret = (uint8_t)buffer[0]; + ret |= (uint8_t)buffer[1] << 8; + return ret; +} + +void file_ignore_bytes(FILE *file, int bytes) +{ + fseek(file, bytes, SEEK_CUR); +} + +void *file_allocate_and_read_bytes(FILE *file, size_t bytes) +{ + void *buffer = malloc(bytes); + fread(buffer, 1, bytes, file); + return buffer; +} + +static inline ALenum GetFormatFromInfo(short channels, short bitsPerSample) { + if (channels == 1) + return AL_FORMAT_MONO16; + return AL_FORMAT_STEREO16; +} + +void audio_load(const char *filename, ALuint buffer) +{ + FILE* file = fopen(filename, "rb"); + char xbuffer[5]; + memset(xbuffer, 0, sizeof xbuffer); + if (fread(xbuffer, sizeof(char), 4, file) != 4 || strcmp(xbuffer, "RIFF") != 0) + printf("Not a WAV file: %s\n", xbuffer); + + file_read_int32_le(xbuffer, file); + + if (fread(xbuffer, sizeof(char), 4, file) != 4 || strcmp(xbuffer, "WAVE") != 0) + printf("Not a WAV file: %s\n", xbuffer); + + if (fread(xbuffer, sizeof(char), 4, file) != 4 || strcmp(xbuffer, "fmt ") != 0) + printf("Invalid WAV file: %s\n", xbuffer); + + int size = file_read_int32_le(xbuffer, file); + short audioFormat = file_read_int16_le(xbuffer, file); + short channels = file_read_int16_le(xbuffer, file); + int sampleRate = file_read_int32_le(xbuffer, file); + int byteRate = file_read_int32_le(xbuffer, file); + file_read_int16_le(xbuffer, file); + short bitsPerSample = file_read_int16_le(xbuffer, file); + + size -= 16; + + file_ignore_bytes(file, size); + + if (fread(xbuffer, sizeof(char), 4, file) != 4 || strcmp(xbuffer, "data") != 0) + printf("Invalid WAV file: %s\n", xbuffer); + + int dataChunkSize = file_read_int32_le(xbuffer, file); + unsigned char* bufferData = file_allocate_and_read_bytes(file, (size_t) dataChunkSize); + + float duration = (float)(dataChunkSize) / byteRate; + alBufferData(buffer, GetFormatFromInfo(channels, bitsPerSample), bufferData, dataChunkSize, sampleRate); + free(bufferData); + fclose(file); +} + +void audio_init(void) +{ + dev = alcOpenDevice(NULL); + if (!dev) + { + fprintf(stderr, "Oops\n"); + return; + } + + ctx = alcCreateContext(dev, NULL); + alcMakeContextCurrent(ctx); + if (!ctx) + { + fprintf(stderr, "Oops2\n"); + return; + } + + alListenerfv(AL_POSITION,listenerPos); + alListenerfv(AL_VELOCITY,listenerVel); + alListenerfv(AL_ORIENTATION,listenerOri); + + alGetError(); // clear any error messages + + // Generate buffers, or else no sound will happen! + alGenBuffers(NUM_BUFFERS, buffer); + + if (alGetError() != AL_NO_ERROR) { + printf("- Error creating buffers !!\n"); + exit(1); + } + + audio_load("off.wav", buffer[FX_OFF]); + audio_load("on.wav", buffer[FX_ON]); + audio_load("bing.wav", buffer[FX_BING]); + audio_load("bip.wav", buffer[FX_BIP]); + audio_load("bop.wav", buffer[FX_BOP]); + + alGetError(); /* clear error */ + alGenSources(NUM_SOURCES, source); + + if (alGetError() != AL_NO_ERROR) + { + printf("- Error creating sources !!\n"); + exit(2); + } + + int ii; + for (ii = 0; ii < NUM_SOURCES; ii++) + { + alSourcef(source[ii], AL_PITCH, 1.0f); + alSourcef(source[ii], AL_GAIN, 1.0f); + alSourcefv(source[ii], AL_POSITION, source0Pos); + alSourcefv(source[ii], AL_VELOCITY, source0Vel); + //alSourcei(source[0], AL_BUFFER, buffer[0]); + alSourcei(source[ii], AL_LOOPING, AL_FALSE); + } +} + +void audio_deinit(void) +{ + int ii; + for (ii = 0; ii < NUM_SOURCES; ii++) + { + alSourceStop(source[ii]); + } + + alDeleteSources(NUM_SOURCES, source); + alDeleteBuffers(NUM_BUFFERS, buffer); + + alcMakeContextCurrent(NULL); + alcDestroyContext(ctx); + alcCloseDevice(dev); +} + +void audio_volume(float f) +{ + int ii; + for (ii = 0; ii < NUM_SOURCES; ii++) + { + alSourcef(source[ii], AL_GAIN, f); + } +} + +float frand(float mult) +{ + return ((float)rand() / (float)RAND_MAX) * mult; +} + +void audio_play(int s, int b) +{ + alSourceStop(source[s]); + alSourcei(source[s], AL_BUFFER, buffer[b]); + alSourcePlay(source[s]); +} diff --git a/audio.h b/audio.h new file mode 100644 index 0000000..ca12476 --- /dev/null +++ b/audio.h @@ -0,0 +1,17 @@ +#ifndef AUDIO_H +#define AUDIO_H + +enum { + FX_ON, + FX_OFF, + FX_BING, + FX_BIP, + FX_BOP, +}; + +void audio_init(void); +void audio_deinit(void); +void audio_volume(float f); +void audio_play(int s, int on); + +#endif /* AUDIO_H */ diff --git a/bing.wav b/bing.wav new file mode 100644 index 0000000000000000000000000000000000000000..1b791cfdb1e77eb4f8a1c7f4933ff40c724b769f GIT binary patch literal 88228 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�%mT{K_MVEIu87hAA);pwXEAj&7V*4X3A4j+Vca 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

gxlwb?O_Xnz9L%3+$B0ocI#IjbB{ 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 literal 0 HcmV?d00001 diff --git a/bip.wav b/bip.wav new file mode 100644 index 0000000000000000000000000000000000000000..400c5d2ea65565d6752ba5aa5847ce2d536c0764 GIT binary patch literal 4460 zcmZu#2Urwm8eR}7%OxsB#Rx2WkpvJcWjA+cmf~8dYNQu|-BAL@a(d{QSRPx>i_u&J zGk^<-LV#tTyF{vp)P#!&t4OFhj1&d(vxB_HJW8)aZY$oJ8`!R!SK7M@>dVD*vJ zm{r1}o<%`#G`-=uxMJ~2>l|xan`9fl?Fri_wtED9f+#z+ou9B$c&j_G`#)W>E=}iw zPQA{*cUX3$w>z|}+Wyd1`@Qe?ZLLwQkM8ZgH{Ei)WnRmLyG!oo+)*!+EU#F;Y(@Tx zMJrQQ@>d;OHMuHr^}y-~m%A<-T&rAN-2UZe#xTQgX_w|b`PMAe)s`ljHfveKpU<_+l$bwxzQ{p)+KYs-(8|8H%gbWj?+u63Ps zebsu`4TT$o8&fyV^*QA;bQ*y%uX9mlU%;;A_5ea0rt!_yti80O-cJ7Sf1df7J2vvc5sYYU=; z11*A8i~yp*NZ@HIVejtIq33C}q}MWGfjTTm9uESC*}A>szS!-4Mt+lUva z6b_;*t{yNic)Fb;)<92yH)4)?K`y9Wz=^0Bzk@4l8|Szzk!;Zi>J{;3F(Vei3*iaS zx=6qO9DSC7ScaDBLeIm{B34E2z}*dSH#a_wQo z07LPRo(?NS8*u^sC|+oY@J{kTcHk>Gv1^bK;)V=icLALdA0%Qx1$x6&k zS!^a~A1fW+V(S{dM?3%@(y?#`t}p}F->wU+bS_GODe3@ifOR$>tQbHCJ2UYIo++k6 z*ysT-?8E7dJ<@|=1eyXL5YewA#$3=QT7(%g!lwjlz#^H>V0u&r^NSrGaU|WJ)DY_f zS1Y_nWKeUc3+N-r)vIbH)j%@gt}B=a>4IQ|a1yWUS$Gc>kIyhaj1_xoiY^bbUV%OA z%lMS6UC1-WjjSRIpd&8#q|;Lj=dAWw{;@JiH-m1#Rt{TTFIxn@W0{T;!~BxBAWyJH z;za$TMQ**Lo=E3nF=x99XbrK48i~>QNzip@oX0${2GgEvhh#vsh4-i|*qy*iLG}-% z1_3wHKcH)ol?yW=aY9C<_E>K(_ZS7UJ3(7SvCxB22Sjr$7dT>dL#m9`Ea(-ggZM^P zxH<)tKvSd_14l#ype;o9Rs0F^m>;V=7(sqS_gEis&(Q~LSp0AW0USXG-1UNM9gxEl zW=V~HPG{1vHjoIBZPGz-mwMJAyv_?5rQMtS)c9 zeC~3?ng44PT3-3EZ~6`UDt!<5y8H5c z>wP}<+2k{H?d_s%MVE@&i{=&k7oRMyE1oVEm+UQ3l?;?DDUB#iExlVhr)*u>$+F6_ z$+A`DyUT;tE?)b<`-=Am-pjpz@v87T;Mhl0s~f8IYSz>w)qGu}saaA>)uz>U)tc1>)Sa%YuX|SKUY}H7 zP(NJXAT{LRucOE)dGSlZ>3;Usr*avE_gay;lLah&CN%i*lUR)>WS zJ#R<0rL?uQ{jPm|`|llkTIkIt9Q=g(JLCR>JD3N3%MP+25cxLZt^-!MOIE;TnZSDU4qMVKuy8=SX! z9SB(#iFVRM8Q?;#HjcJ)_ zlBt*JEYn8*Ierk|ir-_BV-jz&!bEFaZhXYp+t|do$>@SnsF96PuVJ3yPQ#`Nqe-vH z!;{66zf3w#B~E2d-Ji0W-aLJNx?$Sj>AO!4J}rJa{M2biHj^>aHDmEC@Y!E@?=b&Y F`9GQIqyqo| literal 0 HcmV?d00001 diff --git a/bop.wav b/bop.wav new file mode 100644 index 0000000000000000000000000000000000000000..d4dce937f0d99266436d93c9b07c1e0bf6917c14 GIT binary patch literal 4460 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 +#include +#include + +struct car s_cars[MAX_CARS]; + +#ifdef WIN32 +#include +#include +#include + +char* strsep(char** stringp, const char* delim) +{ + char* start = *stringp; + char* p; + + p = (start != NULL) ? strpbrk(start, delim) : NULL; + + if (p == NULL) + { + *stringp = NULL; + } + else + { + *p = '\0'; + *stringp = p + 1; + } + + return start; +} +#endif + +void init_cars(void) +{ + memset(s_cars, 0, sizeof s_cars); + + FILE *f = fopen("cars.txt", "r"); + if (!f) return; + + struct car *car = s_cars; + + while (!feof(f)) + { + char buf[1024]; + if (fgets(buf, sizeof buf, f) <= 0) break; + if (*buf == '#') continue; + + char *bufp = buf, *p; + p = strsep(&bufp, ","); + strncpy(car->tag, p, sizeof car->tag); + p = strsep(&bufp, ","); + strncpy(car->base, p, sizeof car->base); + p = strsep(&bufp, ","); + strncpy(car->cls, p, sizeof car->cls); + p = strsep(&bufp, ","); + car->intake = strtol(p, NULL, 10); + p = strsep(&bufp, ","); + car->weight = strtol(p, NULL, 10); + p = strsep(&bufp, ","); + strncpy(car->name, p, sizeof car->name); + p = strsep(&bufp, ","); + car->maxrpm = strtof(p, NULL); + p = strsep(&bufp, ","); + car->maxfuel = strtof(p, NULL); + p = strsep(&bufp, ","); + car->maxboost = strtof(p, NULL); + p = strsep(&bufp, ","); + car->maxspeed = strtof(p, NULL); + + car++; + + if (car - s_cars == MAX_CARS) break; + } + + fclose(f); +} + +int get_car(const char tag[4], struct car *car) +{ + int i; + for (i = 0; i < MAX_CARS; i++) + { + if (!strcmp(s_cars[i].tag, tag)) + { + if (car != NULL) memcpy(car, &s_cars[i], sizeof *car); + return i; + } + } + return -1; +} + +const char *find_car(const char tag[4], int intake, int weight) +{ + const struct car *best = NULL; + int i; + for (i = 0; i < MAX_CARS; i++) + { + const struct car *car = s_cars + i; + if (!strcmp(car->base, tag)) + { + if (car->intake <= intake && car->weight <= weight) + { + if (best == NULL || (car->intake >= best->intake && car->weight >= best->weight)) + { + best = s_cars + i; + } + } + } + } + return best->tag; +} + +void update_car(int carnum, float dist, float time, float cons) +{ + s_cars[carnum].dist += dist; + s_cars[carnum].time += time; + s_cars[carnum].cons += cons; +} diff --git a/cars.h b/cars.h new file mode 100644 index 0000000..aa354fe --- /dev/null +++ b/cars.h @@ -0,0 +1,31 @@ +#ifndef CARS_H +#define CARS_H + +#define MAX_CARS 60 + +struct car +{ + char tag[4]; + char base[4]; + char cls[20]; + char name[20]; + float maxrpm; + float maxfuel; + float maxboost; + float maxspeed; + int intake; + int weight; + + float dist; + float time; + float cons; +}; + +extern struct car s_cars[MAX_CARS]; + +void init_cars(void); +int get_car(const char tag[4], struct car *car); +const char *find_car(const char tag[4], int intake, int weight); +void update_car(int carnum, float dist, float time, float cons); + +#endif /* CARS_H */ diff --git a/cars.txt b/cars.txt new file mode 100644 index 0000000..4eda8f7 --- /dev/null +++ b/cars.txt @@ -0,0 +1,45 @@ +UF1,UF1,STD,0,0,UF 1000,6983.477051,35.0,0.0,100 +XFG,XFG,STD,0,0,XF GTI,7978.992188,45.0,0.0,120 +XRG,XRG,STD,0,0,XR GT,6980.518066,65.0,0.0,130 +LX4,LX4,TBO,0,0,LX4,8974.058594,40.0,0.0,130 +LX6,LX6,LRF,0,0,LX6,8975.434570,40.0,0.0,140 +RB4,RB4,TBO,0,0,RB4 GT,7480.626465,75.0,0.731383,150 +FXO,FXO,TBO,0,0,FXO TURBO,7482.368164,75.0,0.733507,160 +XRT,XRT,TBO,0,0,XR GT TURBO,7480.955078,75.0,0.830510,160 +RAC,RAC,LRF,0,0,RACEABOUT,6985.558594,42.0,0.482715,160 +FZ5,FZ5,LRF,0,0,FZ50,7971.276855,90.0,0.0,180 +UFR,UFR,NGT,0,0,UF GTR,8978.926758,60.0,0.0,160 +XFR,XFR,NGT,0,0,XF GTR,7979.339355,70.0,0.0,180 +FXR,FXR,GTR,0,0,FXO GTR,7492.066895,100.0,1.759917,200 +XRR,XRR,GTR,0,0,XR GTR,7492.067383,100.0,1.759917,200 +FZR,FZR,GTR,0,0,FZ50 GTR,8474.996094,100.0,0.0,200 +MRT,MRT,S-S,0,0,MRT5,12917.661133,20.0,0.138831,120 +FBM,FBM,S-S,0,0,FORMULA BMW FB02,9179.628906,42.0,0.0,140 +FOX,FOX,S-S,0,0,FORMULA XR,7481.007324,38.0,0.0,150 +FO8,FO8,S-S,0,0,FORMULA V8,9476.702148,125.0,0.0,180 +BF1,BF1,S-S,0,0,BMW SAUBER F1.06,19912.447266,95.0,0.0,220 +FX2,FXR,GT2,23,0,FXO GT2,7492.066895,100.0,1.759917,200 +XR2,XRR,GT2,24,0,XR GT2,7492.067383,100.0,1.759917,200 +FZ2,FZR,GT2,20,0,FZ50 GT2,8474.996094,100.0,0.0,200 +FX9,FXR,GT9,24,0,FXO GT9,7492.066895,100.0,1.759917,200 +XR9,XRR,GT9,25,0,XR GT9,7492.067383,100.0,1.759917,200 +FZ9,FZR,GT9,21,0,FZ50 GT9,8474.996094,100.0,0.0,200 +FXP,FXR,GTP,10,10,FXO GTP,7492.066895,100.0,1.759917,200 +XRP,XRR,GTP,10,20,XR GTP,7492.067383,100.0,1.759917,200 +FZP,FZR,GTP,10,0,FZ50 GTP,8474.996094,100.0,0.0,200 +FX3,FXR,GT3,32,0,FXO GT3,7492.066895,100.0,1.759917,200 +XR3,XRR,GT3,34,0,XR GT3,7492.067383,100.0,1.759917,200 +FZ3,FZR,GT3,28,0,FZ50 GT3,8474.996094,100.0,0.0,200 +FX4,FXR,GT4,44,75,FXO GT4,7492.066895,100.0,1.759917,200 +XR4,XRR,GT4,44,100,XR GT4,7492.067383,100.0,1.759917,200 +FZ4,FZR,GT4,37,100,FZ50 GT4,8474.996094,100.0,0.0,200 +UF4,UFR,GT4,12,50,UF GT4,8978.926758,60.0,0.0,160 +XF4,XFR,GT4,10,60,XF GT4,7979.339355,70.0,0.0,180 +UFB,UFR,BGT,45,0,UF-BR,8978.926758,60.0,0.0,160 +XFB,XFR,BGT,43,0,XF-BR,7979.339355,70.0,0.0,180 +RBS,RB4,S-series,4,60,RB4-S,7480.626465,75.0,0.731383,150 +FXS,FXO,S-series,6,70,FXO-S,7482.368164,75.0,0.733507,160 +XRS,XRT,S-series,5,60,XR GT-S,7480.955078,75.0,0.830510,160 +FZS,FZ5,S-series,22,50,FZ50 S,7971.276855,90.0,0.0,180 +FO,FXO,TWC,5,0,FO TURBO,7482.368164,75.0,0.733507,160 +XT,XRT,TWC,1,0,XT TURBO,7480.955078,75.0,0.830510,160 diff --git a/config.c b/config.c new file mode 100644 index 0000000..4c69f07 --- /dev/null +++ b/config.c @@ -0,0 +1,182 @@ +#include +#include +#include +#include "config.h" + +#define MAXCONFIGLEN 64 + +struct configitem +{ + char setting[MAXCONFIGLEN]; + char value[MAXCONFIGLEN]; + struct configitem *next; +}; + +static struct +{ + const char *filename; + struct configitem *head; +} s_config; + +static void config_free(void) +{ + struct configitem *prev; + struct configitem *curr = s_config.head; + + s_config.head = NULL; + + while (curr != NULL) + { + prev = curr; + curr = curr->next; + free(prev); + } +} + +void config_set_string(const char *key, const char *value) +{ + struct configitem *item; + struct configitem **itemp = &s_config.head; + + while (*itemp != NULL) + { + if (strcasecmp((*itemp)->setting, key) == 0) + { + item = *itemp; + if (value == NULL) + { + *itemp = item->next; + free(item); + } + else + { + strncpy(item->value, value, sizeof item->value); + } + return; + } + itemp = &(*itemp)->next; + } + + if (value == NULL) return; + + *itemp = malloc(sizeof *item); + item = *itemp; + + strncpy(item->setting, key, sizeof item->setting); + strncpy(item->value, value, sizeof item->value); + item->next = NULL; +} + +static void config_rehash(void) +{ + FILE *f = fopen(s_config.filename, "r"); + if (f == NULL) return; + + config_free(); + + while (!feof(f)) + { + char buf[256]; + if (fgets(buf, sizeof buf, f) <= 0) break; + + /* Ignore comments */ + if (*buf == '#') continue; + + char *n = strchr(buf, '='); + if (n == NULL) continue; + *n++ = '\0'; + + char *eol = strchr(n, '\n'); + if (eol != NULL) *eol = '\0'; + + config_set_string(buf, n); + } + + fclose(f); +} + +static void config_write(void) +{ + FILE *f = fopen(s_config.filename, "w"); + if (f == NULL) return; + + struct configitem *curr; + for (curr = s_config.head; curr != NULL; curr = curr->next) + { + fprintf(f, "%s=%s\n", curr->setting, curr->value); + } + + fclose(f); +} + +void config_init(const char *filename) +{ + s_config.filename = filename; + s_config.head = NULL; + + config_rehash(); +} + +void config_deinit(void) +{ + config_write(); + config_free(); +} + +void config_set_int(const char *setting, int value) +{ + char c[MAXCONFIGLEN]; + snprintf(c, sizeof c, "%d", value); + config_set_string(setting, c); +} + +void config_set_float(const char *setting, float value) +{ + char c[MAXCONFIGLEN]; + snprintf(c, sizeof c, "%f", value); + config_set_string(setting, c); +} + +int config_get_string(const char *setting, char **value) +{ + struct configitem *curr; + for (curr = s_config.head; curr != NULL; curr = curr->next) + { + if (strcasecmp(curr->setting, setting) == 0) + { + *value = curr->value; + return 1; + } + } + + *value = NULL; + return 0; +} + +int config_get_int(const char *setting, int *value) +{ + char *c, *endptr; + int v; + + if (!config_get_string(setting, &c)) return 0; + + v = strtol(c, &endptr, 10); + if (*endptr != '\0') return 0; + + *value = v; + return 1; +} + +int config_get_float(const char *setting, float *value) +{ + char *c, *endptr; + float v; + + if (!config_get_string(setting, &c)) return 0; + + v = strtof(c, &endptr); + if (*endptr != '\0') return 0; + + *value = v; + return 1; +} diff --git a/config.h b/config.h new file mode 100644 index 0000000..76a93a3 --- /dev/null +++ b/config.h @@ -0,0 +1,15 @@ +#ifndef CONFIG_H +#define CONFIG_H + +void config_init(const char *filename); +void config_deinit(void); + +void config_set_string(const char *setting, const char *value); +void config_set_int(const char *setting, int value); +void config_set_float(const char *setting, float value); + +int config_get_string(const char *setting, char **value); +int config_get_int(const char *setting, int *value); +int config_get_float(const char *setting, float *value); + +#endif /* CONFIG_H */ diff --git a/gauge.c b/gauge.c new file mode 100644 index 0000000..2cdfdbd --- /dev/null +++ b/gauge.c @@ -0,0 +1,237 @@ +#include +#include +#include +#include +#include "gauge.h" +#include "text.h" + +void init_gauges(void) +{ +} + +void DrawArc(float inner, float outer, float start_angle, float arc_angle, int num_segments) +{ + num_segments = 72; + float theta = arc_angle / (float)(num_segments - 1);//theta is now calculated from the arc angle instead, the - 1 bit comes from the fact that the arc is open + + float tangetial_factor = tanf(theta); + + float radial_factor = cosf(theta); + + float xi = inner * sinf(start_angle); //we now start at the start angle + float yi = inner * cosf(start_angle); + float xo = outer * sinf(start_angle); //we now start at the start angle + float yo = outer * cosf(start_angle); + + + glBegin(GL_TRIANGLE_STRIP);//since the arc is not a closed curve, this is a strip now + int ii; + for(ii = 0; ii < num_segments; ii++) + { + glVertex3f(xi, yi, 0); + glVertex3f(xo, yo, 0); + + float txi = -yi; + float tyi = xi; + + xi += txi * tangetial_factor; + yi += tyi * tangetial_factor; + + xi *= radial_factor; + yi *= radial_factor; + + float txo = -yo; + float tyo = xo; + + xo += txo * tangetial_factor; + yo += tyo * tangetial_factor; + + xo *= radial_factor; + yo *= radial_factor; + } + glEnd(); +} + +/* + float theta = 2 * 3.1415926 / (float)num_segments; + float c = cosf(theta);//precalculate the sine and cosine + float s = sinf(theta); + float t; + + float x = r;//we start at angle = 0 + float y = 0; + + glBegin(GL_LINE_LOOP); + int ii; + for(ii = 0; ii < num_segments; ii++) + { + glVertex3f(x + cx, y + cy, 0);//output vertex + + //apply the rotation matrix + t = x; + x = c * x - s * y; + y = s * t + c * y; + } + glEnd(); +}*/ + +void drawDial(const struct gauge *gauge, float value, int style) +{ + float range = gauge->rangemax - gauge->rangemin; + float angle = gauge->anglemax - gauge->anglemin; + float cura = (value - gauge->rangemin) / range * angle + gauge->anglemin; + + + + switch (style) + { + case GT_NONE: + break; + case GT_BAR: + glColor4f(gauge->dial.r, gauge->dial.g, gauge->dial.b, gauge->dial.a); + DrawArc(gauge->majorstart, gauge->majorend, cura, cura - gauge->anglemin, 10); + break; + case GT_LINE2: + glColor4f(gauge->dial.r, gauge->dial.g, gauge->dial.b, gauge->dial.a); + glBegin(GL_LINES); + glVertex3f(0, 0, 0); + glVertex3f(sin(cura), cos(cura), 0); + glEnd(); + break; + case GT_LINE: + glColor4f(1.0, 0.0, 0.0, 1.0); + glBegin(GL_LINES); + glVertex3f(sin(cura) * 0.8, cos(cura) * 0.8, 0); + glVertex3f(sin(cura), cos(cura), 0); + glEnd(); + break; + default: + case GT_NEEDLE: + glColor4f(gauge->dial.r, gauge->dial.g, gauge->dial.b, gauge->dial.a); + glRotatef(cura * -180.0 / M_PI, 0.0, 0.0, 1.0); + glBegin(GL_TRIANGLE_STRIP); + //glVertex3f(-0.01, -0.04, 0.0); + //glVertex3f(0.01, -0.04, 0.0); + //glVertex3f(-0.02, 0, 0.0); + //glVertex3f(0.02, 0.0, 0.0); + //glVertex3f(-0.005, 1.0, 0.0); + //glVertex3f(0.005, 1.0, 0.0); + glVertex3f(-0.02, 0.2, 0.0); + glVertex3f(0.02, 0.2, 0.0); + glVertex3f(-0.005, 1.0, 0.0); + glVertex3f(0.005, 1.0, 0.0); + //glVertex3f(0.0, 1.0, 0.0); + glEnd(); + break; + } +} + + +void draw_gauge(const struct gauge *gauge, float value, float red, FTGLfont *font, int gaugetype) +{ + float range = gauge->rangemax - gauge->rangemin; + float angle = gauge->anglemax - gauge->anglemin; + float major;// = angle / (range / gauge->majorstep + 0.0); + float minor;// = angle / (range / gauge->minorstep + 0.0); + float cura; + float x1, y1, x2, y2; + int ii; + + float majorstep = gauge->majorstep; + float minorstep = gauge->minorstep; + + while (1) { + major = angle / (range / majorstep + 0.0); + minor = angle / (range / minorstep + 0.0); + if (fabsf(major) < 12 * M_PI / 180.0) { + majorstep *= 2; + minorstep = majorstep / 2; + } else { + break; + } + } + + if (value < gauge->rangemin) value = gauge->rangemin; + if (value > gauge->rangemax * 2) value = gauge->rangemax * 2; + + glPushMatrix(); + glScalef(gauge->radius, gauge->radius, 1.0); + + if (red > 0) { + glLineWidth(1.0); + glColor4f(0.8, 0.0, 0.0, 1.0); + + cura = (red - gauge->rangemin) / range * angle + gauge->anglemin; + +/* + float x1 = sin(cura) * gauge->majorstart; + float y1 = cos(cura) * gauge->majorstart; + float x2 = sin(cura) * gauge->majorend; + float y2 = cos(cura) * gauge->majorend; + glBegin(GL_LINES); + glVertex3f(x1, y1, 0); + glVertex3f(x2, y2, 0); + glEnd();*/ + +// printf("%f -> %f\n", cura, gauge->anglemax); + DrawArc(gauge->majorstart, gauge->majorend, gauge->anglemax, gauge->anglemax - cura, 10); + } + + glLineWidth(gauge->minorwidth); + glColor4f(gauge->minor.r, gauge->minor.g, gauge->minor.b, gauge->minor.a); +// for (cura = gauge->anglemin; cura < gauge->anglemax; cura += minor) + for (ii = 0; ii <= range / minorstep; ii++) + { + if (red > 0 && ii * minorstep >= red) { + glColor4f(1.0, 1.0, 1.0, 1.0); + } + float cura = gauge->anglemin + ii * minor; + x1 = sin(cura) * gauge->minorstart; + y1 = cos(cura) * gauge->minorstart; + x2 = sin(cura) * gauge->minorend; + y2 = cos(cura) * gauge->minorend; + glBegin(GL_LINES); + glVertex3f(x1, y1, 0); + glVertex3f(x2, y2, 0); + glEnd(); + } + + glLineWidth(gauge->majorwidth); + glColor4f(gauge->major.r, gauge->major.g, gauge->major.b, gauge->major.a); +// for (cura = gauge->anglemin; cura < gauge->anglemax; cura += major) + for (ii = 0; ii <= range / majorstep; ii++) + { + if (red > 0 && ii * majorstep > red) { + glColor4f(1.0, 1.0, 1.0, 1.0); + } + float cura = gauge->anglemin + ii * major; + x1 = sin(cura) * gauge->majorstart; + y1 = cos(cura) * gauge->majorstart; + x2 = sin(cura) * gauge->majorend; + y2 = cos(cura) * gauge->majorend; + glBegin(GL_LINES); + glVertex3f(x1, y1, 0); + glVertex3f(x2, y2, 0); + glEnd(); + + char text[10]; + if (gauge->majorstep > 100) { + snprintf(text, sizeof text, "%d", (int)(ii * majorstep / 1000 + gauge->rangemin)); + } else { + if (range == 1) { + snprintf(text, sizeof text, ii == 0 ? "E" : ii == 1 ? "½" : "F"); + } else { + snprintf(text, sizeof text, "%d", (int)(ii * majorstep + gauge->rangemin)); + } + } + + float scale = (gauge->majorend - gauge->majorstart) * 1.5; + float offs = gauge->majorstart - scale; + drawText(text, font, sin(cura) * offs, cos(cura) * offs, scale, scale, TA_CENTRE, TA_CENTRE); + } + + drawDial(gauge, value, gaugetype); + + glPopMatrix(); +} + diff --git a/gauge.h b/gauge.h new file mode 100644 index 0000000..7ccc627 --- /dev/null +++ b/gauge.h @@ -0,0 +1,73 @@ +#ifndef GAUGE_H +#define GAUGE_H + +#include + +struct colour +{ + float r, g, b, a; +}; + +#define OG_SHIFT 1 // key +#define OG_CTRL 2 // key + +#define OG_TURBO 8192 // show turbo gauge +#define OG_KM 16384 // if not set - user prefers MILES +#define OG_BAR 32768 // if not set - user prefers PSI + +// DL_x - bits for OutGaugePack DashLights and ShowLights + +enum +{ + DL_SHIFT, // bit 0 - shift light + DL_FULLBEAM, // bit 1 - full beam + DL_HANDBRAKE, // bit 2 - handbrake + DL_PITSPEED, // bit 3 - pit speed limiter + DL_TC, // bit 4 - TC active or switched off + DL_SIGNAL_L, // bit 5 - left turn signal + DL_SIGNAL_R, // bit 6 - right turn signal + DL_SIGNAL_ANY, // bit 7 - shared turn signal + DL_OILWARN, // bit 8 - oil pressure warning + DL_BATTERY, // bit 9 - battery warning + DL_ABS, // bit 10 - ABS active or switched off + DL_SPARE, // bit 11 + DL_NUM +}; + +struct gauge +{ + char name[16]; + float radius; + float anglemin; + float anglemax; + float rangemin; + float rangemax; + + float majorstep; + float majorwidth; + float majorstart; + float majorend; + struct colour major; + + float minorstep; + float minorwidth; + float minorstart; + float minorend; + struct colour minor; + + struct colour dial; +}; + +enum { + GT_NONE, + GT_NEEDLE, + GT_LINE, + GT_LINE2, + GT_BAR, +}; + +void init_gauges(void); +void drawDial(const struct gauge *gauge, float value, int style); +void draw_gauge(const struct gauge *gauge, float value, float red, FTGLfont *font, int gaugetype); + +#endif /* GAUGE_H */ diff --git a/gauges.txt b/gauges.txt new file mode 100644 index 0000000..4a86482 --- /dev/null +++ b/gauges.txt @@ -0,0 +1,13 @@ +# name, start angle, end angle +# major: r, g, b, a, width, start, end +# minor: r, g, b, a, width, start, end +# dial: r, g, b, a +regular,30.0,270.0 +1.0,1.0,1.0,1.0,3.0,0.9,1.0 +0.8,0.8,0.8,1.0,1.0,0.95,0.97 +1.0,0.0,0.0,0.5 +fuel,150.0,210.0 +1.0,1.0,1.0,1.0,3.0,0.9,1.0 +0.8,0.8,0.8,1.0,1.0,0.95,0.97 +1.0,0.0,0.0,0.5 + diff --git a/gettime.h b/gettime.h new file mode 100644 index 0000000..0344826 --- /dev/null +++ b/gettime.h @@ -0,0 +1,81 @@ +#ifndef GETTIME_H +#define GETTIME_H + +#include + +#ifdef WIN32 + +#include + +#define CLOCK_REALTIME 0 +#define CLOCK_MONOTONIC 0 + +static inline LARGE_INTEGER getFILETIMEoffset() +{ + SYSTEMTIME s; + FILETIME f; + LARGE_INTEGER t; + + s.wYear = 1970; + s.wMonth = 1; + s.wDay = 1; + s.wHour = 0; + s.wMinute = 0; + s.wSecond = 0; + s.wMilliseconds = 0; + SystemTimeToFileTime(&s, &f); + t.QuadPart = f.dwHighDateTime; + t.QuadPart <<= 32; + t.QuadPart |= f.dwLowDateTime; + return t; +} + +static inline int clock_gettime(int X, struct timespec *ts) +{ + LARGE_INTEGER t; + FILETIME f; + double microseconds; + static LARGE_INTEGER offset; + static double frequencyToMicroseconds; + static int initialized = 0; + static BOOL usePerformanceCounter = 0; + + if (!initialized) { + LARGE_INTEGER performanceFrequency; + initialized = 1; + usePerformanceCounter = QueryPerformanceFrequency(&performanceFrequency); + if (usePerformanceCounter) { + QueryPerformanceCounter(&offset); + frequencyToMicroseconds = (double)performanceFrequency.QuadPart / 1000000.; + } else { + offset = getFILETIMEoffset(); + frequencyToMicroseconds = 10.; + } + } + if (usePerformanceCounter) { + QueryPerformanceCounter(&t); + } else { + GetSystemTimeAsFileTime(&f); + t.QuadPart = f.dwHighDateTime; + t.QuadPart <<= 32; + t.QuadPart |= f.dwLowDateTime; + } + + t.QuadPart -= offset.QuadPart; + microseconds = (double)t.QuadPart / frequencyToMicroseconds; + t.QuadPart = microseconds; + ts->tv_sec = t.QuadPart / 1000000; + ts->tv_nsec = (t.QuadPart % 1000000) * 1000; + return 0; +} + +#endif + +static inline unsigned gettime(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec * 1000 + ts.tv_nsec / 1000000; +} + +#endif diff --git a/insim.c b/insim.c new file mode 100644 index 0000000..57040ac --- /dev/null +++ b/insim.c @@ -0,0 +1,439 @@ +//#define USE_IPV6 + +#include +#ifdef WIN32 +#include +#include +#include +#define MSG_NOSIGNAL 0 +#else +//#include +#include +//#include +#endif +#include +#include +#include +#include +#include "cars.h" +#include "socket.h" +#include "insim.h" +#include "network_worker.h" +#include "queue.h" +#include "outgauge.h" + +struct conninfo s_conninfo[256]; +struct carinfo s_carinfo[256]; + +struct insim_buffer { + int state; + uint8_t buffer[65536]; + int pos; + int offset; +}; + +static struct insim_buffer s_insim_tcp; +static struct insim_buffer s_insim_udp; + +static struct queue_t *s_send_queue; +static uint8_t *s_send_packet; +static uint8_t s_send_pos; + +void insim_queue(const void *buffer, size_t size) +{ + uint8_t *packet = malloc(size); + memcpy(packet, buffer, size); + queue_produce(s_send_queue, packet); +} + +int insim_dequeue(int fd) +{ + if (s_send_packet == NULL) queue_consume(s_send_queue, (void *)&s_send_packet); + + if (s_send_packet != NULL) + { + ssize_t s = send(fd, (char *)s_send_packet + s_send_pos, s_send_packet[0] - s_send_pos, MSG_NOSIGNAL); + if (s >= 0) s_send_pos += s; + if (s_send_pos >= s_send_packet[0]) + { + free(s_send_packet); + s_send_packet = 0; + s_send_pos = 0; + return 1; + } + } + return 0; +} + +void insim_request_info(void) +{ + memset(s_conninfo, 0, sizeof s_conninfo); + memset(s_carinfo, 0, sizeof s_carinfo); + + struct IS_TINY p; + p.Size = sizeof p; + p.Type = ISP_TINY; + p.ReqI = 2; + p.SubT = TINY_NCN; + insim_queue(&p, sizeof p); + + p.Size = sizeof p; + p.Type = ISP_TINY; + p.ReqI = 3; + p.SubT = TINY_NPL; + insim_queue(&p, sizeof p); +} + +void insim_handle_tiny(int fd, const void *buffer) +{ + const struct IS_TINY *p = buffer; + switch (p->SubT) + { + case TINY_NONE: + { + struct IS_TINY r; + r.Size = sizeof r; + r.Type = ISP_TINY; + r.ReqI = 0; + r.SubT = TINY_NONE; + insim_queue(&r, sizeof r); + break; + } + } +} + +static void ltc_duty(const char *uname, int state) +{ + int i; + for (i = 0; i < 256; i++) { + if (!strcmp(uname, s_conninfo[i].uname)) { + s_conninfo[i].state = state; + printf("Player %s state %d\n", uname, state); + return; + } + } +} + +void insim_handle_ism(int fd, const void *buffer) +{ + const struct IS_ISM *p = buffer; + + printf("Joined %s\n", p->HName); + insim_request_info(); +} + +void insim_handle_mso(int fd, const void *buffer) +{ + return; + const struct IS_MSO *p = buffer; + printf("%d %d %d %d %s\n", p->UCID, p->PLID, p->UserType, p->TextStart, p->Msg); + + char buf[128], *bufp = buf; + strncpy(buf, p->Msg, sizeof buf); + + int state = 0; + char *name = NULL; + + int i; + for (i = 0; i < 10; i++) { + char *a = strsep(&bufp, " "); + if (a == NULL) return; + if (i == 0 && strcmp(a, "***")) return; + if (i == 1 && (!strcmp(a, "Officer") || !strcmp(a, "Cadet"))) state |= 1; + if (i == 2 && state == 1) name = a; + if (i == 3 && strcmp(a, "has")) return; + if (i == 4 && strcmp(a, "gone")) return; + if (i == 5 && !strcmp(a, "onduty")) ltc_duty(name, 1); + if (i == 5 && !strcmp(a, "offduty")) ltc_duty(name, 0); + } +} + +void insim_handle_ncn(int fd, const void *buffer) +{ + const struct IS_NCN *p = buffer; + struct conninfo *c = s_conninfo + p->UCID; + memset(c, 0, sizeof *c); + c->active = 1; + strncpy(c->uname, p->UName, sizeof c->uname); + strncpy(c->pname, p->PName, sizeof c->pname); + c->admin = p->Admin; + + printf("Know about connection %s (%s) %d\n", c->pname, c->uname, c->admin); +} + +void insim_handle_cnl(int fd, const void *buffer) +{ + const struct IS_CNL *p = buffer; + struct conninfo *c = s_conninfo + p->UCID; + c->active = 0; +} + +void insim_handle_cpr(int fd, const void *buffer) +{ + const struct IS_CPR *p = buffer; + struct conninfo *c = s_conninfo + p->UCID; + if (c->active == 0) fprintf(stderr, "Rename of inactive connection\n"); + strncpy(c->pname, p->PName, sizeof c->pname); +} + +void insim_handle_npl(int fd, const void *buffer) +{ + const struct IS_NPL *p = buffer; + struct carinfo *car = s_carinfo + p->PLID; + memset(car, 0, sizeof *car); + car->active = 1; + //strncpy(car->cname, p->CName, sizeof car->cname); + car->ucid = p->UCID; + car->mass = p->H_Mass; + car->intake = p->H_TRes; + + strncpy(car->cname, find_car(p->CName, car->intake, car->mass), sizeof car->cname); + + printf("Car %d joined (%s) - %s\n", p->PLID, car->cname, s_conninfo[car->ucid].uname); +} + +void insim_handle_pll(int fd, const void *buffer) +{ + const struct IS_PLL *p = buffer; + struct carinfo *car = s_carinfo + p->PLID; + car->active = 0; + printf("Car %d left - %s\n", p->PLID, s_conninfo[car->ucid].uname); +} + +void insim_handle_mci(int fd, const void *buffer) +{ + const struct IS_MCI *p = buffer; + int i; + for (i = 0; i < p->NumC; i++) + { + const struct CompCar *c = p->Info + i; + struct carinfo *car = s_carinfo + c->PLID; + + car->position = c->Position; + car->info = c->Info; + car->sp3 = c->Sp3; + car->x = c->X; + car->y = c->Y; + car->z = c->Z; + car->speed = c->Speed; + car->direction = c->Direction; + car->heading = c->Heading; + car->angvel = c->AngVel; + + car->hist_x[car->hist] = c->X; + car->hist_y[car->hist] = c->Y; + car->hist_s[car->hist] = c->Speed; + car->hist = (car->hist + 1) % CARPATHSIZE; + } +} + +typedef void (*insim_handler)(int fd, const void *buffer); + +static const insim_handler s_handlers[] = +{ + NULL, // ISP_NONE // 0 : not used + NULL, // ISP_ISI // 1 - instruction : insim initialise + NULL, // ISP_VER // 2 - info : version info + insim_handle_tiny, // ISP_TINY // 3 - both ways : multi purpose + NULL, // ISP_SMALL // 4 - both ways : multi purpose + NULL, // ISP_STA // 5 - info : state info + NULL, // ISP_SCH // 6 - instruction : single character + NULL, // ISP_SFP // 7 - instruction : state flags pack + NULL, // ISP_SCC // 8 - instruction : set car camera + NULL, // ISP_CPP // 9 - both ways : cam pos pack + insim_handle_ism, // ISP_ISM // 10 - info : start multiplayer + insim_handle_mso, // ISP_MSO // 11 - info : message out + NULL, // ISP_III // 12 - info : hidden /i message + NULL, // ISP_MST // 13 - instruction : type message or /command + NULL, // ISP_MTC // 14 - instruction : message to a connection + NULL, // ISP_MOD // 15 - instruction : set screen mode + NULL, // ISP_VTN // 16 - info : vote notification + NULL, // ISP_RST // 17 - info : race start + insim_handle_ncn, // ISP_NCN // 18 - info : new connection + insim_handle_cnl, // ISP_CNL // 19 - info : connection left + insim_handle_cpr, // ISP_CPR // 20 - info : connection renamed + insim_handle_npl, // ISP_NPL // 21 - info : new player (joined race) + NULL, // ISP_PLP // 22 - info : player pit (keeps slot in race) + insim_handle_pll, // ISP_PLL // 23 - info : player leave (spectate - loses slot) + NULL, // ISP_LAP // 24 - info : lap time + NULL, // ISP_SPX // 25 - info : split x time + NULL, // ISP_PIT // 26 - info : pit stop start + NULL, // ISP_PSF // 27 - info : pit stop finish + NULL, // ISP_PLA // 28 - info : pit lane enter / leave + NULL, // ISP_CCH // 29 - info : camera changed + NULL, // ISP_PEN // 30 - info : penalty given or cleared + NULL, // ISP_TOC // 31 - info : take over car + NULL, // ISP_FLG // 32 - info : flag (yellow or blue) + NULL, // ISP_PFL // 33 - info : player flags (help flags) + NULL, // ISP_FIN // 34 - info : finished race + NULL, // ISP_RES // 35 - info : result confirmed + NULL, // ISP_REO // 36 - both ways : reorder (info or instruction) + NULL, // ISP_NLP // 37 - info : node and lap packet + insim_handle_mci, // ISP_MCI // 38 - info : multi car info + NULL, // ISP_MSX // 39 - instruction : type message + NULL, // ISP_MSL // 40 - instruction : message to local computer + NULL, // ISP_CRS // 41 - info : car reset + NULL, // ISP_BFN // 42 - both ways : delete buttons / receive button requests + NULL, // ISP_AXI // 43 - info : autocross layout information + NULL, // ISP_AXO // 44 - info : hit an autocross object + NULL, // ISP_BTN // 45 - instruction : show a button on local or remote screen + NULL, // ISP_BTC // 46 - info : sent when a user clicks a button + NULL, // ISP_BTT // 47 - info : sent after typing into a button + NULL, // ISP_RIP // 48 - both ways : replay information packet + NULL, // ISP_SSH // 49 - both ways : screenshot + NULL, // ISP_CON // 50 - info : contact between cars (collision report) + NULL, // ISP_OBH // 51 - info : contact car + object (collision report) + NULL, // ISP_HLV // 52 - info : report incidents that would violate HLVC + NULL, // ISP_PLC // 53 - instruction : player cars + NULL, // ISP_AXM // 54 - both ways : autocross multiple objects + NULL, // ISP_ACR // 55 - info : admin command report +}; + +void insim_handle(int fd, const uint8_t *buffer) +{ + uint8_t type = buffer[1]; + if (type <= ISP_ACR && s_handlers[type] != NULL) + { + s_handlers[type](fd, buffer); + } +} + +int insim_recv(int fd, int can_write, int can_read, void *arg) +{ + struct insim_buffer *buffer = arg; + + if (can_write && buffer->state == 1) + { + insim_dequeue(fd); + } + if (can_read) + { + if (buffer->pos >= sizeof buffer->buffer) + { + printf("buffer full\n"); + return 0; + } + ssize_t res = recv(fd, (char *)buffer->buffer + buffer->pos, sizeof buffer->buffer - buffer->pos, 0); + if (res == -1) + { + #ifdef WIN32 + if (WSAGetLastError() == WSAECONNRESET) + #else + if (errno == ECONNRESET) + #endif + { + printf("connreset\n"); + return 0; + } + #ifdef WIN32 + else if (WSAGetLastError() != WSAEWOULDBLOCK) + #else + else if (errno != EWOULDBLOCK && errno != EAGAIN) + #endif + { + printf("hmm: %s\n", strerror(errno)); + return 0; + } + } + if (res == 0) + { + printf("nothing received\n"); + return 0; + } + + buffer->pos += res; + + while (1) + { + uint8_t size = buffer->buffer[buffer->offset]; + if (size % 4 != 0) + { + printf("invalid packet, size is %d\n", size); + return 0; + } + + if (buffer->pos - buffer->offset >= size) + { + insim_handle(fd, buffer->buffer + buffer->offset); + } + else + { + break; + } + + if (buffer->pos - buffer->offset > size) + { + buffer->offset += size; + } + else + { + buffer->pos = 0; + buffer->offset = 0; + } + } + } + + return 1; +} +/* +#ifdef USE_IPV6 +static struct sockaddr_in6 serv_addr; +#else +static struct sockaddr_in serv_addr; +#endif +*/ + +extern int g_outgauge_port; +extern int g_insim_port; + +static void insim_connect(int fd, void *arg) +{ + if (fd == -1) + { + return; + } + + s_insim_tcp.state = 1; + s_insim_tcp.pos = 0; + s_insim_tcp.offset = 0; + register_socket(fd, SM_READ | SM_WRITE, &insim_recv, &s_insim_tcp); + + fd = network_listen(g_insim_port + 1, 0); + s_insim_udp.state = 2; + s_insim_udp.pos = 0; + s_insim_udp.offset = 0; + register_socket(fd, SM_READ, &insim_recv, &s_insim_udp); + + struct IS_ISI p; + memset(&p, 0, sizeof p); + p.Size = sizeof p; + p.Type = ISP_ISI; + p.ReqI = 0; + p.Zero = 0; + p.UDPPort = g_outgauge_port; //g_insim_port + 1; + p.Flags = ISF_LOCAL; + p.Sp0 = 0; + p.Prefix = 0; + p.Interval = 100; + snprintf(p.Admin, sizeof p.Admin, ""); + snprintf(p.IName, sizeof p.IName, "LFS Dashboard"); + insim_queue(&p, sizeof p); + + struct IS_SMALL s; + s.Size = sizeof s; + s.Type = ISP_SMALL; + s.ReqI = 1; + s.SubT = SMALL_SSG; + s.UVal = 1; + insim_queue(&s, sizeof s); + + insim_request_info(); +} + +void insim_init(const char *hostname, int port) +{ + s_insim_tcp.state = 0; + s_insim_udp.state = 0; + s_send_queue = queue_new(); + network_connect(hostname, port, &insim_connect, NULL); +} diff --git a/insim.h b/insim.h new file mode 100644 index 0000000..b5ecc75 --- /dev/null +++ b/insim.h @@ -0,0 +1,2237 @@ +#ifndef _ISPACKETS_H_ +#define _ISPACKETS_H_ + +#include +///////////////////// + +// InSim for Live for Speed : 0.6B + +// InSim allows communication between up to 8 external programs and LFS. + +// TCP or UDP packets can be sent in both directions, LFS reporting various +// things about its state, and the external program requesting info and +// controlling LFS with special packets, text commands or keypresses. + +// NOTE : This text file was written with a TAB size equal to 4 spaces. + + +// INSIM VERSION NUMBER (updated for version 0.6B) +// ==================== + +static const int INSIM_VERSION = 5; + + +// CHANGES +// ======= + +// Version 0.5Z (no change to INSIM_VERSION) + +// NLP / MCI packets are now output at regular intervals +// CCI_LAG bit added to the CompCar structure + +// Version 0.6B (INSIM_VERSION increased to 5) + +// Lap timing info added to IS_RST (Timing byte) +// NLP / MCI minimum time interval reduced to 40 ms (was 50 ms) +// IS_VTC now cancels game votes even if the majority has not been reached +// IS_MTC (Msg To Connection) now has a variable length (up to 128 characters) +// IS_MTC can be sent to all (UCID = 255) and sound effect can be specified +// IS_CON reports contact between two cars (if ISF_CON is set) +// IS_OBH reports information about any object hit (if ISF_OBH is set) +// IS_HLV reports incidents that would violate HLVC (if ISF_HLV is set) +// IS_PLC sets allowed cars for individual players +// IS_AXM to add / remove / clear autocross objects +// IS_ACR reports successful or attempted admin commands +// OG_SHIFT and OG_CTRL (keys) bits added to OutGaugePack +// New IS_RIP option RIPOPT_FULL_PHYS to use full physics when searching +// ISS_SHIFTU_HIGH is no longer used (no high / low view distinction) +// FIX : Clutch axis / button was not reported from Controls screen +// FIX : TTime in IS_RIP was wrong in mid-joined Multiplayer Replays +// FIX : IS_BTN did not allow the documented limit of 240 characters +// FIX : OutGaugePack ID was always zero regardless of ID in cfg.txt +// FIX : InSim camera with vertical pitch would cause LFS to crash + + +// TYPES : (all multi-byte types are PC style - lowest byte first) +// ===== + +// char 1-byte character +// uint8_t 1-byte unsigned integer +// uint16_t 2-byte unsigned integer +// short 2-byte signed integer +// uint32_t 4-byte unsigned integer +// int 4-byte signed integer +// float 4-byte float + +// RaceLaps (rl) : (various meanings depending on range) + +// 0 : practice +// 1-99 : number of laps... laps = rl +// 100-190 : 100 to 1000 laps... laps = (rl - 100) * 10 + 100 +// 191-238 : 1 to 48 hours... hours = rl - 190 + + +// InSim PACKETS +// ============= + +// All InSim packets use a four byte header + +// Size : total packet size - a multiple of 4 +// Type : packet identifier from the ISP_ enum (see below) +// ReqI : non zero if the packet is a packet request or a reply to a request +// Data : the first data byte + + +// INITIALISING InSim +// ================== + +// To initialise the InSim system, type into LFS : /insim xxxxx +// where xxxxx is the TCP and UDP port you want LFS to open. + +// OR start LFS with the command line option : LFS /insim=xxxxx +// This will make LFS listen for packets on that TCP and UDP port. + +struct Vec +{ + int32_t x; + int32_t y; + int32_t z; +}; + +struct Vector +{ + float x; + float y; + float z; +}; + +// TO START COMMUNICATION +// ====================== + +// TCP : Connect to LFS using a TCP connection, then send this packet : +// UDP : No connection required, just send this packet to LFS : + +struct IS_ISI // InSim Init - packet to initialise the InSim system +{ + uint8_t Size; // 44 + uint8_t Type; // ISP_ISI + uint8_t ReqI; // If non-zero LFS will send an IS_VER packet + uint8_t Zero; // 0 + + uint16_t UDPPort; // Port for UDP replies from LFS (0 to 65535) + uint16_t Flags; // Bit flags for options (see below) + + uint8_t Sp0; // 0 + uint8_t Prefix; // Special host message prefix character + uint16_t Interval; // Time in ms between NLP or MCI (0 = none) + + char Admin[16]; // Admin password (if set in LFS) + char IName[16]; // A short name for your program +}; + +// NOTE 1) UDPPort field when you connect using UDP : + +// zero : LFS sends all packets to the port of the incoming packet +// non-zero : LFS sends all packets to the specified UDPPort + +// NOTE 2) UDPPort field when you connect using TCP : + +// zero : LFS sends NLP / MCI packets using your TCP connection +// non-zero : LFS sends NLP / MCI packets to the specified UDPPort + +// NOTE 3) Flags field (set the relevant bits to turn on the option) : + +#define ISF_RES_0 1 // bit 0 : spare +#define ISF_RES_1 2 // bit 1 : spare +#define ISF_LOCAL 4 // bit 2 : guest or single player +#define ISF_MSO_COLS 8 // bit 3 : keep colours in MSO text +#define ISF_NLP 16 // bit 4 : receive NLP packets +#define ISF_MCI 32 // bit 5 : receive MCI packets +#define ISF_CON 64 // bit 6 : receive CON packets +#define ISF_OBH 128 // bit 7 : receive OBH packets +#define ISF_HLV 256 // bit 8 : receive HLV packets +#define ISF_AXM_LOAD 512 // bit 9 : receive AXM when loading a layout +#define ISF_AXM_EDIT 1024 // bit 10 : receive AXM when changing objects + +// In most cases you should not set both ISF_NLP and ISF_MCI flags +// because all IS_NLP information is included in the IS_MCI packet. + +// The ISF_LOCAL flag is important if your program creates buttons. +// It should be set if your program is not a host control system. +// If set, then buttons are created in the local button area, so +// avoiding conflict with the host buttons and allowing the user +// to switch them with SHIFT+B rather than SHIFT+I. + +// NOTE 4) Prefix field, if set when initialising InSim on a host : + +// Messages typed with this prefix will be sent to your InSim program +// on the host (in IS_MSO) and not displayed on anyone's screen. + + +// ENUMERATIONS FOR PACKET TYPES +// ============================= + +enum // the second byte of any packet is one of these +{ + ISP_NONE, // 0 : not used + ISP_ISI, // 1 - instruction : insim initialise + ISP_VER, // 2 - info : version info + ISP_TINY, // 3 - both ways : multi purpose + ISP_SMALL, // 4 - both ways : multi purpose + ISP_STA, // 5 - info : state info + ISP_SCH, // 6 - instruction : single character + ISP_SFP, // 7 - instruction : state flags pack + ISP_SCC, // 8 - instruction : set car camera + ISP_CPP, // 9 - both ways : cam pos pack + ISP_ISM, // 10 - info : start multiplayer + ISP_MSO, // 11 - info : message out + ISP_III, // 12 - info : hidden /i message + ISP_MST, // 13 - instruction : type message or /command + ISP_MTC, // 14 - instruction : message to a connection + ISP_MOD, // 15 - instruction : set screen mode + ISP_VTN, // 16 - info : vote notification + ISP_RST, // 17 - info : race start + ISP_NCN, // 18 - info : new connection + ISP_CNL, // 19 - info : connection left + ISP_CPR, // 20 - info : connection renamed + ISP_NPL, // 21 - info : new player (joined race) + ISP_PLP, // 22 - info : player pit (keeps slot in race) + ISP_PLL, // 23 - info : player leave (spectate - loses slot) + ISP_LAP, // 24 - info : lap time + ISP_SPX, // 25 - info : split x time + ISP_PIT, // 26 - info : pit stop start + ISP_PSF, // 27 - info : pit stop finish + ISP_PLA, // 28 - info : pit lane enter / leave + ISP_CCH, // 29 - info : camera changed + ISP_PEN, // 30 - info : penalty given or cleared + ISP_TOC, // 31 - info : take over car + ISP_FLG, // 32 - info : flag (yellow or blue) + ISP_PFL, // 33 - info : player flags (help flags) + ISP_FIN, // 34 - info : finished race + ISP_RES, // 35 - info : result confirmed + ISP_REO, // 36 - both ways : reorder (info or instruction) + ISP_NLP, // 37 - info : node and lap packet + ISP_MCI, // 38 - info : multi car info + ISP_MSX, // 39 - instruction : type message + ISP_MSL, // 40 - instruction : message to local computer + ISP_CRS, // 41 - info : car reset + ISP_BFN, // 42 - both ways : delete buttons / receive button requests + ISP_AXI, // 43 - info : autocross layout information + ISP_AXO, // 44 - info : hit an autocross object + ISP_BTN, // 45 - instruction : show a button on local or remote screen + ISP_BTC, // 46 - info : sent when a user clicks a button + ISP_BTT, // 47 - info : sent after typing into a button + ISP_RIP, // 48 - both ways : replay information packet + ISP_SSH, // 49 - both ways : screenshot + ISP_CON, // 50 - info : contact between cars (collision report) + ISP_OBH, // 51 - info : contact car + object (collision report) + ISP_HLV, // 52 - info : report incidents that would violate HLVC + ISP_PLC, // 53 - instruction : player cars + ISP_AXM, // 54 - both ways : autocross multiple objects + ISP_ACR, // 55 - info : admin command report +}; + +enum // the fourth byte of an IS_TINY packet is one of these +{ + TINY_NONE, // 0 - keep alive : see "maintaining the connection" + TINY_VER, // 1 - info request : get version + TINY_CLOSE, // 2 - instruction : close insim + TINY_PING, // 3 - ping request : external progam requesting a reply + TINY_REPLY, // 4 - ping reply : reply to a ping request + TINY_VTC, // 5 - both ways : game vote cancel (info or request) + TINY_SCP, // 6 - info request : send camera pos + TINY_SST, // 7 - info request : send state info + TINY_GTH, // 8 - info request : get time in hundredths (i.e. SMALL_RTP) + TINY_MPE, // 9 - info : multi player end + TINY_ISM, // 10 - info request : get multiplayer info (i.e. ISP_ISM) + TINY_REN, // 11 - info : race end (return to game setup screen) + TINY_CLR, // 12 - info : all players cleared from race + TINY_NCN, // 13 - info request : get all connections + TINY_NPL, // 14 - info request : get all players + TINY_RES, // 15 - info request : get all results + TINY_NLP, // 16 - info request : send an IS_NLP + TINY_MCI, // 17 - info request : send an IS_MCI + TINY_REO, // 18 - info request : send an IS_REO + TINY_RST, // 19 - info request : send an IS_RST + TINY_AXI, // 20 - info request : send an IS_AXI - AutoX Info + TINY_AXC, // 21 - info : autocross cleared + TINY_RIP, // 22 - info request : send an IS_RIP - Replay Information Packet +}; + +enum // the fourth byte of an IS_SMALL packet is one of these +{ + SMALL_NONE, // 0 : not used + SMALL_SSP, // 1 - instruction : start sending positions + SMALL_SSG, // 2 - instruction : start sending gauges + SMALL_VTA, // 3 - report : vote action + SMALL_TMS, // 4 - instruction : time stop + SMALL_STP, // 5 - instruction : time step + SMALL_RTP, // 6 - info : race time packet (reply to GTH) + SMALL_NLI, // 7 - instruction : set node lap interval +}; + + +// GENERAL PURPOSE PACKETS - IS_TINY (4 bytes) and IS_SMALL (8 bytes) +// ======================= + +// To avoid defining several packet structures that are exactly the same, and to avoid +// wasting the ISP_ enumeration, IS_TINY is used at various times when no additional data +// other than SubT is required. IS_SMALL is used when an additional integer is needed. + +// IS_TINY - used for various requests, replies and reports + +struct IS_TINY // General purpose 4 byte packet +{ + uint8_t Size; // always 4 + uint8_t Type; // always ISP_TINY + uint8_t ReqI; // 0 unless it is an info request or a reply to an info request + uint8_t SubT; // subtype, from TINY_ enumeration (e.g. TINY_RACE_END) +}; + +// IS_SMALL - used for various requests, replies and reports + +struct IS_SMALL // General purpose 8 byte packet +{ + uint8_t Size; // always 8 + uint8_t Type; // always ISP_SMALL + uint8_t ReqI; // 0 unless it is an info request or a reply to an info request + uint8_t SubT; // subtype, from SMALL_ enumeration (e.g. SMALL_SSP) + + uint32_t UVal; // value (e.g. for SMALL_SSP this would be the OutSim packet rate) +}; + + +// VERSION REQUEST +// =============== + +// It is advisable to request version information as soon as you have connected, to +// avoid problems when connecting to a host with a later or earlier version. You will +// be sent a version packet on connection if you set ReqI in the IS_ISI packet. + +// This version packet can be sent on request : + +struct IS_VER // VERsion +{ + uint8_t Size; // 20 + uint8_t Type; // ISP_VERSION + uint8_t ReqI; // ReqI as received in the request packet + uint8_t Zero; + + char Version[8]; // LFS version, e.g. 0.3G + char Product[6]; // Product : DEMO or S1 + uint16_t InSimVer; // InSim Version : increased when InSim packets change +}; + +// To request an InSimVersion packet at any time, send this IS_TINY : + +// ReqI : non-zero (returned in the reply) +// SubT : TINY_VER (request an IS_VER) + + +// CLOSING InSim +// ============= + +// You can send this IS_TINY to close the InSim connection to your program : + +// ReqI : 0 +// SubT : TINY_CLOSE (close this connection) + +// Another InSimInit packet is then required to start operating again. + +// You can shut down InSim completely and stop it listening at all by typing /insim=0 +// into LFS (or send a MsgTypePack to do the same thing). + + +// MAINTAINING THE CONNECTION - IMPORTANT +// ========================== + +// If InSim does not receive a packet for 70 seconds, it will close your connection. +// To open it again you would need to send another InSimInit packet. + +// LFS will send a blank IS_TINY packet like this every 30 seconds : + +// ReqI : 0 +// SubT : TINY_NONE (keep alive packet) + +// You should reply with a blank IS_TINY packet : + +// ReqI : 0 +// SubT : TINY_NONE (has no effect other than resetting the timeout) + +// NOTE : If you want to request a reply from LFS to check the connection +// at any time, you can send this IS_TINY : + +// ReqI : non-zero (returned in the reply) +// SubT : TINY_PING (request a TINY_REPLY) + +// LFS will reply with this IS_TINY : + +// ReqI : non-zero (as received in the request packet) +// SubT : TINY_REPLY (reply to ping) + + +// STATE REPORTING AND REQUESTS +// ============================ + +// LFS will send a StatePack any time the info in the StatePack changes. + +struct IS_STA // STAte +{ + uint8_t Size; // 28 + uint8_t Type; // ISP_STA + uint8_t ReqI; // ReqI if replying to a request packet + uint8_t Zero; + + float ReplaySpeed; // 4-byte float - 1.0 is normal speed + + uint16_t Flags; // ISS state flags (see below) + uint8_t InGameCam; // Which type of camera is selected (see below) + uint8_t ViewPLID; // Unique ID of viewed player (0 = none) + + uint8_t NumP; // Number of players in race + uint8_t NumConns; // Number of connections including host + uint8_t NumFinished; // Number finished or qualified + uint8_t RaceInProg; // 0 - no race / 1 - race / 2 - qualifying + + uint8_t QualMins; + uint8_t RaceLaps; // see "RaceLaps" near the top of this document + uint8_t Spare2; + uint8_t Spare3; + + char Track[6]; // short name for track e.g. FE2R + uint8_t Weather; // 0,1,2... + uint8_t Wind; // 0=off 1=weak 2=strong +}; + +// InGameCam is the in game selected camera mode (which is +// still selected even if LFS is actually in SHIFT+U mode). +// For InGameCam's values, see "View identifiers" below. + +// ISS state flags + +#define ISS_GAME 1 // in game (or MPR) +#define ISS_REPLAY 2 // in SPR +#define ISS_PAUSED 4 // paused +#define ISS_SHIFTU 8 // SHIFT+U mode +#define ISS_16 16 // UNUSED +#define ISS_SHIFTU_FOLLOW 32 // FOLLOW view +#define ISS_SHIFTU_NO_OPT 64 // SHIFT+U buttons hidden +#define ISS_SHOW_2D 128 // showing 2d display +#define ISS_FRONT_END 256 // entry screen +#define ISS_MULTI 512 // multiplayer mode +#define ISS_MPSPEEDUP 1024 // multiplayer speedup option +#define ISS_WINDOWED 2048 // LFS is running in a window +#define ISS_SOUND_MUTE 4096 // sound is switched off +#define ISS_VIEW_OVERRIDE 8192 // override user view +#define ISS_VISIBLE 16384 // InSim buttons visible + +// To request a StatePack at any time, send this IS_TINY : + +// ReqI : non-zero (returned in the reply) +// SubT : TINY_SST (Send STate) + +// Setting states + +// These states can be set by a special packet : + +// ISS_SHIFTU_NO_OPT - SHIFT+U buttons hidden +// ISS_SHOW_2D - showing 2d display +// ISS_MPSPEEDUP - multiplayer speedup option +// ISS_SOUND_MUTE - sound is switched off + +struct IS_SFP // State Flags Pack +{ + uint8_t Size; // 8 + uint8_t Type; // ISP_SFP + uint8_t ReqI; // 0 + uint8_t Zero; + + uint16_t Flag; // the state to set + uint8_t OffOn; // 0 = off / 1 = on + uint8_t Sp3; // spare +}; + +// Other states must be set by using keypresses or messages (see below) + + +// SCREEN MODE +// =========== + +// You can send this packet to LFS to set the screen mode : + +struct IS_MOD // MODe : send to LFS to change screen mode +{ + uint8_t Size; // 20 + uint8_t Type; // ISP_MOD + uint8_t ReqI; // 0 + uint8_t Zero; + + int32_t Bits16; // set to choose 16-bit + int32_t RR; // refresh rate - zero for default + int32_t Width; // 0 means go to window + int32_t Height; // 0 means go to window +}; + +// The refresh rate actually selected by LFS will be the highest available rate +// that is less than or equal to the specified refresh rate. Refresh rate can +// be specified as zero in which case the default refresh rate will be used. + +// If Width and Height are both zero, LFS will switch to windowed mode. + + +// TEXT MESSAGES AND KEY PRESSES +// ============================== + +// You can send 64-byte text messages to LFS as if the user had typed them in. +// Messages that appear on LFS screen (up to 128 bytes) are reported to the +// external program. You can also send simulated keypresses to LFS. + +// MESSAGES OUT (FROM LFS) +// ------------ + +struct IS_MSO // MSg Out - system messages and user messages +{ + uint8_t Size; // 136 + uint8_t Type; // ISP_MSO + uint8_t ReqI; // 0 + uint8_t Zero; + + uint8_t UCID; // connection's unique id (0 = host) + uint8_t PLID; // player's unique id (if zero, use UCID) + uint8_t UserType; // set if typed by a user (see User Values below) + uint8_t TextStart; // first character of the actual text (after player name) + + char Msg[128]; +}; + +// User Values (for UserType byte) + +enum +{ + MSO_SYSTEM, // 0 - system message + MSO_USER, // 1 - normal visible user message + MSO_PREFIX, // 2 - hidden message starting with special prefix (see ISI) + MSO_O, // 3 - hidden message typed on local pc with /o command + MSO_NUM +}; + +// NOTE : Typing "/o MESSAGE" into LFS will send an IS_MSO with UserType = MSO_O + +struct IS_III // InsIm Info - /i message from user to host's InSim +{ + uint8_t Size; // 72 + uint8_t Type; // ISP_III + uint8_t ReqI; // 0 + uint8_t Zero; + + uint8_t UCID; // connection's unique id (0 = host) + uint8_t PLID; // player's unique id (if zero, use UCID) + uint8_t Sp2; + uint8_t Sp3; + + char Msg[64]; +}; + +struct IS_ACR // Admin Command Report - any user typed an admin command +{ + uint8_t Size; // 72 + uint8_t Type; // ISP_ACR + uint8_t ReqI; // 0 + uint8_t Zero; + + uint8_t UCID; // connection's unique id (0 = host) + uint8_t Admin; // set if user is an admin + uint8_t Result; // 1 - processed / 2 - rejected / 3 - unknown command + uint8_t Sp3; + + char Text[64]; +}; + +// MESSAGES IN (TO LFS) +// ----------- + +struct IS_MST // MSg Type - send to LFS to type message or command +{ + uint8_t Size; // 68 + uint8_t Type; // ISP_MST + uint8_t ReqI; // 0 + uint8_t Zero; + + char Msg[64]; // last byte must be zero +}; + +struct IS_MSX // MSg eXtended - like MST but longer (not for commands) +{ + uint8_t Size; // 100 + uint8_t Type; // ISP_MSX + uint8_t ReqI; // 0 + uint8_t Zero; + + char Msg[96]; // last byte must be zero +}; + +struct IS_MSL // MSg Local - message to appear on local computer only +{ + uint8_t Size; // 132 + uint8_t Type; // ISP_MSL + uint8_t ReqI; // 0 + uint8_t Sound; // sound effect (see Message Sounds below) + + char Msg[128]; // last byte must be zero +}; + +struct IS_MTC // Msg To Connection - hosts only - send to a connection / a player / all +{ + uint8_t Size; // 8 + TEXT_SIZE (TEXT_SIZE = 4, 8, 12... 128) + uint8_t Type; // ISP_MTC + uint8_t ReqI; // 0 + uint8_t Sound; // sound effect (see Message Sounds below) + + uint8_t UCID; // connection's unique id (0 = host / 255 = all) + uint8_t PLID; // player's unique id (if zero, use UCID) + uint8_t Sp2; + uint8_t Sp3; + +// char Text[TEXT_SIZE]; // up to 128 characters of text - last byte must be zero +}; + +// Message Sounds (for Sound byte) + +enum +{ + SND_SILENT, + SND_MESSAGE, + SND_SYSMESSAGE, + SND_INVALIDKEY, + SND_ERROR, + SND_NUM +}; + +// You can send individual key presses to LFS with the IS_SCH packet. +// For standard keys (e.g. V and H) you should send a capital letter. +// This does not work with some keys like F keys, arrows or CTRL keys. +// You can also use IS_MST with the /press /shift /ctrl /alt commands. + +struct IS_SCH // Single CHaracter +{ + uint8_t Size; // 8 + uint8_t Type; // ISP_SCH + uint8_t ReqI; // 0 + uint8_t Zero; + + uint8_t CharB; // key to press + uint8_t Flags; // bit 0 : SHIFT / bit 1 : CTRL + uint8_t Spare2; + uint8_t Spare3; +}; + + +// MULTIPLAYER NOTIFICATION +// ======================== + +// LFS will send this packet when a host is started or joined : + +struct IS_ISM // InSim Multi +{ + uint8_t Size; // 40 + uint8_t Type; // ISP_ISM + uint8_t ReqI; // usually 0 / or if a reply : ReqI as received in the TINY_ISM + uint8_t Zero; + + uint8_t Host; // 0 = guest / 1 = host + uint8_t Sp1; + uint8_t Sp2; + uint8_t Sp3; + + char HName[32]; // the name of the host joined or started +}; + +// On ending or leaving a host, LFS will send this IS_TINY : + +// ReqI : 0 +// SubT : TINY_MPE (MultiPlayerEnd) + +// To request an IS_ISM packet at any time, send this IS_TINY : + +// ReqI : non-zero (returned in the reply) +// SubT : TINY_ISM (request an IS_ISM) + +// NOTE : If LFS is not in multiplayer mode, the host name in the ISM will be empty. + + +// VOTE NOTIFY AND CANCEL +// ====================== + +// LFS notifies the external program of any votes to restart or qualify + +// The Vote Actions are defined as : + +enum +{ + VOTE_NONE, // 0 - no vote + VOTE_END, // 1 - end race + VOTE_RESTART, // 2 - restart + VOTE_QUALIFY, // 3 - qualify + VOTE_NUM +}; + +struct IS_VTN // VoTe Notify +{ + uint8_t Size; // 8 + uint8_t Type; // ISP_VTN + uint8_t ReqI; // 0 + uint8_t Zero; + + uint8_t UCID; // connection's unique id + uint8_t Action; // VOTE_X (Vote Action as defined above) + uint8_t Spare2; + uint8_t Spare3; +}; + +// When a vote is cancelled, LFS sends this IS_TINY + +// ReqI : 0 +// SubT : TINY_VTC (VoTe Cancelled) + +// When a vote is completed, LFS sends this IS_SMALL + +// ReqI : 0 +// SubT : SMALL_VTA (VoTe Action) +// UVal : action (VOTE_X - Vote Action as defined above) + +// You can instruct LFS host to cancel a vote using an IS_TINY + +// ReqI : 0 +// SubT : TINY_VTC (VoTe Cancel) + + +// ALLOWED CARS +// ============ + +// You can send a packet to limit the cars that can be used by a given connection +// The resulting set of selectable cars is a subset of the cars set to be available +// on the host (by the /cars command) + +// For example : +// Cars = 0 ... no cars can be selected on the specified connection +// Cars = 0xffffffff ... all the host's available cars can be selected + +struct IS_PLC // PLayer Cars +{ + uint8_t Size; // 12 + uint8_t Type; // ISP_PLC + uint8_t ReqI; // 0 + uint8_t Zero; + + uint8_t UCID; // connection's unique id (0 = host / 255 = all) + uint8_t Sp1; + uint8_t Sp2; + uint8_t Sp3; + + uint32_t Cars; // allowed cars - see below +}; + +// XF GTI - 1 +// XR GT - 2 +// XR GT TURBO - 4 +// RB4 GT - 8 +// FXO TURBO - 0x10 +// LX4 - 0x20 +// LX6 - 0x40 +// MRT5 - 0x80 +// UF 1000 - 0x100 +// RACEABOUT - 0x200 +// FZ50 - 0x400 +// FORMULA XR - 0x800 +// XF GTR - 0x1000 +// UF GTR - 0x2000 +// FORMULA V8 - 0x4000 +// FXO GTR - 0x8000 +// XR GTR - 0x10000 +// FZ50 GTR - 0x20000 +// BMW SAUBER F1.06 - 0x40000 +// FORMULA BMW FB02 - 0x80000 + + +// RACE TRACKING +// ============= + +// In LFS there is a list of connections AND a list of players in the race +// Some packets are related to connections, some players, some both + +// If you are making a multiplayer InSim program, you must maintain two lists +// You should use the unique identifier UCID to identify a connection + +// Each player has a unique identifier PLID from the moment he joins the race, until he +// leaves. It's not possible for PLID and UCID to be the same thing, for two reasons : + +// 1) there may be more than one player per connection if AI drivers are used +// 2) a player can swap between connections, in the case of a driver swap (IS_TOC) + +// When all players are cleared from race (e.g. /clear) LFS sends this IS_TINY + +// ReqI : 0 +// SubT : TINY_CLR (CLear Race) + +// When a race ends (return to game setup screen) LFS sends this IS_TINY + +// ReqI : 0 +// SubT : TINY_REN (Race ENd) + +// You can instruct LFS host to cancel a vote using an IS_TINY + +// ReqI : 0 +// SubT : TINY_VTC (VoTe Cancel) + +// The following packets are sent when the relevant events take place : + +struct IS_RST // Race STart +{ + uint8_t Size; // 28 + uint8_t Type; // ISP_RST + uint8_t ReqI; // 0 unless this is a reply to an TINY_RST request + uint8_t Zero; + + uint8_t RaceLaps; // 0 if qualifying + uint8_t QualMins; // 0 if race + uint8_t NumP; // number of players in race + uint8_t Timing; // lap timing (see below) + + char Track[6]; // short track name + uint8_t Weather; + uint8_t Wind; + + uint16_t Flags; // race flags (must pit, can reset, etc - see below) + uint16_t NumNodes; // total number of nodes in the path + uint16_t Finish; // node index - finish line + uint16_t Split1; // node index - split 1 + uint16_t Split2; // node index - split 2 + uint16_t Split3; // node index - split 3 +}; + +// Lap timing info (for Timing byte) + +// bits 6 and 7 (Timing & 0xc0) : + +// 0x40 : standard lap timing is being used +// 0x80 : custom timing - user checkpoints have been placed +// 0xc0 : no lap timing - e.g. open config with no user checkpoints + +// bits 0 and 1 (Timing & 0x03) : number of checkpoints if lap timing is enabled + +// To request an IS_RST packet at any time, send this IS_TINY : + +// ReqI : non-zero (returned in the reply) +// SubT : TINY_RST (request an IS_RST) + +struct IS_NCN // New ConN +{ + uint8_t Size; // 56 + uint8_t Type; // ISP_NCN + uint8_t ReqI; // 0 unless this is a reply to a TINY_NCN request + uint8_t UCID; // new connection's unique id (0 = host) + + char UName[24]; // username + char PName[24]; // nickname + + uint8_t Admin; // 1 if admin + uint8_t Total; // number of connections including host + uint8_t Flags; // bit 2 : remote + uint8_t Sp3; +}; + +struct IS_CNL // ConN Leave +{ + uint8_t Size; // 8 + uint8_t Type; // ISP_CNL + uint8_t ReqI; // 0 + uint8_t UCID; // unique id of the connection which left + + uint8_t Reason; // leave reason (see below) + uint8_t Total; // number of connections including host + uint8_t Sp2; + uint8_t Sp3; +}; + +struct IS_CPR // Conn Player Rename +{ + uint8_t Size; // 36 + uint8_t Type; // ISP_CPR + uint8_t ReqI; // 0 + uint8_t UCID; // unique id of the connection + + char PName[24]; // new name + char Plate[8]; // number plate - NO ZERO AT END! +}; + +struct IS_NPL // New PLayer joining race (if PLID already exists, then leaving pits) +{ + uint8_t Size; // 76 + uint8_t Type; // ISP_NPL + uint8_t ReqI; // 0 unless this is a reply to an TINY_NPL request + uint8_t PLID; // player's newly assigned unique id + + uint8_t UCID; // connection's unique id + uint8_t PType; // bit 0 : female / bit 1 : AI / bit 2 : remote + uint16_t Flags; // player flags + + char PName[24]; // nickname + char Plate[8]; // number plate - NO ZERO AT END! + + char CName[4]; // car name + char SName[16]; // skin name - MAX_CAR_TEX_NAME + uint8_t Tyres[4]; // compounds + + uint8_t H_Mass; // added mass (kg) + uint8_t H_TRes; // intake restriction + uint8_t Model; // driver model + uint8_t Pass; // passengers byte + + int32_t Spare; + + uint8_t SetF; // setup flags (see below) + uint8_t NumP; // number in race (same when leaving pits, 1 more if new) + uint8_t Sp2; + uint8_t Sp3; +}; + +// NOTE : PType bit 0 (female) is not reported on dedicated host as humans are not loaded +// You can use the driver model byte instead if required (and to force the use of helmets) + +// Setup flags (for SetF byte) + +#define SETF_SYMM_WHEELS 1 +#define SETF_TC_ENABLE 2 +#define SETF_ABS_ENABLE 4 + +// More... + +struct IS_PLP // PLayer Pits (go to settings - stays in player list) +{ + uint8_t Size; // 4 + uint8_t Type; // ISP_PLP + uint8_t ReqI; // 0 + uint8_t PLID; // player's unique id +}; + +struct IS_PLL // PLayer Leave race (spectate - removed from player list) +{ + uint8_t Size; // 4 + uint8_t Type; // ISP_PLL + uint8_t ReqI; // 0 + uint8_t PLID; // player's unique id +}; + +struct IS_CRS // Car ReSet +{ + uint8_t Size; // 4 + uint8_t Type; // ISP_CRS + uint8_t ReqI; // 0 + uint8_t PLID; // player's unique id +}; + +struct IS_LAP // LAP time +{ + uint8_t Size; // 20 + uint8_t Type; // ISP_LAP + uint8_t ReqI; // 0 + uint8_t PLID; // player's unique id + + uint32_t LTime; // lap time (ms) + uint32_t ETime; // total time (ms) + + uint16_t LapsDone; // laps completed + uint16_t Flags; // player flags + + uint8_t Sp0; + uint8_t Penalty; // current penalty value (see below) + uint8_t NumStops; // number of pit stops + uint8_t Sp3; +}; + +struct IS_SPX // SPlit X time +{ + uint8_t Size; // 16 + uint8_t Type; // ISP_SPX + uint8_t ReqI; // 0 + uint8_t PLID; // player's unique id + + uint32_t STime; // split time (ms) + uint32_t ETime; // total time (ms) + + uint8_t Split; // split number 1, 2, 3 + uint8_t Penalty; // current penalty value (see below) + uint8_t NumStops; // number of pit stops + uint8_t Sp3; +}; + +struct IS_PIT // PIT stop (stop at pit garage) +{ + uint8_t Size; // 24 + uint8_t Type; // ISP_PIT + uint8_t ReqI; // 0 + uint8_t PLID; // player's unique id + + uint16_t LapsDone; // laps completed + uint16_t Flags; // player flags + + uint8_t Sp0; + uint8_t Penalty; // current penalty value (see below) + uint8_t NumStops; // number of pit stops + uint8_t Sp3; + + uint8_t Tyres[4]; // tyres changed + + uint32_t Work; // pit work + uint32_t Spare; +}; + +struct IS_PSF // Pit Stop Finished +{ + uint8_t Size; // 12 + uint8_t Type; // ISP_PSF + uint8_t ReqI; // 0 + uint8_t PLID; // player's unique id + + uint32_t STime; // stop time (ms) + uint32_t Spare; +}; + +struct IS_PLA // Pit LAne +{ + uint8_t Size; // 8 + uint8_t Type; // ISP_PLA + uint8_t ReqI; // 0 + uint8_t PLID; // player's unique id + + uint8_t Fact; // pit lane fact (see below) + uint8_t Sp1; + uint8_t Sp2; + uint8_t Sp3; +}; + +// IS_CCH : Camera CHange + +// To track cameras you need to consider 3 points + +// 1) The default camera : VIEW_DRIVER +// 2) Player flags : CUSTOM_VIEW means VIEW_CUSTOM at start or pit exit +// 3) IS_CCH : sent when an existing driver changes camera + +struct IS_CCH // Camera CHange +{ + uint8_t Size; // 8 + uint8_t Type; // ISP_CCH + uint8_t ReqI; // 0 + uint8_t PLID; // player's unique id + + uint8_t Camera; // view identifier (see below) + uint8_t Sp1; + uint8_t Sp2; + uint8_t Sp3; +}; + +struct IS_PEN // PENalty (given or cleared) +{ + uint8_t Size; // 8 + uint8_t Type; // ISP_PEN + uint8_t ReqI; // 0 + uint8_t PLID; // player's unique id + + uint8_t OldPen; // old penalty value (see below) + uint8_t NewPen; // new penalty value (see below) + uint8_t Reason; // penalty reason (see below) + uint8_t Sp3; +}; + +struct IS_TOC // Take Over Car +{ + uint8_t Size; // 8 + uint8_t Type; // ISP_TOC + uint8_t ReqI; // 0 + uint8_t PLID; // player's unique id + + uint8_t OldUCID; // old connection's unique id + uint8_t NewUCID; // new connection's unique id + uint8_t Sp2; + uint8_t Sp3; +}; + +struct IS_FLG // FLaG (yellow or blue flag changed) +{ + uint8_t Size; // 8 + uint8_t Type; // ISP_FLG + uint8_t ReqI; // 0 + uint8_t PLID; // player's unique id + + uint8_t OffOn; // 0 = off / 1 = on + uint8_t Flag; // 1 = given blue / 2 = causing yellow + uint8_t CarBehind; // unique id of obstructed player + uint8_t Sp3; +}; + +struct IS_PFL // Player FLags (help flags changed) +{ + uint8_t Size; // 8 + uint8_t Type; // ISP_PFL + uint8_t ReqI; // 0 + uint8_t PLID; // player's unique id + + uint16_t Flags; // player flags (see below) + uint16_t Spare; +}; + +struct IS_FIN // FINished race notification (not a final result - use IS_RES) +{ + uint8_t Size; // 20 + uint8_t Type; // ISP_FIN + uint8_t ReqI; // 0 + uint8_t PLID; // player's unique id (0 = player left before result was sent) + + uint32_t TTime; // race time (ms) + uint32_t BTime; // best lap (ms) + + uint8_t SpA; + uint8_t NumStops; // number of pit stops + uint8_t Confirm; // confirmation flags : disqualified etc - see below + uint8_t SpB; + + uint16_t LapsDone; // laps completed + uint16_t Flags; // player flags : help settings etc - see below +}; + +struct IS_RES // RESult (qualify or confirmed finish) +{ + uint8_t Size; // 84 + uint8_t Type; // ISP_RES + uint8_t ReqI; // 0 unless this is a reply to a TINY_RES request + uint8_t PLID; // player's unique id (0 = player left before result was sent) + + char UName[24]; // username + char PName[24]; // nickname + char Plate[8]; // number plate - NO ZERO AT END! + char CName[4]; // skin prefix + + uint32_t TTime; // race time (ms) + uint32_t BTime; // best lap (ms) + + uint8_t SpA; + uint8_t NumStops; // number of pit stops + uint8_t Confirm; // confirmation flags : disqualified etc - see below + uint8_t SpB; + + uint16_t LapsDone; // laps completed + uint16_t Flags; // player flags : help settings etc - see below + + uint8_t ResultNum; // finish or qualify pos (0 = win / 255 = not added to table) + uint8_t NumRes; // total number of results (qualify doesn't always add a new one) + uint16_t PSeconds; // penalty time in seconds (already included in race time) +}; + +// IS_REO : REOrder - this packet can be sent in either direction + +// LFS sends one at the start of every race or qualifying session, listing the start order + +// You can send one to LFS before a race start, to specify the starting order. +// It may be a good idea to avoid conflict by using /start=fixed (LFS setting). +// Alternatively, you can leave the LFS setting, but make sure you send your IS_REO +// AFTER you receive the SMALL_VTA (VoTe Action). LFS does its default grid reordering at +// the same time as it sends the SMALL_VTA and you can override this by sending an IS_REO. + +struct IS_REO // REOrder (when race restarts after qualifying) +{ + uint8_t Size; // 36 + uint8_t Type; // ISP_REO + uint8_t ReqI; // 0 unless this is a reply to an TINY_REO request + uint8_t NumP; // number of players in race + + uint8_t PLID[32]; // all PLIDs in new order +}; + +// To request an IS_REO packet at any time, send this IS_TINY : + +// ReqI : non-zero (returned in the reply) +// SubT : TINY_REO (request an IS_REO) + +// Pit Lane Facts + +enum +{ + PITLANE_EXIT, // 0 - left pit lane + PITLANE_ENTER, // 1 - entered pit lane + PITLANE_NO_PURPOSE, // 2 - entered for no purpose + PITLANE_DT, // 3 - entered for drive-through + PITLANE_SG, // 4 - entered for stop-go + PITLANE_NUM +}; + +// Pit Work Flags + +enum +{ + PSE_NOTHING, // bit 0 (1) + PSE_STOP, // bit 1 (2) + PSE_FR_DAM, // bit 2 (4) + PSE_FR_WHL, // etc... + PSE_LE_FR_DAM, + PSE_LE_FR_WHL, + PSE_RI_FR_DAM, + PSE_RI_FR_WHL, + PSE_RE_DAM, + PSE_RE_WHL, + PSE_LE_RE_DAM, + PSE_LE_RE_WHL, + PSE_RI_RE_DAM, + PSE_RI_RE_WHL, + PSE_BODY_MINOR, + PSE_BODY_MAJOR, + PSE_SETUP, + PSE_REFUEL, + PSE_NUM +}; + +// View identifiers + +enum +{ + VIEW_FOLLOW, // 0 - arcade + VIEW_HELI, // 1 - helicopter + VIEW_CAM, // 2 - tv camera + VIEW_DRIVER, // 3 - cockpit + VIEW_CUSTOM, // 4 - custom + VIEW_MAX +}; + +static const int VIEW_ANOTHER = 255; // viewing another car + +// Leave reasons + +enum +{ + LEAVR_DISCO, // 0 - disconnect + LEAVR_TIMEOUT, // 1 - timed out + LEAVR_LOSTCONN, // 2 - lost connection + LEAVR_KICKED, // 3 - kicked + LEAVR_BANNED, // 4 - banned + LEAVR_SECURITY, // 5 - OOS or cheat protection + LEAVR_NUM +}; + +// Penalty values (VALID means the penalty can now be cleared) + +enum +{ + PENALTY_NONE, // 0 + PENALTY_DT, // 1 + PENALTY_DT_VALID, // 2 + PENALTY_SG, // 3 + PENALTY_SG_VALID, // 4 + PENALTY_30, // 5 + PENALTY_45, // 6 + PENALTY_NUM +}; + +// Penalty reasons + +enum +{ + PENR_UNKNOWN, // 0 - unknown or cleared penalty + PENR_ADMIN, // 1 - penalty given by admin + PENR_WRONG_WAY, // 2 - wrong way driving + PENR_FALSE_START, // 3 - starting before green light + PENR_SPEEDING, // 4 - speeding in pit lane + PENR_STOP_SHORT, // 5 - stop-go pit stop too short + PENR_STOP_LATE, // 6 - compulsory stop is too late + PENR_NUM +}; + +// Player flags + +#define PIF_SWAPSIDE 1 +#define PIF_RESERVED_2 2 +#define PIF_RESERVED_4 4 +#define PIF_AUTOGEARS 8 +#define PIF_SHIFTER 16 +#define PIF_RESERVED_32 32 +#define PIF_HELP_B 64 +#define PIF_AXIS_CLUTCH 128 +#define PIF_INPITS 256 +#define PIF_AUTOCLUTCH 512 +#define PIF_MOUSE 1024 +#define PIF_KB_NO_HELP 2048 +#define PIF_KB_STABILISED 4096 +#define PIF_CUSTOM_VIEW 8192 + +// Tyre compounds (4 byte order : rear L, rear R, front L, front R) + +enum +{ + TYRE_R1, // 0 + TYRE_R2, // 1 + TYRE_R3, // 2 + TYRE_R4, // 3 + TYRE_ROAD_SUPER, // 4 + TYRE_ROAD_NORMAL, // 5 + TYRE_HYBRID, // 6 + TYRE_KNOBBLY, // 7 + TYRE_NUM +}; + +static const int NOT_CHANGED = 255; + +// Confirmation flags + +#define CONF_MENTIONED 1 +#define CONF_CONFIRMED 2 +#define CONF_PENALTY_DT 4 +#define CONF_PENALTY_SG 8 +#define CONF_PENALTY_30 16 +#define CONF_PENALTY_45 32 +#define CONF_DID_NOT_PIT 64 + +#define CONF_DISQ (CONF_PENALTY_DT | CONF_PENALTY_SG | CONF_DID_NOT_PIT) +#define CONF_TIME (CONF_PENALTY_30 | CONF_PENALTY_45) + +// Race flags + +// HOSTF_CAN_VOTE 1 +// HOSTF_CAN_SELECT 2 +// HOSTF_MID_RACE 32 +// HOSTF_MUST_PIT 64 +// HOSTF_CAN_RESET 128 +// HOSTF_FCV 256 +// HOSTF_CRUISE 512 + +// Passengers byte + +// bit 0 female +// bit 1 front +// bit 2 female +// bit 3 rear left +// bit 4 female +// bit 5 rear middle +// bit 6 female +// bit 7 rear right + + +// TRACKING PACKET REQUESTS +// ======================== + +// To request players, connections, results or a single NLP or MCI, send an IS_TINY + +// In each case, ReqI must be non-zero, and will be returned in the reply packet + +// SubT : TINT_NCN - request all connections +// SubT : TINY_NPL - request all players +// SubT : TINY_RES - request all results +// SubT : TINY_NLP - request a single IS_NLP +// SubT : TINY_MCI - request a set of IS_MCI + + +// AUTOCROSS +// ========= + +// When all objects are cleared from a layout, LFS sends this IS_TINY : + +// ReqI : 0 +// SubT : TINY_AXC (AutoX Cleared) + +// You can request information about the current layout with this IS_TINY : + +// ReqI : non-zero (returned in the reply) +// SubT : TINY_AXI (AutoX Info) + +// The information will be sent back in this packet (also sent when a layout is loaded) : + +struct IS_AXI // AutoX Info +{ + uint8_t Size; // 40 + uint8_t Type; // ISP_AXI + uint8_t ReqI; // 0 unless this is a reply to an TINY_AXI request + uint8_t Zero; + + uint8_t AXStart; // autocross start position + uint8_t NumCP; // number of checkpoints + uint16_t NumO; // number of objects + + char LName[32]; // the name of the layout last loaded (if loaded locally) +}; + +// On false start or wrong route / restricted area, an IS_PEN packet is sent : + +// False start : OldPen = 0 / NewPen = PENALTY_30 / Reason = PENR_FALSE_START +// Wrong route : OldPen = 0 / NewPen = PENALTY_45 / Reason = PENR_WRONG_WAY + +// If an autocross object is hit (2 second time penalty) this packet is sent : + +struct IS_AXO // AutoX Object +{ + uint8_t Size; // 4 + uint8_t Type; // ISP_AXO + uint8_t ReqI; // 0 + uint8_t PLID; // player's unique id +}; + + +// CAR TRACKING - car position info sent at constant intervals +// ============ + +// IS_NLP - compact, all cars in 1 variable sized packet +// IS_MCI - detailed, max 8 cars per variable sized packet + +// To receive IS_NLP or IS_MCI packets at a specified interval : + +// 1) Set the Interval field in the IS_ISI (InSimInit) packet (40, 50, 60... 8000 ms) +// 2) Set one of the flags ISF_NLP or ISF_MCI in the IS_ISI packet + +// If ISF_NLP flag is set, one IS_NLP packet is sent... + +struct NodeLap // Car info in 6 bytes - there is an array of these in the NLP (below) +{ + uint16_t Node; // current path node + uint16_t Lap; // current lap + uint8_t PLID; // player's unique id + uint8_t Position; // current race position : 0 = unknown, 1 = leader, etc... +}; + +struct IS_NLP // Node and Lap Packet - variable size +{ + uint8_t Size; // 4 + NumP * 6 (PLUS 2 if needed to make it a multiple of 4) + uint8_t Type; // ISP_NLP + uint8_t ReqI; // 0 unless this is a reply to an TINY_NLP request + uint8_t NumP; // number of players in race + + struct NodeLap Info[32]; // node and lap of each player, 1 to 32 of these (NumP) +}; + +// If ISF_MCI flag is set, a set of IS_MCI packets is sent... + +struct CompCar // Car info in 28 bytes - there is an array of these in the MCI (below) +{ + uint16_t Node; // current path node + uint16_t Lap; // current lap + uint8_t PLID; // player's unique id + uint8_t Position; // current race position : 0 = unknown, 1 = leader, etc... + uint8_t Info; // flags and other info - see below + uint8_t Sp3; + int32_t X; // X map (65536 = 1 metre) + int32_t Y; // Y map (65536 = 1 metre) + int32_t Z; // Z alt (65536 = 1 metre) + uint16_t Speed; // speed (32768 = 100 m/s) + uint16_t Direction; // car's motion if Speed > 0 : 0 = world y direction, 32768 = 180 deg + uint16_t Heading; // direction of forward axis : 0 = world y direction, 32768 = 180 deg + int16_t AngVel; // signed, rate of change of heading : (16384 = 360 deg/s) +}; + +// NOTE 1) Info byte - the bits in this byte have the following meanings : + +#define CCI_BLUE 1 // this car is in the way of a driver who is a lap ahead +#define CCI_YELLOW 2 // this car is slow or stopped and in a dangerous place + +#define CCI_LAG 32 // this car is lagging (missing or delayed position packets) + +#define CCI_FIRST 64 // this is the first compcar in this set of MCI packets +#define CCI_LAST 128 // this is the last compcar in this set of MCI packets + +// NOTE 2) Heading : 0 = world y axis direction, 32768 = 180 degrees, anticlockwise from above +// NOTE 3) AngVel : 0 = no change in heading, 8192 = 180 degrees per second anticlockwise + +struct IS_MCI // Multi Car Info - if more than 8 in race then more than one of these is sent +{ + uint8_t Size; // 4 + NumC * 28 + uint8_t Type; // ISP_MCI + uint8_t ReqI; // 0 unless this is a reply to an TINY_MCI request + uint8_t NumC; // number of valid CompCar structs in this packet + + struct CompCar Info[8]; // car info for each player, 1 to 8 of these (NumC) +}; + +// You can change the rate of NLP or MCI after initialisation by sending this IS_SMALL : + +// ReqI : 0 +// SubT : SMALL_NLI (Node Lap Interval) +// UVal : interval (0 means stop, otherwise time interval : 40, 50, 60... 8000 ms) + + +// CONTACT - reports contacts between two cars if the closing speed is above 0.25 m/s +// ======= + +// Set the ISF_CON flag in the IS_ISI to receive car contact reports + +struct CarContact // 16 bytes : one car in a contact - two of these in the IS_CON (below) +{ + uint8_t PLID; + uint8_t Info; // like Info byte in CompCar (CCI_BLUE / CCI_YELLOW / CCI_LAG) + uint8_t Sp2; // spare + int8_t Steer; // front wheel steer in degrees (right positive) + + uint8_t ThrBrk; // high 4 bits : throttle / low 4 bits : brake (0 to 15) + uint8_t CluHan; // high 4 bits : clutch / low 4 bits : handbrake (0 to 15) + uint8_t GearSp; // high 4 bits : gear (15=R) / low 4 bits : spare + uint8_t Speed; // m/s + + uint8_t Direction; // car's motion if Speed > 0 : 0 = world y direction, 128 = 180 deg + uint8_t Heading; // direction of forward axis : 0 = world y direction, 128 = 180 deg + int8_t AccelF; // m/s^2 longitudinal acceleration (forward positive) + int8_t AccelR; // m/s^2 lateral acceleration (right positive) + + int16_t X; // position (1 metre = 16) + int16_t Y; // position (1 metre = 16) +}; + +struct IS_CON // CONtact - between two cars (A and B are sorted by PLID) +{ + uint8_t Size; // 40 + uint8_t Type; // ISP_CON + uint8_t ReqI; // 0 + uint8_t Zero; + + uint16_t SpClose; // high 4 bits : reserved / low 12 bits : closing speed (10 = 1 m/s) + uint16_t Time; // looping time stamp (hundredths - time since reset - like TINY_GTH) + + struct CarContact A; + struct CarContact B; +}; + +// Set the ISF_OBH flag in the IS_ISI to receive object contact reports + +struct CarContOBJ // 8 bytes : car in a contact with an object +{ + uint8_t Direction; // car's motion if Speed > 0 : 0 = world y direction, 128 = 180 deg + uint8_t Heading; // direction of forward axis : 0 = world y direction, 128 = 180 deg + uint8_t Speed; // m/s + uint8_t Sp3; + + int16_t X; // position (1 metre = 16) + int16_t Y; // position (1 metre = 16) +}; + +struct IS_OBH // OBject Hit - car hit an autocross object or an unknown object +{ + uint8_t Size; // 24 + uint8_t Type; // ISP_OBH + uint8_t ReqI; // 0 + uint8_t PLID; // player's unique id + + uint16_t SpClose; // high 4 bits : reserved / low 12 bits : closing speed (10 = 1 m/s) + uint16_t Time; // looping time stamp (hundredths - time since reset - like TINY_GTH) + + struct CarContOBJ C; + + int16_t X; // as in ObjectInfo + int16_t Y; // as in ObjectInfo + + uint8_t Sp0; + uint8_t Sp1; + uint8_t Index; // AXO_x as in ObjectInfo or zero if it is an unknown object + uint8_t OBHFlags; // see below +}; + +//'BBBBHHBBBBhhhhBBBB' + + +// OBHFlags byte + +#define OBH_LAYOUT 1 // an added object +#define OBH_CAN_MOVE 2 // a movable object +#define OBH_WAS_MOVING 4 // was moving before this hit +#define OBH_ON_SPOT 8 // object in original position + +// Set the ISF_HLV flag in the IS_ISI to receive reports of incidents that would violate HLVC + +struct IS_HLV // Hot Lap Validity - illegal ground / hit wall / speeding in pit lane +{ + uint8_t Size; // 16 + uint8_t Type; // ISP_HLV + uint8_t ReqI; // 0 + uint8_t PLID; // player's unique id + + uint8_t HLVC; // 0 : ground / 1 : wall / 4 : speeding + uint8_t Sp1; + uint16_t Time; // looping time stamp (hundredths - time since reset - like TINY_GTH) + + struct CarContOBJ C; +}; + + +// AUTOCROSS OBJECTS - reporting / adding / removing +// ================= + +// Set the ISF_AXM_LOAD flag in the IS_ISI for info about objects when a layout is loaded. +// Set the ISF_AXM_EDIT flag in the IS_ISI for info about objects edited by user or InSim. + +// You can also add or remove objects by sending IS_AXM packets. +// Some care must be taken with these - please read the notes below. + +struct ObjectInfo // Info about a single object - explained in the layout file format +{ + int16_t X; + int16_t Y; + int8_t Zchar; + uint8_t Flags; + uint8_t Index; + uint8_t Heading; + + //'2hb3B' +}; + +struct IS_AXM // AutoX Multiple objects - variable size +{ + uint8_t Size; // 8 + NumO * 8 + uint8_t Type; // ISP_AXM + uint8_t ReqI; // 0 + uint8_t NumO; // number of objects in this packet + + uint8_t UCID; // unique id of the connection that sent the packet + uint8_t PMOAction; // see below + uint8_t PMOFlags; // see below + uint8_t Sp3; + + struct ObjectInfo Info[30]; // info about each object, 0 to 30 of these +}; + +//'8B' + +// Values for PMOAction byte + +enum +{ + PMO_LOADING_FILE, // 0 - sent by the layout loading system only + PMO_ADD_OBJECTS, // 1 - adding objects (from InSim or editor) + PMO_DEL_OBJECTS, // 2 - delete objects (from InSim or editor) + PMO_CLEAR_ALL, // 3 - clear all objects (NumO must be zero) + PMO_NUM +}; + +// Info about the PMOFlags byte (only bit 0 is currently used) : + +// If PMOFlags bit 0 is set in a PMO_LOADING_FILE packet, LFS has reached the end of +// a layout file which it is loading. The added objects will then be optimised. + +// Optimised in this case means that static vertex buffers will be created for all +// objects, to greatly improve the frame rate. The problem with this is that when +// there are many objects loaded, optimisation causes a significant glitch which can +// be long enough to cause a driver who is cornering to lose control and crash. + +// PMOFlags bit 0 can also be set in an IS_AXM with PMOAction of PMO_ADD_OBJECTS. +// This causes all objects to be optimised. It is important not to set bit 0 in +// every packet you send to add objects or you will cause severe glitches on the +// clients computers. It is ok to have some objects on the track which are not +// optimised. So if you have a few objects that are being removed and added +// occasionally, the best advice is not to request optimisation at all. Only +// request optimisation (by setting bit 0) if you have added so many objects +// that it is needed to improve the frame rate. + +// NOTE 1) LFS makes sure that all objects are optimised when the race restarts. +// NOTE 2) In the 'more' section of SHIFT+U there is info about optimised objects. + +// If you are using InSim to send many packets of objects (for example loading an +// entire layout through InSim) then you must take care of the bandwidth and buffer +// overflows. You must not try to send all the objects at once. It's probably good +// to use LFS's method of doing this : send the first packet of objects then wait for +// the corresponding IS_AXM that will be output when the packet is processed. Then +// you can send the second packet and again wait for the IS_AXM and so on. + + +// CAR POSITION PACKETS (Initialising OutSim from InSim - See "OutSim" below) +// ==================== + +// To request Car Positions from the currently viewed car, send this IS_SMALL : + +// ReqI : 0 +// SubT : SMALL_SSP (Start Sending Positions) +// UVal : interval (time between updates - zero means stop sending) + +// If OutSim has not been setup in cfg.txt, the SSP packet makes LFS send UDP packets +// if in game, using the OutSim system as documented near the end of this text file. + +// You do not need to set any OutSim values in LFS cfg.txt - OutSim is fully +// initialised by the SSP packet. + +// The OutSim packets will be sent to the UDP port specified in the InSimInit packet. + +// NOTE : OutSim packets are not InSim packets and don't have a 4-byte header. + + +// DASHBOARD PACKETS (Initialising OutGauge from InSim - See "OutGauge" below) +// ================= + +// To request Dashboard Packets from the currently viewed car, send this IS_SMALL : + +// ReqI : 0 +// SubT : SMALL_SSG (Start Sending Gauges) +// UVal : interval (time between updates - zero means stop sending) + +// If OutGauge has not been setup in cfg.txt, the SSG packet makes LFS send UDP packets +// if in game, using the OutGauge system as documented near the end of this text file. + +// You do not need to set any OutGauge values in LFS cfg.txt - OutGauge is fully +// initialised by the SSG packet. + +// The OutGauge packets will be sent to the UDP port specified in the InSimInit packet. + +// NOTE : OutGauge packets are not InSim packets and don't have a 4-byte header. + + +// CAMERA CONTROL +// ============== + +// IN GAME camera control +// ---------------------- + +// You can set the viewed car and selected camera directly with a special packet +// These are the states normally set in game by using the TAB and V keys + +struct IS_SCC // Set Car Camera - Simplified camera packet (not SHIFT+U mode) +{ + uint8_t Size; // 8 + uint8_t Type; // ISP_SCC + uint8_t ReqI; // 0 + uint8_t Zero; + + uint8_t ViewPLID; // Unique ID of player to view + uint8_t InGameCam; // InGameCam (as reported in StatePack) + uint8_t Sp2; + uint8_t Sp3; +}; + +// NOTE : Set InGameCam or ViewPLID to 255 to leave that option unchanged. + +// DIRECT camera control +// --------------------- + +// A Camera Position Packet can be used for LFS to report a camera position and state. +// An InSim program can also send one to set LFS camera position in game or SHIFT+U mode. + +// Type : "Vec" : 3 ints (X, Y, Z) - 65536 means 1 metre + +struct IS_CPP // Cam Pos Pack - Full camera packet (in car OR SHIFT+U mode) +{ + uint8_t Size; // 32 + uint8_t Type; // ISP_CPP + uint8_t ReqI; // instruction : 0 / or reply : ReqI as received in the TINY_SCP + uint8_t Zero; + + struct Vec Pos; // Position vector + + uint16_t H; // heading - 0 points along Y axis + uint16_t P; // pitch - 0 means looking at horizon + uint16_t R; // roll - 0 means no roll + + uint8_t ViewPLID; // Unique ID of viewed player (0 = none) + uint8_t InGameCam; // InGameCam (as reported in StatePack) + + float FOV; // 4-byte float : FOV in degrees + + uint16_t Time; // Time in ms to get there (0 means instant) + uint16_t Flags; // ISS state flags (see below) +}; + +// The ISS state flags that can be set are : + +// ISS_SHIFTU - in SHIFT+U mode +// ISS_SHIFTU_FOLLOW - FOLLOW view +// ISS_VIEW_OVERRIDE - override user view + +// On receiving this packet, LFS will set up the camera to match the values in the packet, +// including switching into or out of SHIFT+U mode depending on the ISS_SHIFTU flag. + +// If ISS_VIEW_OVERRIDE is set, the in-car view Heading Pitch and Roll (but not FOV) will +// be taken from the values in this packet. Otherwise normal in game control will be used. + +// Position vector (Vec Pos) - in SHIFT+U mode, Pos can be either relative or absolute. + +// If ISS_SHIFTU_FOLLOW is set, it's a following camera, so the position is relative to +// the selected car. Otherwise, the position is absolute, as used in normal SHIFT+U mode. + +// NOTE : Set InGameCam or ViewPLID to 255 to leave that option unchanged. + +// SMOOTH CAMERA POSITIONING +// -------------------------- + +// The "Time" value in the packet is used for camera smoothing. A zero Time means instant +// positioning. Any other value (milliseconds) will cause the camera to move smoothly to +// the requested position in that time. This is most useful in SHIFT+U camera modes or +// for smooth changes of internal view when using the ISS_VIEW_OVERRIDE flag. + +// NOTE : You can use frequently updated camera positions with a longer Time value than +// the update frequency. For example, sending a camera position every 100 ms, with a +// Time value of 1000 ms. LFS will make a smooth motion from the rough inputs. + +// If the requested camera mode is different from the one LFS is already in, it cannot +// move smoothly to the new position, so in this case the "Time" value is ignored. + +// GETTING A CAMERA PACKET +// ----------------------- + +// To GET a CamPosPack from LFS, send this IS_TINY : + +// ReqI : non-zero (returned in the reply) +// SubT : TINY_SCP (Send Cam Pos) + +// LFS will reply with a CamPosPack as described above. You can store this packet +// and later send back exactly the same packet to LFS and it will try to replicate +// that camera position. + + +// TIME CONTROL +// ============ + +// Request the current time at any point with this IS_TINY : + +// ReqI : non-zero (returned in the reply) +// SubT : TINY_GTH (Get Time in Hundredths) + +// The time will be sent back in this IS_SMALL : + +// ReqI : non-zero (as received in the request packet) +// SubT : SMALL_RTP (Race Time Packet) +// UVal : Time (hundredths of a second since start of race or replay) + +// You can stop or start time in LFS and while it is stopped you can send packets to move +// time in steps. Time steps are specified in hundredths of a second. +// Warning : unlike pausing, this is a "trick" to LFS and the program is unaware of time +// passing so you must not leave it stopped because LFS is unusable in that state. +// This packet is not available in live multiplayer mode. + +// Stop and Start with this IS_SMALL : + +// ReqI : 0 +// SubT : SMALL_TMS (TiMe Stop) +// UVal : stop (1 - stop / 0 - carry on) + +// When STOPPED, make time step updates with this IS_SMALL : + +// ReqI : 0 +// SubT : SMALL_STP (STeP) +// UVal : number (number of hundredths of a second to update) + + +// REPLAY CONTROL +// ============== + +// You can load a replay or set the position in a replay with an IS_RIP packet. +// Replay positions and lengths are specified in hundredths of a second. +// LFS will reply with another IS_RIP packet when the request is completed. + +struct IS_RIP // Replay Information Packet +{ + uint8_t Size; // 80 + uint8_t Type; // ISP_RIP + uint8_t ReqI; // request : non-zero / reply : same value returned + uint8_t Error; // 0 or 1 = OK / other values are listed below + + uint8_t MPR; // 0 = SPR / 1 = MPR + uint8_t Paused; // request : pause on arrival / reply : paused state + uint8_t Options; // various options - see below + uint8_t Sp3; + + uint32_t CTime; // (hundredths) request : destination / reply : position + uint32_t TTime; // (hundredths) request : zero / reply : replay length + + char RName[64]; // zero or replay name - last byte must be zero +}; + +// NOTE about RName : +// In a request, replay RName will be loaded. If zero then the current replay is used. +// In a reply, RName is the name of the current replay, or zero if no replay is loaded. + +// You can request an IS_RIP packet at any time with this IS_TINY : + +// ReqI : non-zero (returned in the reply) +// SubT : TINY_RIP (Replay Information Packet) + +// Error codes returned in IS_RIP replies : + +enum +{ + RIP_OK, // 0 - OK : completed instruction + RIP_ALREADY, // 1 - OK : already at the destination + RIP_DEDICATED, // 2 - can't run a replay - dedicated host + RIP_WRONG_MODE, // 3 - can't start a replay - not in a suitable mode + RIP_NOT_REPLAY, // 4 - RName is zero but no replay is currently loaded + RIP_CORRUPTED, // 5 - IS_RIP corrupted (e.g. RName does not end with zero) + RIP_NOT_FOUND, // 6 - the replay file was not found + RIP_UNLOADABLE, // 7 - obsolete / future / corrupted + RIP_DEST_OOB, // 8 - destination is beyond replay length + RIP_UNKNOWN, // 9 - unknown error found starting replay + RIP_USER, // 10 - replay search was terminated by user + RIP_OOS, // 11 - can't reach destination - SPR is out of sync +}; + +// Options byte : some options + +#define RIPOPT_LOOP 1 // replay will loop if this bit is set +#define RIPOPT_SKINS 2 // set this bit to download missing skins +#define RIPOPT_FULL_PHYS 4 // use full physics when searching an MPR + +// NOTE : RIPOPT_FULL_PHYS makes MPR searching much slower so should not normally be used. +// This flag was added to allow high accuracy MCI packets to be output when fast forwarding. + + +// SCREENSHOTS +// =========== + +// You can instuct LFS to save a screenshot using the IS_SSH packet. +// The screenshot will be saved as an uncompressed BMP in the data\shots folder. +// BMP can be a filename (excluding .bmp) or zero - LFS will create a file name. +// LFS will reply with another IS_SSH when the request is completed. + +struct IS_SSH // ScreenSHot +{ + uint8_t Size; // 40 + uint8_t Type; // ISP_SSH + uint8_t ReqI; // request : non-zero / reply : same value returned + uint8_t Error; // 0 = OK / other values are listed below + + uint8_t Sp0; // 0 + uint8_t Sp1; // 0 + uint8_t Sp2; // 0 + uint8_t Sp3; // 0 + + char BMP[32]; // name of screenshot file - last byte must be zero +}; + +// Error codes returned in IS_SSH replies : + +enum +{ + SSH_OK, // 0 - OK : completed instruction + SSH_DEDICATED, // 1 - can't save a screenshot - dedicated host + SSH_CORRUPTED, // 2 - IS_SSH corrupted (e.g. BMP does not end with zero) + SSH_NO_SAVE, // 3 - could not save the screenshot +}; + + +// BUTTONS +// ======= + +// You can make up to 240 buttons appear on the host or guests (ID = 0 to 239). +// You should set the ISF_LOCAL flag (in IS_ISI) if your program is not a host control +// system, to make sure your buttons do not conflict with any buttons sent by the host. + +// LFS can display normal buttons in these four screens : + +// - main entry screen +// - game setup screen +// - in game +// - SHIFT+U mode + +// The recommended area for most buttons is defined by : + +#define IS_X_MIN 0 +#define IS_X_MAX 110 + +#define IS_Y_MIN 30 +#define IS_Y_MAX 170 + +// If you draw buttons in this area, the area will be kept clear to +// avoid overlapping LFS buttons with your InSim program's buttons. +// Buttons outside that area will not have a space kept clear. +// You can also make buttons visible in all screens - see below. + +// To delete one button or clear all buttons, send this packet : + +struct IS_BFN // Button FunctioN - delete buttons / receive button requests +{ + uint8_t Size; // 8 + uint8_t Type; // ISP_BFN + uint8_t ReqI; // 0 + uint8_t SubT; // subtype, from BFN_ enumeration (see below) + + uint8_t UCID; // connection to send to or from (0 = local / 255 = all) + uint8_t ClickID; // ID of button to delete (if SubT is BFN_DEL_BTN) + uint8_t Inst; // used internally by InSim + uint8_t Sp3; +}; + +enum // the fourth byte of IS_BFN packets is one of these +{ + BFN_DEL_BTN, // 0 - instruction : delete one button (must set ClickID) + BFN_CLEAR, // 1 - instruction : clear all buttons made by this insim instance + BFN_USER_CLEAR, // 2 - info : user cleared this insim instance's buttons + BFN_REQUEST, // 3 - user request : SHIFT+B or SHIFT+I - request for buttons +}; + +// NOTE : BFN_REQUEST allows the user to bring up buttons with SHIFT+B or SHIFT+I + +// SHIFT+I clears all host buttons if any - or sends a BFN_REQUEST to host instances +// SHIFT+B is the same but for local buttons and local instances + +// To send a button to LFS, send this variable sized packet + +struct IS_BTN // BuTtoN - button header - followed by 0 to 240 characters +{ + uint8_t Size; // 12 + TEXT_SIZE (a multiple of 4) + uint8_t Type; // ISP_BTN + uint8_t ReqI; // non-zero (returned in IS_BTC and IS_BTT packets) + uint8_t UCID; // connection to display the button (0 = local / 255 = all) + + uint8_t ClickID; // button ID (0 to 239) + uint8_t Inst; // some extra flags - see below + uint8_t BStyle; // button style flags - see below + uint8_t TypeIn; // max chars to type in - see below + + uint8_t L; // left : 0 - 200 + uint8_t T; // top : 0 - 200 + uint8_t W; // width : 0 - 200 + uint8_t H; // height : 0 - 200 + +// char Text[TEXT_SIZE]; // 0 to 240 characters of text +}; + +// ClickID byte : this value is returned in IS_BTC and IS_BTT packets. + +// Host buttons and local buttons are stored separately, so there is no chance of a conflict between +// a host control system and a local system (although the buttons could overlap on screen). + +// Programmers of local InSim programs may wish to consider using a configurable button range and +// possibly screen position, in case their users will use more than one local InSim program at once. + +// TypeIn byte : if set, the user can click this button to type in text. + +// Lowest 7 bits are the maximum number of characters to type in (0 to 95) +// Highest bit (128) can be set to initialise dialog with the button's text + +// On clicking the button, a text entry dialog will be opened, allowing the specified number of +// characters to be typed in. The caption on the text entry dialog is optionally customisable using +// Text in the IS_BTN packet. If the first character of IS_BTN's Text field is zero, LFS will read +// the caption up to the second zero. The visible button text then follows that second zero. + +// Text : 65-66-67-0 would display button text "ABC" and no caption + +// Text : 0-65-66-67-0-68-69-70-71-0-0-0 would display button text "DEFG" and caption "ABC" + +// Inst byte : mainly used internally by InSim but also provides some extra user flags + +#define INST_ALWAYS_ON 128 // if this bit is set the button is visible in all screens + +// NOTE : You should not use INST_ALWAYS_ON for most buttons. This is a special flag for buttons +// that really must be on in all screens (including the garage and options screens). You will +// probably need to confine these buttons to the top or bottom edge of the screen, to avoid +// overwriting LFS buttons. Most buttons should be defined without this flag, and positioned +// in the recommended area so LFS can keep a space clear in the main screens. + +// BStyle byte : style flags for the button + +#define ISB_C1 1 // you can choose a standard +#define ISB_C2 2 // interface colour using +#define ISB_C4 4 // these 3 lowest bits - see below +#define ISB_CLICK 8 // click this button to send IS_BTC +#define ISB_LIGHT 16 // light button +#define ISB_DARK 32 // dark button +#define ISB_LEFT 64 // align text to left +#define ISB_RIGHT 128 // align text to right + +// colour 0 : light grey (not user editable) +// colour 1 : title colour (default:yellow) +// colour 2 : unselected text (default:black) +// colour 3 : selected text (default:white) +// colour 4 : ok (default:green) +// colour 5 : cancel (default:red) +// colour 6 : text string (default:pale blue) +// colour 7 : unavailable (default:grey) + +// NOTE : If width or height are zero, this would normally be an invalid button. But in that case if +// there is an existing button with the same ClickID, all the packet contents are ignored except the +// Text field. This can be useful for updating the text in a button without knowing its position. +// For example, you might reply to an IS_BTT using an IS_BTN with zero W and H to update the text. + +// Replies : If the user clicks on a clickable button, this packet will be sent : + +struct IS_BTC // BuTton Click - sent back when user clicks a button +{ + uint8_t Size; // 8 + uint8_t Type; // ISP_BTC + uint8_t ReqI; // ReqI as received in the IS_BTN + uint8_t UCID; // connection that clicked the button (zero if local) + + uint8_t ClickID; // button identifier originally sent in IS_BTN + uint8_t Inst; // used internally by InSim + uint8_t CFlags; // button click flags - see below + uint8_t Sp3; +}; + +// CFlags byte : click flags + +#define ISB_LMB 1 // left click +#define ISB_RMB 2 // right click +#define ISB_CTRL 4 // ctrl + click +#define ISB_SHIFT 8 // shift + click + +// If the TypeIn byte is set in IS_BTN the user can type text into the button +// In that case no IS_BTC is sent - an IS_BTT is sent when the user presses ENTER + +struct IS_BTT // BuTton Type - sent back when user types into a text entry button +{ + uint8_t Size; // 104 + uint8_t Type; // ISP_BTT + uint8_t ReqI; // ReqI as received in the IS_BTN + uint8_t UCID; // connection that typed into the button (zero if local) + + uint8_t ClickID; // button identifier originally sent in IS_BTN + uint8_t Inst; // used internally by InSim + uint8_t TypeIn; // from original button specification + uint8_t Sp3; + + char Text[96]; // typed text, zero to TypeIn specified in IS_BTN +}; + + +// OutSim - MOTION SIMULATOR SUPPORT +// ====== + +// The user's car in multiplayer or the viewed car in single player or +// single player replay can output information to a motion system while +// viewed from an internal view. + +// This can be controlled by 5 lines in the cfg.txt file : + +// OutSim Mode 0 :0-off 1-driving 2-driving+replay +// OutSim Delay 1 :minimum delay between packets (100ths of a sec) +// OutSim IP 0.0.0.0 :IP address to send the UDP packet +// OutSim Port 0 :IP port +// OutSim ID 0 :if not zero, adds an identifier to the packet + +// Each update sends the following UDP packet : + +struct OutSimPack +{ + uint32_t Time; // time in milliseconds (to check order) + + struct Vector AngVel; // 3 floats, angular velocity vector + float Heading; // anticlockwise from above (Z) + float Pitch; // anticlockwise from right (X) + float Roll; // anticlockwise from front (Y) + struct Vector Accel; // 3 floats X, Y, Z + struct Vector Vel; // 3 floats X, Y, Z + struct Vec Pos; // 3 ints X, Y, Z (1m = 65536) + + int32_t ID; // optional - only if OutSim ID is specified +}; + +// NOTE 1) X and Y axes are on the ground, Z is up. + +// NOTE 2) Motion simulators can be dangerous. The Live for Speed developers do +// not support any motion systems in particular and cannot accept responsibility +// for injuries or deaths connected with the use of such machinery. + + +// OutGauge - EXTERNAL DASHBOARD SUPPORT +// ======== + +// The user's car in multiplayer or the viewed car in single player or +// single player replay can output information to a dashboard system +// while viewed from an internal view. + +// This can be controlled by 5 lines in the cfg.txt file : + +// OutGauge Mode 0 :0-off 1-driving 2-driving+replay +// OutGauge Delay 1 :minimum delay between packets (100ths of a sec) +// OutGauge IP 0.0.0.0 :IP address to send the UDP packet +// OutGauge Port 0 :IP port +// OutGauge ID 0 :if not zero, adds an identifier to the packet + +// Each update sends the following UDP packet : + +struct OutGaugePack +{ + uint32_t Time; // time in milliseconds (to check order) + + char Car[4]; // Car name + uint16_t Flags; // Info (see OG_x below) + uint8_t Gear; // Reverse:0, Neutral:1, First:2... + uint8_t PLID; // Unique ID of viewed player (0 = none) + float Speed; // M/S + float RPM; // RPM + float Turbo; // BAR + float EngTemp; // C + float Fuel; // 0 to 1 + float OilPressure; // BAR + float OilTemp; // C + uint32_t DashLights; // Dash lights available (see DL_x below) + uint32_t ShowLights; // Dash lights currently switched on + float Throttle; // 0 to 1 + float Brake; // 0 to 1 + float Clutch; // 0 to 1 + char Display1[16]; // Usually Fuel + char Display2[16]; // Usually Settings + + int32_t ID; // optional - only if OutGauge ID is specified +}; + +// OG_x - bits for OutGaugePack Flags + +#define OG_SHIFT 1 // key +#define OG_CTRL 2 // key + +#define OG_TURBO 8192 // show turbo gauge +#define OG_KM 16384 // if not set - user prefers MILES +#define OG_BAR 32768 // if not set - user prefers PSI + +// DL_x - bits for OutGaugePack DashLights and ShowLights + +/* +enum +{ + DL_SHIFT, // bit 0 - shift light + DL_FULLBEAM, // bit 1 - full beam + DL_HANDBRAKE, // bit 2 - handbrake + DL_PITSPEED, // bit 3 - pit speed limiter + DL_TC, // bit 4 - TC active or switched off + DL_SIGNAL_L, // bit 5 - left turn signal + DL_SIGNAL_R, // bit 6 - right turn signal + DL_SIGNAL_ANY, // bit 7 - shared turn signal + DL_OILWARN, // bit 8 - oil pressure warning + DL_BATTERY, // bit 9 - battery warning + DL_ABS, // bit 10 - ABS active or switched off + DL_SPARE, // bit 11 + DL_NUM +}; +*/ +////// + +struct conninfo +{ + uint8_t active; + uint8_t admin; + uint8_t state; + char uname[24]; + char pname[24]; +}; + +#define CARPATHSIZE 128 + +struct carinfo +{ + char cname[4]; + uint8_t active; + uint8_t ucid; + uint8_t mass; + uint8_t intake; + + uint16_t node; + uint16_t lap; + uint8_t position; + uint8_t info; + uint8_t sp3; + int32_t x; + int32_t y; + int32_t z; + uint16_t speed; + uint16_t direction; + uint16_t heading; + int16_t angvel; + + int32_t hist_x[CARPATHSIZE]; + int32_t hist_y[CARPATHSIZE]; + uint16_t hist_s[CARPATHSIZE]; + int32_t hist; +}; + +extern struct conninfo s_conninfo[256]; +extern struct carinfo s_carinfo[256]; + +void insim_init(const char *hostname, int port); + +#endif diff --git a/lfsdash.c b/lfsdash.c new file mode 100644 index 0000000..abafd03 --- /dev/null +++ b/lfsdash.c @@ -0,0 +1,994 @@ +#include +#include +#include +#include +#include +#include +#include "outgauge.h" +#include "cars.h" +#include "config.h" +#include "gauge.h" +#include "socket.h" +#include "audio.h" +#include "text.h" +#include "insim.h" +#include "network_worker.h" + +static GLuint s_symbols[1]; + +struct sympos { + float a, b, c, d; + int flag; + struct colour off; + struct colour on; +}; + +/* + DL_SHIFT, // bit 0 - shift light + DL_FULLBEAM, // bit 1 - full beam + DL_HANDBRAKE, // bit 2 - handbrake + DL_PITSPEED, // bit 3 - pit speed limiter + DL_TC, // bit 4 - TC active or switched off + DL_SIGNAL_L, // bit 5 - left turn signal + DL_SIGNAL_R, // bit 6 - right turn signal + DL_SIGNAL_ANY, // bit 7 - shared turn signal + DL_OILWARN, // bit 8 - oil pressure warning + DL_BATTERY, // bit 9 - battery warning + DL_ABS, // bit 10 - ABS active or switched off + DL_SPARE, // bit 11 + */ + +static int dl_to_sympos[] = { + 18, 0, 12, 17, 11, 15, 16, 2, 10, 9, 14, 13 +}; + +static struct sympos s_sympos[] = { + { 0.0, 0.0, 0.125, 0.125, (1 << DL_FULLBEAM), { 0.1, 0.1, 0.1, 1.0 }, { 0.15, 0.15, 1.0, 1.0 } }, + { 0.125, 0.0, 0.25, 0.125, 0, { 0.1, 0.1, 0.1, 1.0 }, { 0.5, 0.5, 1.0, 1.0 } }, + { 0.25, 0.0, 0.375, 0.125, (1 << DL_SIGNAL_ANY), { 0.1, 0.1, 0.1, 1.0 }, { 0.15, 1.0, 0.15, 1.0 } }, + { 0.375, 0.0, 0.5, 0.125, 0, { 0.1, 0.1, 0.1, 1.0 }, { 0.5, 0.5, 1.0, 1.0 } }, + + { 0.0, 0.125, 0.125, 0.25, 0, { 1.0, 1.0, 1.0, 1.0 }, { 1.0, 0.15, 0.15, 1.0 } }, + { 0.125, 0.125, 0.25, 0.25, 0, { 1.0, 1.0, 1.0, 1.0 }, { 0.15, 0.15, 1.0, 1.0 } }, + { 0.25, 0.125, 0.375, 0.25, 0, { 1.0, 1.0, 1.0, 1.0 }, { 0.15, 0.15, 1.0, 1.0 } }, + { 0.375, 0.125, 0.5, 0.25, 0, { 1.0, 1.0, 1.0, 1.0 }, { 1.0, 0.15, 0.15, 1.0 } }, + { 0.5, 0.125, 0.625, 0.25, 0, { 0.1, 0.1, 0.1, 1.0 }, { 0.15, 0.15, 0.15, 1.0 } }, + + { 0.0, 0.25, 0.125, 0.375, (1 << DL_BATTERY), { 0.1, 0.1, 0.1, 1.0 }, { 1.0, 0.15, 0.15, 1.0 } }, + { 0.125, 0.25, 0.25, 0.375, (1 << DL_OILWARN), { 0.1, 0.1, 0.1, 1.0 }, { 1.0, 0.15, 0.15, 1.0 } }, + { 0.25, 0.25, 0.375, 0.375, (1 << DL_TC), { 0.1, 0.1, 0.1, 1.0 }, { 1.0, 0.5, 0.0, 1.0 } }, + { 0.375, 0.25, 0.5, 0.375, (1 << DL_HANDBRAKE), { 0.1, 0.1, 0.1, 1.0 }, { 0.15, 0.15, 1.0, 1.0 } }, + { 0.5, 0.25, 0.625, 0.375, 0, { 0.1, 0.1, 0.1, 1.0 }, { 0.25, 0.25, 1.0, 1.0 } }, + { 0.625, 0.25, 0.75, 0.375, (1 << DL_ABS), { 0.1, 0.1, 0.1, 1.0 }, { 1.0, 0.5, 0.0, 1.0 } }, + + { 0.0, 0.375, 0.125, 0.5, (1 << DL_SIGNAL_L), { 0.1, 0.1, 0.1, 1.0 }, { 0.15, 1.0, 0.15, 1.0 } }, + { 0.125, 0.375, 0.25, 0.5, (1 << DL_SIGNAL_R), { 0.1, 0.1, 0.1, 1.0 }, { 0.15, 1.0, 0.15, 1.0 } }, + { 0.25, 0.375, 0.375, 0.5, (1 << DL_PITSPEED), { 0.1, 0.1, 0.1, 1.0 }, { 0.15, 1.0, 0.15, 1.0 } }, + { 0.375, 0.375, 0.5, 0.5, (1 << DL_SHIFT), { 0.1, 0.1, 0.1, 1.0 }, { 1.0, 0.15, 0.15, 1.0 } }, + + { 0.5, 0.0, 0.625, 0.125, 0, { 1.0, 0.5, 0.0, 1.0 }, { 1.0, 0.15, 0.15, 1.0 } }, + +}; + +void drawSymbol(int symbol, int on, int map) +{ + if (map) symbol = dl_to_sympos[symbol]; + + struct sympos *s = &s_sympos[symbol]; + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, s_symbols[0]); + if (on) { + glColor4f(s->on.r, s->on.g, s->on.b, s->on.a); + } else { + glColor4f(s->off.r, s->off.g, s->off.b, s->off.a); + } + glBegin(GL_QUADS); + glTexCoord2f(s->a, (on ? 0.5 : 1.0) - s->d); + glVertex3f(-0.5, -0.5, 0.0); + glTexCoord2f(s->c, (on ? 0.5 : 1.0) - s->d); + glVertex3f(0.5, -0.5, 0.0); + glTexCoord2f(s->c, (on ? 0.5 : 1.0) - s->b); + glVertex3f(0.5, 0.5, 0.0); + glTexCoord2f(s->a, (on ? 0.5 : 1.0) - s->b); + glVertex3f(-0.5, 0.5, 0.0); + glEnd(); + glDisable(GL_TEXTURE_2D); +} + +void drawSymbol2(int symbol, int on, int map, float x, float y, float xs, float ys) +{ + glPushMatrix(); + glTranslatef(x, y, 0.0f); + glScalef(xs, ys, 1.0f); + drawSymbol(symbol, on, map); + glPopMatrix(); +} + +int g_outgauge_port; +int g_insim_port; + +int main(int argc, char **argv) +{ + g_fade = 0; + + int width; + int height; + int fullscreen; + int volume; + int rpmleft; + int dofade; + char *insim_host; + + float warn_fuel1; + float warn_fuel2; + + config_init("lfsdash.txt"); + if (!config_get_int("port", &g_outgauge_port)) g_outgauge_port = 4000; + if (!config_get_int("fullscreen", &fullscreen)) fullscreen = 0; + if (!config_get_int("width", &width)) width = 1024; + if (!config_get_int("height", &height)) height = 600; + if (!config_get_int("volume", &volume)) volume = 100; + if (!config_get_int("rpmleft", &rpmleft)) rpmleft = 0; + if (!config_get_int("fade", &dofade)) dofade = 1; + if (!config_get_string("insim_host", &insim_host)) insim_host = strdup("localhost"); + if (!config_get_int("insim_port", &g_insim_port)) g_insim_port = 29999; + if (!config_get_float("warn_fuel1", &warn_fuel1)) warn_fuel1 = 0.05f; + if (!config_get_float("warn_fuel2", &warn_fuel2)) warn_fuel2 = 0.01f; + + network_worker_init(); + + socket_init(); + outgauge_init(g_outgauge_port); + insim_init(insim_host, g_insim_port); + init_cars(); + + glfwInit(); + glfwOpenWindowHint(GLFW_FSAA_SAMPLES, 1); + if (!glfwOpenWindow(width, height, 0, 0, 0, 0, 0, 0, fullscreen ? GLFW_FULLSCREEN : GLFW_WINDOW)) + { + glfwTerminate(); + return 0; + } + + glfwSetWindowTitle("GL LFS Dashboard"); + + audio_init(); + + struct gauge speed; + speed.radius = 200.0; + speed.anglemin = (180 + 45) * M_PI / 180.0; + speed.anglemax = (180 + 315) * M_PI / 180.0; + speed.rangemin = 0; + speed.rangemax = 160.0; + speed.majorstep = 10.0; + speed.majorwidth = 3.0; + speed.majorstart = 0.9; + speed.majorend = 1.0; + speed.major.r = 0.15; + speed.major.g = 0.15; + speed.major.b = 1.0; + speed.major.a = 1.0; + speed.minorstep = 2.0; + speed.minorwidth = 1.0; + speed.minorstart = 0.95; + speed.minorend = 1.0; + speed.minor.r = 0.15; + speed.minor.g = 0.15; + speed.minor.b = 1.0; + speed.minor.a = 1.0; + speed.dial.r = 1.0; + speed.dial.g = 1.0; + speed.dial.b = 1.0; + speed.dial.a = 0.8; + + struct gauge speed2; + speed2.radius = 200.0; + speed2.anglemin = (180 + 45) * M_PI / 180.0; + speed2.anglemax = (180 + 315) * M_PI / 180.0; + speed2.rangemin = 0; + speed2.rangemax = 160.0; + speed2.majorstep = 10.0; + speed2.majorwidth = 1.5; + speed2.majorstart = 0.92; + speed2.majorend = 1.0; + speed2.major.r = 0.15; + speed2.major.g = 0.15; + speed2.major.b = 1.0; + speed2.major.a = 0.5; + speed2.minorstep = 2.0; + speed2.minorwidth = 1.0; + speed2.minorstart = 0.95; + speed2.minorend = 1.0; + speed2.minor.r = 0.15; + speed2.minor.g = 0.15; + speed2.minor.b = 1.0; + speed2.minor.a = 0.5; + speed2.dial.r = 1.0; + speed2.dial.g = 1.0; + speed2.dial.b = 1.0; + speed2.dial.a = 0.0; + + struct gauge rpm; + rpm.radius = 200.0; + rpm.anglemin = (180 + 45) * M_PI / 180.0; + rpm.anglemax = (180 + 315) * M_PI / 180.0; + rpm.rangemin = 0; + rpm.rangemax = 10000.0; + rpm.majorstep = 1000.0; + rpm.majorwidth = 3.0; + rpm.majorstart = 0.9; + rpm.majorend = 1.0; + rpm.major.r = 0.15; + rpm.major.g = 0.15; + rpm.major.b = 1.0; + rpm.major.a = 1.0; + rpm.minorstep = 200.0; + rpm.minorwidth = 1.0; + rpm.minorstart = 0.95; + rpm.minorend = 1.0; + rpm.minor.r = 0.15; + rpm.minor.g = 0.15; + rpm.minor.b = 1.0; + rpm.minor.a = 1.0; + rpm.dial.r = 1.0; + rpm.dial.g = 1.0; + rpm.dial.b = 1.0; + rpm.dial.a = 0.8; + + struct gauge mpg; + mpg.radius = 160.0; + mpg.anglemin = 120 * M_PI / 180.0; + mpg.anglemax = 60 * M_PI / 180.0; + mpg.rangemin = 0; + mpg.rangemax = 100.0; + mpg.majorstep = 20.0; + mpg.majorwidth = 3.0; + mpg.majorstart = 0.95; + mpg.majorend = 1.0; + mpg.major.r = 0.15; + mpg.major.g = 0.15; + mpg.major.b = 1.0; + mpg.major.a = 1.0; + mpg.minorstep = 10.0; + mpg.minorwidth = 1.0; + mpg.minorstart = 0.95; + mpg.minorend = 1.0; + mpg.minor.r = 0.15; + mpg.minor.g = 0.15; + mpg.minor.b = 1.0; + mpg.minor.a = 1.0; + mpg.dial.r = 1.0; + mpg.dial.g = 1.0; + mpg.dial.b = 1.0; + mpg.dial.a = 0.8; + + struct gauge boost; + boost.radius = 160.0; + boost.anglemin = 240 * M_PI / 180.0; + boost.anglemax = 300 * M_PI / 180.0; + boost.rangemin = 0.0; + boost.rangemax = 2.0; + boost.majorstep = 0.5; + boost.majorwidth = 2.0; + boost.majorstart = 0.95; + boost.majorend = 1.0; + boost.major.r = 0.15; + boost.major.g = 0.15; + boost.major.b = 1.0; + boost.major.a = 1.0; + boost.minorstep = 0.1; + boost.minorwidth = 1.0; + boost.minorstart = 0.95; + boost.minorend = 1.0; + boost.minor.r = 0.15; + boost.minor.g = 0.15; + boost.minor.b = 1.0; + boost.minor.a = 1.0; + boost.dial.r = 1.0; + boost.dial.g = 1.0; + boost.dial.b = 1.0; + boost.dial.a = 0.8; + + struct gauge fuel; + fuel.radius = 160.0; + fuel.anglemin = 240 * M_PI / 180.0; + fuel.anglemax = 300 * M_PI / 180.0; + fuel.rangemin = 0.0; + fuel.rangemax = 1.0; + fuel.majorstep = 0.5; + fuel.majorwidth = 2.0; + fuel.majorstart = 0.95; + fuel.majorend = 1.0; + fuel.major.r = 0.15; + fuel.major.g = 0.15; + fuel.major.b = 1.0; + fuel.major.a = 1.0; + fuel.minorstep = 0.1; + fuel.minorwidth = 1.0; + fuel.minorstart = 0.95; + fuel.minorend = 1.0; + fuel.minor.r = 0.15; + fuel.minor.g = 0.15; + fuel.minor.b = 1.0; + fuel.minor.a = 1.0; + fuel.dial.r = 1.0; + fuel.dial.g = 1.0; + fuel.dial.b = 1.0; + fuel.dial.a = 0.8; + + struct gauge oil; + oil.radius = 160.0; + oil.anglemin = 120 * M_PI / 180.0; + oil.anglemax = 60 * M_PI / 180.0; + oil.rangemin = 0.0; + oil.rangemax = 2.0; + oil.majorstep = 0.5; + oil.majorwidth = 2.0; + oil.majorstart = 0.95; + oil.majorend = 1.0; + oil.major.r = 0.15; + oil.major.g = 0.15; + oil.major.b = 1.0; + oil.major.a = 1.0; + oil.minorstep = 0.1; + oil.minorwidth = 1.0; + oil.minorstart = 0.95; + oil.minorend = 1.0; + oil.minor.r = 0.15; + oil.minor.g = 0.15; + oil.minor.b = 1.0; + oil.minor.a = 1.0; + oil.dial.r = 1.0; + oil.dial.g = 1.0; + oil.dial.b = 1.0; + oil.dial.a = 0.8; + +/* + struct gauge clock; + clock.radius = 60.0; + clock.anglemin = 0 * M_PI / 180.0; + clock.anglemax = 360 * M_PI / 180.0; + clock.rangemin = 0.0; + clock.rangemax = 12.0; + clock.majorstep = 1.0; + clock.majorwidth = 2.0; + clock.majorstart = 0.9; + clock.majorend = 1.0; + clock.major.r = 0.15; + clock.major.g = 0.15; + clock.major.b = 1.0; + clock.major.a = 1.0; + clock.minorstep = 0.25; + clock.minorwidth = 1.0; + clock.minorstart = 0.95; + clock.minorend = 1.0; + clock.minor.r = 0.15; + clock.minor.g = 0.15; + clock.minor.b = 1.0; + clock.minor.a = 1.0; + clock.dial.r = 1.0; + clock.dial.g = 1.0; + clock.dial.b = 1.0; + clock.dial.a = 0.8; +*/ + char text[40]; + + //float last_fuel = 1.0; + //float consumption = 0.0; + double next = glfwGetTime(); + double last_time = next; + struct car car; + + int running = 1; + + //int pthread_create(pthread_t *thread, const pthread_attr_t *attr, + //void *(*start_routine)(void*), void *arg); + + pthread_t thread; + pthread_create(&thread, NULL, &socket_run, NULL); + + FTGLfont *font = ftglCreateTextureFont("arialbd.ttf");//"Arial.ttf");//"/usr/share/fonts/truetype/msttcorefonts/Arial.ttf"); + //DS_DIGII + if (!font) return 0; + + ftglSetFontFaceSize(font, TEXT_SIZE, TEXT_SIZE); + + glGenTextures(1, s_symbols); + glBindTexture(GL_TEXTURE_2D, s_symbols[0]); + glfwLoadTexture2D("symbols512.tga", GLFW_BUILD_MIPMAPS_BIT); + // Use trilinear interpolation for minification + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + // Use bilinear interpolation for magnification + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR); + // Enable texturing + //glEnable(GL_TEXTURE_2D); + + int s1 = FX_OFF; + int s2 = FX_OFF; + int s3 = FX_OFF; + int s6 = FX_OFF; + + float fade = 0.0f; + float flash = 0.0f; + float binger = 0.0f; + + audio_volume(volume * 0.01); + + int bing = 0; + + int mode = 0; + uint32_t mode_timer = 0; + float hold = 0; + int hold2 = 0; + int ack = 0; + + get_car("UF1", &car); + rpm.rangemax = (int)(car.maxrpm / 1000 + 2) * 1000.0; + + float maxspeed = 0; + float speedtimer = 0; + float zoom = 65536.0; + + while (running) { + + double curtime = glfwGetTime(); + if (curtime >= next) + { + next = curtime + 0.01; + float interval = curtime - last_time; + + flash += interval; + if (flash >= 1) flash = 0; + + if (bing == 2) + { + binger += interval; + if (binger >= 1) { + binger = 0; + if (!g_fade) audio_play(3, FX_BING); + } + } + last_time = curtime; + + if (speedtimer > 0) { + speedtimer -= interval; + if (speedtimer <= 0) + { + speedtimer = 0; + maxspeed = 0; + } + } + + if (g_pressed == 3) { + if (g_outgauge.time - g_shift_time > 1000 && + g_outgauge.time - g_ctrl_time > 1000) + { + //if (g_released == 3) + //{ + g_pressed = 0; + g_released = 0; + audio_play(4, FX_BIP); + mode++; + mode_timer = g_outgauge.time; + //} + } + } + + if (ack == 0) { + if (mode == 1) { + if (g_pressed == 1 && volume > 0) { + volume -= 10; + if (volume < 0) volume = 0; + audio_volume(volume * 0.01); + audio_play(4, FX_BOP); + ack = 1; + } + if (g_pressed == 2 && volume < 100) { + volume += 10; + if (volume > 100) volume = 100; + audio_volume(volume * 0.01); + audio_play(4, FX_BIP); + ack = 1; + } + } + } + + // Acknowledge key release + if (g_released) + { + g_pressed &= ~g_released; + g_released = 0; + mode_timer = g_outgauge.time; + ack = 0; + } + + if (mode && g_outgauge.time - mode_timer > 2000) + { + mode = 0; + } + /* + } + if (g_released == 1) { + volume -= 10; + if (volume < 0) volume = 0; + audio_volume(volume * 0.01); + audio_play(4, FX_BOP); + } + if (g_released == 2) { + volume += 10; + if (volume > 100) volume = 100; + audio_volume(volume * 0.01); + audio_play(4, FX_BIP); + } + if (g_released == 3) { + g_pressed = 0; + g_released = 0; + } + }*/ + } + + glfwGetWindowSize(&width, &height); + height = height > 0 ? height : 1; + glViewport(0, 0, width, height); + glClearColor(0.0, 0.0, 0.0, 0.0); + glClear(GL_COLOR_BUFFER_BIT); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_ALPHA_TEST); + glEnable(GL_LINE_SMOOTH); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, width, 0, height, -100, 100); + + //socket_run(); + + /* Car change */ + if (*g_outgauge.car != '\0' && strcmp(g_outgauge.car, car.tag)) + { + int carnum = get_car(g_outgauge.car, &car); + if (carnum > -1) + { + rpm.rangemax = (int)(car.maxrpm / 1000 + 2) * 1000.0; + } + } + + float speedunit = (g_outgauge.flags & OG_KM) ? 3.6 : 2.23693629; + float distunit = (g_outgauge.flags & OG_KM) ? 0.001 : 0.0006215; + + if (g_outgauge.speed > maxspeed) { + maxspeed = g_outgauge.speed; + speedtimer = 15.0f; + } + + float litres = g_consumption * car.maxfuel * 60 * 60; + float hurr; + if (g_outgauge.flags & OG_KM) + { + hurr = litres / (g_outgauge.speed * speedunit) * 100; + speed.rangemax = (int)(car.maxspeed * 1.609f / 10) * 10 + 10; + speed2.rangemax = speed.rangemax / 1.609f; + } + else + { + float gal = litres * 0.219969157; + hurr = g_outgauge.speed * speedunit / gal; + //printf("mpg %f\n", hurr); + speed.rangemax = car.maxspeed; + speed2.rangemax = speed.rangemax * 1.609f; + } + if (hurr < 0) hurr = 0; + if (hurr > 100) hurr = 100; + + if (speed.rangemax < 150) { + speed.majorstep = 10; + speed.minorstep = 2; + } else { + speed.majorstep = 20; + speed.minorstep = 10; + } + if (speed2.rangemax < 150) { + speed2.majorstep = 10; + speed2.minorstep = 2; + } else { + speed2.majorstep = 20; + speed2.minorstep = 10; + } + + float gsize = width * 0.20; + if (gsize > height * 0.4) gsize = height * 0.4; + speed.radius = gsize * 0.9; + speed2.radius = speed.radius * 0.6; + rpm.radius = speed.radius; + + float msize = gsize * 0.2; + float symsize = gsize * 0.22; + + float sgsize = height * 0.25; + fuel.radius = sgsize * 0.8; + boost.radius = sgsize * 0.8; + mpg.radius = sgsize * 0.8; + oil.radius = sgsize * 0.8; + + float lsize = sgsize * 0.16; + + glPushMatrix(); + if (rpmleft) { + glTranslatef(width * 0.5 + gsize * 1.5, height - gsize, 0); + } else { + glTranslatef(width * 0.5 - gsize * 1.5, height - gsize, 0); + } + glColor4f(1.0, 1.0, 1.0, 1.0); + snprintf(text, sizeof text, "%d", (int)(g_outgauge.speed * speedunit)); + drawText(text, font, 20, -speed.radius * 0.8, msize, msize, TA_RIGHT, TA_BOTTOM); + drawText((g_outgauge.flags & OG_KM) ? "km/h" : "mph", font, 26, -speed.radius * 0.8, msize * 0.3, msize * 0.4, TA_LEFT, TA_BOTTOM); + if (g_outgauge.dashlights & (1 << DL_PITSPEED)) { + drawSymbol2(DL_PITSPEED, g_outgauge.showlights & (1 << DL_PITSPEED), 1, 0, gsize * -0.4, symsize * 1.3, symsize * 1.3); + } + if (maxspeed > 0.001f) + { + glPushMatrix(); + glScalef(speed.radius, speed.radius, 1.0); + drawDial(&speed, maxspeed * speedunit, GT_LINE); + glPopMatrix(); + if (speedtimer < 13) + { + snprintf(text, sizeof text, "%0.1f%s", maxspeed * speedunit, (g_outgauge.flags & OG_KM) ? "km/h" : "mph"); + drawText(text, font, 0, -speed.radius * 0.6, msize, msize, TA_CENTRE, TA_BOTTOM); + } + } + draw_gauge(&speed2, 0, -1, font, GT_NONE); + draw_gauge(&speed, g_outgauge.speed * speedunit, -1, font, GT_NEEDLE); + glPopMatrix(); + + glPushMatrix(); + if (rpmleft) { + glTranslatef(width * 0.5 - gsize * 1.5, height - gsize, 0); + } else { + glTranslatef(width * 0.5 + gsize * 1.5, height - gsize, 0); + } + glColor4f(1.0, 1.0, 1.0, 1.0); + snprintf(text, sizeof text, "%d", (int)(g_outgauge.rpm)); + drawText(text, font, 40, -rpm.radius * 0.8, msize, msize, TA_RIGHT, TA_BOTTOM); + drawText("rpm", font, 46, -rpm.radius * 0.8, msize * 0.3, msize * 0.4, TA_LEFT, TA_BOTTOM); + if (g_outgauge.dashlights & (1 << DL_SHIFT)) { + drawSymbol2(DL_SHIFT, g_outgauge.showlights & (1 << DL_SHIFT), 1, 0, gsize * -0.4, symsize * 1.3, symsize * 1.3); + } + draw_gauge(&rpm, g_outgauge.rpm, car.maxrpm, font, GT_NEEDLE); + glPopMatrix(); + + if (g_owncar == g_outgauge.playerid) { + glPushMatrix(); + glTranslatef(width * 0.5 - gsize * 1.5, fuel.radius * 0.75, 0); + drawSymbol2(4, 0, 0, fuel.radius * -0.65, 0, lsize, lsize); + if (g_outgauge.fuel <= warn_fuel2) { + if (flash < 0.5) { + drawSymbol2(19, 1, 0, -fuel.radius * 0.6, -fuel.radius * 0.3, lsize, lsize); + } + } + else if (g_outgauge.fuel <= warn_fuel1) { + drawSymbol2(19, 0, 0, -fuel.radius * 0.6, -fuel.radius * 0.3, lsize * 0.6, lsize * 0.6); + } + draw_gauge(&fuel, g_outgauge.fuel, -1, font, GT_BAR); + + drawSymbol2(5, 0, 0, mpg.radius * 0.65, 0, lsize, lsize); + draw_gauge(&mpg, hurr, -1, font, GT_BAR); + + float tcsize = sgsize * 0.08; + + glColor4f(1.0, 1.0, 1.0, 1.0); + drawText("Trip Computer", font, 0, tcsize * 4.8, tcsize, tcsize, TA_CENTRE, TA_BOTTOM); + //snprintf(text, sizeof text, "dist %0.1f time %0.1f fuel %0.1f", s_cars[carnum].dist * distunit, s_cars[carnum].time, s_cars[carnum].cons); + snprintf(text, sizeof text, "Rem: %0.1f%%", g_outgauge.fuel * 100); + drawText(text, font, -fuel.radius * 0.3, tcsize * 3.6, tcsize, tcsize, TA_LEFT, TA_BOTTOM); + snprintf(text, sizeof text, "Eco: %0.1f %s", hurr, (g_outgauge.flags & OG_KM) ? "l/100km" : "mpg"); + drawText(text, font, -fuel.radius * 0.3, tcsize * 2.4, tcsize, tcsize, TA_LEFT, TA_BOTTOM); + + float dist = litres / (g_outgauge.speed * speedunit) * (g_outgauge.fuel * car.maxfuel) * distunit; + snprintf(text, sizeof text, "Dist: %0.1f %s", dist, (g_outgauge.flags & OG_KM) ? "km" : "miles"); + drawText(text, font, -fuel.radius * 0.3, tcsize * 1.2, tcsize, tcsize, TA_LEFT, TA_BOTTOM); + + glPopMatrix(); + } + + glPushMatrix(); + glTranslatef(width * 0.5 + gsize * 1.5, oil.radius * 0.75, 0); + + if (g_outgauge.flags & OG_TURBO) + { + if (g_outgauge.flags & OG_BAR) { + boost.majorstep = 0.4; + boost.minorstep = 0.1; + } else { + boost.majorstep = 2.0; + boost.minorstep = 1.0; + } + float mult = (g_outgauge.flags & OG_BAR) ? 1.0 : 14.5037738; + boost.rangemax = (int)(car.maxboost * mult / boost.majorstep + 1) * boost.majorstep; + + drawSymbol2(6, 0, 0, boost.radius * -0.65, 0, lsize, lsize); + draw_gauge(&boost, g_outgauge.turbo * mult, -1, font, GT_BAR); + } + + //drawSymbol2(7, 0, 0, oil.radius * 0.65, 0, lsize, lsize); + //draw_gauge(&oil, g_outgauge.oiltemp, -1, font, GT_BAR); + glPopMatrix(); + + //glPushMatrix(); + //glTranslatef(width * 0.5 + gsize * 1.5, boost.radius * 0.75, 0); + + time_t rawtime; + time(&rawtime); + struct tm *timeinfo = localtime(&rawtime); + //draw_gauge(&clock, (timeinfo->tm_hour % 12) + timeinfo->tm_min / 60.0, -1, font, GT_NEEDLE); + //glPopMatrix(); + + //printf("%f %f\n", g_outgauge.oilpressure, g_outgauge.oiltemp); + + float symspace = symsize * -1.2; + + /* + glPushMatrix(); + glTranslatef(width * 0.5 + symspace, height - gsize * 0.25 + symspace * 6, 0.0f); + input.radius = symsize * 0.5; + input.dial.r = 0; input.dial.g = 0; input.dial.b = 1; + draw_gauge(&input, g_outgauge.clutch, -1, font, GT_BAR); + glTranslatef(-symspace, 0, 0); + input.dial.r = 1; input.dial.g = 0; input.dial.b = 0; + draw_gauge(&input, g_outgauge.brake, -1, font, GT_BAR); + glTranslatef(-symspace, 0, 0); + input.dial.r = 0; input.dial.g = 1; input.dial.b = 0; + draw_gauge(&input, g_outgauge.throttle, -1, font, GT_BAR); + glPopMatrix(); + */ + + glPushMatrix(); + glTranslatef(width * 0.5, height - gsize * 0.25, 0.0f); + + if (g_outgauge.dashlights & (1 << DL_SIGNAL_ANY)) + { + drawSymbol2(DL_SIGNAL_ANY, g_outgauge.showlights & (1 << DL_SIGNAL_ANY), 1, 0, 0, symsize, symsize); + } else { + if (g_outgauge.dashlights & (1 << DL_SIGNAL_L)) { + drawSymbol2(DL_SIGNAL_L, g_outgauge.showlights & (1 << DL_SIGNAL_L), 1, -symsize * 0.75, 0, symsize, symsize); + } + if (g_outgauge.dashlights & (1 << DL_SIGNAL_R)) { + drawSymbol2(DL_SIGNAL_R, g_outgauge.showlights & (1 << DL_SIGNAL_R), 1, symsize * 0.75, 0, symsize, symsize); + } + } + if (g_outgauge.dashlights & (1 << DL_FULLBEAM)) { + drawSymbol2(DL_FULLBEAM, g_outgauge.showlights & (1 << DL_FULLBEAM), 1, 0, symspace, symsize, symsize); + } + if (g_outgauge.dashlights & (1 << DL_HANDBRAKE)) { + drawSymbol2(DL_HANDBRAKE, g_outgauge.showlights & (1 << DL_HANDBRAKE), 1, 0, symspace * 2, symsize, symsize); + } + if (g_outgauge.dashlights & (1 << DL_ABS)) { + drawSymbol2(DL_ABS, g_outgauge.showlights & (1 << DL_ABS), 1, 0, symspace * 3, symsize, symsize); + } + if (g_outgauge.dashlights & (1 << DL_TC)) { + drawSymbol2(DL_TC, g_outgauge.showlights & (1 << DL_TC), 1, 0, symspace * 4, symsize, symsize); + } + if (g_outgauge.dashlights & (1 << DL_OILWARN)) { + if (g_outgauge.dashlights & (1 << DL_BATTERY)) { + drawSymbol2(DL_BATTERY, g_outgauge.showlights & (1 << DL_BATTERY), 1, -symsize * 0.75, symspace * 5, symsize, symsize); + } + drawSymbol2(DL_OILWARN, g_outgauge.showlights & (1 << DL_OILWARN), 1, symsize * 0.75, symspace * 5, symsize, symsize); + } else { + if (g_outgauge.dashlights & (1 << DL_BATTERY)) { + drawSymbol2(DL_BATTERY, g_outgauge.showlights & (1 << DL_BATTERY), 1, 0, symspace * 5, symsize, symsize); + } + } + glPopMatrix(); + + glPushMatrix(); + glTranslatef(width, 0, 0); + glScalef(-height * 0.075, height * 0.075, 1.0); + glColor4f(0.15, 1.0, 0.15, 0.8); + glBegin(GL_TRIANGLE_STRIP); + glVertex3f(0.0, 0.0, 0.0); + glVertex3f(0.25, 0.0, 0.0); + glVertex3f(0.0, g_outgauge.throttle, 0.0); + glVertex3f(0.25, g_outgauge.throttle, 0.0); + glEnd(); + glColor4f(1.0, 0.15, 0.15, 0.8); + glBegin(GL_TRIANGLE_STRIP); + glVertex3f(0.30, 0.0, 0.0); + glVertex3f(0.55, 0.0, 0.0); + glVertex3f(0.30, g_outgauge.brake, 0.0); + glVertex3f(0.55, g_outgauge.brake, 0.0); + glEnd(); + glColor4f(0.15, 0.15, 1.0, 0.8); + glBegin(GL_TRIANGLE_STRIP); + glVertex3f(0.60, 0.0, 0.0); + glVertex3f(0.85, 0.0, 0.0); + glVertex3f(0.60, g_outgauge.clutch, 0.0); + glVertex3f(0.85, g_outgauge.clutch, 0.0); + glEnd(); + glPopMatrix(); + + glPushMatrix(); + glColor4f(0.15, 0.15, 1.0, 1.0); + glTranslatef(width * 0.5, 0, 0.0f); + if (g_outgauge.gear == 0) { + snprintf(text, sizeof text, "R"); + } else if (g_outgauge.gear == 1) { + snprintf(text, sizeof text, "N"); + } else { + snprintf(text, sizeof text, "%d", g_outgauge.gear - 1); + } + drawText(text, font, 0, sgsize * 0.7, sgsize * 0.7, sgsize * 0.7, TA_CENTRE, TA_BOTTOM); + snprintf(text, sizeof text, "%s", s_carinfo[g_outgauge.playerid].cname); + drawText(text, font, 0, sgsize * 0.4, sgsize * 0.2, sgsize * 0.2, TA_CENTRE, TA_BOTTOM); + snprintf(text, sizeof text, "%s", s_conninfo[s_carinfo[g_outgauge.playerid].ucid].uname); + drawText(text, font, 0, sgsize * 0.2, sgsize * 0.15, sgsize * 0.15, TA_CENTRE, TA_BOTTOM); + //snprintf(text, sizeof text, "dist %0.1f time %0.1f fuel %0.1f", s_cars[carnum].dist * distunit, s_cars[carnum].time, s_cars[carnum].cons); + //snprintf(text, sizeof text, "p %d r %d; %d %d, %u", g_pressed, g_released, volume, mode, mode_timer); + //drawText(text, font, 0, sgsize * 1.2, sgsize * 0.2, sgsize * 0.2, TA_CENTRE, TA_CENTRE); + //snprintf(text, sizeof text, "s %u c %u; %u", g_shift_time, g_ctrl_time, g_outgauge.time); + //drawText(text, font, 0, sgsize * 1.5, sgsize * 0.2, sgsize * 0.2, TA_CENTRE, TA_CENTRE); + snprintf(text, sizeof text, "%02d:%02d", timeinfo->tm_hour, timeinfo->tm_min); + drawText(text, font, 0, sgsize * 1.3, sgsize * 0.2, sgsize * 0.2, TA_CENTRE, TA_BOTTOM); + glPopMatrix(); +/* + int ox = s_carinfo[g_outgauge.playerid].x; + int oy = s_carinfo[g_outgauge.playerid].y; + int oz = s_carinfo[g_outgauge.playerid].z; + int head = s_carinfo[g_outgauge.playerid].heading; + int speed = s_carinfo[g_outgauge.playerid].speed; + + glPushMatrix(); + glTranslatef(width / 2, height / 2, 0); + int i; + for (i = 0; i < 256; i++) + { + struct carinfo *ci = s_carinfo + i; + if (!ci->active) continue; + + float dx = (ci->x - ox) / zoom; + float dy = (ci->y - oy) / zoom; + + //float ddx = (ci->x - ox) / 65536.0; + //float ddy = (ci->y - oy) / 65536.0; + //ci->dist = sqrtf(ddx * ddx + ddy * ddy); + +// if (ci->dist >= width / 2 && ci->dist >= height / 2) continue; + + const struct conninfo *pi = s_conninfo + ci->ucid; + int idx = (ci->x - ox) >> 16; + int idy = (ci->y - oy) >> 16; + int dist = sqrt(idx * idx + idy * idy); + + snprintf(text, sizeof text, "%s - %d (%0.1f %s)", pi->uname, dist, (ci->speed - speed) / 327.68f * speedunit, (g_outgauge.flags & OG_KM) ? "km/h" : "mph"); + //printf("%d %s %d %d\n", i, text, head / 182, ci->heading / 182); + + glPushMatrix(); + glRotatef(-head / 182.0444f, 0.0, 0.0, 1.0); + glTranslatef(dx, dy, 0); + if (ci->hist > 0) { + glBegin(GL_LINE_STRIP); + int j; + for (j = CARPATHSIZE; j > 0; j--) + { + int d = (j + ci->hist - 1) % CARPATHSIZE; + if (ci->hist_x[d] == 0 && ci->hist_y[d] == 0) continue; + glColor4f(ci->hist_s[d] / 32768.0f, 1.0 - ci->hist_s[d] / 32768.0f, 0.0, 0.9); + glVertex3f((ci->hist_x[d] - ci->x) / zoom, (ci->hist_y[d] - ci->y) / zoom, 0.0); + } + glEnd(); + } + if (pi->state == 1) { + glColor4f(1.0, 0.0, 0.0, 0.5); + } else if (pi->state == 2) { + glColor4f(0.0, 1.0, 1.0, 0.5); + } else { + glColor4f(2.0, 1.0, 1.0, 0.5); + } + glRotatef(head / 182.0444f, 0.0, 0.0, 1.0); + drawText(text, font, 5, 3, 10, 10, TA_LEFT, TA_TOP); + glRotatef(-(head - ci->heading) / 182.0444, 0.0, 0.0, 1.0); + glBegin(GL_TRIANGLES); + glVertex3f(0, 5, 0); + glVertex3f(2.5, -5, 0); + glVertex3f(-2.5, -5, 0); + glEnd(); + + glPopMatrix(); + } + glPopMatrix(); + */ + + int os1 = (g_outgauge.showlights & (1 << DL_SIGNAL_L)) ? FX_ON : FX_OFF; + int os2 = (g_outgauge.showlights & (1 << DL_SIGNAL_R)) ? FX_ON : FX_OFF; + int os3 = (g_outgauge.showlights & (1 << DL_SIGNAL_ANY)) ? FX_ON : FX_OFF; + int os6 = (g_outgauge.showlights & (1 << DL_PITSPEED)) ? FX_ON : FX_OFF; + + if (s1 != os1) { + s1 = os1; + if (!g_fade) audio_play(0, s1); + } + if (s2 != os2) { + s2 = os2; + if (!g_fade) audio_play(1, s2); + } + if (s3 != os3) { + s3 = os3; + if (!g_fade) audio_play(2, s3); + } + if (s6 != os6) { + s6 = os6; + if (!g_fade) audio_play(5, s6); + } + + if (g_outgauge.fuel > warn_fuel1 || g_outgauge.rpm == 0) + { + bing = 0; + } + else if (g_outgauge.fuel <= warn_fuel2) + { + bing = 2; + } + else if (bing != 1 && g_outgauge.fuel <= warn_fuel1) + { + bing = 1; + if (!g_fade) audio_play(3, FX_BING); + } + + if (g_fade && fade < 0.8) { + fade = fade + 0.01; + } else if (!g_fade && fade > 0.0) { + fade = fade - 0.01; + } + + //glPopMatrix(); + + if (dofade && fade > 0) { + glColor4f(0.0, 0.0, 0.0, fade); + glBegin(GL_QUADS); + glVertex3f(0.0, 0.0, 0.0); + glVertex3f(width, 0.0, 0.0); + glVertex3f(width, height, 0.0); + glVertex3f(0.0, height, 0.0); + glEnd(); + } + + glfwSwapBuffers(); + +// printf("%f\n", g_outgauge.speed * 2.23693629); + + running = !glfwGetKey(GLFW_KEY_ESC) && glfwGetWindowParam( GLFW_OPENED) && !glfwGetMouseButton(1); +/* + int wheel = glfwGetMouseWheel(); + if (wheel >= 1) { + zoom = 65536.0 * wheel; + } else { + zoom = 65536 / (-wheel + 2); + } +*/ + //printf("%u %u %u\n", g_outgauge.flags, g_outgauge.dashlights, g_outgauge.showlights); + + usleep(100); + } + + s_running = 0; + + audio_deinit(); + + glfwTerminate(); + + network_worker_deinit(); + + config_set_int("port", g_outgauge_port); + config_set_int("fullscreen", fullscreen); + config_set_int("width", width); + config_set_int("height", height); + config_set_int("volume", volume); + config_set_int("rpmleft", rpmleft); + config_set_int("fade", dofade); + config_set_string("insim_host", insim_host); + config_set_int("insim_port", g_insim_port); + config_set_float("warn_fuel1", warn_fuel1); + config_set_float("warn_fuel2", warn_fuel2); + config_deinit(); + + return 0; +} + diff --git a/list.h b/list.h new file mode 100644 index 0000000..a9b4dd7 --- /dev/null +++ b/list.h @@ -0,0 +1,72 @@ +#ifndef LIST_H +#define LIST_H + +#include + +#define LIST(X, T, compare_func) \ +struct X ## _list_t \ +{ \ + size_t used; \ + size_t size; \ + T *items; \ +}; \ +\ +static inline void X ## _list_init(struct X ## _list_t *list) \ +{ \ + list->used = 0; \ + list->size = 0; \ + list->items = NULL; \ +} \ +\ +static inline void X ## _list_free(struct X ## _list_t *list) \ +{ \ + free(list->items); \ +} \ +\ +static inline void X ## _list_add(struct X ## _list_t *list, T item) \ +{ \ + if (list->used >= list->size) \ + { \ + list->size += 64U; \ + T *new_items = realloc(list->items, sizeof *list->items * list->size); \ + if (new_items == NULL) \ + { \ + fprintf(stderr, "Reallocating %s list to %u items (%u bytes) failed\n", #X, (unsigned)list->size, (unsigned)(sizeof *list->items * list->size)); \ + list->size -= 64U; \ + return; \ + } \ + list->items = new_items; \ + } \ + list->items[list->used++] = item; \ +} \ +\ +static inline void X ## _list_del_item(struct X ## _list_t *list, T item) \ +{ \ + size_t i; \ + for (i = 0; i < list->used; i++) \ + { \ + if (compare_func(&list->items[i], &item)) { \ + list->items[i] = list->items[--list->used]; \ + return; \ + } \ + } \ +} \ +\ +static inline void X ## _list_del_index(struct X ## _list_t *list, size_t index) \ +{ \ + list->items[index] = list->items[--list->used]; \ +} \ +\ +static inline int X ## _list_contains(struct X ## _list_t *list, T item) \ +{ \ + size_t i; \ + for (i = 0; i < list->used; i++) \ + { \ + if (compare_func(&list->items[i], &item)) { \ + return 1; \ + } \ + } \ + return 0; \ +} + +#endif /* LIST_H */ diff --git a/network_worker.c b/network_worker.c new file mode 100644 index 0000000..7292046 --- /dev/null +++ b/network_worker.c @@ -0,0 +1,194 @@ +#define _WIN32_WINNT 0x0501 + +#include +#include +#include +#include +#ifdef WIN32 +#include +#include +#else +#include +#include +#include +#include +#endif +#include +//#include "network.h" +#include "network_worker.h" +#include "socket.h" +#include "worker.h" + +static struct worker s_network_worker; + +struct network_job +{ + char *host; + int port; + network_callback callback; + void *data; +}; + +int resolve(const char *hostname, int port, struct sockaddr_in *addr) +{ + struct addrinfo *ai; + struct addrinfo hints; + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_INET; + #ifndef WIN32 + hints.ai_flags = AI_ADDRCONFIG; + #endif + hints.ai_socktype = SOCK_STREAM; + + char port_name[6]; + snprintf(port_name, sizeof port_name, "%u", port); + + int e = getaddrinfo(hostname, port_name, &hints, &ai); + + if (e != 0) + { + fprintf(stderr, "getaddrinfo: %s\n", strerror(errno)); + return 0; + } + + struct addrinfo *runp; + for (runp = ai; runp != NULL; runp = runp->ai_next) + { + struct sockaddr_in *ai_addr = (struct sockaddr_in *)runp->ai_addr; + + /* Take the first address */ + *addr = *ai_addr; + break; + } + + freeaddrinfo(ai); + + return 1; +} + +static void network_worker(void *arg) +{ + struct network_job *job = arg; + + if (job->callback != NULL) + { + struct sockaddr_in addr; + if (!resolve(job->host, job->port, &addr)) + { + fprintf(stderr, "Unable to resolve %s:%d\n", job->host, job->port); + if (job->callback != NULL) + { + job->callback(-1, job->data); + } + } + else if (job->callback != NULL) + { + int fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) + { + #ifdef WIN32 + fprintf(stderr, "socket: %d\n", WSAGetLastError()); + #else + fprintf(stderr, "socket: %s\n", strerror(errno)); + #endif + } + else + { + socket_set_nonblock(fd); + socket_set_nodelay(fd); + if (connect(fd, (struct sockaddr *)&addr, sizeof addr) < 0) + { + #ifdef WIN32 + if (WSAGetLastError() != WSAEINPROGRESS && WSAGetLastError() != WSAEWOULDBLOCK) + { + fprintf(stderr, "connect: %d\n", WSAGetLastError()); + fd = -1; + } + #else + if (errno != EINPROGRESS && errno != EWOULDBLOCK) + { + fprintf(stderr, "connect: %s\n", strerror(errno)); + fd = -1; + } + #endif + } + } + job->callback(fd, job->data); + } + } + + free(job->host); + free(job); +} + +void network_worker_init(void) +{ + worker_init(&s_network_worker, "network", 60000, 1, network_worker); +} + +void network_worker_deinit(void) +{ + worker_deinit(&s_network_worker); +} + +void *network_connect(const char *host, int port, network_callback callback, void *data) +{ + struct network_job *job = malloc(sizeof *job); + job->host = strdup(host); + job->port = port; + job->callback = callback; + job->data = data; + + worker_queue(&s_network_worker, job); + return job; +} + +int network_listen(int port, int tcp) +{ + int fd; + +#ifdef USE_IPV6 + fd = socket(AF_INET6, tcp ? SOCK_STREAM : SOCK_DGRAM, 0); +#else + fd = socket(AF_INET, tcp ? SOCK_STREAM : SOCK_DGRAM, 0); +#endif + + if (fd < 0) + { +#ifdef WIN32 + fprintf(stderr, "socket: %d\n", WSAGetLastError()); +#else + fprintf(stderr, "socket: %s\n", strerror(errno)); +#endif + return -1; + } + +#ifdef USE_IPV6 + struct sockaddr_in6 serv_addr; + serv_addr.sin6_family = AF_INET6; + serv_addr.sin6_addr = in6addr_any; + serv_addr.sin6_port = htons(port); +#else + struct sockaddr_in serv_addr; + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = INADDR_ANY; + serv_addr.sin_port = htons(port); +#endif + + int on = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof on) == -1) + { + fprintf(stderr, "setsockopt: Could not set SO_REUSEADDR: %s\n", strerror(errno)); + } + + if (bind(fd, (struct sockaddr *)&serv_addr, sizeof serv_addr) < 0) + { + fprintf(stderr, "bind: %s\n", strerror(errno)); + return -1; + } + + socket_set_nonblock(fd); + if (tcp) socket_set_nodelay(fd); + + return fd; +} diff --git a/network_worker.h b/network_worker.h new file mode 100644 index 0000000..46b7d02 --- /dev/null +++ b/network_worker.h @@ -0,0 +1,12 @@ +#ifndef NETWORK_WORKER_H +#define NETWORK_WORKER_H + +typedef void (*network_callback)(int fd, void *data); + +void network_worker_init(void); +void network_worker_deinit(void); + +void *network_connect(const char *host, int port, network_callback callback, void *data); +int network_listen(int port, int tcp); + +#endif /* NETWORK_WORKER_H */ diff --git a/off.wav b/off.wav new file mode 100644 index 0000000000000000000000000000000000000000..370cf8afaa889412368581ea02640b8110884381 GIT binary patch literal 6564 zcmXX~2YeM(^PRi5_$kd z1cZbZLTI4`f&?T6sR<#;dwK8e?w$X+`+b?;?kl_R&dixJXYQt7_by%5R3&<+|(d)a2%J0+2LFH-K;0c2#tFAfL+Ryoyt_eOhy8Q)iN6ies_%y_PTj5j{kA@kAC%r|B_g1ZM=h1-b>gmvt+f zTQ;ZcbMtevyWUf;8`2Nwp_vC}}kUVatnQ_5{;75VLDr>6DtTL-go2d3t-8{WKrr+|9 zdN|@?tO@v^27GTgk4HplO&BL&k-ql|NeE z72%G!s9)6k1^NcYmW(dxP};UM(ugp2i|t~Vcwf8}Y#D4Cd|Od+(JlWSzvgr~_k`~c zuMt@*a#p#SzS*_YHPt=c{i*9y*Hk@8pM)r148#TIdKY*{ zmyRy|t?Xb~e>qUrbJTHc^{n&s59<|Xgn7gIhx7@lfo|R|a599^jD|@f3MIa$CKz<-EYFD)c=S$9Nu(4u{Xks)pGQH{E zU}>QAj{mmbu!7bZI!T*o1I^|+{Fls?`TU5J^*wr3Pj%1ikXa!e-HGmL`gnaAFXkp@ zV{@vJWbESuT;MEpp7osZq`3FE>pAN?!}JRJQf;NSL|>+dIz7%eoSmJTuIaB(JG#gJ zayQXSL~Bvn6>(l{q;>SzdTd1-(Z<|P~LykyDKsMsjd>Fx$vBJW|L()R$fn31T&`Xp$1cwCurfF`L$rhxm6MB}d8m)!LMAj+N)-6uj**JR7Gs>sT6xHa7JSZ-^MHOpEnSIK*v z$NT6I-KWPCD{2eB@QT`ctR4-m%5q$BtZ}Y#jzNEBx-ProU5T!}jugiM%#r<+LZ5=i zow*}s%zo}f9nl$+shG>S8+YNF9K*wSFyH1|yqZ?h4`MOu5UDMLK4nlA{Y0xVseCj{ zj6@Gt)H1cR+7Q?{`kea7x8!tdvh~z@V2zVs%AT@^JPU^Owz^xV&C_N_SS2&s>}+;5 zXP7h19M}qLrL|semTP%4hhYAl!|a>S-|`t)BE3o<(+GNyao(GUH!4Z7Y= z>!_{L)@W)D`ZT}xrruj$q<^O;!Edl;%;<&gc@`Vpoz92GD?H7dFUEGFt zgi42;WlgetW|3Ljj5S|2+8bHH8^P*EtdVYH8Q+>q%$8QXm2PENQ-DG|C!+2LnOR`( zF_KCsK*OO^^WbN^m@K-9E~2ieiKD)F9ea$f_-v6u+i3@_qV@D6IH5FjCH5tK(L?X> zdtg#mwxrI-4H-nki<831S>(;vje%0)-eM-oVb7=RKO2;22W#_CaSVsZjx)zA;)ALZU}zf<#n`?Vo;~_vF=vBlw3k;c$y zN<%jn0v)#qLxdioG`kn#%HuoenghHAd|k+M(J3E5yWa#uiYTH|RQ(`w9*^jHP;)TGiM{X{PDN+W<^jm1Ik)8691GUB z#OHqe9j>JDaUk)WJAef{Xct9^is*n3#5ZCtCj4SCP0Rv+hJ&NsL_B6t4RnG@7F~d; zIw#Ug^pNj^nXACdcc9WuxfXoX!v3f}H-fKL+!ofId+`@MiFfcm&HUa4DS2SWbFNC2foT^S2Cw6Qzzmv#DYzINuoUlS zf?LCYNiS-R`jv;b2iUuu0ULJmFW3Qn$6xVyu=gWWbO8Sc=LtY8MLi$kz90wC2QCT+ zqG5Oqg~d=+N`OZ7g4fRLe62RTVL30Ah!fImlmNXW}pW@fv4W^+KQ^c{JrJp2(l`Cy4MfaQO>Nn6Y5dRIuhJsQZ3YAsxHQe2$_D=%xVw@CP$!ce=%E65$w7pwlIIX*Hs6C5-@$PX-~0+ofu$mso4|yi zFN!C_vHm#v4CjyRdbdGE8)Mxn6b;r8<%17r`LrEh3NSs0S{wo{`|U2+%1Tp~^Uu)L zMRxD3!#4+UM;1D*$nLXHstttd+tD_nIIRB$kb0j!z@Gjabiy|1&Y$3XD%2wdm01FA zjl=|Q4fU~5jZEGKPA$Q{P2rS+9!-Vjq$8?Z$i!>=2nTzrz)NlXsx2`47;DeK+*?M! zpuf}{T!XIr7Q7t-&#k~N59}e(K8=+&qt=VC{ybiQ>7dRF?OtAsTvnrQ>!Aqh**+Il z*TKpfP>x3S^ofH`CfKvJ72Y*Lj+HTs)c+8I=sVSaPWXueKVG1^=*TLll|&CbLhL!< zQx=$%jgGj_rI@^B$fb}U!^~pu0BGYFTlrs*iO`0_1sZ( zkn;UEA}Z!5Sk=Jl|5cR;tWce*A>ZoA)=5v`D+k%ANYkJ(2f%^#(9s2`+!x?|e|{G` zx89h%@8i>COpe9S#*Mricc$Rjis`KOQtJI_)Ldzi()co%2l-Y+4@aVx9HdhK*cJe* z8^Gx>Yy&E|7%ZFvUvp8%#o*%x)bt3LavkeDNA#5ushTx!LeD>jrX_(PKS4c}&K{)S z!NDW68~j@V&P>7-{1E)?hI?CM|Irc^ZiaeCqmG4GOX;u#!Ialw-EGgkpbtGUnR?qAr+QfNydIvH!;sr^p<@s{9l5pN}t2)E%fFiMEne~tE1HRsg#7EGitI2fuA3h_9G6}<-$g>7{Ccj}^t>)5Y#@EU_$6<-xc)gDdhtPh8(mT4bK z{S6?^*c*po#cG&qHQ}ievI(<&Re(386>47{0o5ijCI1 zxBh}XgcZTmv&1u)>er{Jc@gsVBZ`2{a32_;CYxeJfgPi&h5BES>gycfdl}uSbVJS1 zJow24PD=CAP@Qyi+*Q>44tiY8(MLGv;kb$4DLuN1J8uKgJRq8j^EG@@-(R!OS8yd8 ztKJ4rAHcU4Jk)J$E8?uMRyarFsBFJiN)TlqYL~0_4F=GAhTr95g&eGwfeu%D)FaS< z(}+;<@d2v)99D`9OOR(N4he5+`qe{?;?S>cG56K;RtH!jIMWQOToaX4dswv>ebMgi z#+V89QHAPuY)XSv9si4-+85-*{zmrq|EGp(=b@_b1m69KRG@Z>>&h&!-n1*I;(7p6 z$SPCsRozt0lpb8O*{(RRrp+zX;Wp5{3(W7LGC8=eYLjdCxDTF8WT>!I`yaLYA)r(d MDAqw1HNn#V2LL|RZvX%Q literal 0 HcmV?d00001 diff --git a/on.wav b/on.wav new file mode 100644 index 0000000000000000000000000000000000000000..0aba073be16db05bd014eff23ad053e11061e7f7 GIT binary patch literal 7100 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 D{MXJ_ literal 0 HcmV?d00001 diff --git a/outgauge.c b/outgauge.c new file mode 100644 index 0000000..0d63e6a --- /dev/null +++ b/outgauge.c @@ -0,0 +1,105 @@ +//#define USE_IPV6 + +#ifdef WIN32 +#include +//#include +#else +#include +#include +#include +#endif +#include +#include +#include +#include "socket.h" +#include "network_worker.h" +#include "outgauge.h" +#include "cars.h" + +struct outgauge g_outgauge; +int g_fade; +int g_pressed; +int g_released; +uint32_t g_shift_time; +uint32_t g_ctrl_time; +int g_owncar; +static int s_car; + +static uint32_t s_prev_time; +static float s_prev_fuel; +float g_consumption; + +int outgauge_recv(int fd, int can_write, int can_read, void *arg) +{ + if (can_read) + { + struct outgauge o; + ssize_t len = recv(fd, (char *)&o, sizeof o, 0); + + /* Ignore out of order packet */ + //if (o.time < s_prev_time) + + if (o.rpm < 0) o.rpm = 0; + memcpy(&g_outgauge, &o, len); + g_fade &= ~1; + + g_released |= g_pressed & (~(o.flags & 3)); + + if (!(g_pressed & 1) && (o.flags & 1)) g_shift_time = o.time; + if (!(g_pressed & 2) && (o.flags & 2)) g_ctrl_time = o.time; + g_pressed |= (o.flags & 3); + + if (g_owncar != o.playerid) + { + if (o.fuel > 0) + { + g_owncar = o.playerid; + g_consumption = 0; + s_car = get_car(o.car, NULL); + s_prev_fuel = o.fuel; + g_fade &= ~2; + } + else + { + g_fade |= 2; + } + } + else + { + g_fade &= ~2; + } + + if (s_prev_time > 0) + { + if (o.fuel > s_prev_fuel) + { + g_consumption = 0; + } + else + { + float interval = (o.time - s_prev_time) * 0.001; + if (interval > 0) + { + float weight = 0.004; + g_consumption = (1.0 - weight) * g_consumption + weight * (s_prev_fuel - o.fuel) / interval; + + if (g_owncar == o.playerid) + { + update_car(s_car, o.speed * interval, interval, g_consumption); + } + } + } + } + s_prev_time = o.time; + s_prev_fuel = o.fuel; + } + + return 1; +} + +void outgauge_init(int port) +{ + int fd = network_listen(port, 0); + if (fd < 0) return; + register_socket(fd, SM_READ, &outgauge_recv, NULL); +} diff --git a/outgauge.h b/outgauge.h new file mode 100644 index 0000000..6ed2979 --- /dev/null +++ b/outgauge.h @@ -0,0 +1,43 @@ +#ifndef OUTGAUGE_H +#define OUTGAUGE_H + +#include + +struct outgauge +{ + uint32_t time; + char car[4]; + uint16_t flags; + uint8_t gear; + uint8_t playerid; + float speed; + float rpm; + float turbo; + float enginetemp; + float fuel; + float oilpressure; + float oiltemp; + uint32_t dashlights; + uint32_t showlights; + float throttle; + float brake; + float clutch; + char display1[16]; + char display2[16]; + uint32_t id; +}; + +extern struct outgauge g_outgauge; +extern float g_consumption; +extern int g_owncar; + +extern int g_fade; +extern int g_pressed; +extern int g_released; +extern uint32_t g_shift_time; +extern uint32_t g_ctrl_time; + + +void outgauge_init(int port); + +#endif /* OUTGAUGE_H */ diff --git a/queue.c b/queue.c new file mode 100644 index 0000000..be68b92 --- /dev/null +++ b/queue.c @@ -0,0 +1,98 @@ +#include +#include +#include "queue.h" + +struct queue_object_t +{ + struct queue_object_t *next; + void *data; +}; + +struct queue_t +{ + pthread_mutex_t produce_mutex; + pthread_mutex_t consume_mutex; + struct queue_object_t *head; + struct queue_object_t *tail; + struct queue_object_t *divider; +}; + +struct queue_t *queue_new(void) +{ + struct queue_t *queue = malloc(sizeof *queue); + struct queue_object_t *qo = malloc(sizeof *qo); + qo->next = NULL; + qo->data = NULL; + + queue->head = qo; + queue->tail = qo; + queue->divider = qo; + + pthread_mutex_init(&queue->produce_mutex, NULL); + pthread_mutex_init(&queue->consume_mutex, NULL); + + return queue; +} + +void queue_delete(struct queue_t *queue) +{ + int n = 0; + while (queue->head != NULL) + { + struct queue_object_t *qo = queue->head; + queue->head = qo->next; + + //free(qo->data); + free(qo); + n++; + } + + pthread_mutex_destroy(&queue->produce_mutex); + pthread_mutex_destroy(&queue->consume_mutex); + + free(queue); +} + +int queue_produce(struct queue_t *queue, void *data) +{ + if (queue == NULL) return 0; + + struct queue_object_t *qo = malloc(sizeof *qo); + qo->next = NULL; + qo->data = data; + + /* Lock needed? */ + pthread_mutex_lock(&queue->produce_mutex); + queue->tail->next = qo; + queue->tail = qo; + /* */ + + int n = 0; + while (queue->head != queue->divider) + { + struct queue_object_t *qo = queue->head; + queue->head = qo->next; + + //free(qo->data); + free(qo); + n++; + } + pthread_mutex_unlock(&queue->produce_mutex); + + return 1; +} + +int queue_consume(struct queue_t *queue, void **data) +{ + pthread_mutex_lock(&queue->consume_mutex); + if (queue->divider != queue->tail) + { + *data = queue->divider->next->data; + queue->divider = queue->divider->next; + pthread_mutex_unlock(&queue->consume_mutex); + return 1; + } + + pthread_mutex_unlock(&queue->consume_mutex); + return 0; +} diff --git a/queue.h b/queue.h new file mode 100644 index 0000000..4d4799c --- /dev/null +++ b/queue.h @@ -0,0 +1,11 @@ +#ifndef QUEUE_H +#define QUEUE_H + +struct queue_t; + +struct queue_t *queue_new(void); +void queue_delete(struct queue_t *queue); +int queue_produce(struct queue_t *queue, void *data); +int queue_consume(struct queue_t *queue, void **data); + +#endif /* QUEUE_H */ diff --git a/socket.c b/socket.c new file mode 100644 index 0000000..b97b812 --- /dev/null +++ b/socket.c @@ -0,0 +1,261 @@ +#ifndef WIN32 +//#define USE_POLL +#endif + +#include +#include +#include +#ifdef WIN32 +#include +#include +#else +#include +#ifdef USE_POLL +#include +#endif /* USE_POLL */ +#include +#include +#include +#include +#include +#endif +#include +#include +#include "socket.h" +#include "list.h" + +int s_running; + +struct socket_t +{ + int fd; + int mode; + socket_func socket_func; + void *arg; +}; + +static inline int socket_compare(struct socket_t **a, struct socket_t **b) +{ + return (*a)->fd == (*b)->fd; +} + +LIST(socket, struct socket_t *, socket_compare) +static struct socket_list_t s_sockets; +static pthread_mutex_t s_sockets_mutex; + +#ifdef USE_POLL +static int s_epoll_fd = -1; +#endif /* USE_POLL */ + +#ifdef USE_POLL +static struct socket_t *socket_get_by_fd(int fd) +{ + size_t i; + for (i = 0; i < s_sockets.used; i++) + { + struct socket_t *s = s_sockets.items[i]; + if (s->fd == fd) return s; + } + + return NULL; +} +#endif + +void register_socket(int fd, int mode, socket_func socket_func, void *arg) +{ + struct socket_t *s = malloc(sizeof *s); + s->fd = fd; + s->mode = mode; + s->socket_func = socket_func; + s->arg = arg; + + pthread_mutex_lock(&s_sockets_mutex); + socket_list_add(&s_sockets, s); + +#ifdef USE_POLL + if (s_epoll_fd == -1) + { + s_epoll_fd = epoll_create(32); + if (s_epoll_fd == -1) fprintf(stderr, "register_socket(): epoll_create: %s\n", strerror(errno)); + } + + struct epoll_event ev; + ev.events = EPOLLIN; + ev.data.ptr = s; + int r = epoll_ctl(s_epoll_fd, EPOLL_CTL_ADD, fd, &ev); + if (r == -1) fprintf(stderr, "register_socket(): epoll_ctl: %s\n", strerror(errno)); +#endif /* USE_POLL */ + + pthread_mutex_unlock(&s_sockets_mutex); +} + +void socket_flag_write(int fd) +{ +#ifdef USE_POLL + struct epoll_event ev; + ev.events = EPOLLIN | EPOLLOUT; + ev.data.ptr = socket_get_by_fd(fd);; + int r = epoll_ctl(s_epoll_fd, EPOLL_CTL_MOD, fd, &ev); + if (r == -1) fprintf(stderr, "socket_flag_write(): epoll_ctl: %s\n", strerror(errno)); +#endif /* USE_POLL */ +} + +void socket_clear_write(int fd) +{ +#ifdef USE_POLL + struct epoll_event ev; + ev.events = EPOLLIN; + ev.data.ptr = socket_get_by_fd(fd); + int r = epoll_ctl(s_epoll_fd, EPOLL_CTL_MOD, fd, &ev); + if (r == -1) fprintf(stderr, "socket_clear_write(): epoll_ctl: %s\n", strerror(errno)); +#endif /* USE_POLL */ +} + +void deregister_socket(int fd) +{ + struct socket_t s; + s.fd = fd; + + pthread_mutex_lock(&s_sockets_mutex); + socket_list_del_item(&s_sockets, &s); + pthread_mutex_unlock(&s_sockets_mutex); +} + +void socket_init(void) +{ + #ifdef WIN32 + WSADATA WSA_Data; + WSAStartup (0x101, & WSA_Data); + #endif + + pthread_mutex_init(&s_sockets_mutex, NULL); +} + +void socket_deinit(void) +{ + if (s_sockets.used > 0) + { + fprintf(stderr, "[network] socket_deinit(): %u sockets remaining in list\n", (unsigned)s_sockets.used); + } + +#ifdef USE_POLL + close(s_epoll_fd); +#endif + + socket_list_free(&s_sockets); +} + +void socket_set_nonblock(int fd) +{ +#ifdef WIN32 + u_long iMode = 1; + ioctlsocket(fd, FIONBIO, &iMode); +#else + int flags = fcntl(fd, F_GETFL, 0); + int res = fcntl(fd, F_SETFL, flags | O_NONBLOCK); + if (res != 0) + { + fprintf(stderr, "[network] socket_set_nonblocK(): Could not set nonblocking IO: %s\n", strerror(errno)); + } +#endif +} + +void socket_set_nodelay(int fd) +{ + int b = 1; + int res = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (const char *)&b, sizeof b); + if (res != 0) + { + fprintf(stderr, "[network] socket_set_nonblock(): Could not set TCP_NODELAY: %s\n", strerror(errno)); + } +} + +#ifdef USE_POLL + +#define EPOLL_EVENTS 256 + +void *socket_run(void *arg) +{ + s_running = 1; + + while (s_running) + { + struct epoll_event events[EPOLL_EVENTS]; + int n = epoll_wait(s_epoll_fd, events, EPOLL_EVENTS, 10000); + if (n == -1) fprintf(stderr, "socket_run(): epoll_wait: %s\n", strerror(errno)); + + int i; + for (i = 0; i < n; i++) + { + struct socket_t *s = events[i].data.ptr; + int state = s->socket_func(s->fd, !!(events[i].events & EPOLLOUT), !!(events[i].events & EPOLLIN), s->arg); + if (!state) + { + close(s->fd); + s->fd = -1; + } + } + } +} + +#else /* USE_POLL */ + +void *socket_run(void *arg) +{ + s_running = 1; + + while (s_running) + { + + unsigned i; + int n; + fd_set read_fd, write_fd; + struct timeval tv; + + FD_ZERO(&read_fd); + FD_ZERO(&write_fd); + + /* Add service sockets */ + for (i = 0; i < s_sockets.used; i++) + { + int fd = s_sockets.items[i]->fd; + if (fd >= 0) + { + if (s_sockets.items[i]->mode & SM_READ ) FD_SET(fd, &read_fd); + if (s_sockets.items[i]->mode & SM_WRITE) FD_SET(fd, &write_fd); + } + } + + tv.tv_sec = 10; + tv.tv_usec = 0; + + n = select(FD_SETSIZE, &read_fd, &write_fd, NULL, &tv); + if (n == -1) + { + fprintf(stderr, "select: %s\n", strerror(errno)); + return NULL; + } + if (n == 0) continue; + + for (i = 0; i < s_sockets.used; i++) + { + struct socket_t *s = s_sockets.items[i]; + int can_write = FD_ISSET(s->fd, &write_fd); + int can_read = FD_ISSET(s->fd, &read_fd); + + if (can_write || can_read) + { + int state = s->socket_func(s->fd, can_write, can_read, s->arg); + if (!state) + { + close(s->fd); + s->fd = -1; + } + } + } + } + printf("Socket thread finished\n"); + return NULL; +} + +#endif /* USE_POLL */ diff --git a/socket.h b/socket.h new file mode 100644 index 0000000..69801a9 --- /dev/null +++ b/socket.h @@ -0,0 +1,26 @@ +#ifndef SOCKET_H +#define SOCKET_H + +extern int s_running; + +enum { + SM_READ = 1, + SM_WRITE = 2, +}; + +typedef int(*socket_func)(int fd, int can_write, int can_read, void *arg); + +void register_socket(int fd, int mode, socket_func socket_func, void *arg); +void deregister_socket(int fd); + +void *socket_run(void *arg); + +void socket_init(void); + +void socket_set_nonblock(int fd); +void socket_set_nodelay(int fd); + +void socket_flag_write(int fd); +void socket_clear_write(int fd); + +#endif /* SOCKET_H */ diff --git a/symbols512.tga b/symbols512.tga new file mode 100644 index 0000000000000000000000000000000000000000..703c791ae7261cb7a3f4f65aa6fbcd1dbd8e860d GIT binary patch literal 306160 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%0DPt$pU)4EOD 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 +#include +#include "text.h" + +void drawText(const char *text, FTGLfont *font, float x, float y, float xs, float ys, int xa, int ya) +{ + float bounds[6]; + ftglGetFontBBox(font, text, -1, bounds); + int cx = bounds[3] - bounds[0]; + int cy = bounds[4] - bounds[1]; + + float ox = 0, oy = 0; + switch (xa) + { + case TA_LEFT: ox = 0; break; + case TA_RIGHT: ox = -1; break; + case TA_CENTRE: ox = -0.5; break; + } + switch (ya) + { + case TA_TOP: oy = -1; break; + case TA_BOTTOM: oy = 0; break; + case TA_CENTRE: oy = -0.5; break; + } + + glPushMatrix(); + glTranslatef(x, y, 0.0f); + glScalef(xs / TEXT_SIZE, ys / TEXT_SIZE, 1.0f); + glTranslatef(cx * ox, cy * oy, 0.0f); + ftglRenderFont(font, text, FTGL_RENDER_ALL); + glPopMatrix(); +} diff --git a/text.h b/text.h new file mode 100644 index 0000000..cc1a3c4 --- /dev/null +++ b/text.h @@ -0,0 +1,16 @@ +#ifndef TEXT_H +#define TEXT_H + +#define TEXT_SIZE 64 + +enum { + TA_LEFT, + TA_RIGHT, + TA_TOP, + TA_BOTTOM, + TA_CENTRE, +}; + +void drawText(const char *text, FTGLfont *font, float x, float y, float xs, float ys, int xa, int ya); + +#endif /* TEXT_H */ diff --git a/timer.c b/timer.c new file mode 100644 index 0000000..24cde1a --- /dev/null +++ b/timer.c @@ -0,0 +1,101 @@ +#include +#include +#include +#include "list.h" +#include "timer.h" +#include "gettime.h" + +struct timer_t +{ + char *name; + unsigned interval; + timer_func_t timer_func; + void *arg; + + unsigned next_trigger; +}; + +static inline int timer_t_compare(struct timer_t **a, struct timer_t **b) +{ + return *a == *b; +} + +LIST(timer, struct timer_t *, timer_t_compare) +static struct timer_list_t s_timers; +static pthread_mutex_t s_timers_mutex; + +struct timer_t *register_timer(const char *name, unsigned interval, timer_func_t timer_func, void *arg, int wait) +{ + struct timer_t *t = malloc(sizeof *t); + t->name = strdup(name); + t->interval = interval; + t->timer_func = timer_func; + t->arg = arg; + + /* Make timer run on next tick */ + t->next_trigger = gettime() + (wait ? t->interval : 0); + + pthread_mutex_lock(&s_timers_mutex); + timer_list_add(&s_timers, t); + pthread_mutex_unlock(&s_timers_mutex); + + fprintf(stderr, "Registered %s timer with %u ms interval\n", name, interval); + + return t; +} + +void deregister_timer(struct timer_t *t) +{ + timer_list_del_item(&s_timers, t); + + fprintf(stderr, "Deregistered %s timer\n", t->name); + + free(t->name); + free(t); +} + +void timers_deinit(void) +{ + while (s_timers.used > 0) + { + deregister_timer(s_timers.items[0]); + } + + timer_list_free(&s_timers); +} + +void process_timers(unsigned tick) +{ + unsigned i; + + for (i = 0; i < s_timers.used; i++) + { + struct timer_t *t = s_timers.items[i]; + if (tick >= t->next_trigger) + { + t->next_trigger = tick + t->interval; + t->timer_func(t->arg); + } + } +} + +void timer_set_interval(struct timer_t *t, unsigned interval) +{ + /* Adjust next triggering */ + t->next_trigger += (interval - t->interval); + t->interval = interval; +} + +void timer_set_interval_by_name(const char *name, unsigned interval) +{ + unsigned i; + for (i = 0; i < s_timers.used; i++) + { + struct timer_t *t = s_timers.items[i]; + if (strcmp(t->name, name) == 0) + { + timer_set_interval(t, interval); + return; + } + } +} diff --git a/timer.h b/timer.h new file mode 100644 index 0000000..abe6c38 --- /dev/null +++ b/timer.h @@ -0,0 +1,15 @@ +#ifndef TIMER_H +#define TIMER_H + +typedef void(*timer_func_t)(void *arg); + +struct timer_t; + +struct timer_t *register_timer(const char *name, unsigned interval, timer_func_t timer_func, void *arg, int wait); +void deregister_timer(struct timer_t *handle); +void process_timers(unsigned tick); + +void timer_set_interval(struct timer_t *t, unsigned interval); +void timer_set_interval_by_name(const char *name, unsigned interval); + +#endif /* TIMER_H */ diff --git a/worker.c b/worker.c new file mode 100644 index 0000000..374e0cf --- /dev/null +++ b/worker.c @@ -0,0 +1,133 @@ +#ifdef WIN32 +//#include +#endif +#include +#include +#include +#include +#include +//#include +#include +#include "queue.h" +#include "worker.h" +//#include "mcc.h" +#include "gettime.h" + +void *worker_thread(void *arg) +{ + struct worker *worker = arg; + + bool timeout = false; + int jobs = 0; + //pid_t tid = (pid_t)syscall(SYS_gettid); + unsigned tid = 0; + +#ifndef WIN32 + nice(worker->nice); +#endif + + fprintf(stderr, "Queue worker %s thread (%u) started with nice %d\n", worker->name, tid, worker->nice); + + while (1) + { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += worker->timeout; + + int s = sem_timedwait(&worker->sem, &ts); + if (s == -1) + { + worker->thread_timeout = true; + if (errno == ETIMEDOUT) { + timeout = true; + break; + } + fprintf(stderr, "Queue worker %s thread (%u): %s\n", worker->name, tid, strerror(errno)); + break; + } + + void *data; + if (queue_consume(worker->queue, &data)) + { + /* Null item added to the queue indicate we should exit. */ + if (data == NULL) break; + + worker->callback(data); + jobs++; + } + } + + if (timeout) + { + fprintf(stderr, "Queue worker %s thread (%u) exiting after %d jobs due to timeout\n", worker->name, tid, jobs); + } + else + { + fprintf(stderr, "Queue worker %s thread (%u) exiting after %d jobs\n", worker->name, tid, jobs); + } + + return NULL; +} + +void worker_init(struct worker *worker, const char *name, unsigned timeout, int nice, worker_callback callback) +{ + memset(worker, 0, sizeof *worker); + + strncpy(worker->name, name, sizeof worker->name); + worker->thread_valid = false; + worker->thread_timeout = false; + worker->timeout = timeout / 1000; + worker->nice = nice; + + worker->queue = queue_new(); + worker->callback = callback; + sem_init(&worker->sem, 0, 0); + + fprintf(stderr, "Queue worker %s initialised\n", worker->name); +} + +void worker_deinit(struct worker *worker) +{ + if (worker->thread_valid) + { + if (!worker->thread_timeout) + { + if (queue_produce(worker->queue, NULL)) + { + sem_post(&worker->sem); + } + } + + pthread_join(worker->thread, NULL); + } + + queue_delete(worker->queue); + + fprintf(stderr, "Queue worker %s deinitialised\n", worker->name); +} + +void worker_queue(struct worker *worker, void *data) +{ + if (worker->thread_timeout) + { + pthread_join(worker->thread, NULL); + worker->thread_timeout = false; + worker->thread_valid = false; + } + + if (!worker->thread_valid) + { + worker->thread_valid = (pthread_create(&worker->thread, NULL, &worker_thread, worker) == 0); + } + + if (queue_produce(worker->queue, data)) + { + sem_post(&worker->sem); + } + else + { + fprintf(stderr, "Queue worker %s unable to queue\n", worker->name); + } +} + + diff --git a/worker.h b/worker.h new file mode 100644 index 0000000..158bcce --- /dev/null +++ b/worker.h @@ -0,0 +1,27 @@ +#ifndef WORKER_H +#define WORKER_H + +#include +#include + +typedef void(*worker_callback)(void *arg); + +struct worker +{ + char name[16]; + int thread_valid; + int thread_timeout; + unsigned timeout; + int nice; + + struct queue_t *queue; + pthread_t thread; + worker_callback callback; + sem_t sem; +}; + +void worker_init(struct worker *worker, const char *name, unsigned timeout, int nice, worker_callback callback); +void worker_deinit(struct worker *worker); +void worker_queue(struct worker* worker, void *data); + +#endif /* WORKER_H */