diff --git a/Makefile b/Makefile index f441bfc..bd32a20 100644 --- a/Makefile +++ b/Makefile @@ -3,23 +3,24 @@ DESTDIR ?= CXX ?= g++ OPTIFLAG = -O2 -g -SDL_CONFIG ?= sdl-config +SDL_CONFIG ?= sdl2-config VERSION = 1.0.8 CFLAGS := -Wall -Isrc $(OPTIFLAG) $(CFLAGS_NOSDL) `$(SDL_CONFIG) --cflags` -DHAVE_SDL_MIXER "-DTRIPLANE_DATA=\"$(PREFIX)/share/games/triplane\"" "-DTRIPLANE_VERSION=\"$(VERSION)\"" LDFLAGS = -LIBS := `$(SDL_CONFIG) --libs` -lSDL_mixer -lm +LIBS := `$(SDL_CONFIG) --libs` -lSDL2_mixer -lz -lm INSTALL_DATA ?= install -m 644 INSTALL_PROGRAM ?= install COMMON_OBJS = src/gfx/bitmap.o src/gfx/font.o \ src/gfx/gfx.o src/util/wutil.o src/util/random.o \ - src/io/sdl_compat.o src/io/video.o \ + src/io/sdl_compat.o src/io/video.o src/io/network.o \ src/io/mouse.o src/io/dksfile.o src/io/timing.o TRIPLANE_OBJS = src/triplane.o src/world/tripai.o \ - src/world/tripmis.o src/gfx/fades.o src/menus/tripmenu.o \ + src/world/tripmis.o src/gfx/fades.o \ + src/menus/menusupport.o src/menus/tripmenu.o \ src/world/terrain.o src/world/fobjects.o src/world/tmexept.o \ src/gfx/extra.o src/settings.o src/world/plane.o src/io/joystick.o src/io/sound.o \ - src/world/tripaudio.o + src/world/tripaudio.o src/io/netclient.o LVLEDIT_OBJS = src/tools/lvledit/lvledit.o PGDVIEW_OBJS = src/tools/pgdview/pgdview.o PCX2PGD_OBJS = src/tools/pcx2pgd/pcx2pgd.o @@ -83,7 +84,6 @@ install: mkdir -p $(DESTDIR)$(PREFIX)/share/man/man6 $(INSTALL_DATA) doc/triplane.6 $(DESTDIR)$(PREFIX)/share/man/man6/triplane.6 test: - if [ ! -d triplane-testsuite ]; then echo Please darcs get http://iki.fi/lindi/darcs/triplane-testsuite; false; fi SDL_VIDEODRIVER=dummy bash tools/run-all-tests tools/run-one-test ./triplane triplane-testsuite build-data-from-source: tools/pcx2pgd @@ -98,5 +98,5 @@ dist: # man -Tps doc/triplane.6 > triplane.ps # ps2pdf triplane.ps -.PHONY: all checkdepend depend clean install test +.PHONY: all checkdepend depend clean install test build-data-from-source dist -include .depend diff --git a/README.install b/README.install index 3461016..b1a35a0 100644 --- a/README.install +++ b/README.install @@ -5,9 +5,10 @@ Dependencies ------------ * g++ -* SDL 1.2 -* SDL mixer 1.2 +* SDL 2.0 +* SDL mixer 2.0 * GNU make +* zlib Build procedure --------------- diff --git a/data/fokker.lst b/data/fokker.lst index 1ebd4dd..27a6832 100644 --- a/data/fokker.lst +++ b/data/fokker.lst @@ -68,6 +68,7 @@ data/menus/roster.pgd data/misc/score.pgd data/misc/lappu1.pgd data/misc/lappu2.pgd +data/menus/netmenu.pgd ;SFX data/sfx/aargh1.wav Die1 data/sfx/aargh2.wav Die2 diff --git a/data/menus/netmenu.pgd b/data/menus/netmenu.pgd new file mode 100644 index 0000000..6d391ec Binary files /dev/null and b/data/menus/netmenu.pgd differ diff --git a/data_src/pcx.lst b/data_src/pcx.lst index 44c795b..3190bea 100644 --- a/data_src/pcx.lst +++ b/data_src/pcx.lst @@ -367,3 +367,4 @@ data/icons/ase3.pgd data_src/pcx/icons/ase3.pcx \\x41\\x53\\x45\\x33\\x00\\x00\\ data/icons/ase4.pgd data_src/pcx/icons/ase4.pcx \\x41\\x53\\x45\\x34\\x00\\x00\\x00 \\x20 1 3 data/icons/ase1.pgd data_src/pcx/icons/ase1.pcx \\x41\\x53\\x45\\x31\\x00\\x00\\x00 \\xc4 5 9 data/icons/ase2.pgd data_src/pcx/icons/ase2.pcx \\x41\\x53\\x45\\x32\\x00\\x00\\x00 \\x00 2 8 +data/menus/netmenu.pgd data_src/pcx/menus/netmenu.pcx NETMEN\\x00 \\x00 320 200 diff --git a/data_src/pcx/menus/netmenu.pcx b/data_src/pcx/menus/netmenu.pcx new file mode 100644 index 0000000..edeb18e Binary files /dev/null and b/data_src/pcx/menus/netmenu.pcx differ diff --git a/doc/TODO b/doc/TODO index 4477e4f..5a30993 100644 --- a/doc/TODO +++ b/doc/TODO @@ -6,5 +6,4 @@ PDF manual Support for more than two joysticks Network game src/gfx/extra.cpp: embedded graphics, should read from data file instead -investigate if http://scale2x.sourceforge.net/ could be used for scaling diff --git a/doc/triplane.6 b/doc/triplane.6 index 3b47734..5bef8ec 100644 --- a/doc/triplane.6 +++ b/doc/triplane.6 @@ -23,14 +23,9 @@ Start the game without sounds. Note: This option also disables sounds in the configuration file so sounds will be disabled also the next time you start triplane. To enable sounds set Physical Examination->Hearing->Sounds on in the game menus. -.IP "\fB\-2, -3, -4\fP" -Scale the 320x200-pixel game window 2x, 3x or 4x using software -scaling. This helps you play Triplane on displays that cannot switch to -such low resolutions. -.IP "\fB\-2svga\fP" -Scale the 800x600-pixel window 2x using software scaling, to -produce a 1600x1200-pixel window. This resolution is used only in -multiplayer mode. +.IP "\fB\-1, -2, -3, -4\fP" +Scale the initial 320x200-pixel game window 1x, 2x, 3x or 4x. +The default is -3. You can resize the window freely at runtime. .PP Note that triplane does not warn about unknown options. .SH "GETTING STARTED" @@ -53,6 +48,7 @@ controls: .IP "\(bu use X and W to steer the plane" 4 .IP "\(bu use 1 to drop a bomb" 4 .IP "\(bu use S to roll the plane upside down" 4 +.IP "\(bu use Pause or F4 to pause the game" 4 .PP When you have destroyed all the targets try to land by releasing power and gliding to the runway. Holding up and down simultaneously diff --git a/src/gfx/bitmap.cpp b/src/gfx/bitmap.cpp index 8e7b2a5..f40be55 100644 --- a/src/gfx/bitmap.cpp +++ b/src/gfx/bitmap.cpp @@ -32,6 +32,7 @@ #include "gfx/bitmap.h" #include "gfx/gfx.h" #include "io/trip_io.h" +#include "io/network.h" #include "util/wutil.h" #include #include @@ -39,139 +40,70 @@ #define RLE_REPETITION_MARK 192 -#define MAX_BITMAPS 8192 +#define MAX_BITMAPS 8192 /* must be <= 65536 for netsend */ Bitmap *all_bitmaps[MAX_BITMAPS]; -int all_bitmaps_n = 0; +int all_bitmaps_last_alloc = -1; unsigned char *pointti; -static void all_bitmaps_add(Bitmap * b) { - if (draw_with_vircr_mode) - return; +static int all_bitmaps_add(Bitmap * b) { + int wrapped = 0; - assert(all_bitmaps_n < MAX_BITMAPS); - all_bitmaps[all_bitmaps_n++] = b; -} + if (all_bitmaps_last_alloc == -1) { + memset(all_bitmaps, 0, MAX_BITMAPS * sizeof(Bitmap *)); + } -static void all_bitmaps_delete(Bitmap * b) { - int i; + do { + all_bitmaps_last_alloc++; + if (all_bitmaps_last_alloc >= MAX_BITMAPS) { + all_bitmaps_last_alloc = 0; + wrapped++; + assert(wrapped <= 1); /* else all bitmap positions are used */ + } + } while (all_bitmaps[all_bitmaps_last_alloc] != NULL); - if (draw_with_vircr_mode) - return; + all_bitmaps[all_bitmaps_last_alloc] = b; + return all_bitmaps_last_alloc; +} - for (i = 0; i < all_bitmaps_n; i++) - if (all_bitmaps[i] == b) - break; - if (i < all_bitmaps_n) - all_bitmaps[i] = all_bitmaps[--all_bitmaps_n]; +static void all_bitmaps_delete(int id) { + all_bitmaps[id] = NULL; } -void all_bitmaps_refresh(void) { +void all_bitmaps_resend_if_sent(void) { int i; - if (draw_with_vircr_mode) - return; - - for (i = 0; i < all_bitmaps_n; i++) - all_bitmaps[i]->refresh_sdlsurface(); -} - -/* Make a copy of the image data in source, enlarged zoom times */ -static unsigned char *duplicate_enlarged(const unsigned char *source, int width, int height, int zoom) { - uint8_t *target = (uint8_t *) walloc(width * height * zoom * zoom); - int i, j, k; - const uint8_t *in = source; - uint8_t *out = target; - - /* optimized versions using 32-bit and 16-bit writes when possible */ - if (zoom == 4 && sizeof(char *) >= 4) { /* word size >= 4 */ - uint32_t cccc; - for (j = 0; j < height * zoom; j += zoom) { - for (i = 0; i < width * zoom; i += zoom) { - cccc = *in | (*in << 8) | (*in << 16) | (*in << 24); - in++; - for (k = 0; k < zoom; k++) { - *(uint32_t *) (&out[(j + k) * (width * zoom) + i]) = cccc; - } - } - } - } else if (zoom == 3) { - uint16_t cc, c; - for (j = 0; j < height * zoom; j += zoom) { - for (i = 0; i < width * zoom; i += zoom) { - c = *in++; - cc = c | (c << 8); - for (k = 0; k < zoom; k++) { - *(uint16_t *) (&out[(j + k) * (width * zoom) + i]) = cc; - out[(j + k) * (width * zoom) + i + 2] = c; - } - } - } - } else if (zoom == 2) { - uint16_t cc; - for (j = 0; j < height * zoom; j += zoom) { - for (i = 0; i < width * zoom; i += zoom) { - cc = *in | (*in << 8); - in++; - for (k = 0; k < zoom; k++) { - *(uint16_t *) (&out[(j + k) * (width * zoom) + i]) = cc; - } - } - } - } else { /* unoptimized version */ - int l; - uint8_t c; - for (j = 0; j < height * zoom; j += zoom) { - for (i = 0; i < width * zoom; i += zoom) { - c = *in++; - for (k = 0; k < zoom; k++) { - for (l = 0; l < zoom; l++) { - out[(j + k) * (width * zoom) + (i + l)] = c; - } - } - } - } + for (i = 0; i < MAX_BITMAPS; i++) { + if (all_bitmaps[i] == NULL) + continue; + all_bitmaps[i]->resend_bitmapdata(); } - - return target; } -void Bitmap::refresh_sdlsurface() { - unsigned char *imgmult = NULL; - SDL_Surface *tmps; +void all_bitmaps_send_now(void) { + int i; - if (sdlsurface != NULL) { - SDL_FreeSurface(sdlsurface); - sdlsurface = NULL; + for (i = 0; i < MAX_BITMAPS; i++) { + if (all_bitmaps[i] == NULL) + continue; + all_bitmaps[i]->send_bitmapdata(); } +} - if (draw_with_vircr_mode) - return; /* sdlsurfaces are not used */ - - if (pixel_multiplier > 1) { - imgmult = duplicate_enlarged(image_data, width, height, pixel_multiplier); - tmps = SDL_CreateRGBSurfaceFrom(imgmult, width * pixel_multiplier, height * pixel_multiplier, 8, width * pixel_multiplier, 0, 0, 0, 0); - } else { - tmps = SDL_CreateRGBSurfaceFrom(image_data, width, height, 8, width, 0, 0, 0, 0); +void Bitmap::send_bitmapdata() { + if (!data_sent) { + data_sent = netsend_bitmapdata(id, width, height, hastransparency, image_data); } +} - if (tmps == NULL) { - fprintf(stderr, "SDL_CreateRGBSurfaceFrom: %s\n", SDL_GetError()); - exit(1); - } - SDL_SetPalette(tmps, SDL_LOGPAL, curpal, 0, 256); - if (hastransparency) - SDL_SetColorKey(tmps, SDL_SRCCOLORKEY | SDL_RLEACCEL, 0xff); - sdlsurface = SDL_DisplayFormat(tmps); - if (sdlsurface == NULL) { - fprintf(stderr, "SDL_DisplayFormat: %s\n", SDL_GetError()); - exit(1); +void Bitmap::resend_bitmapdata() { + if (data_sent) { + netsend_bitmapdata(id, width, height, hastransparency, image_data); } +} - SDL_FreeSurface(tmps); - if (pixel_multiplier > 1) { - wfree(imgmult); - } +void Bitmap::clear_data_sent() { + data_sent = 0; } Bitmap::Bitmap(const char *image_name, int transparent) { @@ -241,33 +173,40 @@ Bitmap::Bitmap(const char *image_name, int transparent) { name = image_name; hastransparency = transparent; - sdlsurface = NULL; - refresh_sdlsurface(); - all_bitmaps_add(this); + data_sent = 0; + id = all_bitmaps_add(this); } -Bitmap::Bitmap(int width, int height, unsigned char *image_data, const char *name) { - this->image_data = image_data; +Bitmap::Bitmap(int width, int height, + unsigned char *image_data, + const char *name, + int hastransparency, + int copy_image_data) { this->width = width; this->height = height; - this->external_image_data = 1; this->name = name; - this->hastransparency = 1; - this->sdlsurface = NULL; - refresh_sdlsurface(); - all_bitmaps_add(this); + this->hastransparency = hastransparency; + this->data_sent = 0; + + if (copy_image_data) { + this->image_data = (unsigned char *) walloc(width * height); + this->external_image_data = 0; + memcpy(this->image_data, image_data, width * height); + } else { + this->image_data = image_data; + this->external_image_data = 1; + } + + this->id = all_bitmaps_add(this); } Bitmap::~Bitmap() { - all_bitmaps_delete(this); - if (sdlsurface != NULL) { - SDL_FreeSurface(sdlsurface); - sdlsurface = NULL; - } + all_bitmaps_delete(id); if (!external_image_data) free(image_data); + netsend_bitmapdel(id); } void Bitmap::blit_fullscreen(void) { @@ -275,11 +214,10 @@ void Bitmap::blit_fullscreen(void) { assert(!hastransparency); pointti = image_data; - if (update_vircr_mode) - memcpy(vircr, image_data, 320 * 200); + send_bitmapdata(); + netsend_bitmapblitfs(id); - if (!draw_with_vircr_mode) - SDL_BlitSurface(sdlsurface, NULL, video_state.surface, NULL); + memcpy(vircr, image_data, 320 * 200); } /* @@ -289,7 +227,6 @@ void Bitmap::blit_fullscreen(void) { void Bitmap::blit(int xx, int yy, int rx, int ry, int rx2, int ry2) { int fromminy, fromminx, frommaxy, frommaxx, bwidth; int xi, yi, tx, ty; - SDL_Rect clip, pos; if (current_mode == SVGA_MODE) { if (rx == 0 && ry == 0 && rx2 == 319 && ry2 == 199) { @@ -317,51 +254,41 @@ void Bitmap::blit(int xx, int yy, int rx, int ry, int rx2, int ry2) { if ((ry > ry2) || (rx > rx2)) return; - if (update_vircr_mode) { - fromminy = (yy >= ry) ? 0 : ry - yy; - fromminx = (xx >= rx) ? 0 : rx - xx; - frommaxy = (yy + height - 1 <= ry2) ? height - 1 : ry2 - yy; - frommaxx = (xx + width - 1 <= rx2) ? width - 1 : rx2 - xx; - - if (fromminx <= frommaxx) { - if (hastransparency) { - for (yi = fromminy, ty = fromminy + yy; yi <= frommaxy; yi++, ty++) { - for (xi = fromminx, tx = fromminx + xx; xi <= frommaxx; xi++, tx++) { - unsigned char val = image_data[width * yi + xi]; - if (val != 0xff) { - vircr[bwidth * ty + tx] = val; - } + if (xx + width <= rx || xx > rx2 || yy + height <= ry || yy > ry2) + return; /* no part is inside the clip rectangle */ + + send_bitmapdata(); + if ((rx == 0 && /* no clip rectangle set */ + ry == 0 && + rx2 == ((current_mode == SVGA_MODE) ? 799 : 319) && + ry2 == ((current_mode == SVGA_MODE) ? 599 : 199)) || + /* or all of the bitmap is inside the clip rectangle */ + (xx >= rx && xx + width - 1 <= rx2 && + yy >= ry && yy + height - 1 <= ry2)) + netsend_bitmapblit(id, xx, yy); + else + netsend_bitmapblitclipped(id, xx, yy, rx, ry, rx2, ry2); + + fromminy = (yy >= ry) ? 0 : ry - yy; + fromminx = (xx >= rx) ? 0 : rx - xx; + frommaxy = (yy + height - 1 <= ry2) ? height - 1 : ry2 - yy; + frommaxx = (xx + width - 1 <= rx2) ? width - 1 : rx2 - xx; + + if (fromminx <= frommaxx) { + if (hastransparency) { + for (yi = fromminy, ty = fromminy + yy; yi <= frommaxy; yi++, ty++) { + for (xi = fromminx, tx = fromminx + xx; xi <= frommaxx; xi++, tx++) { + unsigned char val = image_data[width * yi + xi]; + if (val != 0xff) { + vircr[bwidth * ty + tx] = val; } } - } else { /* can use memcpy without transparency */ - for (yi = fromminy, ty = fromminy + yy; yi <= frommaxy; yi++, ty++) - memcpy(&vircr[bwidth * ty + fromminx + xx], &image_data[width * yi + fromminx], frommaxx - fromminx + 1); } + } else { /* can use memcpy without transparency */ + for (yi = fromminy, ty = fromminy + yy; yi <= frommaxy; yi++, ty++) + memcpy(&vircr[bwidth * ty + fromminx + xx], &image_data[width * yi + fromminx], frommaxx - fromminx + 1); } } - - if (!draw_with_vircr_mode) { - clip.x = rx; - clip.y = ry; - clip.w = rx2 - rx + 1; - clip.h = ry2 - ry + 1; - pos.x = xx; - pos.y = yy; - if (pixel_multiplier > 1) { - clip.x *= pixel_multiplier; - clip.y *= pixel_multiplier; - clip.w *= pixel_multiplier; - clip.h *= pixel_multiplier; - pos.x *= pixel_multiplier; - pos.y *= pixel_multiplier; - } - SDL_SetClipRect(video_state.surface, &clip); - if (SDL_BlitSurface(sdlsurface, NULL, video_state.surface, &pos) != 0) { - fprintf(stderr, "SDL_BlitSurface: %s\n", SDL_GetError()); - exit(1); - } - SDL_SetClipRect(video_state.surface, NULL); - } } unsigned char *Bitmap::info(int *width, int *height) { @@ -393,9 +320,8 @@ Bitmap::Bitmap(int x1, int y1, int xl, int yl, Bitmap * source_image) { name = source_image->name; hastransparency = source_image->hastransparency; - sdlsurface = NULL; - refresh_sdlsurface(); - all_bitmaps_add(this); + data_sent = 0; + id = all_bitmaps_add(this); } /* Create a new Bitmap from the contents of vircr at (x,y) to (x+w,y+h) */ @@ -403,8 +329,6 @@ Bitmap::Bitmap(int x, int y, int w, int h) { int vircrw = (current_mode == VGA_MODE) ? 320 : 800; int fromy, toy; - assert(update_vircr_mode); /* otherwise vircr may not be valid */ - width = w; height = h; image_data = (unsigned char *) walloc(w * h); @@ -415,9 +339,8 @@ Bitmap::Bitmap(int x, int y, int w, int h) { name = "from_vircr"; hastransparency = 0; - sdlsurface = NULL; - refresh_sdlsurface(); - all_bitmaps_add(this); + data_sent = 0; + id = all_bitmaps_add(this); } void Bitmap::blit_to_bitmap(Bitmap * to, int xx, int yy) { @@ -442,7 +365,63 @@ void Bitmap::blit_to_bitmap(Bitmap * to, int xx, int yy) { to_point[laskx + xx + (lasky + yy) * kokox] = image_data[laskx + lasky * width]; } - to->refresh_sdlsurface(); + if (to->data_sent) { + send_bitmapdata(); + netsend_blittobitmap(id, to->id, xx, yy); + } +} + +void Bitmap::recolor(unsigned char oldcolor, unsigned char newcolor) { + int i; + + assert(!external_image_data); + + for (i = 0; i < width * height; i++) + if (image_data[i] == oldcolor) + image_data[i] = newcolor; + + data_sent = 0; +} + +void Bitmap::blit_data(int tox, int toy, + const unsigned char *data, int w, int h, + int mask_color) { + int lx, ly; + + assert(tox + w <= width); + assert(toy + h <= height); + + for (lx = 0; lx < w; lx++) + for (ly = 0; ly < h; ly++) + if (data[ly * w + lx] != 255) + image_data[(toy + ly) * width + tox + lx] = + (mask_color == -1) ? data[ly * w + lx] : mask_color; +} + +void Bitmap::outline(unsigned char outlinecolor) { + unsigned char *old_data = image_data; + + assert(!external_image_data); + + width += 2; + height += 2; + hastransparency = 1; + image_data = (unsigned char *) walloc(width * height); + + memset(image_data, 255, width * height); + + blit_data(0, 0, old_data, width - 2, height - 2, outlinecolor); + blit_data(1, 0, old_data, width - 2, height - 2, outlinecolor); + blit_data(2, 0, old_data, width - 2, height - 2, outlinecolor); + blit_data(0, 1, old_data, width - 2, height - 2, outlinecolor); + blit_data(2, 1, old_data, width - 2, height - 2, outlinecolor); + blit_data(0, 2, old_data, width - 2, height - 2, outlinecolor); + blit_data(1, 2, old_data, width - 2, height - 2, outlinecolor); + blit_data(2, 2, old_data, width - 2, height - 2, outlinecolor); + blit_data(1, 1, old_data, width - 2, height - 2); + + wfree(old_data); + data_sent = 0; } Bitmap *rotate_bitmap(Bitmap * picture, int degrees) { @@ -479,7 +458,7 @@ Bitmap *rotate_bitmap(Bitmap * picture, int degrees) { } free(temp_data); - picture2->refresh_sdlsurface(); + picture2->clear_data_sent(); return picture2; } diff --git a/src/gfx/bitmap.h b/src/gfx/bitmap.h index b54d2d2..21b6ab7 100644 --- a/src/gfx/bitmap.h +++ b/src/gfx/bitmap.h @@ -31,11 +31,16 @@ class Bitmap { int16_t width, height; int external_image_data; // boolean: is image_data owned by this instance int hastransparency; - SDL_Surface *sdlsurface; + int id; // a unique ID for this Bitmap + int data_sent; // has image_data been sent to the network? public: Bitmap(const char *image_name, int transparent = 1); - Bitmap(int xl, int yl, unsigned char *image_data, const char *name = "unknown"); + Bitmap(int xl, int yl, + unsigned char *image_data, + const char *name = "unknown", + int hastransparency = 1, + int copy_image_data = 0); Bitmap(int x1, int y1, int xl, int yl, Bitmap * source_image); Bitmap(int x, int y, int w, int h); ~Bitmap(); @@ -43,11 +48,22 @@ class Bitmap { void blit(int xx, int yy, int rx = 0, int ry = 0, int rx2 = 319, int ry2 = 199); void blit_fullscreen(void); void blit_to_bitmap(Bitmap * to, int xx, int yy); + void recolor(unsigned char oldcolor, unsigned char newcolor); + void outline(unsigned char outlinecolor); + unsigned char *info(int *width = NULL, int *height = NULL); - void refresh_sdlsurface(); + void clear_data_sent(); + void send_bitmapdata(); + void resend_bitmapdata(); + + private: + void blit_data(int tox, int toy, + const unsigned char *data, int w, int h, + int mask_color=-1); }; -void all_bitmaps_refresh(void); +void all_bitmaps_resend_if_sent(void); +void all_bitmaps_send_now(void); Bitmap *rotate_bitmap(Bitmap * picture, int degrees); int bitmap_exists(const char *name); diff --git a/src/gfx/fades.cpp b/src/gfx/fades.cpp index 57ed1e4..b47eb5f 100644 --- a/src/gfx/fades.cpp +++ b/src/gfx/fades.cpp @@ -21,9 +21,10 @@ #include #include "gfx/fades.h" #include "gfx/gfx.h" +#include "io/network.h" #include "util/wutil.h" -void horisontal_split(void) { +static void horisontal_split(void) { Bitmap *upper; Bitmap *lower; int c1, c2 = 1; @@ -45,7 +46,7 @@ void horisontal_split(void) { } -void vertical_split() { +static void vertical_split() { Bitmap *left; Bitmap *right; int c1, c2; @@ -70,7 +71,7 @@ void vertical_split() { } -void pixel_fade(void) { +static void pixel_fade(void) { int c1, c2; for (c1 = 0; c1 < 20; c1++) { @@ -86,13 +87,11 @@ void pixel_fade(void) { } -void partial_fade(void) { +static void partial_fade(void) { unsigned char next_color[256]; int hit_value, temp_hit_value; int c, c2, c3, temp; - assert(update_vircr_mode); - next_color[0] = 0; for (c = 1; c < 256; c++) { @@ -133,16 +132,18 @@ void partial_fade(void) { do_all(); } -void random_fade_out(void) { - int t; +void selected_fade_out(int t) { + int display_was_enabled; - t = wrandom(5); + netsend_fade_out(t); if (current_mode == SVGA_MODE) { do_all_clear(); return; } + display_was_enabled = network_display_enable(0); + switch (t) { case 0: horisontal_split(); @@ -165,6 +166,12 @@ void random_fade_out(void) { break; } + network_display_enable(display_was_enabled); +} +void random_fade_out(void) { + int t; + t = wrandom(5); + selected_fade_out(t); } diff --git a/src/gfx/fades.h b/src/gfx/fades.h index 3a0b26f..6b9f71c 100644 --- a/src/gfx/fades.h +++ b/src/gfx/fades.h @@ -21,6 +21,7 @@ #ifndef FADES_H #define FADES_H +void selected_fade_out(int t); void random_fade_out(void); #endif diff --git a/src/gfx/font.cpp b/src/gfx/font.cpp index 586a974..2f71929 100644 --- a/src/gfx/font.cpp +++ b/src/gfx/font.cpp @@ -24,7 +24,7 @@ #include #include -Font::Font(const char *font_name) { +Font::Font(const char *font_name, int fgcolor, int outlinecolor) { int temp; int temppi; int kokox, kokoy; @@ -32,7 +32,9 @@ Font::Font(const char *font_name) { Bitmap *valikuva; scaled = 0; - scaled_space = 3; + scaled_space = 2; + charspace = 1; + linespace = 0; for (temppi = 0; temppi < 256; temppi++) glyphs[temppi] = NULL; @@ -61,6 +63,23 @@ Font::Font(const char *font_name) { glyphs[temp] = NULL; } + if (fgcolor != -1) { + for (temp = 0; temp < 256; temp++) + if (glyphs[temp] != NULL) + glyphs[temp]->recolor(0, fgcolor); + } + + if (outlinecolor != -1) { + for (temp = 0; temp < 256; temp++) + if (glyphs[temp] != NULL) + glyphs[temp]->outline(outlinecolor); + width += 2; + height += 2; + charspace--; + charspace--; // to compensate for count_scale ignoring outline + linespace--; + } + count_scale(); } @@ -79,6 +98,11 @@ void Font::set_space(int space) { scaled_space = space; } +void Font::set_spacing(int charspace_, int linespace_) { + charspace = charspace_; + linespace = linespace_; +} + int Font::printf(int x, int y, const char *fmt, ...) { int temp = 0; int xkohta = 0; @@ -92,33 +116,33 @@ int Font::printf(int x, int y, const char *fmt, ...) { if (!scaled) { while (teksti[temp]) { if (teksti[temp] == '\n') { - y += height; + y += height + linespace; xkohta = 0; } else { if (glyphs[(unsigned char) teksti[temp]] != NULL) { glyphs[(unsigned char) teksti[temp]]->blit(x + xkohta, y); } - xkohta += width + 1; + xkohta += width + charspace; } temp++; } - return (x + temp * (width + 1)); + return (x + temp * (width + charspace)); } while (teksti[temp]) { if (teksti[temp] == '\n') { - y += height; + y += height + linespace; xkohta = 0; } else { if ((glyphs[(unsigned char) teksti[temp]] != NULL) && ((unsigned char) teksti[temp] != 32)) { glyphs[(unsigned char) teksti[temp]]->blit(x + xkohta - char_start[(unsigned char) teksti[temp]], y); - xkohta += char_width[(unsigned char) teksti[temp]]; + xkohta += char_width[(unsigned char) teksti[temp]] + charspace; } else { - xkohta += scaled_space; + xkohta += scaled_space + charspace; } @@ -146,11 +170,11 @@ int Font::scanf(int x, int y, char *str, int max_len) { tausta_roska->blit(x, y); printf(printf(x, y, "%s", str), y, "_"); do_all(); + if (!kbhit()) { + continue; + } ch = getch(); - if (!ch) { - getch(); - ch = 0; - } else if (ch != 13) { + if (ch != 0 && ch != 13) { if (ch == 8) { if (kohta) { kohta--; @@ -214,6 +238,7 @@ void Font::count_scale(void) { char_width[temp] = valileveys - char_start[temp] + 2; } + char_width[temp]--; } } diff --git a/src/gfx/font.h b/src/gfx/font.h index 19eddb9..60a94bb 100644 --- a/src/gfx/font.h +++ b/src/gfx/font.h @@ -21,6 +21,7 @@ #ifndef FONT_H #define FONT_H +#include "gfx/bitmap.h" class Font { private: @@ -31,9 +32,11 @@ class Font { int char_start[256]; int scaled; int scaled_space; + int charspace; + int linespace; public: - Font(const char *font_name); + Font(const char *font_name, int fgcolor=-1, int outlinecolor=-1); ~Font(); int printf(int x, int y, const char *fmt, ...); int scanf(int x, int y, char *str, int max_len); @@ -41,6 +44,7 @@ class Font { void unscale(void); void count_scale(void); void set_space(int space); + void set_spacing(int charspace_=1, int linespace_=0); }; #endif diff --git a/src/gfx/gfx.cpp b/src/gfx/gfx.cpp index a392965..65239b9 100644 --- a/src/gfx/gfx.cpp +++ b/src/gfx/gfx.cpp @@ -36,16 +36,8 @@ void putpix(int x, int y, unsigned char c, int x1, int y1, int x2, int y2) { return; if (y > y2) return; - if (update_vircr_mode) { - if (current_mode == VGA_MODE) { - vircr[x + (y << 8) + (y << 6)] = c; - } else { - vircr[x + y * 800] = c; - } - } - if (!draw_with_vircr_mode) - fillrect(x, y, 1, 1, c); + fillrect(x, y, 1, 1, c); } void draw_line(int x1, int y1, int x2, int y2, unsigned char vari) { @@ -78,15 +70,7 @@ void boxi(int x1, int y1, int x2, int y2, unsigned char vari) { } void fill_vircr(int x1, int y1, int x2, int y2, unsigned char vari) { - int lasky; - - if (update_vircr_mode) { - for (lasky = y1; lasky <= y2; lasky++) - memset(&vircr[x1 + lasky * 320], vari, x2 - x1 + 1); - } - - if (!draw_with_vircr_mode) - fillrect(x1, y1, x2 - x1 + 1, y2 - y1 + 1, vari); + fillrect(x1, y1, x2 - x1 + 1, y2 - y1 + 1, vari); } void tyhjaa_vircr(void) { diff --git a/src/io/dksfile.cpp b/src/io/dksfile.cpp index 93d9b14..df24234 100644 --- a/src/io/dksfile.cpp +++ b/src/io/dksfile.cpp @@ -156,7 +156,6 @@ int dksopen(const char *nimi) { int extdksopen(const char *nimi) { int lask; int kohta = -1; - int faili = 0; for (lask = 0; lask < MAX_ENTRIES; lask++) { if (!strcmp(dirri[lask].nimi, nimi)) { @@ -167,7 +166,6 @@ int extdksopen(const char *nimi) { if (kohta == -1) { dks_faili = fopen(nimi, "rb"); - faili = 1; if (dks_faili == NULL) return (0); } else { diff --git a/src/io/joystick.cpp b/src/io/joystick.cpp index 0dc8736..a7edd10 100644 --- a/src/io/joystick.cpp +++ b/src/io/joystick.cpp @@ -117,20 +117,20 @@ void save_joysticks_data(const char *filename) { */ void open_close_joysticks(int joy1, int joy2) { if (SDL_NumJoysticks() >= 1) { - if (!joy1 && SDL_JoystickOpened(0)) { + if (!joy1 && joydev[0] != NULL) { SDL_JoystickClose(joydev[0]); joydev[0] = NULL; } - if (joy1 && !SDL_JoystickOpened(0)) { + if (joy1 && joydev[0] == NULL) { joydev[0] = SDL_JoystickOpen(0); } } if (SDL_NumJoysticks() >= 2) { - if (!joy2 && SDL_JoystickOpened(1)) { + if (!joy2 && joydev[1] != NULL) { SDL_JoystickClose(joydev[1]); joydev[1] = NULL; } - if (joy2 && !SDL_JoystickOpened(1)) { + if (joy2 && joydev[1] == NULL) { joydev[1] = SDL_JoystickOpen(1); } } diff --git a/src/io/mouse.cpp b/src/io/mouse.cpp index b60abea..d9fd6df 100644 --- a/src/io/mouse.cpp +++ b/src/io/mouse.cpp @@ -29,18 +29,60 @@ #include "io/video.h" #include +/* + * SDL_RenderSetLogicalSize does not expose functions to calculate its + * mapping, and both SDL_WarpMouseInWindow and SDL_GetMouseState work + * with physical coordinates. We thus need to convert them manually. + * Perhaps one day SDL will have equivalent functions that we can use + * instead of these... + */ +static void logical_to_physical(int logx, int logy, int *physx, int *physy) { + float scaleX, scaleY; + SDL_Rect vp; + + SDL_RenderGetScale(video_state.renderer, &scaleX, &scaleY); + SDL_RenderGetViewport(video_state.renderer, &vp); + + *physx = floor(vp.x * scaleX) + floor(logx * scaleX); + *physy = floor(vp.y * scaleY) + floor(logy * scaleY); +} + +static void physical_to_logical(int physx, int physy, int *logx, int *logy) { + float scaleX, scaleY; + SDL_Rect vp; + + SDL_RenderGetScale(video_state.renderer, &scaleX, &scaleY); + SDL_RenderGetViewport(video_state.renderer, &vp); + + if (physx / scaleX <= vp.x) + *logx = 0; + else if (physx / scaleX >= vp.x + vp.w) + *logx = vp.w - 1; + else + *logx = ceil((physx - floor(vp.x * scaleX)) / scaleX); + + if (physy / scaleY <= vp.y) + *logy = 0; + else if (physy / scaleY >= vp.y + vp.h) + *logy = vp.h - 1; + else + *logy = ceil((physy - floor(vp.y * scaleY)) / scaleY); +} + void hiiri_to(int x, int y) { - SDL_WarpMouse(x * pixel_multiplier, y * pixel_multiplier); + int px, py; + logical_to_physical(x, y, &px, &py); + SDL_WarpMouseInWindow(video_state.window, px, py); } void koords(int *x, int *y, int *n1, int *n2) { Uint8 ret; + int px, py; SDL_PumpEvents(); - ret = SDL_GetMouseState(x, y); + ret = SDL_GetMouseState(&px, &py); *n1 = !!(ret & SDL_BUTTON(1)); *n2 = !!(ret & SDL_BUTTON(3)); - *x /= pixel_multiplier; - *y /= pixel_multiplier; + physical_to_logical(px, py, x, y); } diff --git a/src/io/netclient.cpp b/src/io/netclient.cpp new file mode 100644 index 0000000..41a9f99 --- /dev/null +++ b/src/io/netclient.cpp @@ -0,0 +1,847 @@ +/* + * Triplane Classic - a side-scrolling dogfighting game. + * Copyright (C) 1996,1997,2009 Dodekaedron Software Creations Oy + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * tjt@users.sourceforge.net + */ + +/******************************************************************************* + + Purpose: + Client of networked game + +*******************************************************************************/ + +#include "triplane.h" +#include "util/wutil.h" +#include "io/network.h" +#include "io/netclient.h" +#include "io/joystick.h" +#include "io/sdl_compat.h" +#include "io/video.h" +#include "gfx/bitmap.h" +#include "gfx/fades.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NETC_SEND_BUFFER_SIZE 512 +#define NETC_RECVC_BUFFER_SIZE 100*1024 +#define NETC_RECVU_BUFFER_SIZE 1024*1024 + +// must be >= 12+2400*200 and <= NETC_RECVU_BUFFER_SIZE +#define MAXIMUM_S_PACKET_LENGTH 512*1024 + +#define NETCINFO_LINES 8 + +static int8_t netc_controls_country = -1; + +static int netc_socket = -1; +static char netc_sendbuf[NETC_SEND_BUFFER_SIZE]; +static unsigned int netc_sendbufend = 0; +static char netc_recvcbuf[NETC_RECVC_BUFFER_SIZE]; +static unsigned int netc_recvcbufend = 0; +static char netc_recvubuf[NETC_RECVU_BUFFER_SIZE]; +static unsigned int netc_recvubufend = 0; +static int netc_game_mode = 0; +static uint32_t netc_last_endofframe = 0, netc_last_warn = 0; + +static z_stream netc_recvzs; + +static Bitmap *netc_bitmaps[65536]; + +static char netcinfo_texts[NETCINFO_LINES][400]; +static uint32_t netcinfo_times[NETCINFO_LINES]; +static int netcinfo_next = 0; + +static void netc_printf(const char *fmt, ...) { + char teksti[400]; + va_list arg; + + va_start(arg, fmt); + vsprintf(teksti, fmt, arg); + va_end(arg); + + printf("Net: %s\n", teksti); + + strcpy(netcinfo_texts[netcinfo_next], teksti); + netcinfo_times[netcinfo_next] = SDL_GetTicks(); + netcinfo_next = (netcinfo_next + 1) % NETCINFO_LINES; +} + +static void netc_print_texts(void) { + int x = 12, y = 6, linesep = 10; + int i; + uint32_t oldesttime; + + if (netc_game_mode == 0) + foverlay->printf(x, y, "Viewing screen from host"); + y += linesep; + + // don't display info older than 10 seconds + oldesttime = SDL_GetTicks() - 10000; + + i = (netcinfo_next + 1) % NETCINFO_LINES; + do { + if (netcinfo_times[i] != 0 && netcinfo_times[i] > oldesttime) { + foverlay->printf(x, y, "%s", netcinfo_texts[i]); + y += linesep; + } + i = (i + 1) % NETCINFO_LINES; + } while (i != (netcinfo_next + 1) % NETCINFO_LINES); +} + +static char *netc_printed_text(const char *header) { + static char result[(1+NETCINFO_LINES)*400]; + int i; + + strcpy(result, header); + + i = (netcinfo_next + 1) % NETCINFO_LINES; + do { + if (netcinfo_texts[i][0] != '\0') { + strcat(result, netcinfo_texts[i]); + strcat(result, "\n"); + } + i = (i + 1) % NETCINFO_LINES; + } while (i != (netcinfo_next + 1) % NETCINFO_LINES); + + return result; +} + +static void netc_clear_printed_text(void) { + int i; + for (i = 0; i < NETCINFO_LINES; i++) + netcinfo_texts[i][0] = '\0'; + netcinfo_next = 0; +} + +static void netcbitmap_init(void) { + memset(netc_bitmaps, 0, 65536 * sizeof(Bitmap *)); +} + +static void netcbitmap_free_all(void) { + int bid; + for (bid = 0; bid < 65536; bid++) + if (netc_bitmaps[bid] != NULL) + delete netc_bitmaps[bid]; + memset(netc_bitmaps, 0, 65536 * sizeof(Bitmap *)); +} + +static void netcbitmap_maybefree(uint16_t bitmapid) { + if (netc_bitmaps[bitmapid] != NULL) { + delete netc_bitmaps[bitmapid]; + netc_bitmaps[bitmapid] = NULL; + } +} + +static void netc_close(void) { + if (netc_socket >= 0) { + close(netc_socket); + netc_socket = -1; + } +} + +static int16_t read_from_net_s16(const char *data) { + int16_t val; + memcpy(&val, data, sizeof(int16_t)); + val = SDL_SwapBE16(val); + return val; +} + +static uint16_t read_from_net_16(const char *data) { + uint16_t val; + memcpy(&val, data, sizeof(uint16_t)); + val = SDL_SwapBE16(val); + return val; +} + +static void netc_send_packet(uint32_t length, uint8_t type, void *data) { + if (NETC_SEND_BUFFER_SIZE - netc_sendbufend < length) { + netc_printf("Error: send buffer full (connection stalled?)"); + netc_close(); + return; + } + + uint32_t length_c = SDL_SwapBE32(length); + memcpy(&netc_sendbuf[netc_sendbufend], &length_c, 4); + memcpy(&netc_sendbuf[netc_sendbufend + 4], &type, 1); + if (length > 5) + memcpy(&netc_sendbuf[netc_sendbufend + 5], data, length - 5); + + netc_sendbufend += length; +} + +static void netc_send_greeting(uint8_t clientversion, + const char *playername, + const char *password) { + char data[43]; + memset(data, 0, 43); + memcpy(data, &clientversion, 1); + strcpy(data + 1, playername); + strcpy(data + 22, password); + netc_send_packet(48, NET_PKTTYPE_C_GREETING, data); +} + +static void netc_send_quit(void) { + netc_send_packet(5, NET_PKTTYPE_C_QUIT, NULL); +} + +static void netc_send_pong(uint8_t pingid) { + netc_send_packet(6, NET_PKTTYPE_C_PONG, &pingid); +} + +static void netc_send_wantcontrols(uint8_t playernum) { + netc_send_packet(6, NET_PKTTYPE_C_WANTCONTROLS, &playernum); +} + +static void netc_send_disablecontrols() { + netc_send_packet(5, NET_PKTTYPE_C_DISABLECONTROLS, NULL); +} + +static void netc_send_setcontrols(uint8_t controls) { + netc_send_packet(6, NET_PKTTYPE_C_SETCONTROLS, &controls); +} + +static void netc_recv_quit(void) { + netc_printf("Server quitting"); + netc_close(); +} + +static void netc_recv_videomode(uint8_t new_mode) { + // the palette is a dummy, it will be updated right after this packet + if (new_mode == 0) + init_vga("PALET5"); + else + init_vesa("PALET5"); +} + +static void netc_recv_setpal_range(uint8_t firstcolor, uint16_t n, + const char *palette) { + int i; + char pal[256][3]; + + for (i = 0; i < n; i++) { + pal[i][0] = *palette++ / 4; + pal[i][1] = *palette++ / 4; + pal[i][2] = *palette++ / 4; + } + + setpal_range(pal, firstcolor, n, 0); +} + +static void netc_recv_setpal_range_black(uint8_t firstcolor, uint16_t n) { + setpal_range(NULL, firstcolor, n, 0); +} + +static void netc_endframe(void) { + netc_print_texts(); + do_all(); +} + +static void netc_recv_endofframe(void) { + netc_endframe(); + netc_last_endofframe = SDL_GetTicks(); +} + +static void netc_recv_ping(uint8_t pingid) { + netc_send_pong(pingid); +} + +static void netc_recv_gamemode(uint8_t new_mode) { + netc_game_mode = new_mode; +} + +static void netc_recv_infomsg(const char *msg) { + netc_printf("%s", msg); +} + +static void netc_recv_fillrect(uint16_t x, uint16_t y, + uint16_t w, uint16_t h, + uint8_t color) { + fillrect(x, y, w, h, color); +} + +static void netc_recv_bitmapdata(uint16_t bitmapid, + uint16_t width, uint16_t height, + uint8_t hastransparency, + char *new_image_data) { + netcbitmap_maybefree(bitmapid); + netc_bitmaps[bitmapid] = new Bitmap(width, height, + (unsigned char *) new_image_data, + "fromserver", + hastransparency, + 1); +} + +static void netc_recv_bitmapdel(uint16_t bitmapid) { + netcbitmap_maybefree(bitmapid); +} + +static void netc_recv_bitmapblitfs(uint16_t bitmapid) { + if (netc_bitmaps[bitmapid] != NULL) { + netc_bitmaps[bitmapid]->blit_fullscreen(); + } +} + +static void netc_recv_bitmapblit(uint16_t bitmapid, int16_t xx, int16_t yy) { + if (netc_bitmaps[bitmapid] != NULL) { + netc_bitmaps[bitmapid]->blit(xx, yy); + } +} + +static void netc_recv_bitmapblitclipped(uint16_t bitmapid, + int16_t xx, int16_t yy, + uint16_t rx, uint16_t ry, + uint16_t rx2, uint16_t ry2) { + if (netc_bitmaps[bitmapid] != NULL) { + netc_bitmaps[bitmapid]->blit(xx, yy, rx, ry, rx2, ry2); + } +} + +static void netc_recv_blittobitmap(uint16_t source_bitmapid, + uint16_t target_bitmapid, + int16_t xx, + int16_t yy) { + if (netc_bitmaps[source_bitmapid] != NULL && + netc_bitmaps[target_bitmapid] != NULL) { + netc_bitmaps[source_bitmapid]-> + blit_to_bitmap(netc_bitmaps[target_bitmapid], xx, yy); + } +} + +static void netc_recv_fade_out(uint8_t type) { + selected_fade_out(type); +} + +static void netc_recv_play_music(char *modname) { + sdl_play_music_named(modname); +} + +static void netc_recv_stop_music(void) { + sdl_stop_music(); +} + +static void netc_recv_play_sample(char *samplename, + uint8_t leftvol, uint8_t rightvol, + uint8_t looping) { + sdl_play_sample_named(samplename, leftvol, rightvol, looping); +} + +static void netc_recv_stop_all_samples(void) { + sdl_stop_all_samples(); +} + +// dispatcher to process an incoming packet from server +// returns 1 if it was processed successfully, 0 if not +static int netc_receive_packet(uint32_t length, uint8_t type, void *data) { + char *cdata = (char *)data; + + if (type == NET_PKTTYPE_QUIT && length == 5) { + netc_recv_quit(); + } else if (type == NET_PKTTYPE_VIDEOMODE && length == 6) { + uint8_t new_mode = *(uint8_t *)cdata; + if (new_mode > 1) + return 0; + netc_recv_videomode(new_mode); + } else if (type == NET_PKTTYPE_SETPAL_RANGE && length >= 8) { + uint8_t firstcolor = *(uint8_t *)cdata; + uint16_t n = read_from_net_16(cdata + 1); + if (n == 0 || firstcolor + n > 256 || length != 8+((unsigned int)n)*3) + return 0; + netc_recv_setpal_range(firstcolor, n, cdata + 3); + } else if (type == NET_PKTTYPE_SETPAL_RANGE_BLACK && length == 8) { + uint8_t firstcolor = *(uint8_t *)cdata; + uint16_t n = read_from_net_16(cdata + 1); + if (n == 0 || firstcolor + n > 256) + return 0; + netc_recv_setpal_range_black(firstcolor, n); + } else if (type == NET_PKTTYPE_ENDOFFRAME && length == 5) { + netc_recv_endofframe(); + } else if (type == NET_PKTTYPE_PING && length == 6) { + uint8_t pingid = *(uint8_t *)cdata; + netc_recv_ping(pingid); + } else if (type == NET_PKTTYPE_GAMEMODE && length == 6) { + uint8_t new_mode = *(uint8_t *)cdata; + if (new_mode > 1) + return 0; + netc_recv_gamemode(new_mode); + } else if (type == NET_PKTTYPE_INFOMSG && length == 76) { + if (!check_printable_string(cdata, 71)) + return 0; + netc_recv_infomsg(cdata); + } else if (type == NET_PKTTYPE_FILLRECT && length == 14) { + uint16_t x = read_from_net_16(cdata); + uint16_t y = read_from_net_16(cdata + 2); + uint16_t w = read_from_net_16(cdata + 4); + uint16_t h = read_from_net_16(cdata + 6); + uint8_t color = *(uint8_t *)(cdata + 8); + + if (w == 0 || h == 0 || + x + w > ((current_mode == VGA_MODE) ? 320 : 800) || + y + h > ((current_mode == VGA_MODE) ? 200 : 600)) + return 0; + netc_recv_fillrect(x, y, w, h, color); + } else if (type == NET_PKTTYPE_BITMAPDATA && length >= 12) { + uint16_t bitmapid = read_from_net_16(cdata); + uint16_t width = read_from_net_16(cdata + 2); + uint16_t height = read_from_net_16(cdata + 4); + uint8_t hastransparency = *(uint8_t *)(cdata + 6); + if (width > 2400 || height > 600 || hastransparency > 1 || + length != 12+((unsigned int)width)*((unsigned int)height)) + return 0; + netc_recv_bitmapdata(bitmapid, width, height, hastransparency, + cdata + 7); + } else if (type == NET_PKTTYPE_BITMAPDEL && length == 7) { + uint16_t bitmapid = read_from_net_16(cdata); + netc_recv_bitmapdel(bitmapid); + } else if (type == NET_PKTTYPE_BITMAPBLITFS && length == 7) { + uint16_t bitmapid = read_from_net_16(cdata); + netc_recv_bitmapblitfs(bitmapid); + } else if (type == NET_PKTTYPE_BITMAPBLIT && length == 11) { + uint16_t bitmapid = read_from_net_16(cdata); + int16_t xx = read_from_net_s16(cdata + 2); + int16_t yy = read_from_net_s16(cdata + 4); + if (xx < -4000 || xx > 4000 || yy < -4000 || yy > 4000) + return 0; + netc_recv_bitmapblit(bitmapid, xx, yy); + } else if (type == NET_PKTTYPE_BITMAPBLITCLIPPED && length == 19) { + uint16_t bitmapid = read_from_net_16(cdata); + int16_t xx = read_from_net_s16(cdata + 2); + int16_t yy = read_from_net_s16(cdata + 4); + uint16_t rx = read_from_net_16(cdata + 6); + uint16_t ry = read_from_net_16(cdata + 8); + uint16_t rx2 = read_from_net_16(cdata + 10); + uint16_t ry2 = read_from_net_16(cdata + 12); + if (xx < -4000 || xx > 4000 || yy < -4000 || yy > 4000 || + rx > rx2 || ry > ry2 || rx2 > 799 || ry2 > 599) + return 0; + netc_recv_bitmapblitclipped(bitmapid, xx, yy, rx, ry, rx2, ry2); + } else if (type == NET_PKTTYPE_BLITTOBITMAP && length == 13) { + uint16_t source_bitmapid = read_from_net_16(cdata); + uint16_t target_bitmapid = read_from_net_16(cdata + 2); + int16_t xx = read_from_net_s16(cdata + 4); + int16_t yy = read_from_net_s16(cdata + 6); + if (xx < -4000 || xx > 4000 || yy < -4000 || yy > 4000) + return 0; + netc_recv_blittobitmap(source_bitmapid, target_bitmapid, xx, yy); + } else if (type == NET_PKTTYPE_FADE_OUT && length == 6) { + uint8_t type = *(uint8_t *)cdata; + if (type > 4) + return 0; + netc_recv_fade_out(type); + } else if (type == NET_PKTTYPE_PLAY_MUSIC && length == 12) { + if (!check_strict_string(cdata, 7)) + return 0; + netc_recv_play_music(cdata); + } else if (type == NET_PKTTYPE_STOP_MUSIC && length == 5) { + netc_recv_stop_music(); + } else if (type == NET_PKTTYPE_PLAY_SAMPLE && length == 15) { + if (!check_strict_string(cdata, 7)) + return 0; + uint8_t leftvol = *(uint8_t *)(cdata + 7); + uint8_t rightvol = *(uint8_t *)(cdata + 8); + uint8_t looping = *(uint8_t *)(cdata + 9); + if (leftvol > 32 || rightvol > 32 || looping > 1) + return 0; + netc_recv_play_sample(cdata, leftvol, rightvol, looping); + } else if (type == NET_PKTTYPE_STOP_ALL_SAMPLES && length == 5) { + netc_recv_stop_all_samples(); + } else { + return 0; + } + + return 1; // ok +} + +/* process possible incoming data in netc_recvubuf */ +static void netc_maybe_receive(void) { + uint32_t length; + uint8_t type; + char *data = netc_recvubuf; + uint32_t left = netc_recvubufend; + + while (left >= 5) { /* 5 bytes is the minimum packet size */ + memcpy(&length, data, 4); + length = SDL_SwapBE32(length); + if (length < 5 || length > MAXIMUM_S_PACKET_LENGTH) { + netc_printf("Error: invalid data from server (packet length %u)", + length); + netc_close(); + return; + } + if (left < length) + break; /* not a full packet */ + + memcpy(&type, &data[4], 1); + + if (!netc_receive_packet(length, type, &data[5])) { + netc_printf("Unknown data from server (type %u length %u)", + type, length); + return; + } + if (netc_socket == -1) + return; + + data += length; + left -= length; + } + + if (left == 0) { + netc_recvubufend = 0; + } else { + memmove(netc_recvubuf, data, left); + netc_recvubufend = left; + } +} + +static void netc_uncompress_init(void) { + int r; + + netc_recvzs.next_in = Z_NULL; + netc_recvzs.avail_in = 0; + netc_recvzs.next_out = Z_NULL; + netc_recvzs.avail_out = 0; + netc_recvzs.zalloc = Z_NULL; + netc_recvzs.zfree = Z_NULL; + netc_recvzs.opaque = Z_NULL; + + r = inflateInit(&netc_recvzs); + assert(r == Z_OK); +} + +static void netc_uncompress_deinit(void) { + inflateEnd(&netc_recvzs); +} + +/* + * Uncompresses the data in netc_recvcbuf into netc_recvubuf and calls + * netc_maybe_receive() for any new uncompressed data. + */ +static void netc_uncompress_and_receive(void) { + int r; + + do { + netc_recvzs.next_in = (Bytef *) netc_recvcbuf; + netc_recvzs.avail_in = netc_recvcbufend; + netc_recvzs.next_out = (Bytef *) &netc_recvubuf[netc_recvubufend]; + netc_recvzs.avail_out = NETC_RECVU_BUFFER_SIZE - netc_recvubufend; + + r = inflate(&netc_recvzs, Z_SYNC_FLUSH); + + if (r != Z_OK && r != Z_STREAM_END && r != Z_BUF_ERROR) { + netc_printf("Error %d uncompressing data from server", r); + netc_close(); + return; + } + + if (netc_recvzs.avail_in == 0) { + netc_recvcbufend = 0; + } else { + memmove(netc_recvcbuf, + netc_recvzs.next_in, + netc_recvzs.avail_in); + netc_recvcbufend = netc_recvzs.avail_in; + } + + netc_recvubufend = NETC_RECVU_BUFFER_SIZE - netc_recvzs.avail_out; + + netc_maybe_receive(); + + if (r == Z_STREAM_END) { + inflateReset(&netc_recvzs); + } + } while (r == Z_STREAM_END || (r == Z_OK && netc_recvzs.avail_out == 0)); +} + +static int netc_connect(const char *host, int port) { + struct sockaddr_in sin; + struct hostent *he; + int flags; + fd_set writefds; + struct timeval timeout; + + netc_sendbufend = 0; + netc_recvubufend = 0; + netc_recvcbufend = 0; + + netc_printf("Connecting to %s port %d", host, port); + + he = gethostbyname(host); + if (he == NULL) { + netc_printf("gethostbyname: %s", hstrerror(h_errno)); + return 0; + } + + netc_socket = socket(PF_INET, SOCK_STREAM, 0); + if (netc_socket < 0) { + perror("socket"); + exit(1); + } + + flags = fcntl(netc_socket, F_GETFL); + flags |= O_NONBLOCK; + if (fcntl(netc_socket, F_SETFL, flags) < 0) { + perror("fcntl"); + exit(1); + } + + sin.sin_family = AF_INET; + sin.sin_port = htons(port); + sin.sin_addr = *(struct in_addr *)he->h_addr; + + if (connect(netc_socket, + (struct sockaddr *)&sin, + sizeof(struct sockaddr_in)) == 0) + goto connect_ok; + if (errno != EINPROGRESS) { + netc_printf("connect: %s", strerror(errno)); + goto connect_error; + } + + for (;;) { + FD_ZERO(&writefds); + FD_SET(netc_socket, &writefds); + timeout.tv_sec = 0; + timeout.tv_usec = 40000; // 1/25 seconds (less than a frame) + + if (select(netc_socket + 1, NULL, &writefds, NULL, &timeout) < 0) { + perror("select"); + exit(1); + } + + if (FD_ISSET(netc_socket, &writefds)) { + int val; + socklen_t len = sizeof(int); + if (getsockopt(netc_socket, SOL_SOCKET, SO_ERROR, + &val, &len) < 0) { + perror("getsockopt"); + exit(1); + } + if (val == 0) + goto connect_ok; + else if (val == EINPROGRESS) + continue; + else { + netc_printf("connect: %s", strerror(val)); + goto connect_error; + } + } + + netc_endframe(); + update_key_state(); + if (key && key[SDL_SCANCODE_ESCAPE]) { + netc_printf("Aborted"); + wait_relase(); + goto connect_error; + } + } + + connect_ok: + netc_printf("Connected to server"); + return 1; + + connect_error: + close(netc_socket); + netc_socket = -1; + return 0; +} + +static void netc_doselect(void) { + fd_set readfds, writefds, exceptfds; + struct timeval timeout; + + assert(netc_socket >= 0); + + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&exceptfds); + FD_SET(netc_socket, &readfds); + FD_SET(netc_socket, &exceptfds); + if (netc_sendbufend > 0) + FD_SET(netc_socket, &writefds); + + timeout.tv_sec = 0; + timeout.tv_usec = 20000; // 1/50 seconds (less than half a frame) + + if (select(netc_socket + 1, + &readfds, &writefds, &exceptfds, + &timeout) < 0) { + perror("select"); + exit(1); + } + + if (FD_ISSET(netc_socket, &exceptfds)) { + netc_printf("Server connection exception"); + netc_close(); + return; + } + + if (FD_ISSET(netc_socket, &readfds)) { + int r = read(netc_socket, + &netc_recvcbuf[netc_recvcbufend], + NETC_RECVC_BUFFER_SIZE - netc_recvcbufend); + if (r < 0) { + netc_printf("Read error from server: %s", strerror(errno)); + netc_close(); + return; + } else if (r == 0) { /* EOF */ + netc_printf("EOF from server"); + netc_close(); + return; + } else { + netc_recvcbufend += r; + netc_uncompress_and_receive(); + if (netc_socket == -1) + return; + } + } + + if (FD_ISSET(netc_socket, &writefds) && netc_sendbufend > 0) { + int w = write(netc_socket, netc_sendbuf, netc_sendbufend); + if (w < 0) { + netc_printf("Write error to server: %s", strerror(errno)); + netc_close(); + return; + } else if (w == 0) { + ; /* will retry later */ + } else if (((unsigned int)w) < netc_sendbufend) { + memmove(netc_sendbuf, &netc_sendbuf[w], netc_sendbufend - w); + netc_sendbufend -= w; + } else { + netc_sendbufend = 0; + } + } +} + +static void netc_controls(void) { + uint8_t controls; + int down, up, roll, guns, bombs; + static int power = 0; // stored for on/off power + static uint8_t last_sent_controls = 255; + + if (netc_game_mode != 1) { + last_sent_controls = 255; + return; + } + + if (netc_controls_country >= 0) { + get_controls_for_player(netc_controls_country, + &down, &up, &power, + &roll, &guns, &bombs); + + controls = 0; + if (down) controls |= 1; + if (up) controls |= 2; + if (power) controls |= 4; + if (roll) controls |= 8; + if (guns) controls |= 16; + if (bombs) controls |= 32; + + if (controls != last_sent_controls) { + netc_send_setcontrols(controls); + last_sent_controls = controls; + } + } +} + +/* + * Activates or deactivates (countrynum==-1) controls for countrynum, + * using solo controls of player rosternum. + */ +void netclient_activate_controls(int countrynum, int rosternum) { + if (countrynum >= 0) { + set_keys_from_roster(countrynum, rosternum); + if (netc_socket != -1 && netc_controls_country != countrynum) + netc_send_wantcontrols(countrynum); + netc_controls_country = countrynum; + } else { + if (netc_socket != -1 && netc_controls_country >= 0) + netc_send_disablecontrols(); + netc_controls_country = -1; + } +} + +void netclient_loop(const char *host, int port, + const char *playername, const char *password) { + int client_exit = 0; + + netc_game_mode = 0; + netc_clear_printed_text(); + netcbitmap_init(); + netc_uncompress_init(); + + if (!netc_connect(host, port)) + goto netclient_loop_end; + + netc_send_greeting(1, playername, password); + + if (netc_controls_country >= 0) + netc_send_wantcontrols(netc_controls_country); + + netc_last_endofframe = SDL_GetTicks(); + + while (netc_socket != -1) { + netc_doselect(); + if (netc_socket == -1) + break; + if (netc_last_endofframe + 2000 < SDL_GetTicks()) { + if (netc_last_warn + 3000 < SDL_GetTicks()) { + netc_printf("No data from server for %d seconds", + (SDL_GetTicks() - netc_last_endofframe)/1000); + netc_last_warn = SDL_GetTicks(); + } + netc_endframe(); // draw things on screen + } + + update_key_state(); + if (key && key[SDL_SCANCODE_ESCAPE]) { + client_exit = 1; + wait_relase(); + break; + } + netc_controls(); + } + + if (netc_socket != -1) { + netc_printf("Client quitting"); + netc_send_quit(); + netc_doselect(); + netc_close(); + } + + netclient_loop_end: + netc_uncompress_deinit(); + netcbitmap_free_all(); + + if (!client_exit) { + big_warning(netc_printed_text("Network game client:\n")); + } +} diff --git a/src/io/netclient.h b/src/io/netclient.h new file mode 100644 index 0000000..a494839 --- /dev/null +++ b/src/io/netclient.h @@ -0,0 +1,28 @@ +/* + * Triplane Classic - a side-scrolling dogfighting game. + * Copyright (C) 1996,1997,2009 Dodekaedron Software Creations Oy + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * tjt@users.sourceforge.net + */ + +#ifndef NETCLIENT_H +#define NETCLIENT_H + +void netclient_activate_controls(int countrynum, int rosternum); +void netclient_loop(const char *host, int port, + const char *playername, const char *password); + +#endif diff --git a/src/io/network.cpp b/src/io/network.cpp new file mode 100644 index 0000000..2ee1e95 --- /dev/null +++ b/src/io/network.cpp @@ -0,0 +1,1208 @@ +/* + * Triplane Classic - a side-scrolling dogfighting game. + * Copyright (C) 1996,1997,2009 Dodekaedron Software Creations Oy + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * tjt@users.sourceforge.net + */ + +/******************************************************************************* + + Purpose: + Networked game server + +*******************************************************************************/ + +#include "util/wutil.h" +#include "io/network.h" +#include "io/video.h" +#include "gfx/bitmap.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// maximum number of simultaneous (accept()ed) connections +#define MAX_CONNECTIONS 20 +// buffer sizes for sending +// uncompressed: needs to fit at least a maximum-sized packet +#define SENDU_BUFFER_SIZE 512*1024 +// compressed: needs to at least fit all data of a single frame +// this is allocated for every connection +#define SENDC_BUFFER_SIZE 4*1024*1024 +// receive buffer size, allocated for every connection +#define RECEIVE_BUFFER_SIZE 2048 +// maximum possible packet length from client to host +#define MAXIMUM_C_PACKET_LENGTH 80 +// max. number of lines of network information and chat to display on screen +#define NETINFO_LINES 15 + +static int network_host_active = 0, network_display_enabled = 1; +static int listen_socket = -1; +static char net_password[21]; +static int game_mode = 0, last_pingid = 0; +static uint32_t last_ping_end = 0; + +// values for clients[i].state +#define CS_UNUSED 0 +#define CS_CONNECTED 1 +#define CS_ACCEPTED 2 + +static struct clientdata { + uint8_t state; // values CS_* + // fields below are invalid if state == CS_UNUSED + int socket; + struct sockaddr_in addr; + uint32_t connecttime; + char rbuffer[RECEIVE_BUFFER_SIZE]; + int rbufend; + // fields below are invalid if state < CS_ACCEPTED + // FIXME make cbuffer a ring buffer? + char cbuffer[SENDC_BUFFER_SIZE]; + int cbufend; + z_stream zs; + char name[21]; + int last_pingid; + // which country the client has sent WANTCONTROLS for, -1 if none + int wanted_controls; +} clients[MAX_CONNECTIONS]; + +// which clientname is allowed to have the controls for each player if +// it wants to (empty string = host only) +static char controls_clientname[4][21]; +// which clientid currently has the controls +static int controls_active_for_client[4]; + +static uint8_t net_controls[4]; // the current controls (in bits 0-5) + +// uncompressed packets to be compressed and moved to client buffers +static char netsend_ubuffer[SENDU_BUFFER_SIZE]; +static int netsend_ubufend = 0; +static int netsend_target = -1; + +static char netinfo_texts[NETINFO_LINES][400]; +static uint32_t netinfo_times[NETINFO_LINES]; +static int netinfo_next = 0; +static Font *netinfo_font; + +/* + * Prints a message on screen in the host and possibly the clients + * to_clients: 0 = to host only, 1 = to host and all clients + * fmt, ...: printf()-style format string and arguments + * Please don't include newlines in fmt (this prints exactly one + * line). + */ +static void netinfo_printf(uint8_t to_clients, const char *fmt, ...) { + char teksti[400]; + va_list arg; + + va_start(arg, fmt); + vsprintf(teksti, fmt, arg); + va_end(arg); + + printf("Net: %s\n", teksti); + + if (to_clients) + netsend_infomsg(teksti); + + strcpy(netinfo_texts[netinfo_next], teksti); + netinfo_times[netinfo_next] = SDL_GetTicks(); + netinfo_next = (netinfo_next + 1) % NETINFO_LINES; +} + +void network_print_serverinfo(void) { + int x = 12, y = 6, linesep = 10; + int i, nclients = 0; + uint32_t oldesttime; + int display_was_enabled; + + if (!network_host_active) + return; + + // don't show this info in the client windows + display_was_enabled = network_display_enable(0); + + for (i = 0; i < MAX_CONNECTIONS; i++) + if (clients[i].state >= CS_ACCEPTED) + nclients++; + + netinfo_font->printf(x, y, "Server active (%d client%s)", + nclients, nclients==1 ? "" : "s"); + y += linesep; + + // don't display info older than 10 seconds + oldesttime = SDL_GetTicks() - 10000; + + i = (netinfo_next + 1) % NETINFO_LINES; + do { + if (netinfo_times[i] != 0 && netinfo_times[i] > oldesttime) { + netinfo_font->printf(x, y, "%s", netinfo_texts[i]); + y += linesep; + } + i = (i + 1) % NETINFO_LINES; + } while (i != (netinfo_next + 1) % NETINFO_LINES); + + network_display_enable(display_was_enabled); +} + +static const char *inetaddr_str(const struct sockaddr_in *addr) { + static char buf[100]; + if (addr->sin_family != AF_INET) + return "?unknown address family?"; + sprintf(buf, "%s:%d", + inet_ntoa(addr->sin_addr), + ntohs(addr->sin_port)); + return buf; +} + +static const char *clientaddr_str(int clientid) { + return inetaddr_str(&clients[clientid].addr); +} + +/* Return id of (accepted) client having name, or -1 if none */ +static int clientid_of_name(const char *clientname) { + int i; + + for (i = 0; i < MAX_CONNECTIONS; i++) { + if (clients[i].state < CS_ACCEPTED) + continue; + if (strcmp(clients[i].name, clientname) == 0) + return i; + } + + return -1; +} + +static void compress_init(z_stream *zs) { + int r; + + zs->next_in = Z_NULL; + zs->avail_in = 0; + zs->next_out = Z_NULL; + zs->avail_out = 0; + zs->zalloc = Z_NULL; + zs->zfree = Z_NULL; + zs->opaque = Z_NULL; + + r = deflateInit(zs, 1); + assert(r == Z_OK); +} + +static void compress_deinit(z_stream *zs) { + deflateEnd(zs); +} + +static void client_close(int clientid) { + int i; + + netinfo_printf(0, "Closing connection to client #%d", clientid); + + close(clients[clientid].socket); + if (clients[clientid].state >= CS_ACCEPTED) + compress_deinit(&clients[clientid].zs); + clients[clientid].state = CS_UNUSED; + clients[clientid].socket = -1; + + for (i = 0; i < 4; i++) { + if (controls_active_for_client[i] == clientid) { + network_reallocate_controls(); + break; + } + } +} + +/* does not remove compressed data from netsend_ubuffer */ +static void do_compress(int clientid, int flushmode) { + struct clientdata *client = &clients[clientid]; + int r; + + if (client->state < CS_ACCEPTED) + return; + + client->zs.next_in = (Bytef *) netsend_ubuffer; + client->zs.avail_in = netsend_ubufend; + + client->zs.next_out = (Bytef *) + &client->cbuffer[client->cbufend]; + client->zs.avail_out = SENDC_BUFFER_SIZE - client->cbufend; + + r = deflate(&client->zs, flushmode); + assert(r == Z_OK || r == Z_BUF_ERROR || r == Z_STREAM_END); + + if (client->zs.avail_out == 0) { + // client cbuffer is full + netinfo_printf(0, "Error: %s did not keep up with sent data", + clients[clientid].name); + client_close(clientid); + return; + } + + client->cbufend = SENDC_BUFFER_SIZE - client->zs.avail_out; + + assert(client->zs.avail_in == 0); // all data was compressed + + if (flushmode == Z_FINISH) { + assert(r == Z_STREAM_END); + deflateReset(&client->zs); + } +} + +static void net_do_compress(int flushmode) { + if (netsend_target == -1) { + int i; + for (i = 0; i < MAX_CONNECTIONS; i++) + do_compress(i, flushmode); + } else { + do_compress(netsend_target, flushmode); + } + netsend_ubufend = 0; +} + +static void write_to_net(const void *data, int bytes) { + assert(bytes <= SENDU_BUFFER_SIZE); + + if (bytes > SENDU_BUFFER_SIZE - netsend_ubufend) + net_do_compress(Z_NO_FLUSH); + + assert(bytes <= SENDU_BUFFER_SIZE - netsend_ubufend); + memcpy(&netsend_ubuffer[netsend_ubufend], data, bytes); + netsend_ubufend += bytes; +} + +static void write_to_net_8(uint8_t value) { + write_to_net(&value, sizeof(uint8_t)); +} + +static void write_to_net_s16(int16_t value) { + int16_t be_value = SDL_SwapBE16(value); + write_to_net(&be_value, sizeof(int16_t)); +} + +static void write_to_net_16(uint16_t value) { + uint16_t be_value = SDL_SwapBE16(value); + write_to_net(&be_value, sizeof(uint16_t)); +} + +static void write_to_net_32(uint32_t value) { + uint32_t be_value = SDL_SwapBE32(value); + write_to_net(&be_value, sizeof(uint32_t)); +} + +static void write_to_net_fixedstring(const char *s, int length) { + char buf[300]; + assert(length <= 300); + memset(buf, 0, length); + strncpy(buf, s, length-1); + write_to_net(buf, length); +} + +static void write_to_net_hdr(uint32_t length, uint8_t type) { + write_to_net_32(length); + write_to_net_8(type); +} + +// changes target of future netsend_* commands +// newtarget = clientid or -1 = all +// returns old target +static int netsend_change_target(int newtarget) { + int oldtarget = netsend_target; + + if (newtarget == oldtarget) + return oldtarget; + + assert(newtarget >= -1 && newtarget < MAX_CONNECTIONS); + + net_do_compress(Z_PARTIAL_FLUSH); + assert(netsend_ubufend == 0); + + netsend_target = newtarget; + + return oldtarget; +} + +void netsend_videomode(char mode) { + if (!network_host_active || !network_display_enabled) + return; + + write_to_net_hdr(6, NET_PKTTYPE_VIDEOMODE); + write_to_net_8(mode); +} + +// oper = 0: send as is +// oper = 1: reverse order and multiply by 4 +// oper = 2: multiply by 4 +void netsend_setpal_range(const char pal[][3], + int firstcolor, int n, int oper) { + int i; + + if (!network_host_active || !network_display_enabled) + return; + + write_to_net_hdr(8+n*3, NET_PKTTYPE_SETPAL_RANGE); + write_to_net_8(firstcolor); + write_to_net_16(n); + + if (oper == 1) { + for (i = n - 1; i >= 0; i--) { + write_to_net_8(4 * pal[i][0]); + write_to_net_8(4 * pal[i][1]); + write_to_net_8(4 * pal[i][2]); + } + } else if (oper == 2) { + for (i = 0; i < n; i++) { + write_to_net_8(4 * pal[i][0]); + write_to_net_8(4 * pal[i][1]); + write_to_net_8(4 * pal[i][2]); + } + } else { + for (i = 0; i < n; i++) { + write_to_net_8(pal[i][0]); + write_to_net_8(pal[i][1]); + write_to_net_8(pal[i][2]); + } + } +} + +void netsend_setpal_range_black(int firstcolor, int n) { + if (!network_host_active || !network_display_enabled) + return; + + write_to_net_hdr(8, NET_PKTTYPE_SETPAL_RANGE_BLACK); + write_to_net_8(firstcolor); + write_to_net_16(n); +} + +void netsend_endofframe(void) { + if (!network_host_active || !network_display_enabled) + return; + + write_to_net_hdr(5, NET_PKTTYPE_ENDOFFRAME); + net_do_compress(Z_PARTIAL_FLUSH); +} + +void netsend_ping(int pingid) { + if (!network_host_active) + return; + + write_to_net_hdr(6, NET_PKTTYPE_PING); + write_to_net_8(pingid); + net_do_compress(Z_PARTIAL_FLUSH); +} + +void netsend_gamemode(char mode) { + if (!network_host_active) + return; + + write_to_net_hdr(6, NET_PKTTYPE_GAMEMODE); + write_to_net_8(mode); +} + +void netsend_infomsg(const char *msg) { + if (!network_host_active) + return; + + write_to_net_hdr(76, NET_PKTTYPE_INFOMSG); + write_to_net_fixedstring(msg, 71); +} + +void netsend_fillrect(int x, int y, int w, int h, int c) { + if (!network_host_active || !network_display_enabled) + return; + + write_to_net_hdr(14, NET_PKTTYPE_FILLRECT); + write_to_net_16(x); + write_to_net_16(y); + write_to_net_16(w); + write_to_net_16(h); + write_to_net_8(c); +} + +int netsend_bitmapdata(int bitmapid, + int width, int height, int hastransparency, + const unsigned char *image_data) { + if (!network_host_active || !network_display_enabled) + return 0; + + write_to_net_hdr(12+width*height, NET_PKTTYPE_BITMAPDATA); + write_to_net_16(bitmapid); + write_to_net_16(width); + write_to_net_16(height); + write_to_net_8(hastransparency ? 1 : 0); + write_to_net(image_data, width*height); + + return 1; +} + +void netsend_bitmapdel(int bitmapid) { + if (!network_host_active || !network_display_enabled) + return; + + write_to_net_hdr(7, NET_PKTTYPE_BITMAPDEL); + write_to_net_16(bitmapid); +} + +void netsend_bitmapblitfs(int bitmapid) { + if (!network_host_active || !network_display_enabled) + return; + + write_to_net_hdr(7, NET_PKTTYPE_BITMAPBLITFS); + write_to_net_16(bitmapid); +} + +void netsend_bitmapblit(int bitmapid, int xx, int yy) { + if (!network_host_active || !network_display_enabled) + return; + + write_to_net_hdr(11, NET_PKTTYPE_BITMAPBLIT); + write_to_net_16(bitmapid); + write_to_net_s16(xx); + write_to_net_s16(yy); +} + +void netsend_bitmapblitclipped(int bitmapid, int xx, int yy, + int rx, int ry, int rx2, int ry2) { + if (!network_host_active || !network_display_enabled) + return; + + write_to_net_hdr(19, NET_PKTTYPE_BITMAPBLITCLIPPED); + write_to_net_16(bitmapid); + write_to_net_s16(xx); + write_to_net_s16(yy); + write_to_net_16(rx); + write_to_net_16(ry); + write_to_net_16(rx2); + write_to_net_16(ry2); +} + +void netsend_blittobitmap(int source_bitmapid, int target_bitmapid, + int xx, int yy) { + if (!network_host_active || !network_display_enabled) + return; + + write_to_net_hdr(13, NET_PKTTYPE_BLITTOBITMAP); + write_to_net_16(source_bitmapid); + write_to_net_16(target_bitmapid); + write_to_net_s16(xx); + write_to_net_s16(yy); +} + +void netsend_fade_out(int type) { + if (!network_host_active || !network_display_enabled) + return; + + write_to_net_hdr(6, NET_PKTTYPE_FADE_OUT); + write_to_net_8(type); +} + +void netsend_play_music(const char *modname) { + if (!network_host_active) + return; + + write_to_net_hdr(12, NET_PKTTYPE_PLAY_MUSIC); + write_to_net_fixedstring(modname, 7); +} + +void netsend_stop_music() { + if (!network_host_active) + return; + + write_to_net_hdr(5, NET_PKTTYPE_STOP_MUSIC); +} + +void netsend_play_sample(const char *samplename, int leftvol, + int rightvol, int looping) { + if (!network_host_active) + return; + + write_to_net_hdr(15, NET_PKTTYPE_PLAY_SAMPLE); + write_to_net_fixedstring(samplename, 7); + write_to_net_8(leftvol); + write_to_net_8(rightvol); + write_to_net_8(looping ? 1 : 0); +} + +void netsend_stop_all_samples(void) { + if (!network_host_active) + return; + + write_to_net_hdr(5, NET_PKTTYPE_STOP_ALL_SAMPLES); +} + +/* sends any initial packets to a newly accepted client */ +static void send_initial_data(int clientid) { + int oldtarget = netsend_change_target(clientid); + + netsend_gamemode(game_mode); + netsend_mode_and_curpal(); + all_bitmaps_resend_if_sent(); + /* FIXME send any currently playing sounds and music */ + + netsend_change_target(oldtarget); +} + + +void network_activate_host(const char *listenaddr, + int port, + const char *password, + Font *info_font) { + struct sockaddr_in sin; + int i; + + strncpy(net_password, password, 20); + net_password[20] = 0; + + network_host_active = 1; + netsend_ubufend = 0; + last_pingid = 0; + game_mode = 0; + netinfo_next = 0; + netinfo_font = info_font; + + for (i = 0; i < MAX_CONNECTIONS; i++) { + clients[i].state = CS_UNUSED; + clients[i].socket = -1; + } + + for (i = 0; i < NETINFO_LINES; i++) { + netinfo_times[i] = 0; + } + + for (i = 0; i < 4; i++) { + /* + * The default is to allow no controls for network players + * (otherwise we might need to check config.player_type to see + * which players exist). + */ + controls_clientname[i][0] = '\0'; + controls_active_for_client[i] = -1; + net_controls[i] = 0; + } + + listen_socket = socket(PF_INET, SOCK_STREAM, 0); + if (listen_socket < 0) { + perror("socket"); + exit(1); + } + + int val = 1; + setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(int)); + /* don't care if this failed */ + + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_ANY); + if (listenaddr[0] != '\0') + inet_aton(listenaddr, &sin.sin_addr); + sin.sin_port = htons(port); + + if (bind(listen_socket, (sockaddr *)&sin, sizeof(sin)) < 0) { + perror("bind"); + exit(1); + } + + if (listen(listen_socket, 5) < 0) { + perror("listen"); + exit(1); + } + + netinfo_printf(0, "Server listening on TCP port %d", port); +} + +static void net_recv_quit(int client) { + netinfo_printf(0, "%s has disconnected", clients[client].name); + client_close(client); +} + +static void net_recv_pong(int client, uint8_t pingid) { + clients[client].last_pingid = pingid; +} + +static void net_recv_want_controls(int client, uint8_t playernum) { + clients[client].wanted_controls = playernum; + network_reallocate_controls(); +} + +static void net_recv_disable_controls(int client) { + int i; + + clients[client].wanted_controls = -1; + + for (i = 0; i < 4; i++) { + if (controls_active_for_client[i] == client) { + network_reallocate_controls(); + break; + } + } +} + +static void net_recv_set_controls(int client, + uint8_t controls) { + int i; + + if (game_mode != 1) + return; /* and ignore the packet */ + + for (i = 0; i < 4; i++) { + if (controls_active_for_client[i] == client) { + net_controls[i] = controls; + break; + } + } + /* else just ignore the packet */ +} + +/* + * Possibly sets the controls for playernum from the network. This + * function either leaves the arguments alone, or changes them to + * contain values from the network. + */ +void network_controls_for_player(int playernum, + int *down, int *up, + int *power, int *roll, + int *guns, int *bombs) { + if (!network_host_active) + return; + + if (controls_clientname[playernum][0] == '\0') + return; /* no network controls for this player */ + + uint8_t controls = net_controls[playernum]; + *down = (controls & 1) ? 1 : 0; + *up = (controls & 2) ? 1 : 0; + *power = (controls & 4) ? 1 : 0; + *roll = (controls & 8) ? 1 : 0; + *guns = (controls & 16) ? 1 : 0; + *bombs = (controls & 32) ? 1 : 0; +} + +/* dispatcher to process an incoming packet from client */ +static void net_receive_packet(int client, + uint32_t length, uint8_t type, void *data) { + /* + * Note that no endianness conversions are required, since all + * data except the length is 8-bit. + */ + if (clients[client].state == CS_CONNECTED) { /* has not greeted yet */ + if (type != NET_PKTTYPE_C_GREETING || + length != 48 || + ((uint8_t *)data)[0] != 1) { + netinfo_printf(0, "Error: invalid first packet from client #%d", client); + client_close(client); + return; + } + + char *c_password = &((char *)data)[22]; + c_password[20] = 0; + if (strcmp(c_password, net_password) != 0) { + netinfo_printf(0, "Error: invalid password from client #%d", + client); + client_close(client); + return; + } + + char *c_playername = &((char *)data)[1]; + c_playername[20] = 0; + if (!check_strict_string(c_playername, 21)) { + netinfo_printf(0, "Error: invalid player name from client #%d", + client); + client_close(client); + return; + } + if (clientid_of_name(c_playername) != -1) { + // c_playername is already in use! + netinfo_printf(0, "Error: name of client #%d (%s) is already in use", + client, c_playername); + client_close(client); + return; + } + strcpy(clients[client].name, c_playername); + + netinfo_printf(0, "New client #%d is %s", + client, clients[client].name); + + clients[client].cbufend = 0; + compress_init(&clients[client].zs); + // Pretend the client has replied to a currently ongoing ping + clients[client].last_pingid = last_pingid; + clients[client].wanted_controls = -1; + clients[client].state = CS_ACCEPTED; + send_initial_data(client); + } else if (type == NET_PKTTYPE_C_QUIT) { + if (length != 5) + return; + net_recv_quit(client); + } else if (type == NET_PKTTYPE_C_PONG) { + if (length != 6) + return; + uint8_t pingid = ((uint8_t *)data)[0]; + net_recv_pong(client, pingid); + } else if (type == NET_PKTTYPE_C_WANTCONTROLS) { + if (length != 6) + return; + uint8_t playernum = ((uint8_t *)data)[0]; + if (playernum > 3) + return; + net_recv_want_controls(client, playernum); + } else if (type == NET_PKTTYPE_C_DISABLECONTROLS) { + if (length != 5) + return; + net_recv_disable_controls(client); + } else if (type == NET_PKTTYPE_C_SETCONTROLS) { + if (length != 6) + return; + uint8_t controls = *((uint8_t *)data); + controls &= 0x3f; /* only 6 lower bits are used */ + net_recv_set_controls(client, controls); + } else return; /* ignores unknown packet types */ +} + +/* process possible incoming data in clients[client].rbuffer */ +static void net_maybe_receive(int client) { + uint32_t length; + uint8_t type; + char *clientdata = clients[client].rbuffer; + uint32_t left = clients[client].rbufend; + + while (left >= 5) { /* 5 bytes is the minimum packet size */ + memcpy(&length, clientdata, 4); + length = SDL_SwapBE32(length); + if (length < 5 || length > MAXIMUM_C_PACKET_LENGTH) { + netinfo_printf(0, "Error: invalid data from client #%d (packet length %u)", + client, length); + client_close(client); + return; + } + if (left < length) + break; /* not a full packet */ + + memcpy(&type, &clientdata[4], 1); + + net_receive_packet(client, length, type, &clientdata[5]); + if (clients[client].state == CS_UNUSED) + return; + + clientdata += length; + left -= length; + } + + if (left == 0) { + clients[client].rbufend = 0; + } else { + memmove(clients[client].rbuffer, clientdata, left); + clients[client].rbufend = left; + } +} + +int network_display_enable(int enable) { // returns previous value + int oldval = network_display_enabled; + network_display_enabled = enable; + return oldval; +} + +/* + * Finds the color that clientname wishes to control, if any. + * Returns the color (0-3) or -1 if none. + */ +int network_find_preferred_color(const char *clientname) { + int i = clientid_of_name(clientname); + if (i == -1) + return -1; + else + return clients[i].wanted_controls; +} + +/* + * Finds the next or previous client that wants to control something. + * Finds a new clientname starting from clientname, and writes the + * result back into the variable. + * + * A choice can be selected by giving the name to + * network_set_allowed_controls. This function also cycles through the + * special case of an empty client name. + * + * If previous = 1, finds the previous one, otherwise finds the next + * one (in alphabetical order of client names). If the given + * clientname is empty, finds the last or first one instead. + * + * clientname should have space for at least 21 bytes. + */ +void network_find_next_controls(int previous, char *clientname) { + int i, clientid = -1; + + if (!network_host_active) { + clientname[0] = '\0'; + return; + } + + /* + * Find a client that wants to control something and has the + * next-smaller or next-larger clientname + */ + for (clientid = -1, i = 0; i < MAX_CONNECTIONS; i++) { + if (clients[i].state < CS_ACCEPTED) + continue; + if (clients[i].wanted_controls != -1 && + /* in the right direction from clientname */ + (clientname[0] == '\0' || + (previous ? + strcmp(clients[i].name, clientname) < 0 : + strcmp(clients[i].name, clientname) > 0)) && + (clientid == -1 || /* no existing candidate */ + /* closer to the existing candidate */ + (previous ? + strcmp(clients[i].name, clients[clientid].name) > 0 : + strcmp(clients[i].name, clients[clientid].name) < 0))) { + /* i is a new candidate */ + clientid = i; + } + } + + if (clientid == -1) { /* no next/previous client */ + clientname[0] = '\0'; + return; + } + + strcpy(clientname, clients[clientid].name); +} + +/* + * Sets which client is allowed to control playernum (0-3). + * clientname: client name to allow (NULL or "" = allow host only) + * + * Call network_reallocate_controls after this to have the current + * situation reflect the change! + */ +void network_set_allowed_controls(int playernum, const char *clientname) { + if (clientname == NULL || clientname[0] == '\0') + controls_clientname[playernum][0] = '\0'; + else + strcpy(controls_clientname[playernum], clientname); +} + +/* Stores current values of allowed controls for playernum into *_ret */ +void network_get_allowed_controls(int playernum, char *clientname_ret) { + strcpy(clientname_ret, controls_clientname[playernum]); +} + +/* Selects controlling players again using current "allowed" settings */ +void network_reallocate_controls(void) { + int i, shouldbe; + + if (!network_host_active) + return; + + for (i = 0; i < 4; i++) { + if (controls_clientname[i][0] == '\0') + shouldbe = -1; + else + shouldbe = clientid_of_name(controls_clientname[i]); + if (shouldbe != -1 && clients[shouldbe].wanted_controls == -1) + shouldbe = -1; + if (controls_active_for_client[i] != shouldbe) { + controls_active_for_client[i] = shouldbe; + net_controls[i] = 0; + // FIXME notify the players of the change? + } + } +} + +/* Returns a string describing who controls playernum */ +const char *network_controlling_player_string(int playernum) { + static char buf[100]; + + if (!network_host_active) + return "no network game"; + + if (controls_clientname[playernum][0] == '\0') { + return "local player"; + } else if (controls_active_for_client[playernum] == -1) { + sprintf(buf, "remote player %s", + controls_clientname[playernum]); + } else { + sprintf(buf, "remote player %s", + clients[controls_active_for_client[playernum]].name); + } + return buf; +} + +void network_change_game_mode(int newmode) { + game_mode = newmode; + + if (!network_host_active) + return; + + netsend_gamemode(newmode); +} + +static void network_handle_timeouts() { + static uint32_t last_periodic_ping = 0; + int i; + + uint32_t time = SDL_GetTicks(); + + if (time > last_periodic_ping + 2000) { + for (i = 0; i < MAX_CONNECTIONS; i++) { + /* Close old non-accepted connections */ + if (clients[i].state == CS_CONNECTED && + time > clients[i].connecttime + 15000) { + netinfo_printf(0, "No data received from client #%d", i); + client_close(i); + } + + /* Close connections that haven't replied to the last 10 pings */ + if (clients[i].state >= CS_ACCEPTED && + (256 + last_pingid - clients[i].last_pingid) % 256 > 10) { + netinfo_printf(0, "Ping timeout for %s", + clients[i].name); + client_close(i); + } + } + + /* Send pings every 2 seconds */ + network_ping(0); + last_periodic_ping = time; + } +} + +/* this is called periodically (once or twice every frame) */ +void network_update(void) { + fd_set readfds, writefds, exceptfds; + struct timeval timeout; + int i; + int maxfd = listen_socket; + int retryselect = 1, canretryselect = 1; + + if (!network_host_active) + return; + + net_do_compress(Z_NO_FLUSH); + + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&exceptfds); + + if (listen_socket != -1) + FD_SET(listen_socket, &readfds); + + for (i = 0; i < MAX_CONNECTIONS; i++) { + if (clients[i].state > CS_UNUSED) { + if (maxfd < clients[i].socket) + maxfd = clients[i].socket; + FD_SET(clients[i].socket, &readfds); + FD_SET(clients[i].socket, &exceptfds); + if (clients[i].state >= CS_ACCEPTED && + clients[i].cbufend > 0) + FD_SET(clients[i].socket, &writefds); + } + } + + while (retryselect && canretryselect) { + /* + * FIXME It would be better to use the select timeout instead + * of SDL_Delay in nopeuskontrolli() when network_host_active. + */ + timeout.tv_sec = 0; + timeout.tv_usec = 0; + + if (select(maxfd + 1, &readfds, &writefds, &exceptfds, &timeout) < 0) { + perror("select"); + exit(1); + } + + retryselect = 0; + + if (FD_ISSET(listen_socket, &readfds)) { + for (i = 0; i < MAX_CONNECTIONS; i++) + if (clients[i].state == CS_UNUSED) + break; + if (i == MAX_CONNECTIONS) { + struct sockaddr_in saddr; + socklen_t size = sizeof(struct sockaddr_in); + int sock = accept(listen_socket, + (struct sockaddr *)&saddr, + &size); + close(sock); + netinfo_printf(0, "Error: too many connections, rejecting %s", + inetaddr_str(&saddr)); + } else { + socklen_t size = sizeof(struct sockaddr_in); + assert(clients[i].state == CS_UNUSED); + clients[i].socket = accept(listen_socket, + (struct sockaddr *)&clients[i].addr, + &size); + if (clients[i].socket < 0) { + netinfo_printf(0, "Error when accepting new client: %s", + strerror(errno)); + clients[i].state = CS_UNUSED; + clients[i].socket = -1; + /* but don't quit */ + } else { + clients[i].state = CS_CONNECTED; + clients[i].connecttime = SDL_GetTicks(); + clients[i].rbufend = 0; + netinfo_printf(0, "New client #%d from %s", + i, clientaddr_str(i)); + } + } + } + + for (i = 0; i < MAX_CONNECTIONS; i++) { + if (clients[i].state > CS_UNUSED) { + if (FD_ISSET(clients[i].socket, &exceptfds)) { + netinfo_printf(0, "Exception from client #%d", i); + client_close(i); + canretryselect = 0; + continue; + } + + if (FD_ISSET(clients[i].socket, &readfds)) { + int r = read(clients[i].socket, + &clients[i].rbuffer[clients[i].rbufend], + RECEIVE_BUFFER_SIZE - clients[i].rbufend); + if (r == RECEIVE_BUFFER_SIZE - clients[i].rbufend) + retryselect = 1; + if (r < 0) { + netinfo_printf(0, "Read error from client #%d: %s", + i, strerror(errno)); + client_close(i); + canretryselect = 0; + continue; + } else if (r == 0) { /* EOF */ + netinfo_printf(0, "EOF from client #%d", i); + client_close(i); + canretryselect = 0; + continue; + } + clients[i].rbufend += r; + net_maybe_receive(i); + } + + if (FD_ISSET(clients[i].socket, &writefds) && + clients[i].state >= CS_ACCEPTED && + clients[i].cbufend > 0) { + + int w = write(clients[i].socket, + clients[i].cbuffer, + clients[i].cbufend); + if (w < 0) { + netinfo_printf(0, "Write error to client #%d: %s", + i, strerror(errno)); + client_close(i); + canretryselect = 0; + continue; + } else if (w < clients[i].cbufend) { + retryselect = 1; + if (w > 0) { + memmove(clients[i].cbuffer, + clients[i].cbuffer + w, + clients[i].cbufend - w); + clients[i].cbufend -= w; + } + } else { /* all sent */ + clients[i].cbufend = 0; + } + } + } + } + } + + network_handle_timeouts(); +} + +/* prepare to quit */ +void network_quit(void) { + int i; + + if (!network_host_active) + return; + + netinfo_printf(1, "Preparing to quit, closing all connections"); + + write_to_net_hdr(5, NET_PKTTYPE_QUIT); + network_update(); + + if (listen_socket != -1) { + close(listen_socket); + listen_socket = -1; + } + + for (i = 0; i < MAX_CONNECTIONS; i++) + if (clients[i].state > CS_UNUSED) + client_close(i); + + network_host_active = 0; +} + +int network_is_active(void) { + return network_host_active; +} + +/* + * Pings all current clients. If seconds > 0, after this + * network_last_ping_done() will return 0 until either all clients + * have replied or the given number of seconds has passed. + */ +void network_ping(int seconds) { + if (!network_host_active) + return; + + last_pingid++; + if (last_pingid > 255) + last_pingid = 0; + + netsend_ping(last_pingid); + + if (seconds > 0) + last_ping_end = SDL_GetTicks() + 1000 * seconds; +} + +/* + * Has everyone replied to the latest ping? + * Returns: 0 if a client has not replied to the latest ping and there + * is still time to wait; 1 if all clients have replied and there is + * still time to wait; 2 if the waiting time has run out. + */ +int network_last_ping_done(void) { + int i; + + if (!network_host_active) + return 1; // safe answer for how this is used + + if (last_ping_end != 0 && SDL_GetTicks() > last_ping_end) { + last_ping_end = 0; + return 2; + } + + for (i = 0; i < MAX_CONNECTIONS; i++) + if (clients[i].state >= CS_ACCEPTED && + clients[i].last_pingid != last_pingid) + break; /* no reply yet */ + + if (i == MAX_CONNECTIONS) /* all have replied */ + return 1; + + if (last_ping_end == 0) + return 2; + + return 0; +} diff --git a/src/io/network.h b/src/io/network.h new file mode 100644 index 0000000..8e2d5ab --- /dev/null +++ b/src/io/network.h @@ -0,0 +1,286 @@ +/* + * Triplane Classic - a side-scrolling dogfighting game. + * Copyright (C) 1996,1997,2009 Dodekaedron Software Creations Oy + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * tjt@users.sourceforge.net + */ + +#ifndef NETWORK_H +#define NETWORK_H + +#include "gfx/font.h" + +/* + * Network traffic is a stream of packets (not the underlying IP + * packets), each of which contains: + * 1. Length (uint32_t) + * 2. Type (uint8_t) + * 3. Data (length - 5 bytes) + * + * All numbers are in network byte order (i.e., big-endian), most of + * them unsigned. Below, char foo[N] denotes an ASCII string padded to + * the right with NULs to fill N bytes (the Nth byte must be NUL). + * + * All data sent from the server to the clients is compressed using + * zlib (the above-mentioned packets are in the decompressed data). + * Data sent from the clients to the server is not compressed. + * + * If the compressed data stream contains metadata that indicates the + * end of the stream (i.e., zlib's inflate() returns Z_STREAM_END), + * the client needs to restart the decompression. The server may use + * this to reset the compression (though currently it is never used). + * + * When sending NET_PKTTYPE_ENDOFFRAME and NET_PKTTYPE_PING packets, + * the server ensures that enough compressed data to be able to + * decompress the packet is sent immediately. The client does not need + * to care about this. + * + * The valid packets sent from the server to the clients are: + * + * type=NET_PKTTYPE_QUIT: the server is quitting now + * length=5 + * + * type=NET_PKTTYPE_VIDEOMODE: + * uint8_t new_mode VGA_MODE=0=320x200 SVGA_MODE=1=800x600 + * length=6 + * (always followed by a SETPAL_RANGE packet containing all colors + * and BITMAPDATA packets for all bitmaps) + * + * type=NET_PKTTYPE_SETPAL_RANGE: sets palette[firstcolor .. firstcolor+n-1] + * uint8_t firstcolor + * uint16_t n + * uint8_t palette[n][3] r,g,b values 0..255 + * length=8+n*3 + * + * type=NET_PKTTYPE_SETPAL_RANGE_BLACK: + * uint8_t firstcolor + * uint16_t n + * length=8 + * (sets palette[firstcolor .. firstcolor+n-1] to black) + * + * type=NET_PKTTYPE_ENDOFFRAME: end of frame (please display it now) + * length=5 + * + * type=NET_PKTTYPE_PING: should reply with C_PONG packet + * uint8_t pingid copy this into your reply + * length=6 + * (this is used to check network connectivity and to wait for all + * clients just before starting a game; if you don't reply, the server + * will disconnect you after about 20 seconds) + * + * type=NET_PKTTYPE_GAMEMODE: + * uint8_t new_mode 0=in menus now, 1=a game is in progress + * length=6 + * + * type=NET_PKTTYPE_INFOMSG: informational message from host + * char msg[71] only [a-zA-Z0-9] characters + * length=76 + * (should be displayed somewhere on screen for a while) + * + * type=NET_PKTTYPE_FILLRECT: + * uint16_t x, y, w, h coordinates are always inside the screen + * uint8_t color + * length=14 + * + * type=NET_PKTTYPE_BITMAPDATA: new or changed bitmap data for bitmapid + * uint16_t bitmapid + * uint16_t width + * uint16_t height + * uint8_t hastransparency 0=no, 1=yes + * uint8_t new_image_data[width*height] row-major order + * length=12+width*height + * + * type=NET_PKTTYPE_BITMAPDEL: can delete bitmap with bitmapid now + * uint16_t bitmapid + * length=7 + * + * type=NET_PKTTYPE_BITMAPBLITFS: Bitmap::blit_fullscreen() + * uint16_t bitmapid + * length=7 + * + * type=NET_PKTTYPE_BITMAPBLIT: Bitmap::blit() with no clipping + * uint16_t bitmapid + * int16_t xx, yy + * length=11 + * + * type=NET_PKTTYPE_BITMAPBLITCLIPPED: Bitmap::blit() with a clip rectangle + * uint16_t bitmapid + * int16_t xx, yy + * uint16_t rx, ry, rx2, ry2 + * length=19 + * + * type=NET_PKTTYPE_BLITTOBITMAP: Bitmap::blit_to_bitmap() + * uint16_t source_bitmapid + * uint16_t target_bitmapid + * int16_t xx, yy top-left position in target + * length=13 + * + * type=NET_PKTTYPE_FADE_OUT: do a screen fade effect + * uint8_t type 0-4 as in random_fade_out() + * length=6 + * (this packet can safely just be ignored) + * + * type=NET_PKTTYPE_PLAY_MUSIC: + * char modname[7] + * length=12 + * + * type=NET_PKTTYPE_STOP_MUSIC: + * length=5 + * + * type=NET_PKTTYPE_PLAY_SAMPLE: + * char samplename[7] + * uint8_t leftvol, rightvol both 0 to 32 + * uint8_t looping 0=no, 1=yes + * length=15 + * + * type=NET_PKTTYPE_STOP_ALL_SAMPLES: + * length=5 + * + * The valid packets sent from the clients to the server are: + * + * type=NET_PKTTYPE_C_GREETING: must be sent as the first packet + * uint8_t clientversion always 1 for now + * char playername[21] only [a-zA-Z0-9] characters are accepted + * char password[21] must be the one set by the server + * length=48 + * (the server starts sending game data after receiving this packet) + * + * type=NET_PKTTYPE_C_QUIT: this client will quit now + * length=5 + * (it is not mandatory to send this before closing the connection) + * + * type=NET_PKTTYPE_C_PONG: a reply to NET_PKTTYPE_PING + * uint8_t pingid same as in the ping + * length=6 + * + * type=NET_PKTTYPE_C_WANTCONTROLS: + * uint8_t playernum 0-3 + * length=6 + * (this client wants to set controls for playernum) + * FIXME there is currently no confirmation for this + * + * type=NET_PKTTYPE_C_DISABLECONTROLS: + * length=5 + * (this client no longer wants to set controls) + * + * type=NET_PKTTYPE_C_SETCONTROLS: + * uint8_t controls bits 6&7 = 0, bits 0-5 = the controls + * length=6 + * (set the player controls for the current frame; + * bits of controls: 0=down, 1=up, 2=power, 3=roll, 4=guns, 5=bombs; + * zero bit = not pressed, one bit = pressed; + * the client should send these packets only when the game mode set by + * NET_PKTTYPE_GAMEMODE is 1) + * + * A very simple dummy client for debugging and seeing how much data + * is sent (the echo command sends a C_GREETING packet using the default + * password): + * echo -ne "\0\0\0\x30\xc8\x01debugtestingxtesting\0triplane\0\0\0\0\0\0\0\0\0\0\0\0\0" | nc localhost 9763 | pv >/dev/null + * or to see some of the data: + * echo -ne "\0\0\0\x30\xc8\x01debugtestingxtesting\0triplane\0\0\0\0\0\0\0\0\0\0\0\0\0" | nc localhost 9763 | perl -e 'use IO::Uncompress::Inflate qw(inflate); inflate("-" => "-") while !eof();' | hd | head -100 + * However, these dummy clients do not reply to pings, so they will + * only receive about 20 seconds of data before the server disconnects + * them. + */ + +#define NET_PKTTYPE_QUIT 1 +#define NET_PKTTYPE_VIDEOMODE 2 +#define NET_PKTTYPE_SETPAL_RANGE 3 +#define NET_PKTTYPE_SETPAL_RANGE_BLACK 4 +#define NET_PKTTYPE_ENDOFFRAME 5 +#define NET_PKTTYPE_PING 6 +#define NET_PKTTYPE_GAMEMODE 7 +#define NET_PKTTYPE_INFOMSG 8 +//#define NET_PKTTYPE_CHATMSG 9 + +#define NET_PKTTYPE_FILLRECT 10 +#define NET_PKTTYPE_BITMAPDATA 11 +#define NET_PKTTYPE_BITMAPDEL 12 +#define NET_PKTTYPE_BITMAPBLITFS 13 +#define NET_PKTTYPE_BITMAPBLIT 14 +#define NET_PKTTYPE_BITMAPBLITCLIPPED 15 +#define NET_PKTTYPE_BLITTOBITMAP 16 + +#define NET_PKTTYPE_FADE_OUT 21 + +#define NET_PKTTYPE_PLAY_MUSIC 31 +#define NET_PKTTYPE_STOP_MUSIC 32 +#define NET_PKTTYPE_PLAY_SAMPLE 33 +#define NET_PKTTYPE_STOP_ALL_SAMPLES 34 + +#define NET_PKTTYPE_C_GREETING 200 +#define NET_PKTTYPE_C_QUIT 201 +#define NET_PKTTYPE_C_PONG 202 + +#define NET_PKTTYPE_C_WANTCONTROLS 211 +#define NET_PKTTYPE_C_DISABLECONTROLS 212 +#define NET_PKTTYPE_C_SETCONTROLS 213 + +//#define NET_PKTTYPE_C_CHATMSG 220 + +void netsend_videomode(char new_mode); +void netsend_setpal_range(const char pal[][3], + int firstcolor, int n, int oper); +void netsend_setpal_range_black(int firstcolor, int n); +void netsend_endofframe(void); +void netsend_ping(int pingid); +void netsend_fillrect(int x, int y, int w, int h, int c); +void netsend_infomsg(const char *msg); +void netsend_chatmsg(const char *sender, const char *msg); +int netsend_bitmapdata(int bitmapid, + int width, int height, int hastransparency, + const unsigned char *image_data); +void netsend_bitmapdel(int bitmapid); +void netsend_bitmapblitfs(int bitmapid); +void netsend_bitmapblit(int bitmapid, int xx, int yy); +void netsend_bitmapblitclipped(int bitmapid, int xx, int yy, + int rx, int ry, int rx2, int ry2); +void netsend_blittobitmap(int source_bitmapid, int target_bitmapid, + int xx, int yy); + +void netsend_fade_out(int type); + +void netsend_play_music(const char *modname); +void netsend_stop_music(); +void netsend_play_sample(const char *samplename, int leftvol, + int rightvol, int looping); +void netsend_stop_all_samples(void); + +void network_controls_for_player(int playernum, + int *down, int *up, + int *power, int *roll, + int *guns, int *bombs); + +void network_print_serverinfo(void); +int network_display_enable(int enable); +int network_find_preferred_color(const char *clientname); +void network_find_next_controls(int previous, char *clientname); +void network_set_allowed_controls(int playernum, const char *clientname); +void network_get_allowed_controls(int playernum, char *clientname_ret); +void network_reallocate_controls(void); +const char *network_controlling_player_string(int playernum); +void network_change_game_mode(int newmode); +void network_activate_host(const char *listenaddr, + int port, + const char *password, + Font *info_font); +void network_update(void); +void network_quit(void); +int network_is_active(void); +void network_ping(int seconds); +int network_last_ping_done(void); + +#endif diff --git a/src/io/sdl_compat.cpp b/src/io/sdl_compat.cpp index 20adc4b..1661e19 100644 --- a/src/io/sdl_compat.cpp +++ b/src/io/sdl_compat.cpp @@ -20,6 +20,9 @@ #include #include +#include +#include "io/network.h" +#include "io/video.h" #ifdef HAVE_SDL_MIXER /* @@ -36,18 +39,32 @@ #include "util/wutil.h" #include "io/timing.h" +#define MAX_LOADED_SAMPLES 50 +#define MAX_LOADED_MUSICS 10 + +static sb_sample *loaded_samples[MAX_LOADED_SAMPLES]; +static int next_free_sample = 0; +static sb_mod_file *loaded_musics[MAX_LOADED_MUSICS]; +static int next_free_music = 0; + +const Uint8 *key = NULL; +int key_size = 0; + int kbhit(void) { SDL_Event e; int ret; nopeuskontrolli(); - ret = SDL_PeepEvents(&e, 1, SDL_PEEKEVENT, ~0); + SDL_PumpEvents(); + ret = SDL_PeepEvents(&e, 1, SDL_PEEKEVENT, SDL_KEYUP, SDL_KEYUP); if (ret) { - if (e.type == SDL_KEYUP) { - return 1; - } else { - SDL_PollEvent(&e); + // leave SDL_KEYUP event in queue (getch() should be called next) + return 1; + } else { + // clear event queue (triplane code needs only SDL_KEYUP events) + while (SDL_PollEvent(&e)) { + ; } } return 0; @@ -56,31 +73,47 @@ int kbhit(void) { int getch(void) { SDL_Event e; - for (;;) { - if (SDL_PollEvent(&e)) { - if (e.type == SDL_KEYUP) { - int s, m; - s = e.key.keysym.sym; - m = e.key.keysym.mod; - if (s == SDLK_RSHIFT || s == SDLK_LSHIFT) { - continue; - } - if (m == KMOD_LSHIFT || m == KMOD_RSHIFT) { - if (s >= SDLK_a && s <= SDLK_z) { - s = toupper(s); - } + SDL_PumpEvents(); + while (SDL_PollEvent(&e)) { + if (e.type == SDL_KEYUP) { + int s, m; + s = e.key.keysym.sym; + m = e.key.keysym.mod; + if (s == SDLK_RSHIFT || s == SDLK_LSHIFT) { + continue; + } + if (m == KMOD_LSHIFT || m == KMOD_RSHIFT) { + if (s >= SDLK_a && s <= SDLK_z) { + s = toupper(s); } - return s; } + return s; } } + + return 0; // no key was found } void update_key_state(void) { - SDL_Event e; - SDL_PumpEvents(); - while (SDL_PollEvent(&e)) { + while (getch() != 0) ; + + key = SDL_GetKeyboardState(&key_size); +} + +void wait_relase(void) { + int c = 0; + + while (c != key_size) { + nopeuskontrolli(); + do_all(); + update_key_state(); + if (!key) + continue; + + for (c = 0; c < key_size; c++) + if (key[c]) + break; } } @@ -138,6 +171,24 @@ void sdl_play_sample(sb_sample * sample, int looping) { } Mix_SetPanning(ch, l, r); + netsend_play_sample(sample->name, sample->left_volume, + sample->right_volume, looping); +#endif +} + +void sdl_play_sample_named(const char *samplename, + int leftvol, int rightvol, int looping) { +#ifdef HAVE_SDL_MIXER + int i; + + for (i = 0; i < next_free_sample; i++) { + if (strcmp(loaded_samples[i]->name, samplename) == 0) { + loaded_samples[i]->left_volume = leftvol; + loaded_samples[i]->right_volume = rightvol; + sdl_play_sample(loaded_samples[i], looping); + break; + } + } #endif } @@ -145,6 +196,7 @@ void sdl_play_sample(sb_sample * sample, int looping) { void sdl_stop_all_samples(void) { #ifdef HAVE_SDL_MIXER Mix_HaltChannel(-1); + netsend_stop_all_samples(); #endif } @@ -173,6 +225,8 @@ sb_sample *sdl_sample_load(const char *name) { dksclose(); sample = (sb_sample *) walloc(sizeof(sb_sample)); + memset(sample->name, 0, 7); + strncpy(sample->name, name, 6); sample->chunk = Mix_LoadWAV_RW(SDL_RWFromConstMem(p, len), 1); if (sample->chunk == NULL) { @@ -182,6 +236,9 @@ sb_sample *sdl_sample_load(const char *name) { free(p); + assert(next_free_sample < MAX_LOADED_SAMPLES); + loaded_samples[next_free_sample++] = sample; + return sample; #else return NULL; @@ -194,8 +251,20 @@ sb_sample *sdl_sample_load(const char *name) { */ void sdl_free_sample(sb_sample * sample) { #ifdef HAVE_SDL_MIXER + int i; + Mix_FreeChunk(sample->chunk); free(sample); + + for (i = 0; i < next_free_sample; i++) { + if (loaded_samples[i] == sample) { + memmove(&loaded_samples[i], + &loaded_samples[i+1], + (next_free_sample - i - 1) * sizeof(sb_sample *)); + next_free_sample--; + break; + } + } #endif } @@ -220,10 +289,11 @@ sb_mod_file *sdl_load_mod_file(const char *name) { dksclose(); mod = (sb_mod_file *) walloc(sizeof(sb_mod_file)); + memset(mod->name, 0, 7); + strncpy(mod->name, name, 6); rwops = SDL_RWFromConstMem(p, len); - mod->music = Mix_LoadMUS_RW(rwops); - SDL_FreeRW(rwops); + mod->music = Mix_LoadMUSType_RW(rwops, MUS_MOD, SDL_TRUE); if (mod->music == NULL) { fprintf(stderr, "sdl_load_mod_file: %s\n", Mix_GetError()); exit(1); @@ -231,6 +301,9 @@ sb_mod_file *sdl_load_mod_file(const char *name) { free(p); + assert(next_free_music < MAX_LOADED_MUSICS); + loaded_musics[next_free_music++] = mod; + return mod; #else return NULL; @@ -239,8 +312,20 @@ sb_mod_file *sdl_load_mod_file(const char *name) { void sdl_free_mod_file(sb_mod_file * mod) { #ifdef HAVE_SDL_MIXER + int i; + Mix_FreeMusic(mod->music); free(mod); + + for (i = 0; i < next_free_music; i++) { + if (loaded_musics[i] == mod) { + memmove(&loaded_musics[i], + &loaded_musics[i+1], + (next_free_music - i - 1) * sizeof(sb_mod_file *)); + next_free_music--; + break; + } + } #endif } @@ -248,11 +333,26 @@ void sdl_play_music(sb_mod_file * mod) { #ifdef HAVE_SDL_MIXER sdl_stop_all_samples(); Mix_PlayMusic(mod->music, 1); + netsend_play_music(mod->name); +#endif +} + +void sdl_play_music_named(const char *modname) { +#ifdef HAVE_SDL_MIXER + int i; + + for (i = 0; i < next_free_music; i++) { + if (strcmp(loaded_musics[i]->name, modname) == 0) { + sdl_play_music(loaded_musics[i]); + break; + } + } #endif } void sdl_stop_music(void) { #ifdef HAVE_SDL_MIXER Mix_HaltMusic(); + netsend_stop_music(); #endif } diff --git a/src/io/sdl_compat.h b/src/io/sdl_compat.h index 29dd68d..0e607ad 100644 --- a/src/io/sdl_compat.h +++ b/src/io/sdl_compat.h @@ -27,19 +27,13 @@ #define SAMPLE_VOLUME 20 -#define PAUSE_KEY SDLK_PAUSE - #define SOUNDCARD_NONE 0 #define SOUNDCARD_GUS 1 #define SOUNDCARD_SB 2 #define SOUNDCARD_SDL 3 -int kbhit(void); -int getch(void); - -extern unsigned char *key; - typedef struct { + char name[7]; int right_volume, left_volume; #ifdef HAVE_SDL_MIXER Mix_Chunk *chunk; @@ -47,15 +41,25 @@ typedef struct { } sb_sample; typedef struct { + char name[7]; #ifdef HAVE_SDL_MIXER Mix_Music *music; #endif } sb_mod_file; +extern const Uint8 *key; +extern int key_size; + +int kbhit(void); +int getch(void); void update_key_state(void); +void wait_relase(void); + int sdl_init_sounds(void); void sdl_uninit_sounds(void); void sdl_play_sample(sb_sample * sample, int looping = 0); +void sdl_play_sample_named(const char *samplename, + int leftvol, int rightvol, int looping); void sdl_stop_all_samples(void); sb_sample *sdl_sample_load(const char *name); void sdl_free_sample(sb_sample * sample); @@ -63,6 +67,7 @@ void sdl_free_sample(sb_sample * sample); sb_mod_file *sdl_load_mod_file(const char *name); void sdl_free_mod_file(sb_mod_file * mod); void sdl_play_music(sb_mod_file * mod); +void sdl_play_music_named(const char *modname); void sdl_stop_music(void); #endif diff --git a/src/io/timing.cpp b/src/io/timing.cpp index cb3dffa..d7ef768 100644 --- a/src/io/timing.cpp +++ b/src/io/timing.cpp @@ -20,6 +20,7 @@ #include #include +#include "io/network.h" #include "sdl_compat.h" static int enabled = 1; @@ -29,6 +30,7 @@ void nopeuskontrolli(int fps) { uint32_t target_tick; if (!enabled) { + network_update(); return; } @@ -46,6 +48,8 @@ void nopeuskontrolli(int fps) { if (SDL_GetTicks() > viimeinen_aika + 1000 / fps) { viimeinen_aika = SDL_GetTicks(); } + + network_update(); } void nopeuskontrolli_enable(int enable) { diff --git a/src/io/video.cpp b/src/io/video.cpp index 896ef28..d5e17d3 100644 --- a/src/io/video.cpp +++ b/src/io/video.cpp @@ -20,22 +20,20 @@ #include "io/video.h" #include "io/dksfile.h" +#include "io/network.h" #include "util/wutil.h" #include #include #include #include -struct video_state_t video_state = { NULL, 0, 0 }; +struct video_state_t video_state = { NULL, NULL, NULL, NULL, 0 }; struct naytto ruutu; int current_mode = VGA_MODE; unsigned char *vircr; -int update_vircr_mode = 1; -int draw_with_vircr_mode = 1; -int pixel_multiplier = 1; /* current pixel multiplier */ -int pixel_multiplier_vga = 1, pixel_multiplier_svga = 1; +int pixel_multiplier_vga = 3; int wantfullscreen = 1; SDL_Color curpal[256]; @@ -50,6 +48,11 @@ void setpal_range(const char pal[][3], int firstcolor, int n, int reverse) { SDL_Color *cc = (SDL_Color *) walloc(n * sizeof(SDL_Color)); int i, from = (reverse ? n - 1 : 0); + if (pal == NULL) + netsend_setpal_range_black(firstcolor, n); + else + netsend_setpal_range(pal, firstcolor, n, reverse ? 1 : 2); + for (i = 0; i < n; i++) { if (pal == NULL) { cc[i].r = cc[i].g = cc[i].b = 0; @@ -58,103 +61,73 @@ void setpal_range(const char pal[][3], int firstcolor, int n, int reverse) { cc[i].g = 4 * pal[from][1]; cc[i].b = 4 * pal[from][2]; } + cc[i].a = 255; if (reverse) from--; else from++; } - if (draw_with_vircr_mode) { - SDL_SetPalette(video_state.surface, video_state.haverealpalette ? SDL_PHYSPAL : SDL_LOGPAL, cc, firstcolor, n); - } else { - SDL_SetPalette(video_state.surface, SDL_PHYSPAL | SDL_LOGPAL, cc, firstcolor, n); - } memcpy(&curpal[firstcolor], cc, n * sizeof(SDL_Color)); wfree(cc); } -static Uint32 getcolor(unsigned char c) { - if (video_state.haverealpalette) - return c; - else - return SDL_MapRGB(video_state.surface->format, curpal[c].r, curpal[c].g, curpal[c].b); +void netsend_mode_and_curpal(void) { + char pal[256][3]; + int i; + + netsend_videomode(current_mode); + + for (i = 0; i < 256; i++) { + pal[i][0] = curpal[i].r; + pal[i][1] = curpal[i].g; + pal[i][2] = curpal[i].b; + } + + netsend_setpal_range(pal, 0, 256, 0); } void fillrect(int x, int y, int w, int h, int c) { - SDL_Rect r; - r.x = x; - r.y = y; - r.w = w; - r.h = h; - if (pixel_multiplier > 1) { - r.x *= pixel_multiplier; - r.y *= pixel_multiplier; - r.w *= pixel_multiplier; - r.h *= pixel_multiplier; + netsend_fillrect(x, y, w, h, c); + + int screenw = (current_mode == VGA_MODE) ? 320 : 800; + if (w == 1 && h == 1) { + vircr[x + y * screenw] = c; + } else { + int i; + for (i = 0; i < h; i++) + memset(&vircr[x + (y + i) * screenw], c, w); } - SDL_FillRect(video_state.surface, &r, getcolor(c)); } void do_all(int do_retrace) { - if (draw_with_vircr_mode) { - if (pixel_multiplier > 1) { - int i, j, k; - int w = (current_mode == VGA_MODE) ? 320 : 800; - int h = (current_mode == VGA_MODE) ? 200 : 600; - uint8_t *in = vircr, *out = (uint8_t *) video_state.surface->pixels; - /* optimized versions using 32-bit and 16-bit writes when possible */ - if (pixel_multiplier == 4 && sizeof(char *) >= 4) { /* word size >= 4 */ - uint32_t cccc; - for (j = 0; j < h * pixel_multiplier; j += pixel_multiplier) { - for (i = 0; i < w * pixel_multiplier; i += pixel_multiplier) { - cccc = *in | (*in << 8) | (*in << 16) | (*in << 24); - in++; - for (k = 0; k < pixel_multiplier; k++) { - *(uint32_t *) (&out[(j + k) * (w * pixel_multiplier) + i]) = cccc; - } - } - } - } else if (pixel_multiplier == 3) { - uint16_t cc, c; - for (j = 0; j < h * pixel_multiplier; j += pixel_multiplier) { - for (i = 0; i < w * pixel_multiplier; i += pixel_multiplier) { - c = *in++; - cc = c | (c << 8); - for (k = 0; k < pixel_multiplier; k++) { - *(uint16_t *) (&out[(j + k) * (w * pixel_multiplier) + i]) = cc; - out[(j + k) * (w * pixel_multiplier) + i + 2] = c; - } - } - } - } else if (pixel_multiplier == 2) { - uint16_t cc; - for (j = 0; j < h * pixel_multiplier; j += pixel_multiplier) { - for (i = 0; i < w * pixel_multiplier; i += pixel_multiplier) { - cc = *in | (*in << 8); - in++; - for (k = 0; k < pixel_multiplier; k++) { - *(uint16_t *) (&out[(j + k) * (w * pixel_multiplier) + i]) = cc; - } - } - } - } else { /* unoptimized version */ - int l; - uint8_t c; - for (j = 0; j < h * pixel_multiplier; j += pixel_multiplier) { - for (i = 0; i < w * pixel_multiplier; i += pixel_multiplier) { - c = *in++; - for (k = 0; k < pixel_multiplier; k++) { - for (l = 0; l < pixel_multiplier; l++) { - out[(j + k) * (w * pixel_multiplier) + (i + l)] = c; - } - } - } - } - } - } - } - - SDL_Flip(video_state.surface); + // this code is called at the end of every displayed frame + network_print_serverinfo(); + + int w = (current_mode == VGA_MODE) ? 320 : 800; + int wh = (current_mode == VGA_MODE) ? 320 * 200 : 800 * 600; + int i; + uint8_t *in = vircr; + uint32_t *out = video_state.texture_buffer; + + for (i = 0; i < wh; i++, in++, out++) + *out = ((255 << 24) | + (curpal[*in].r << 16) | + (curpal[*in].g << 8) | + curpal[*in].b); + + SDL_UpdateTexture(video_state.texture, + NULL, + video_state.texture_buffer, + w * sizeof(uint32_t)); + SDL_SetRenderDrawColor(video_state.renderer, 0, 0, 0, 255); + SDL_RenderClear(video_state.renderer); + SDL_RenderCopy(video_state.renderer, video_state.texture, NULL, NULL); + + SDL_RenderPresent(video_state.renderer); + + netsend_endofframe(); + network_update(); } static void sigint_handler(int dummy) { @@ -173,50 +146,50 @@ void init_video(void) { signal(SIGINT, sigint_handler); atexit(SDL_Quit); video_state.init_done = 1; - - SDL_WM_SetCaption("Triplane Classic", "Triplane Classic"); + video_state.window = SDL_CreateWindow("Triplane Classic", + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + pixel_multiplier_vga * 320, + pixel_multiplier_vga * 200, + (wantfullscreen + ? SDL_WINDOW_FULLSCREEN_DESKTOP + : SDL_WINDOW_RESIZABLE)); + assert(video_state.window); + video_state.renderer = SDL_CreateRenderer(video_state.window, -1, 0); + assert(video_state.renderer); + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest"); SDL_ShowCursor(SDL_DISABLE); - - if (!draw_with_vircr_mode) { - vircr = (unsigned char *) walloc(800 * 600); - } + vircr = (unsigned char *) walloc(800 * 600); } } static int init_mode(int new_mode, const char *paletname) { - Uint32 mode_flags; - const SDL_VideoInfo *vi; int las, las2; int w = (new_mode == SVGA_MODE) ? 800 : 320; int h = (new_mode == SVGA_MODE) ? 600 : 200; init_video(); - mode_flags = SDL_HWSURFACE | SDL_DOUBLEBUF | SDL_HWPALETTE; - - if (!draw_with_vircr_mode) - mode_flags |= SDL_ANYFORMAT; - if (wantfullscreen) - mode_flags |= SDL_FULLSCREEN; + // FIXME resize the window in non-fullscreen mode? (what about user resizes?) - if (draw_with_vircr_mode && pixel_multiplier > 1) - wfree(vircr); - - pixel_multiplier = (new_mode == SVGA_MODE) ? pixel_multiplier_svga : pixel_multiplier_vga; + if (video_state.texture != NULL) { + SDL_DestroyTexture(video_state.texture); + video_state.texture = NULL; + } + if (video_state.texture_buffer != NULL) { + wfree(video_state.texture_buffer); + video_state.texture_buffer = NULL; + } - video_state.surface = SDL_SetVideoMode(w * pixel_multiplier, h * pixel_multiplier, 8, mode_flags); - assert(video_state.surface); + SDL_RenderSetLogicalSize(video_state.renderer, w, h); - if (draw_with_vircr_mode) { - if (pixel_multiplier > 1) { - vircr = (uint8_t *) walloc(w * h); - } else { - vircr = (uint8_t *) video_state.surface->pixels; - } - } - /* else vircr is preallocated in init_video */ - vi = SDL_GetVideoInfo(); - video_state.haverealpalette = (vi->vfmt->palette != NULL); + video_state.texture = SDL_CreateTexture(video_state.renderer, + SDL_PIXELFORMAT_ARGB8888, + SDL_TEXTUREACCESS_STREAMING, + w, + h); + assert(video_state.texture); + video_state.texture_buffer = (uint32_t *) walloc(w * h * sizeof(uint32_t)); dksopen(paletname); @@ -227,8 +200,8 @@ static int init_mode(int new_mode, const char *paletname) { dksclose(); + netsend_videomode(new_mode); setpal_range(ruutu.paletti, 0, 256); - all_bitmaps_refresh(); current_mode = new_mode; return 1; diff --git a/src/io/video.h b/src/io/video.h index b85d1a5..6f1fa79 100644 --- a/src/io/video.h +++ b/src/io/video.h @@ -35,9 +35,11 @@ struct naytto { extern naytto ruutu; struct video_state_t { - SDL_Surface *surface; + SDL_Window *window; + SDL_Renderer *renderer; + SDL_Texture *texture; + uint32_t *texture_buffer; int init_done; - int haverealpalette; }; extern struct video_state_t video_state; extern SDL_Color curpal[256]; @@ -45,6 +47,7 @@ extern SDL_Color curpal[256]; void alusta_naytto(const char *paletin_nimi); void setpal(int vari, char R, char G, char B); void setpal_range(const char pal[][3], int firstcolor, int n, int reverse = 0); +void netsend_mode_and_curpal(void); void fillrect(int x, int y, int w, int h, int c); void do_all(int do_retrace = 0); int init_vesa(const char *paletname); @@ -54,10 +57,7 @@ void init_video(void); extern unsigned char *vircr; extern Bitmap *standard_background; extern int current_mode; -extern int pixel_multiplier; -extern int update_vircr_mode; -extern int draw_with_vircr_mode; -extern int pixel_multiplier_vga, pixel_multiplier_svga; +extern int pixel_multiplier_vga; extern int wantfullscreen; #endif diff --git a/src/menus/menusupport.cpp b/src/menus/menusupport.cpp new file mode 100644 index 0000000..54babab --- /dev/null +++ b/src/menus/menusupport.cpp @@ -0,0 +1,182 @@ +/* + * Triplane Classic - a side-scrolling dogfighting game. + * Copyright (C) 1996,1997,2009 Dodekaedron Software Creations Oy + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * tjt@users.sourceforge.net + */ + +/* Support functions for menus */ + +#include "menus/menusupport.h" +#include "io/sdl_compat.h" +#include "io/mouse.h" +#include +#include + +static int menu_mousetab = 0, menu_n1 = 0, menu_n2 = 0; +static int menu_mousedx = 0, menu_mousedy = 0; + +void menu_keys(int *exit_flag, int *help_flag) { + int ch; + + while (kbhit()) { + ch = getch(); + if (ch == SDLK_F1) { + if (help_flag != NULL) + *help_flag = !*help_flag; + } else if (ch == SDLK_ESCAPE) { + if (exit_flag != NULL) + *exit_flag = 1; + } else if (ch == SDLK_SPACE) { + menu_n1 = 1; + } else if (ch == SDLK_RETURN) { + menu_n2 = 1; + } else if (ch == SDLK_TAB) { + menu_mousetab++; + } else if (ch == SDLK_LEFT) { + menu_mousedx = -1; + } else if (ch == SDLK_RIGHT) { + menu_mousedx = 1; + } else if (ch == SDLK_UP) { + menu_mousedy = -1; + } else if (ch == SDLK_DOWN) { + menu_mousedy = 1; + } + } +} + +/* + * positions is an array, ordered first by y then by x, ending with an + * entry with active=-1; or NULL if none + */ +void menu_mouse(int *x, int *y, int *n1, int *n2, + const menu_position *positions) { + const menu_position *p, *bestp; + int dist, bestdist; + + koords(x, y, n1, n2); + + if (positions == NULL) { + menu_mousetab = 0; + menu_mousedx = menu_mousedy = 0; + } + + if (menu_mousedx != 0 || menu_mousedy != 0) { + /* + * We select the nearest active position that is at most 45 + * degrees off from the requested direction + */ + bestp = NULL; + bestdist = -1; + for (p = positions; p->active >= 0; p++) { + if (p->active == 0) + continue; + if (p->x == *x && p->y == *y) + continue; + + if (menu_mousedx == 0) { + if (abs(p->x - *x) > abs(p->y - *y)) + continue; + } else { + if (menu_mousedx * (p->x - *x) < 0) + continue; + } + + if (menu_mousedy == 0) { + if (abs(p->y - *y) > abs(p->x - *x)) + continue; + } else { + if (menu_mousedy * (p->y - *y) < 0) + continue; + } + + dist = ((p->y - *y) * (p->y - *y)) + + ((p->x - *x) * (p->x - *x)); + if (bestp == NULL || dist < bestdist) { + bestp = p; + bestdist = dist; + } + } + + if (bestp != NULL) { + *x = bestp->x; + *y = bestp->y; + hiiri_to(*x, *y); + } + + menu_mousedx = menu_mousedy = 0; + } + + if (menu_mousetab > 0) { + for (p = positions; p->active >= 0; p++) { + if (p->active == 0 || + p->y < *y || + (p->y == *y && p->x <= *x)) { + continue; + } + if (menu_mousetab == 1) + break; + menu_mousetab--; + } + + if (p->active < 0) { // no next found, so find first active + for (p = positions; p->active == 0; p++) + ; + } + + if (p->active == 1) { + *x = p->x; + *y = p->y; + hiiri_to(*x, *y); + } + + menu_mousetab = 0; + } + + if (menu_n1 == 1) { + *n1 = 1; + menu_n1 = 0; + } + if (menu_n2 == 1) { + *n2 = 1; + menu_n2 = 0; + } +} + +void wait_mouse_relase(int nokb) { + int n1 = 1, n2 = 1, x, y; + + while (n1 || n2) { + koords(&x, &y, &n1, &n2); + + if (!nokb) + while (kbhit()) + getch(); + } +} + +void wait_press_and_release(void) { + int x, y, n1, n2; + + n1 = 0; + while (!n1) { + if (kbhit()) + break; + koords(&x, &y, &n1, &n2); + } + + wait_mouse_relase(0); +} diff --git a/src/menus/menusupport.h b/src/menus/menusupport.h new file mode 100644 index 0000000..5356c50 --- /dev/null +++ b/src/menus/menusupport.h @@ -0,0 +1,39 @@ +/* + * Triplane Classic - a side-scrolling dogfighting game. + * Copyright (C) 1996,1997,2009 Dodekaedron Software Creations Oy + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * tjt@users.sourceforge.net + */ + +#ifndef MENUSUPPORT_H +#define MENUSUPPORT_H + +/* Support functions for menus */ + +#include + +struct menu_position { + int x, y; + int active; +}; + +void menu_keys(int *exit_flag, int *help_flag); +void menu_mouse(int *x, int *y, int *n1, int *n2, + const menu_position *positions = NULL); +void wait_mouse_relase(int nokb = 0); +void wait_press_and_release(void); + +#endif diff --git a/src/menus/tripmenu.cpp b/src/menus/tripmenu.cpp index 79a5f36..dc384fc 100644 --- a/src/menus/tripmenu.cpp +++ b/src/menus/tripmenu.cpp @@ -22,12 +22,16 @@ #include "io/trip_io.h" #include "triplane.h" -#include "tripmenu.h" +#include "menus/tripmenu.h" +#include "menus/menusupport.h" #include #include #include +#include #include "io/joystick.h" #include "io/sdl_compat.h" +#include "io/network.h" +#include "io/netclient.h" #include "util/wutil.h" #include "world/plane.h" #include "world/tripaudio.h" @@ -44,12 +48,9 @@ int mission_headline_pixels; int aces_number_of_entries; int aces_score[MAX_PLAYERS_IN_ROSTER]; - #define CHARS_PER_LINE 70 #define LINELENGHT 100 -sb_mod_file *national_mod = NULL; - char rank_names[6][10] = { "2nd Lt.", "1st Lt.", @@ -61,30 +62,6 @@ char rank_names[6][10] = { /**************************** Functions ***************************************/ -void show_feat5(void) { - Bitmap *feat5; - feat5 = new Bitmap("FEAT5"); - int n1 = 0, n2 = 0; - int x, y; - - wait_mouse_relase(); - - feat5->info(&x, &y); - - feat5->blit((320 - x) >> 1, (200 - y) >> 1); - do_all(); - - while (!(n1 || n2)) { - koords(&x, &y, &n1, &n2); - - } - - - wait_mouse_relase(); - - -} - int get_rank(int player) { int l, l2, l3; @@ -582,20 +559,24 @@ void load_descriptions(int number) { int solo_player_menu(void) { char facenames[4][7] = { "GERFAC", "FINFAC", "ENGFAC", "JAPFAC" }; char missionnames[4][7] = { "MISSI0", "MISSI1", "MISSI2", "MISSI3" }; - char modnames[4][7] = { "mgerma", "mfinla", "mengla", "mjapan" }; Bitmap *misboa = 0; Bitmap *misbak = 0; Bitmap *face = 0; Bitmap *mission = 0; - int flag = 0; + int flag = 0, exit_flag = 0; int l; int x, y, n1, n2; int highest_mission = 0; + menu_position positions[] = { + { 110, 32, 1 }, { 110, 32+27*1, 1 }, { 110, 32+27*2, 1 }, + { 110, 32+27*3, 1 }, { 110, 32+27*4, 1 }, { 110, 32+27*5, 1 }, + { 300, 170, 1 }, { 0, 0, -1 } }; + if (findparameter("-solomenumission")) { - sscanf(parametrit[findparameter("-solomenumission") + 1], "%d", &mission_re_fly); + sscanf(findparameter_arg("-solomenumission"), "%d", &mission_re_fly); } if (mission_re_fly == 999) { @@ -609,6 +590,10 @@ int solo_player_menu(void) { } + for (l = 0; l <= 5; l++) { + positions[l].active = (l <= highest_mission); + } + if (mission_re_fly != -1) { solo_mission = mission_re_fly; flag = 3; @@ -631,23 +616,16 @@ int solo_player_menu(void) { if ((mission_re_fly == -1) && is_there_sound && config.music_on && !findparameter("-nomusic")) { sdl_stop_music(); - national_mod = sdl_load_mod_file(modnames[solo_country]); - if (national_mod == NULL) { - printf("Error locating music.\n"); - exit(1); - - } - - sdl_play_music(national_mod); - + sdl_play_music(national_mod[solo_country]); } while (flag == 0) { - if (kbhit() && getch() == SDLK_ESCAPE) { + menu_keys(&exit_flag, NULL); + if (exit_flag) { flag = 2; } - koords(&x, &y, &n1, &n2); + menu_mouse(&x, &y, &n1, &n2, positions); cursor->blit(x - 10, y - 10); do_all_clear(); @@ -703,16 +681,12 @@ int solo_player_menu(void) { case 3: if ((mission_re_fly == -1) && is_there_sound && config.music_on && !findparameter("-nomusic")) { sdl_stop_music(); - sdl_free_mod_file(national_mod); - } return 2; case 2: if (is_there_sound && config.music_on && !findparameter("-nomusic")) { sdl_stop_music(); - sdl_free_mod_file(national_mod); - } return 0; @@ -733,7 +707,6 @@ void roster_menu(void) { static int number = -1; int keysetmode = 0; int help_on = 0; - char ch; Bitmap *help; Bitmap *rosteri; Bitmap *buttl, *buttr; @@ -745,6 +718,10 @@ void roster_menu(void) { Bitmap *ribbon[6]; int l, l2, l3, l4; int rank; + menu_position positions[] = { + { 282, 31, 0 /* !keysetmode */ }, { 264, 109, 0 /* !keysetmode */ }, + { 58, 147, 1 }, { 141, 176, 0 /* keysetmode */ }, + { 195, 178, 1 }, { 213, 178, 1 }, { 302, 194, 1 }, { 0, 0, -1 } }; if (number == -1 && roster[0].pilotname[0]) number = 0; @@ -777,20 +754,12 @@ void roster_menu(void) { hiiri_to(264, 109); while (!exit_flag) { - if (kbhit()) { - if (!(ch = getch())) { - ch = getch(); - if (ch == 59) - wtoggle(&help_on); - - } else { - if (ch == 27) - exit_flag = 1; - } - - } + menu_keys(&exit_flag, &help_on); - koords(&x, &y, &n1, &n2); + positions[0].active = !keysetmode; + positions[1].active = !keysetmode; + positions[3].active = keysetmode; + menu_mouse(&x, &y, &n1, &n2, positions); if (number != -1) { if (!keysetmode) { @@ -806,10 +775,10 @@ void roster_menu(void) { setk1->blit(125, 172); frost->printf(125, 93, "Up [%s]\nDown [%s]\nRoll [%s]", - SDL_GetKeyName((SDLKey) roster[number].up), SDL_GetKeyName((SDLKey) roster[number].down), - SDL_GetKeyName((SDLKey) roster[number].roll)); - frost->printf(170, 114, "Power [%s]\nBombs [%s]\nGuns [%s]\n", SDL_GetKeyName((SDLKey) roster[number].power), - SDL_GetKeyName((SDLKey) roster[number].bombs), SDL_GetKeyName((SDLKey) roster[number].guns)); + SDL_GetKeyName((SDL_Keycode) roster[number].up), SDL_GetKeyName((SDL_Keycode) roster[number].down), + SDL_GetKeyName((SDL_Keycode) roster[number].roll)); + frost->printf(170, 114, "Power [%s]\nBombs [%s]\nGuns [%s]\n", SDL_GetKeyName((SDL_Keycode) roster[number].power), + SDL_GetKeyName((SDL_Keycode) roster[number].bombs), SDL_GetKeyName((SDL_Keycode) roster[number].guns)); } } else { @@ -1014,43 +983,43 @@ void roster_menu(void) { rosteri->blit(0, 0); - frost->printf(125, 100, "Key for upward turn [%s]", SDL_GetKeyName((SDLKey) roster[number].up)); + frost->printf(125, 100, "Key for upward turn [%s]", SDL_GetKeyName((SDL_Keycode) roster[number].up)); do_all(); - roster[number].up = select_key(number, roster[number].up); + roster[number].up = select_key(roster[number].up); rosteri->blit(0, 0); - frost->printf(125, 100, "Key for downward turn [%s]", SDL_GetKeyName((SDLKey) roster[number].down)); + frost->printf(125, 100, "Key for downward turn [%s]", SDL_GetKeyName((SDL_Keycode) roster[number].down)); do_all(); - roster[number].down = select_key(number, roster[number].down); + roster[number].down = select_key(roster[number].down); rosteri->blit(0, 0); - frost->printf(125, 100, "Key for roll [%s]", SDL_GetKeyName((SDLKey) roster[number].roll)); + frost->printf(125, 100, "Key for roll [%s]", SDL_GetKeyName((SDL_Keycode) roster[number].roll)); do_all(); - roster[number].roll = select_key(number, roster[number].roll); + roster[number].roll = select_key(roster[number].roll); rosteri->blit(0, 0); - frost->printf(125, 100, "Key for engine power [%s]", SDL_GetKeyName((SDLKey) roster[number].power)); + frost->printf(125, 100, "Key for engine power [%s]", SDL_GetKeyName((SDL_Keycode) roster[number].power)); do_all(); - roster[number].power = select_key(number, roster[number].power); + roster[number].power = select_key(roster[number].power); rosteri->blit(0, 0); - frost->printf(125, 100, "Key for bomb drop [%s]", SDL_GetKeyName((SDLKey) roster[number].bombs)); + frost->printf(125, 100, "Key for bomb drop [%s]", SDL_GetKeyName((SDL_Keycode) roster[number].bombs)); do_all(); - roster[number].bombs = select_key(number, roster[number].bombs); + roster[number].bombs = select_key(roster[number].bombs); rosteri->blit(0, 0); - frost->printf(125, 100, "Key for guns [%s]", SDL_GetKeyName((SDLKey) roster[number].guns)); + frost->printf(125, 100, "Key for guns [%s]", SDL_GetKeyName((SDL_Keycode) roster[number].guns)); do_all(); - roster[number].guns = select_key(number, roster[number].guns); + roster[number].guns = select_key(roster[number].guns); break; case 5: @@ -1151,6 +1120,43 @@ void options_menu(void) { Bitmap *opt1, *opt2; int optimode = 0; int l; + menu_position positions01[] = { /* for optimode 0 and 1 */ + { 252, 29, 1 }, + { 217, 64, 1 }, { 227, 64, 1 }, { 252, 64, 1 }, + { 217, 74, 1 }, { 227, 74, 1 }, + { 217, 84, 1 }, { 227, 84, 1 }, + { 217, 94, 1 }, { 227, 94, 1 }, + { 217, 104, 1 }, { 227, 104, 1 }, { 252, 104, 1 }, + { 217, 114, 1 }, { 227, 114, 1 }, + { 217, 124, 0 /* optimode==1 */ }, { 227, 124, 0 /* optimode==1 */ }, + { 217, 134, 1 }, { 227, 134, 1 }, + { 252, 134, 1 }, { 284, 172, 1 }, { 0, 0, -1 } }; + menu_position positions2[] = { /* for optimode 2 */ + { 252, 29, 1 }, + { 217, 64, 1 }, { 227, 64, 1 }, { 252, 64, 1 }, + { 217, 74, 1 }, { 227, 74, 1 }, + { 252, 104, 1 }, { 252, 134, 1 }, { 284, 172, 1 }, + { 0, 0, -1 } }; + menu_position positions3[] = { /* for optimode 3 */ + { 37, 14, 0 /* config.all_planes_are!=0 */ }, + { 252, 29, 1 }, + { 37, 35, 0 /* config.all_planes_are!=0 */ }, + { 37, 56, 0 /* config.all_planes_are!=0 */ }, + { 217, 64, 1 }, { 227, 64, 1 }, { 252, 64, 1 }, + { 217, 74, 1 }, { 227, 74, 1 }, + { 37, 77, 0 /* config.all_planes_are!=0 */ }, + { 217, 84, 1 }, { 227, 84, 1 }, + { 222, 94, 1 }, + { 217, 104, 1 }, { 227, 104, 1 }, { 252, 104, 1 }, + { 37, 114, 0 /* config.alliance!=0 */ }, + { 217, 114, 1 }, { 227, 114, 1 }, + { 217, 124, 1 }, { 227, 124, 1 }, + { 217, 134, 1 }, { 227, 134, 1 }, { 252, 134, 1 }, + { 37, 144, 0 /* config.alliance!=0 */ }, + { 217, 144, 1 }, { 227, 144, 1 }, + { 217, 154, 1 }, { 227, 154, 1 }, + { 37, 172, 0 /* config.alliance!=0 */ }, + { 284, 172, 1 }, { 0, 0, -1 } }; char selitykset[4][80] = { "This form has questions about your vision.", @@ -1170,11 +1176,26 @@ void options_menu(void) { opt2 = new Bitmap("OPT2"); while (!exit_flag) { - if (kbhit()) - if (getch() == 27) - exit_flag = 1; - - koords(&x, &y, &n1, &n2); + menu_keys(&exit_flag, NULL); + + if (optimode == 0) { + positions01[15].active = 0; + positions01[16].active = 0; + } else if (optimode == 1) { + positions01[15].active = 1; + positions01[16].active = 1; + } else if (optimode == 3) { + positions3[0].active = (config.all_planes_are != 0); + positions3[2].active = (config.all_planes_are != 0); + positions3[3].active = (config.all_planes_are != 0); + positions3[9].active = (config.all_planes_are != 0); + positions3[16].active = (config.alliance != 0); + positions3[24].active = (config.alliance != 0); + positions3[29].active = (config.alliance != 0); + } + menu_mouse(&x, &y, &n1, &n2, + (optimode == 2) ? positions2 : + (optimode == 3) ? positions3 : positions01); optionme->blit(0, 0); kohta[3 - optimode]->blit(248, 13); frost->printf(73, 43, "%s", selitykset[optimode]); @@ -1887,6 +1908,11 @@ void transfer_menu(void) { Bitmap *color_bites[6]; Bitmap *descs[6]; int l; + menu_position positions[] = { + { 90, 20, 1 }, + { 78, 70, 1 }, { 78+1*80, 70, 1 }, { 78+2*80, 70, 1 }, + { 78, 70+50, 1 }, { 78+1*80, 70+50, 1 }, { 78+2*80, 70+50, 1 }, + { 0, 0, -1 } }; optionme = new Bitmap("TRANS2"); @@ -1910,11 +1936,9 @@ void transfer_menu(void) { optionme = new Bitmap("TRANSF"); while (!exit_flag) { - if (kbhit()) - if (getch() == 27) - exit_flag = 1; + menu_keys(&exit_flag, NULL); - koords(&x, &y, &n1, &n2); + menu_mouse(&x, &y, &n1, &n2, positions); optionme->blit(0, 0); color_bites[config.current_multilevel]->blit(39 + config.current_multilevel * 80 - (config.current_multilevel / 3) * 240, @@ -2003,11 +2027,15 @@ static void joystick_setup(int joy, Bitmap * controlme) { controlme->blit(0, 0); frost->printf(54, 93, "Keep joystick idle and press"); frost->printf(54, 100, "Space (Esc=use old settings)"); - do_all(); do { + do_all(); + if (!kbhit()) { + c = 0; + continue; + } c = getch(); - } while (c != 27 && c != ' '); - if (c == 27) + } while (c != SDLK_ESCAPE && c != ' '); + if (c == SDLK_ESCAPE) goto joystick_setup_exit; save_axis_state(idle, joy); @@ -2016,11 +2044,15 @@ static void joystick_setup(int joy, Bitmap * controlme) { controlme->blit(0, 0); frost->printf(54, 93, "Do '%s' on joystick and", acts[i].prompt); frost->printf(54, 100, "press Space or D=disable this"); - do_all(); do { + do_all(); + if (!kbhit()) { + c = 0; + continue; + } c = getch(); - } while (c != 27 && c != ' ' && c != 'd' && c != 'D'); - if (c == 27) { + } while (c != SDLK_ESCAPE && c != ' ' && c != 'd' && c != 'D'); + if (c == SDLK_ESCAPE) { goto joystick_setup_exit; } else if (c == 'd' || c == 'D') { set_disabled_action(acts[i].act); @@ -2040,7 +2072,6 @@ static void joystick_setup(int joy, Bitmap * controlme) { } void controls_menu(void) { - char ch; int help_on = 0; int exit_flag = 0; int x, y, n1, n2; @@ -2049,6 +2080,10 @@ void controls_menu(void) { Bitmap *napp[4]; Bitmap *help; int active = 0; + menu_position positions[] = { + { 80, 15, 1 }, + { 17, 28, 1 }, { 44, 28, 1 }, { 17, 51, 1 }, { 44, 51, 1 }, + { 160, 65, 1 }, { 266, 65, 1 }, { 153, 130, 1 }, { 0, 0, -1 } }; controlme = new Bitmap("NAPPIS"); napp[0] = new Bitmap("NAPPRE"); @@ -2060,20 +2095,9 @@ void controls_menu(void) { while (!exit_flag) { - if (kbhit()) { - if (!(ch = getch())) { - ch = getch(); - if (ch == 59) - wtoggle(&help_on); - - } else { - if (ch == 27) - exit_flag = 1; - } - - } + menu_keys(&exit_flag, &help_on); - koords(&x, &y, &n1, &n2); + menu_mouse(&x, &y, &n1, &n2, positions); controlme->blit(0, 0); napp[active]->blit((active % 2) * 27 + 10, (active / 2) * 23 + 22); @@ -2130,10 +2154,10 @@ void controls_menu(void) { if (config.joystick[0] != active && config.joystick[1] != active) { frost->printf(170, 93, "Up [%s] Down [%s] Roll [%s]", - SDL_GetKeyName((SDLKey) player_keys[active].up), SDL_GetKeyName((SDLKey) player_keys[active].down), - SDL_GetKeyName((SDLKey) player_keys[active].roll)); - frost->printf(170, 100, "Power [%s] Bombs [%s] Guns [%s]", SDL_GetKeyName((SDLKey) player_keys[active].power), - SDL_GetKeyName((SDLKey) player_keys[active].bombs), SDL_GetKeyName((SDLKey) player_keys[active].guns)); + SDL_GetKeyName((SDL_Keycode) player_keys[active].up), SDL_GetKeyName((SDL_Keycode) player_keys[active].down), + SDL_GetKeyName((SDL_Keycode) player_keys[active].roll)); + frost->printf(170, 100, "Power [%s] Bombs [%s] Guns [%s]", SDL_GetKeyName((SDL_Keycode) player_keys[active].power), + SDL_GetKeyName((SDL_Keycode) player_keys[active].bombs), SDL_GetKeyName((SDL_Keycode) player_keys[active].guns)); } else { int joy = (config.joystick[0] == active) ? 0 : 1; char *ups = get_joy_action_string(&joystick_config[joy].up); @@ -2178,45 +2202,45 @@ void controls_menu(void) { controlme->blit(0, 0); napp[active]->blit((active % 2) * 27 + 10, (active / 2) * 23 + 22); - frost->printf(56, 97, "Key for upward turn [%s]", SDL_GetKeyName((SDLKey) player_keys[active].up)); + frost->printf(56, 97, "Key for upward turn [%s]", SDL_GetKeyName((SDL_Keycode) player_keys[active].up)); do_all(); - player_keys[active].up = select_key(active, player_keys[active].up); + player_keys[active].up = select_key(player_keys[active].up); controlme->blit(0, 0); napp[active]->blit((active % 2) * 27 + 10, (active / 2) * 23 + 22); - frost->printf(56, 97, "Key for downward turn [%s]", SDL_GetKeyName((SDLKey) player_keys[active].down)); + frost->printf(56, 97, "Key for downward turn [%s]", SDL_GetKeyName((SDL_Keycode) player_keys[active].down)); do_all(); - player_keys[active].down = select_key(active, player_keys[active].down); + player_keys[active].down = select_key(player_keys[active].down); controlme->blit(0, 0); napp[active]->blit((active % 2) * 27 + 10, (active / 2) * 23 + 22); - frost->printf(56, 97, "Key for roll [%s]", SDL_GetKeyName((SDLKey) player_keys[active].roll)); + frost->printf(56, 97, "Key for roll [%s]", SDL_GetKeyName((SDL_Keycode) player_keys[active].roll)); do_all(); - player_keys[active].roll = select_key(active, player_keys[active].roll); + player_keys[active].roll = select_key(player_keys[active].roll); controlme->blit(0, 0); napp[active]->blit((active % 2) * 27 + 10, (active / 2) * 23 + 22); - frost->printf(56, 97, "Key for engine power [%s]", SDL_GetKeyName((SDLKey) player_keys[active].power)); + frost->printf(56, 97, "Key for engine power [%s]", SDL_GetKeyName((SDL_Keycode) player_keys[active].power)); do_all(); - player_keys[active].power = select_key(active, player_keys[active].power); + player_keys[active].power = select_key(player_keys[active].power); controlme->blit(0, 0); napp[active]->blit((active % 2) * 27 + 10, (active / 2) * 23 + 22); - frost->printf(56, 97, "Key for bomb drop [%s]", SDL_GetKeyName((SDLKey) player_keys[active].bombs)); + frost->printf(56, 97, "Key for bomb drop [%s]", SDL_GetKeyName((SDL_Keycode) player_keys[active].bombs)); do_all(); - player_keys[active].bombs = select_key(active, player_keys[active].bombs); + player_keys[active].bombs = select_key(player_keys[active].bombs); controlme->blit(0, 0); napp[active]->blit((active % 2) * 27 + 10, (active / 2) * 23 + 22); - frost->printf(56, 97, "Key for guns [%s]", SDL_GetKeyName((SDLKey) player_keys[active].guns)); + frost->printf(56, 97, "Key for guns [%s]", SDL_GetKeyName((SDL_Keycode) player_keys[active].guns)); do_all(); - player_keys[active].guns = select_key(active, player_keys[active].guns); + player_keys[active].guns = select_key(player_keys[active].guns); save_keyset(); break; @@ -2262,6 +2286,40 @@ void controls_menu(void) { } +/* Guess an appropriate roster entry for network client clientname */ +static void set_roster_from_clientname(int playernum, + const char *clientname) { + int i, l; + + if (clientname[0] == '\0') { + if (config.player_type[playernum] == 3) + config.player_number[playernum] = -2; + return; + } + + for (i = 0; i < MAX_PLAYERS_IN_ROSTER; i++) { + if (roster[i].pilotname[0] == '\0') + break; + + if (strcmp(clientname, roster[i].pilotname) == 0) { + /* clientname found at i; check that it is not already used */ + for (l = 0; l < 4; l++) { + if (l == playernum) + continue; + if (config.player_number[l] == i) + break; + } + if (l == 4) { + config.player_number[playernum] = i; + return; + } + } + } + + /* clientname was not found */ + if (config.player_type[playernum] == 3) + config.player_number[playernum] = -2; +} void assign_menu(void) { int exit_flag = 0; @@ -2276,7 +2334,26 @@ void assign_menu(void) { int lym[4] = { 0, 11, 24, 36 }; int response; int help_on = 0; - char ch; + char clientname[4][21] = { "", "", "", "" }; + char tmpname[21] = ""; + char unallocated[200] = ""; + bool autoallocate[4] = { true, true, true, true }; + menu_position positions[] = { + /* those with active=0 are for network_is_active() */ + { 100, 15, 1 }, + { 32, 48+lym[0], 1 }, { 185, 48+lym[0], 1 }, + { 32, 48+lym[1], 1 }, { 185, 48+lym[1], 1 }, + { 32, 48+lym[2], 1 }, { 138, 48+lym[2], 0 }, { 147, 48+lym[2], 0 }, + { 185, 48+lym[2], 1 }, { 291, 48+lym[2], 0 }, { 300, 48+lym[2], 0 }, + { 32, 48+lym[3], 1 }, { 138, 48+lym[3], 1 }, { 147, 48+lym[3], 1 }, + { 185, 48+lym[3], 1 }, { 291, 48+lym[3], 1 }, { 300, 48+lym[3], 1 }, + { 32, 121+lym[0], 1 }, { 185, 121+lym[0], 1 }, + { 32, 121+lym[1], 1 }, { 185, 121+lym[1], 1 }, + { 32, 121+lym[2], 1 }, { 138, 121+lym[2], 0 }, { 147, 121+lym[2], 0 }, + { 185, 121+lym[2], 1 }, { 291, 121+lym[2], 0 }, { 300, 121+lym[2], 0 }, + { 32, 121+lym[3], 1 }, { 138, 121+lym[3], 1 }, { 147, 121+lym[3], 1 }, + { 185, 121+lym[3], 1 }, { 291, 121+lym[3], 1 }, { 300, 121+lym[3], 1 }, + { 0, 0, -1 } }; if (!roster[0].pilotname[0]) { response = small_warning("You have no pilots in the roster and\n" @@ -2295,22 +2372,87 @@ void assign_menu(void) { ruksi = new Bitmap("RUKSI"); help = new Bitmap("HELP5"); + if (network_is_active()) { + for (l = 0; l < 4; l++) { + network_get_allowed_controls(l, clientname[l]); + } + } + + if (network_is_active()) { + positions[6].active = 1; + positions[7].active = 1; + positions[9].active = 1; + positions[10].active = 1; + positions[22].active = 1; + positions[23].active = 1; + positions[25].active = 1; + positions[26].active = 1; + } else { + positions[6].active = 0; + positions[7].active = 0; + positions[9].active = 0; + positions[10].active = 0; + positions[22].active = 0; + positions[23].active = 0; + positions[25].active = 0; + positions[26].active = 0; + } while (!exit_flag) { - if (kbhit()) { - if (!(ch = getch())) { - ch = getch(); - if (ch == 59) - wtoggle(&help_on); - - } else { - if (ch == 27) - exit_flag = 1; + if (network_is_active()) { + // try to auto-allocate controls + unallocated[0] = '\0'; + tmpname[0] = '\0'; + for (;;) { + network_find_next_controls(0, tmpname); + if (tmpname[0] == '\0') + break; // no more players wanting controls + for (l = 0; l < 4; l++) + if (strcmp(clientname[l], tmpname) == 0) + break; + if (l != 4) // tmpname already controls something + continue; + l = network_find_preferred_color(tmpname); + if (l == -1) + continue; // doesn't want controls (shouldn't happen) + if (!autoallocate[l]) + goto notallocated; + if (clientname[l][0] != '\0') + goto notallocated; // l is already network-controlled + // handle a solo player, if any + for (l2 = 0; l2 < 4; l2++) { + if (config.player_type[l2] == 1) { + if (l2 != l) { + if (!autoallocate[l2] || clientname[l2][0] != '\0') + // we should not change current solo player + goto notallocated; + config.player_type[l2] = 0; + config.player_number[l2] = -1; + } + // auto-allocate tmpname for solo country l + config.player_type[l] = 1; + goto allocate; + } + } + // we are not in solo mode + // auto-allocate tmpname for country l + config.player_type[l] = 3; + allocate: + strcpy(clientname[l], tmpname); + set_roster_from_clientname(l, clientname[l]); + continue; + notallocated: + l2 = strlen(unallocated); + snprintf(&unallocated[l2], 200-l2, "%s%s (%c)", + l2==0 ? "" : ", ", tmpname, + // FIXME add colored boxes to font to make this look nice? + "GFEJ"[l]); } - } - koords(&x, &y, &n1, &n2); + menu_keys(&exit_flag, &help_on); + + menu_mouse(&x, &y, &n1, &n2, positions); acesme->blit(0, 0); @@ -2328,9 +2470,10 @@ void assign_menu(void) { ruksi->blit(28 + lx, 45 + ly + lym[config.player_type[l]]); - if (config.player_number[l] != -1) { + if (config.player_number[l] == -2) { + frost->printf(46 + lx, 82 + ly, "Anonymous pilot"); + } else if (config.player_number[l] != -1) { frost->printf(46 + lx, 82 + ly, roster[config.player_number[l]].pilotname); - } for (l2 = 0; l2 < 4; l2++) { @@ -2357,12 +2500,45 @@ void assign_menu(void) { } } + if (network_is_active()) { + for (l = 0; l < 4; l++) { + ly = (l / 2) * 73; + lx = (l % 2) ? 153 : 0; + + if (config.player_type[l] != 1 && config.player_type[l] != 3) + continue; + + if (clientname[l][0] == '\0') { + frost->printf(46 + lx, 70 + ly, "Local player"); + } else { + frost->printf(46 + lx, 70 + ly, "Remote: %s", clientname[l]); + } + + if (x >= (134 + lx) && x < (143 + lx) && y >= (67 + ly) && y <= (77 + ly)) { + menusubselect1 = l; + menuselect = 5; + //frost->printf(82,177,"Select next network player"); + } + + if (x >= (143 + lx) && x <= (151 + lx) && y >= (67 + ly) && y <= (77 + ly)) { + menusubselect1 = l; + menuselect = 6; + //frost->printf(82,177,"Select previous network player"); + } + } + } + cursor->blit(x - 10, y - 10); if (help_on) help->blit(0, 0); do_all(); if ((n1 || n2) && menuselect) { + // disable automatic network player allocation for a + // country that has been touched manually + if (menuselect != 2 && menusubselect1 >= 0 && menusubselect1 < 4) + autoallocate[menusubselect1] = false; + switch (menuselect) { case 1: @@ -2370,8 +2546,11 @@ void assign_menu(void) { if (menusubselect2 == 1) { for (l = 0; l < 4; l++) { - if (l == menusubselect1) + if (l == menusubselect1) { + if (config.player_number[l] == -2) + config.player_number[l] = -1; // updated below continue; + } config.player_type[l] = 0; config.player_number[l] = -1; @@ -2392,11 +2571,14 @@ void assign_menu(void) { config.player_number[menusubselect1] = -1; else { if (config.player_number[menusubselect1] == -1) { - - for (l = 0; l < MAX_PLAYERS_IN_ROSTER; l++) { - if (!roster[l].pilotname[0]) { - config.player_type[menusubselect1] = 0; - config.player_number[menusubselect1] = -1; + for (l = 0; l <= MAX_PLAYERS_IN_ROSTER; l++) { + if (l == MAX_PLAYERS_IN_ROSTER || !roster[l].pilotname[0]) { + if (menusubselect2 == 3) { + config.player_number[menusubselect1] = -2; + } else { + config.player_type[menusubselect1] = 0; + config.player_number[menusubselect1] = -1; + } break; } else { for (l3 = 0; l3 < 4; l3++) { @@ -2416,17 +2598,17 @@ void assign_menu(void) { } } - if (l == MAX_PLAYERS_IN_ROSTER) { - config.player_type[menusubselect1] = 0; - config.player_number[menusubselect1] = -1; + } + } + if (network_is_active()) { + for (l = 0; l < 4; l++) { + if (config.player_type[l] == 0 || config.player_type[l] == 2) { + clientname[l][0] = '\0'; } - } } - - break; case 2: @@ -2436,115 +2618,63 @@ void assign_menu(void) { break; case 3: - ly = 0; lx = config.player_number[menusubselect1]; - l2 = lx + 1; - - while (!ly) { - ly = 1; - - if (lx == l2) { - l2 = -1; - break; + l2 = lx; + do { + l2 = (l2 == -2) ? 0 : l2 + 1; + if (l2 >= MAX_PLAYERS_IN_ROSTER || (l2 >= 0 && !roster[l2].pilotname[0])) { + if (config.player_type[menusubselect1] == 3) + l2 = -2; + else + l2 = 0; } - - if (l2 != -1) { - if (!roster[l2].pilotname[0]) { - ly = 0; - l2 = -1; - continue; - } - - - for (l = 0; l < 4; l++) { - if (l == menusubselect1) - continue; - - if (config.player_number[l] == l2) { - l2++; - ly = 0; - continue; - } - - - } - - } else { - - l2 = -1; + if (l2 == lx) + l2 = (config.player_type[menusubselect1] == 3) ? -2 : -1; + if (l2 < 0) break; + for (l = 0; l < 4; l++) { + if (l == menusubselect1) + continue; + if (config.player_number[l] == l2) + break; } - } - + } while (l != 4); config.player_number[menusubselect1] = l2; - break; case 4: - ly = 0; lx = config.player_number[menusubselect1]; - l2 = lx - 1; - if (l2 < -1) - l2 = -1; - - - - while (!ly) { - ly = 1; - - if (l2 != -1) { - - for (l = 0; l < 4; l++) { - if (l == menusubselect1) - continue; - - if (config.player_number[l] == l2) { - l2--; - ly = 0; + l2 = lx; + do { + if (l2 == 0 && config.player_type[menusubselect1] == 3) { + l2 = -2; + } else if (l2 <= 0) { + for (l2 = MAX_PLAYERS_IN_ROSTER - 1; l2 >= 0; l2--) + if (roster[l2].pilotname[0]) break; - } - - - } - } else { - - l2 = -1; - break; + l2--; } - } - - if (l2 == -1) { - for (l2 = MAX_PLAYERS_IN_ROSTER - 1; l2 >= 0; l2--) { - if (roster[l2].pilotname[0]) { - for (l = 0; l < 4; l++) { - if (l == menusubselect1) - continue; - - if (config.player_number[l] == l2) { - break; - - } - } - - if (l == 4) - break; - - } + if (l2 == lx) + l2 = (config.player_type[menusubselect1] == 3) ? -2 : -1; + if (l2 < 0) + break; + for (l = 0; l < 4; l++) { + if (l == menusubselect1) + continue; + if (config.player_number[l] == l2) + break; } - - - config.player_number[menusubselect1] = l2; - break; - } - + } while (l != 4); config.player_number[menusubselect1] = l2; - - - break; - + case 5: case 6: + network_find_next_controls((menuselect == 6), + clientname[menusubselect1]); + set_roster_from_clientname(menusubselect1, + clientname[menusubselect1]); + break; } } @@ -2558,8 +2688,32 @@ void assign_menu(void) { config.player_type[l] = 0; config.player_number[l] = -1; } + if (config.player_number[l] == -2 && config.player_type[l] != 3) { + config.player_type[l] = 0; + config.player_number[l] = -1; + } + if (network_is_active()) { + // check for and remove duplicate network players + if (config.player_type[l] == 3 && clientname[l][0] != '\0') { + for (l2 = l + 1; l2 < 4; l2++) { + if (config.player_type[l2] == 3 && + strcmp(clientname[l], clientname[l2]) == 0) { + config.player_type[l2] = 0; + config.player_number[l2] = -1; + } + } + } + + if (config.player_type[l] == 1 || config.player_type[l] == 3) + network_set_allowed_controls(l, clientname[l]); + else + network_set_allowed_controls(l, ""); + } } + if (network_is_active()) + network_reallocate_controls(); + delete acesme; delete ruksi; delete help; @@ -2575,13 +2729,13 @@ void aces_menu(void) { int menuselect; int current_page = 0; int help_on = 0; - char ch; Bitmap *acesme; Bitmap *firstpage; Bitmap *buttl; Bitmap *buttr; Bitmap *help; - + menu_position positions[] = { { 239, 181, 1 }, { 257, 181, 1 }, + { 280, 181, 1 }, { 0, 0, -1 } }; if (is_there_sound && config.music_on) { sdl_stop_music(); @@ -2601,21 +2755,9 @@ void aces_menu(void) { do_all_clear(); while (!exit_flag) { + menu_keys(&exit_flag, &help_on); - if (kbhit()) { - if (!(ch = getch())) { - ch = getch(); - if (ch == 59) - wtoggle(&help_on); - - } else { - if (ch == 27) - exit_flag = 1; - } - - } - - koords(&x, &y, &n1, &n2); + menu_mouse(&x, &y, &n1, &n2, positions); if (!current_page) firstpage->blit(75, 34); @@ -2739,6 +2881,10 @@ int kangas_menu(void) { int menuselect; int place_x = -2079; int showing_texts = 1; + menu_position positions[] = { + { 15, 18, 1}, + { 8, 195, 1}, { 26, 195, 1}, { 62, 195, 1}, { 80, 195, 1}, + { 0, 0, -1} }; Bitmap *kangas = new Bitmap("KANGAS", 0); Bitmap *buttr = new Bitmap("BUTTR"); @@ -2776,11 +2922,9 @@ int kangas_menu(void) { } while (!exit_flag) { - if (kbhit() && getch() == SDLK_ESCAPE) { - exit_flag = 1; - } + menu_keys(&exit_flag, NULL); - koords(&x, &y, &n1, &n2); + menu_mouse(&x, &y, &n1, &n2, positions); kangas->blit_fullscreen(); kangas_terrain_to_screen(place_x); @@ -2877,7 +3021,6 @@ int kangas_menu(void) { if ((mission_re_fly == -1) && is_there_sound && config.music_on && !findparameter("-nomusic")) { sdl_stop_music(); - sdl_free_mod_file(national_mod); } @@ -2912,12 +3055,9 @@ void credits_menu(void) { init_vga("PALET7"); while (!exit_flag) { - if (kbhit()) { - getch(); - exit_flag = 1; - } + menu_keys(&exit_flag, NULL); + menu_mouse(&x, &y, &n1, &n2); - koords(&x, &y, &n1, &n2); credi1->blit(0, 0); @@ -2965,7 +3105,6 @@ void letter_menu(void) { int exit_flag = 0; int x, y, n1, n2; char country_names[4][10] = { "German", "Finnish", "English", "Japanese" }; - char modnames[4][7] = { "mgerma", "mfinla", "mengla", "mjapan" }; Bitmap *letter; Bitmap *temp; @@ -2976,26 +3115,16 @@ void letter_menu(void) { delete temp; if (is_there_sound && config.music_on && !findparameter("-nomusic")) { - national_mod = sdl_load_mod_file(modnames[solo_country]); - if (national_mod == NULL) { - printf("Error locating music.\n"); - exit(1); - - } - sdl_play_music(national_mod); - + sdl_play_music(national_mod[solo_country]); } letter = new Bitmap("LETTER"); while (!exit_flag) { - if (kbhit()) { - getch(); - exit_flag = 1; - } + menu_keys(&exit_flag, NULL); + menu_mouse(&x, &y, &n1, &n2); - koords(&x, &y, &n1, &n2); letter->blit(0, 0); medal1->blit(15, 80); frost->printf(145, 104, "From: %s central command headquaters.", country_names[solo_country]); @@ -3031,11 +3160,311 @@ void letter_menu(void) { if (is_there_sound && config.music_on && !findparameter("-nomusic")) { sdl_stop_music(); - sdl_free_mod_file(national_mod); } } +void netgame_menu(void) { + int help_on = 0; + int exit_flag = 0; + int i, x, y, n1, n2, menuselect; + Bitmap *netmenu, *right; + Bitmap *assignme, *playerselect; + char str[100]; + menu_position positions[] = { + { 40, 43, 1 }, { 168, 43, 1 }, + { 82, 53, 1 }, { 210, 53, 1 }, + { 40, 73, 1 }, { 168, 73, 1 }, + { 106, 130, 1 }, { 114, 130, 1 }, + { 40, 153, 1 }, + { 67, 174, 1 }, { 195, 174, 1 }, { 284, 180, 1 }, + { 0, 0, -1 } }; + + if (config.netc_solo_controls < 0 || + !roster[config.netc_solo_controls].pilotname[0]) + config.netc_solo_controls = 0; + + if (strcmp(config.netc_playername, "netplayer") == 0 && + roster[config.netc_solo_controls].pilotname[0] && + check_strict_string(roster[config.netc_solo_controls].pilotname, 21)) + strcpy(config.netc_playername, + roster[config.netc_solo_controls].pilotname); + + netmenu = new Bitmap("NETMEN"); + right = new Bitmap("RIGHT"); + assignme = new Bitmap("ASSIGN"); + playerselect = new Bitmap(134, 79, 19, 10, assignme); + + while (!exit_flag) { + menu_keys(&exit_flag, &help_on); + menu_mouse(&x, &y, &n1, &n2, positions); + netmenu->blit(0, 0); + + frost->printf(20+4, 11, "JOIN A NETWORK GAME"); + + frost->printf(20, 30, "Server address:"); + frost->printf(20+10, 40, "%s", config.netc_host); + frost->printf(20, 50, "Server port:"); + frost->printf(20+55, 50, "%d", config.netc_port); + // FIXME hide password? + frost->printf(20, 60, "Server password:"); + frost->printf(20+10, 70, "%s", config.netc_password); + + frost->printf(20, 118, "Use solo controls of:"); + playerselect->blit(101, 125); + if (roster[config.netc_solo_controls].pilotname[0]) + frost->printf(20+10, 128, "%s", + roster[config.netc_solo_controls].pilotname); + + frost->printf(20, 140, "Player name:"); + frost->printf(20+10, 150, "%s", config.netc_playername); + + // FIXME draw the button in the bitmap? + frost->printf(20+5, 170, "[ START THE CLIENT ]"); + + frost->printf(148+4, 11, "HOST A NETWORK GAME"); + + frost->printf(148, 30, "Listen address:"); + frost->printf(148+10, 40, "%s", config.neth_listenaddr); + frost->printf(148, 50, "Server port:"); + frost->printf(148+55, 50, "%d", config.neth_listenport); + // FIXME hide password? + frost->printf(148, 60, "Server password:"); + frost->printf(148+10, 70, "%s", config.neth_password); + + // FIXME draw the button in the bitmap? + if (network_is_active()) { + frost->printf(148+5, 170, "[ STOP THE SERVER ]"); + } else { + frost->printf(148+3, 170, "[ START THE SERVER ]"); + } + + menuselect = 0; + + if (x >= 267 && x <= 301 && y >= 155 && y <= 190) { + menuselect = 1; + } + + if (x >= 20 && x <= 115 && y >= 168 && y <= 182) { + menuselect = 2; + } + + if (x >= 148 && x <= 243 && y >= 168 && y <= 182) { + menuselect = 3; + } + + if (x >= 20+12 && x < 20+12+4*18 && y >= 103 && y <= 103+15) { + menuselect = 10 + (x - 20 - 12) / 18; + } + + if (x >= 102 && x <= 117 && y >= 126 && y <= 126+7) { + menuselect = 15 + (x >= 110); + } + + if (x >= 20+10 && x <= 115 && y >= 35 && y <= 49) { + menuselect = 20; + } + + if (x >= 20+55 && x <= 115 && y >= 50 && y <= 62) { + menuselect = 21; + } + + if (x >= 20+10 && x <= 115 && y >= 65 && y <= 79) { + menuselect = 22; + } + + if (x >= 20+10 && x <= 115 && y >= 145 && y <= 159) { + menuselect = 23; + } + + if (x >= 148+10 && x <= 243 && y >= 35 && y <= 49) { + menuselect = 30; + } + + if (x >= 148+55 && x <= 243 && y >= 50 && y <= 62) { + menuselect = 31; + } + + if (x >= 148+10 && x <= 243 && y >= 65 && y <= 80) { + menuselect = 32; + } + + cursor->blit(x - 10, y - 10); + do_all(); + + if (n1 || n2) { + switch (menuselect) { + + case 1: // Exit menu + if (n1) + random_fade_out(); + else { + tyhjaa_vircr(); + do_all(); + } + exit_flag = 1; + break; + + case 2: // Start the client + netmenu->blit(0, 0, 20+5, 170, 145, 179); + frost->printf(20+5, 170, "Connecting... (Esc to abort)"); + do_all(); + if (network_is_active()) { + small_warning("You cannot be both server and client\n" + "at the same time.\n" + "\n" + "Please stop the server first."); + break; + } + if (!roster[config.netc_solo_controls].pilotname[0]) { + small_warning("Your roster is empty, but you need a\n" + "player in the roster to set your keys.\n" + "\n" + "Please go to the roster menu and\n" + "create a player."); + break; + } + + + set_keys_none(); + /* Hardcode preferred color for now. */ + netclient_activate_controls(0 /*red*/, config.netc_solo_controls); + netclient_loop(config.netc_host, config.netc_port, + config.netc_playername, config.netc_password); + // Return back to netgame menu after client is done + init_vga("PALET5"); + break; + + case 3: // Activate/deactivate the host + if (network_is_active()) { + network_quit(); + wait_mouse_relase(); + } else { + if (n1) + random_fade_out(); + else { + tyhjaa_vircr(); + do_all(); + } + network_activate_host(config.neth_listenaddr, + config.neth_listenport, + config.neth_password, + foverlay); + // After activating the host, move to the assign + // players menu (to select players) and then back + // to the main menu + exit_flag = 1; + wait_mouse_relase(); + // Unset any solo players, so that the assign + // players menu starts assigning players for a + // multiplayer game by default + for (i = 0; i < 4; i++) { + if (config.player_type[i] == 1) { + config.player_type[i] = 0; + config.player_number[i] = -1; + } + } + assign_menu(); + } + break; + + case 15: // Solo controls up arrow + config.netc_solo_controls++; + if (config.netc_solo_controls >= MAX_PLAYERS_IN_ROSTER || + !roster[config.netc_solo_controls].pilotname[0]) { + config.netc_solo_controls = 0; + } + if (roster[config.netc_solo_controls].pilotname[0] && + check_strict_string(roster[config.netc_solo_controls].pilotname, 21)) + strcpy(config.netc_playername, + roster[config.netc_solo_controls].pilotname); + wait_mouse_relase(); // so this does not repeat + break; + + case 16: // Solo controls down arrow + if (config.netc_solo_controls <= 0) { + for (config.netc_solo_controls = MAX_PLAYERS_IN_ROSTER - 1; + /* finally select 0 if no others are ok */ + config.netc_solo_controls > 0; + config.netc_solo_controls--) + if (roster[config.netc_solo_controls].pilotname[0]) + break; + } else { + config.netc_solo_controls--; + } + if (roster[config.netc_solo_controls].pilotname[0] && + check_strict_string(roster[config.netc_solo_controls].pilotname, 21)) + strcpy(config.netc_playername, + roster[config.netc_solo_controls].pilotname); + wait_mouse_relase(); // so this does not repeat + break; + + case 20: // Address of host (client) + netmenu->blit(0, 0, 20+10, 40, 145, 49); + frost->scanf(20+10, 40, config.netc_host, 79); + break; + + case 21: // Port number (client) + netmenu->blit(0, 0, 20+55, 50, 145, 59); + sprintf(str, "%d", config.netc_port); + frost->scanf(20+55, 50, str, 15); + config.netc_port = atoi(str); + if (config.netc_port < 1 || config.netc_port > 65535) + config.netc_port = 9763; + break; + + case 22: // Game password (client) + netmenu->blit(0, 0, 20+10, 70, 145, 79); + frost->scanf(20+10, 70, config.netc_password, 20); + if (config.netc_password[0] == '\0') + strcpy(config.netc_password, "triplane"); + break; + + case 23: // Player name (client) + netmenu->blit(0, 0, 20+10, 150, 145, 159); + frost->scanf(20+10, 150, config.netc_playername, 20); + if (config.netc_playername[0] == '\0' || + !check_strict_string(config.netc_playername, 21)) + strcpy(config.netc_playername, "netplayer"); + break; + + case 30: // Listen address (host) + netmenu->blit(0, 0, 148+10, 40, 255, 49); + frost->scanf(148+10, 40, config.neth_listenaddr, 79); + if (config.neth_listenaddr[0] == '\0') + strcpy(config.neth_listenaddr, "0.0.0.0"); + break; + + case 31: // Listen port (host) + netmenu->blit(0, 0, 148+55, 50, 255, 59); + sprintf(str, "%d", config.neth_listenport); + frost->scanf(148+55, 50, str, 15); + config.neth_listenport = atoi(str); + if (config.neth_listenport < 1 || config.neth_listenport > 65535) + config.neth_listenport = 9763; + break; + + case 32: // Game password (host) + netmenu->blit(0, 0, 148+10, 70, 255, 79); + frost->scanf(148+10, 70, config.neth_password, 20); + if (config.neth_password[0] == '\0') + strcpy(config.neth_password, "triplane"); + break; + + default: + break; + } + } + } + + + delete playerselect; + delete assignme; + delete right; + delete netmenu; + + wait_mouse_relase(); +} void main_menu(void) { int exit_flag = 0; @@ -3043,9 +3472,13 @@ void main_menu(void) { int menuselect; int l, l2, l3; int help_on = 0; - int ch; Bitmap *help; + menu_position positions[] = { + { 51, 40, 1 }, { 103, 40, 1 }, { 156, 40, 1 }, { 214, 40, 1 }, + { 268, 40, 1 }, { 51, 77, 1 }, { 51, 120, 1 }, { 254, 120, 1 }, + { 51, 164, 1 }, { 268, 169, 1 }, { 0, 0, -1 } }; + help = new Bitmap("HELP1"); if (is_there_sound && config.music_on) { @@ -3058,19 +3491,13 @@ void main_menu(void) { hiiri_to(254, 120); while (!exit_flag) { - if (kbhit()) { - ch = getch(); - if (ch == SDLK_F1) { - wtoggle(&help_on); - } else { - if (ch == SDLK_ESCAPE) - exit_flag = 1; - } - } + menu_keys(&exit_flag, &help_on); - koords(&x, &y, &n1, &n2); + menu_mouse(&x, &y, &n1, &n2, positions); menu1->blit(0, 0); // 0,0,799,599 - grid2->printf(34, 156, "Press F1\nfor Help"); + // FIXME write this in a nicer way in the menu1 bitmap data? + // FIXME fix this part of the help bitmap + grid2->printf(39, 157, "NETWORK\n GAME"); for (l = 0; l < 4; l++) if (config.player_type[l] == 1) @@ -3113,35 +3540,49 @@ void main_menu(void) { } - for (l = 0; l < 4; l++) { - if (config.player_type[l] == 3) { - frost->printf(110, 130 + l * 11, "%d. %s", l + 1, roster[config.player_number[l]].pilotname); - - } - - if (config.player_type[l] == 2) { - frost->printf(110, 130 + l * 11, "%d. Computer pilot", l + 1); - + if (network_is_active()) { + for (l = 0; l < 4; l++) { + if (config.player_type[l] == 3) { + frost->printf(100, 121 + l * 15, "%d. %s", + l + 1, + (config.player_number[l] == -2) + ? "Anonymous pilot" + : roster[config.player_number[l]].pilotname); + frost->printf(109, 121 + l * 15 + 7, + "(%s)", + network_controlling_player_string(l)); + } else if (config.player_type[l] == 2) { + frost->printf(100, 121 + l * 15, + "%d. Computer pilot", l + 1); + } else if (config.player_type[l] == 0) { + frost->printf(100, 121 + l * 15, + "%d. Not active", l + 1); + } } - - if (config.player_type[l] == 0) { - frost->printf(110, 130 + l * 11, "%d. Not active", l + 1); - - + } else { + for (l = 0; l < 4; l++) { + if (config.player_type[l] == 3) { + frost->printf(110, 130 + l * 11, "%d. %s", + l + 1, + (config.player_number[l] == -2) + ? "Anonymous pilot" + : roster[config.player_number[l]].pilotname); + } else if (config.player_type[l] == 2) { + frost->printf(110, 130 + l * 11, "%d. Computer pilot", l + 1); + } else if (config.player_type[l] == 0) { + frost->printf(110, 130 + l * 11, "%d. Not active", l + 1); + } } - - - } - } - } else { frost->printf(100, 100, "Sologame active"); frost->printf(100, 110, "%s selected", plane_name[l]); frost->printf(110, 130, "%s flying", roster[config.player_number[l]].pilotname); - + if (network_is_active()) + frost->printf(110, 140, "(%s)", + network_controlling_player_string(l)); } if (help_on) @@ -3193,6 +3634,15 @@ void main_menu(void) { menuselect = 9; } + if (x >= 30 && x <= 73 && y >= 147 && y <= 182) { + frost->printf(245, 32, "Start/join a\nnetwork game"); + menuselect = 10; + } + + if (menuselect == 0) { + frost->printf(247, 32, "Press F1\n for Help"); + } + cursor->blit(x - 10, y - 10); @@ -3258,10 +3708,6 @@ void main_menu(void) { init_vga("PALET5"); - for (l = 0; l < 16; l++) { - player_exists[l] = 0; - plane_present[l] = 0; - } for (l = 0; l < 4; l++) { if (config.player_type[l] == 1 || config.player_type[l] == 3) { if (config.player_number[l] != -1) { @@ -3276,48 +3722,7 @@ void main_menu(void) { break; } - playing_solo = 0; - solo_mode = -1; - - for (l = 0; l < 4; l++) { - switch (config.player_type[l]) { - case 0: - player_exists[l] = 0; - break; - - case 1: - player_exists[l] = 1; - computer_active[l] = 0; - playing_solo = 1; - solo_country = l; - solo_mode = l; - break; - - case 2: - if ((l == 1 || l == 2) && config.current_multilevel == 5) { - player_exists[l] = 0; - computer_active[l] = 0; - config.player_type[l] = 0; - - } else { - player_exists[l] = 1; - computer_active[l] = 1; - } - break; - - case 3: - player_exists[l] = 1; - computer_active[l] = 0; - break; - - - } - - } - - - - + set_player_types(); if (is_there_sound && (!findparameter("-nomusic"))) { @@ -3455,6 +3860,13 @@ void main_menu(void) { break; + case 10: + if (n1) + random_fade_out(); + wait_mouse_relase(); + netgame_menu(); + + break; } } @@ -3512,15 +3924,3 @@ void print_filled_roster(int number) { frost->printf(122, 177, "Total: %d", ts + roster[number].solo_mis_totals); } - -void wait_mouse_relase(int nokb) { - int n1 = 1, n2 = 1, x, y; - - while (n1 || n2) { - koords(&x, &y, &n1, &n2); - - if (!nokb) - while (kbhit()) - getch(); - } -} diff --git a/src/menus/tripmenu.h b/src/menus/tripmenu.h index 355d015..9f49b3a 100644 --- a/src/menus/tripmenu.h +++ b/src/menus/tripmenu.h @@ -40,10 +40,10 @@ void options_menu(void); void controls_menu(void); int kangas_menu(void); int solo_player_menu(void); +void netgame_menu(void); void print_clear_roster(Bitmap * rosteri); void print_filled_roster(int number); -void wait_mouse_relase(int nokb = 0); void load_descriptions(int number); void show_descriptions(int number); void sort_and_show(int percent = 0); @@ -54,6 +54,5 @@ void aces_multi_total(void); void aces_solo_total(void); void aces_one_solo(int country, int mission); int calculate_multitotal(int player); -void show_feat5(void); #endif diff --git a/src/settings.cpp b/src/settings.cpp index 3a07121..1f32170 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -21,6 +21,7 @@ #include #include #include +#include "io/sdl_compat.h" #include "io/sound.h" #include #include @@ -33,7 +34,6 @@ //\\ Keys -Uint8 *key = SDL_GetKeyState(NULL); struct keymap player_keys[4]; //\\ Rosterdata @@ -106,44 +106,22 @@ FILE *settings_open(const char *filename, const char *mode) { return fp; } -void wait_relase(void) { - int c = 0; - - while (c != SDLK_LAST) { - update_key_state(); - for (c = 0; c < SDLK_LAST; c++) - if (key[c] && c != SDLK_NUMLOCK && c != SDLK_CAPSLOCK && c != SDLK_SCROLLOCK) - break; - } - -} - -int select_key(int player, int old) { - int c; - int flag = 1; - - while (flag) { - if (key[SDLK_ESCAPE]) - flag = 0; - - update_key_state(); - - for (c = 0; c < SDLK_LAST; c++) - if (key[c] && c != SDLK_NUMLOCK && c != SDLK_CAPSLOCK && c != SDLK_SCROLLOCK) - break; - - if (c != SDLK_LAST) - if ((c != SDLK_ESCAPE) && (c != SDLK_PAUSE)) { - wait_relase(); - return c; - } - if (player == 100) - return 100; +int select_key(int old) { + int ch; + + for (;;) { + while (!kbhit()) + do_all(); + ch = getch(); + + if (ch == SDLK_ESCAPE) + return old; + else if (ch == SDLK_PAUSE || ch == SDLK_NUMLOCKCLEAR || + ch == SDLK_CAPSLOCK || ch == SDLK_SCROLLLOCK) + continue; + else + return ch; } - - wait_relase(); - return old; - } void swap_roster_endianes(void) { @@ -277,7 +255,7 @@ void load_keyset(void) { player_keys[1].up = SDLK_DOWN; player_keys[1].down = SDLK_UP; - player_keys[1].roll = SDLK_KP5; + player_keys[1].roll = SDLK_KP_5; player_keys[1].power = SDLK_KP_PLUS; player_keys[1].guns = SDLK_HOME; player_keys[1].bombs = SDLK_LEFT; @@ -394,6 +372,14 @@ void save_roster(void) { swap_roster_endianes(); } +static void clean_string(char *s, int len) { + int sl; + + s[len - 1] = '\0'; + sl = strlen(s); + memset(s + sl, 0, len - sl); +} + void swap_config_endianes(void) { int i; config.current_multilevel = SDL_SwapLE32(config.current_multilevel); @@ -443,6 +429,16 @@ void swap_config_endianes(void) { config.joystick[1] = SDL_SwapLE32(config.joystick[1]); config.joystick_calibrated[1] = SDL_SwapLE32(config.joystick_calibrated[1]); + + clean_string(config.netc_host, 80); + config.netc_port = SDL_SwapLE32(config.netc_port); + clean_string(config.netc_password, 40); + clean_string(config.netc_playername, 40); + config.netc_solo_controls = SDL_SwapLE32(config.netc_solo_controls); + + clean_string(config.neth_listenaddr, 80); + config.neth_listenport = SDL_SwapLE32(config.neth_listenport); + clean_string(config.neth_password, 40); } void load_config(void) { @@ -499,6 +495,20 @@ void load_config(void) { config.joystick[1] = -1; config.joystick_calibrated[1] = 0; + memset(config.netc_host, 0, 80); + config.netc_port = 9763; + memset(config.netc_password, 0, 40); + strcpy(config.netc_password, "triplane"); + memset(config.netc_playername, 0, 40); + strcpy(config.netc_playername, "netplayer"); + config.netc_solo_controls = 1; + + memset(config.neth_listenaddr, 0, 80); + strcpy(config.neth_listenaddr, "0.0.0.0"); + config.neth_listenport = 9763; + memset(config.neth_password, 0, 40); + strcpy(config.neth_password, "triplane"); + faili = settings_open(CONFIGURATION_FILENAME, "rb"); if (faili != NULL) { diff --git a/src/settings.h b/src/settings.h index 9ebb415..4331fa9 100644 --- a/src/settings.h +++ b/src/settings.h @@ -156,15 +156,22 @@ struct configuration { int32_t joystick[2]; int32_t joystick_calibrated[2]; - + char netc_host[80]; + int32_t netc_port; + char netc_password[40]; + char netc_playername[40]; + int32_t netc_solo_controls; + + char neth_listenaddr[80]; + int32_t neth_listenport; + char neth_password[40]; }; extern configuration config; void load_keyset(void); void save_keyset(void); -int select_key(int player, int old); -void wait_relase(void); +int select_key(int old); void load_roster(void); void save_roster(void); diff --git a/src/triplane.cpp b/src/triplane.cpp index 030fbb7..c851f68 100644 --- a/src/triplane.cpp +++ b/src/triplane.cpp @@ -26,7 +26,9 @@ #include "triplane.h" #include "io/joystick.h" +#include "io/netclient.h" #include "gfx/gfx.h" +#include "menus/menusupport.h" #include "menus/tripmenu.h" #include "world/terrain.h" #include "world/fobjects.h" @@ -40,6 +42,7 @@ #include #include #include "io/trip_io.h" +#include "io/network.h" #include "io/sdl_compat.h" #include "settings.h" @@ -143,11 +146,7 @@ int struct_heigth[MAX_STRUCTURES]; Font *fontti; Font *frost; Font *grid2; - -//\ Parameter control - -char parametrit[40][40]; -int parametri_kpl; +Font *foverlay; //\ Shots control @@ -177,6 +176,8 @@ int hangarmenu_max_gas[16]; int hangarmenu_max_ammo[16]; int hangarmenu_max_bombs[16]; +keymap current_keys[4]; +int32_t current_joystick[2]; /* Timing */ short int viimeiset_framet = 0; @@ -202,8 +203,6 @@ int player_on_airfield[16]; int collision_detect = 1; int part_collision_detect = 1; -int power_reverse = 0; -int power_on_off = 0; int loading_texts = 0; int solo_mode = -1; int aftermath; @@ -362,7 +361,6 @@ extern char mission_names[24][30]; //\\\\ Prototypes void hangarmenu_handle(void); -int findparameter(const char *jono); void controls(void); void detect_collision(void); void main_engine(void); @@ -381,7 +379,6 @@ void itgun_sound(int itgun_x); void rotate_water_palet(void); void cause_damage(int amount, int plane); int small_warning(const char *message); -int big_warning(const char *message); void load_all_samples(void); void init_sologame(void); void write_files(void); @@ -607,17 +604,74 @@ void init_sologame(void) { init_mission(solo_country, solo_mission); } +int big_warning(const char *message) { + Bitmap *warnkuva; + int flag = 1, exit_flag = 0; + int x, y, n1, n2; + int response = 0; + menu_position positions[] = { + { 61, 167, 1 }, { 259, 167, 1 }, { 0, 0, -1 } }; + + warnkuva = new Bitmap("WARN1"); + + wait_mouse_relase(); // ensure that exiting requires a click + + while (flag) { + menu_keys(&exit_flag, NULL); + menu_mouse(&x, &y, &n1, &n2, positions); + + if (exit_flag) { + flag = 0; + response = 0; + } + + tyhjaa_vircr(); + warnkuva->blit(37, 19); + frost->printf(41, 37, message); + cursor->blit(x - 10, y - 10); + + do_all(); + + if (n1 || n2) { + if (x >= 42 && x <= 81 && y >= 158 && y <= 177) { + flag = 0; + response = 1; + } + + if (x >= 240 && x <= 279 && y >= 158 && y <= 177) { + flag = 0; + response = 0; + } + } + } + + wait_mouse_relase(); + + delete warnkuva; + + return response; +} int small_warning(const char *message) { Bitmap *warnkuva; - int flag = 1; + int flag = 1, exit_flag = 0; int x, y, n1, n2; int response = 0; + menu_position positions[] = { + { 111, 124, 1 }, { 206, 124, 1 }, { 0, 0, -1 } }; warnkuva = new Bitmap("WARN2"); + wait_mouse_relase(); // ensure that exiting requires a click + while (flag) { - koords(&x, &y, &n1, &n2); + menu_keys(&exit_flag, NULL); + menu_mouse(&x, &y, &n1, &n2, positions); + + if (exit_flag) { + flag = 0; + response = 0; + } tyhjaa_vircr(); warnkuva->blit(87, 59); @@ -643,21 +697,14 @@ int small_warning(const char *message) { } } - while (n1 || n2) - koords(&x, &y, &n1, &n2); + + wait_mouse_relase(); delete warnkuva; return response; } -int big_warning(const char *message) { - if (message == NULL) - return 0; - else - return 0; -} - void cause_damage(int amount, int plane) { player_endurance[plane] -= amount; @@ -903,17 +950,165 @@ void init_player(int l, int pommit) { } } -int findparameter(const char *jono) { - int laskuri; +/* set playing_solo, solo_country and other variables from current config */ +void set_player_types(void) { + int l; + + playing_solo = 0; + solo_mode = -1; + + for (l = 0; l < 16; l++) { + player_exists[l] = 0; + plane_present[l] = 0; + } + + for (l = 0; l < 4; l++) { + switch (config.player_type[l]) { + case 0: + player_exists[l] = 0; + break; + + case 1: + player_exists[l] = 1; + computer_active[l] = 0; + playing_solo = 1; + solo_country = l; + solo_mode = l; + break; + + case 2: + if ((l == 1 || l == 2) && config.current_multilevel == 5) { + player_exists[l] = 0; + computer_active[l] = 0; + config.player_type[l] = 0; + } else { + player_exists[l] = 1; + computer_active[l] = 1; + } + break; - for (laskuri = 1; laskuri < parametri_kpl; laskuri++) - if (!strncmp(parametrit[laskuri], jono, strlen(jono))) - return (laskuri); + case 3: + player_exists[l] = 1; + computer_active[l] = 0; + break; + } + } +} - return (0); +void set_keys_none(void) { + // SDLK_UNKNOWN and SDL_SCANCODE_UNKNOWN are 0 + memset(current_keys, 0, 4*sizeof(keymap)); + current_joystick[0] = -1; + current_joystick[1] = -1; } +void set_keys_from_multiplayer(int country) { + current_keys[country].up = SDL_GetScancodeFromKey(player_keys[country].up); + current_keys[country].down = SDL_GetScancodeFromKey(player_keys[country].down); + current_keys[country].roll = SDL_GetScancodeFromKey(player_keys[country].roll); + current_keys[country].power = SDL_GetScancodeFromKey(player_keys[country].power); + current_keys[country].guns = SDL_GetScancodeFromKey(player_keys[country].guns); + current_keys[country].bombs = SDL_GetScancodeFromKey(player_keys[country].bombs); + current_joystick[0] = config.joystick[0]; + current_joystick[1] = config.joystick[1]; +} +void set_keys_from_roster(int country, int player_num) { + current_keys[country].up = SDL_GetScancodeFromKey(roster[player_num].up); + current_keys[country].down = SDL_GetScancodeFromKey(roster[player_num].down); + current_keys[country].roll = SDL_GetScancodeFromKey(roster[player_num].roll); + current_keys[country].power = SDL_GetScancodeFromKey(roster[player_num].power); + current_keys[country].guns = SDL_GetScancodeFromKey(roster[player_num].guns); + current_keys[country].bombs = SDL_GetScancodeFromKey(roster[player_num].bombs); + // don't set current_joystick +} + +// find new controls for player (0-3), writing them to the arguments +// *power needs to have the previous value for power (for on/off power) +void get_controls_for_player(int player, + int *down, int *up, int *power, + int *roll, int *guns, int *bombs) { + // for on/off power: when the power key is held down, have we + // already changed the setting + static int onoff_power_changed[4] = {0, 0, 0, 0}; + int joynum; + int inmenu = (hangarmenu_active[player] || in_closing[player]); + + for (joynum = 0; joynum <= 1; joynum++) { + if (current_joystick[joynum] == player) { + get_joystick_action(joynum, inmenu, + down, up, power, roll, guns, bombs); + if (!joystick_has_roll_button(joynum) && !inmenu) { + // Autoroll code + *roll = 0; + if (*down == *up) /* not turning up/down */ + if ((player_upsidedown[player] && (player_angle[player] < 23040 || player_angle[player] > 69120)) || + (!player_upsidedown[player] && (player_angle[player] < 69120 && player_angle[player] > 23040))) + if (!player_rolling[player]) + *roll = 1; + } + break; + } + } + + if (joynum == 2 && key) { // no joystick control for this player + if (key[current_keys[player].down]) + *down = 1; + else + *down = 0; + + if (key[current_keys[player].up]) + *up = 1; + else + *up = 0; + + if (!config.poweronoff) { + if (key[current_keys[player].power]) + *power = (config.powerrev ? 0 : 1); + else + *power = (config.powerrev ? 1 : 0); + } else { // on/off power code + if (key[current_keys[player].power]) { + if (!onoff_power_changed[player]) { + *power = !(*power); + onoff_power_changed[player] = 1; + } + } else { + onoff_power_changed[player] = 0; + // and don't change *power + } + + if (in_closing[player]) + *power = 0; + } + + if (key[current_keys[player].bombs]) + *bombs = 1; + else + *bombs = 0; + + if (key[current_keys[player].roll]) + *roll = 1; + else + *roll = 0; + + if (key[current_keys[player].guns]) + *guns = 1; + else + *guns = 0; + } else if (joynum == 2) { // no joystick, keyboard inactive + // act as if no keys are pressed + *down = 0; + *up = 0; + if (!config.poweronoff) + *power = (config.powerrev ? 1 : 0); + *bombs = 0; + *roll = 0; + *guns = 0; + } + + network_controls_for_player(player, down, up, power, roll, guns, bombs); +} void controls(void) { int l; @@ -1096,99 +1291,10 @@ void controls(void) { if (l > 3) continue; - - if (!playing_solo && config.joystick[0] == l) { - get_joystick_action(0, (hangarmenu_active[l] || in_closing[l]), - &new_mc_down[l], &new_mc_up[l], &new_mc_power[l], &new_mc_roll[l], &new_mc_guns[l], &new_mc_bomb[l]); - if (!joystick_has_roll_button(0) && !(hangarmenu_active[l] || in_closing[l])) { - // Autoroll code - new_mc_roll[l] = 0; - if (new_mc_down[l] == new_mc_up[l]) /* not turning up/down */ - if ((player_upsidedown[l] && (player_angle[l] < 23040 || player_angle[l] > 69120)) || - (!player_upsidedown[l] && (player_angle[l] < 69120 && player_angle[l] > 23040))) - if (!player_rolling[l]) - new_mc_roll[l] = 1; - } - } else { - if (!playing_solo && config.joystick[1] == l) { - get_joystick_action(1, (hangarmenu_active[l] || in_closing[l]), - &new_mc_down[l], &new_mc_up[l], &new_mc_power[l], &new_mc_roll[l], &new_mc_guns[l], &new_mc_bomb[l]); - if (!joystick_has_roll_button(1) && !(hangarmenu_active[l] || in_closing[l])) { - // Autoroll code - new_mc_roll[l] = 0; - if (new_mc_down[l] == new_mc_up[l]) /* not turning up/down */ - if ((player_upsidedown[l] && (player_angle[l] < 23040 || player_angle[l] > 69120)) || - (!player_upsidedown[l] && (player_angle[l] < 69120 && player_angle[l] > 23040))) - if (!player_rolling[l]) - new_mc_roll[l] = 1; - } - } else { - if ((playing_solo ? key[roster[config.player_number[solo_country]].down] : key[player_keys[l].down])) - new_mc_down[l] = 1; - else - new_mc_down[l] = 0; - - if ((playing_solo ? key[roster[config.player_number[solo_country]].up] : key[player_keys[l].up])) - new_mc_up[l] = 1; - else - new_mc_up[l] = 0; - - if (!power_on_off) { - if (!power_reverse) { - if ((playing_solo ? key[roster[config.player_number[solo_country]].power] : key[player_keys[l].power])) - new_mc_power[l] = 1; - else - new_mc_power[l] = 0; - } else { - if ((playing_solo ? key[roster[config.player_number[solo_country]].power] : key[player_keys[l].power])) - new_mc_power[l] = 0; - else - new_mc_power[l] = 1; - } - } else { - if ((playing_solo ? key[roster[config.player_number[solo_country]].power] : key[player_keys[l].power])) { - if (!controls_power2[l]) { - if (new_mc_power[l]) - new_mc_power[l] = 0; - else - new_mc_power[l] = 1; - } - controls_power2[l] = 1; - - } else - controls_power2[l] = 0; - - if (in_closing[l]) - new_mc_power[l] = 0; - - } - - new_mc_bomb[l] = 0; - - if ((playing_solo ? key[roster[config.player_number[solo_country]].bombs] : key[player_keys[l].bombs])) { - new_mc_bomb[l] = 1; - - } - - new_mc_roll[l] = 0; - if ((playing_solo ? key[roster[config.player_number[solo_country]].roll] : key[player_keys[l].roll])) { - - new_mc_roll[l] = 1; - - - } - - - new_mc_guns[l] = 0; - - if ((playing_solo ? key[roster[config.player_number[solo_country]].guns] : key[player_keys[l].guns])) { - new_mc_guns[l] = 1; - - } - } - } - - + get_controls_for_player(l, + &new_mc_down[l], &new_mc_up[l], + &new_mc_power[l], &new_mc_roll[l], + &new_mc_guns[l], &new_mc_bomb[l]); if (player_spinning[l]) { if (!player_rolling[l]) @@ -1457,9 +1563,6 @@ void main_engine(void) { part_collision_detect = config.partcollision; } - power_on_off = config.poweronoff; - power_reverse = config.powerrev; - for (l = 0; l < 16; l++) { player_sides[l] = player_tsides[l]; @@ -1628,9 +1731,15 @@ void main_engine(void) { if (playing_solo) { init_exeptions(solo_country, solo_mission); tyhjaa_vircr(); + set_keys_none(); + set_keys_from_roster(solo_country, config.player_number[solo_country]); } //// Open joysticks if (!playing_solo) { + set_keys_from_multiplayer(0); + set_keys_from_multiplayer(1); + set_keys_from_multiplayer(2); + set_keys_from_multiplayer(3); open_close_joysticks(config.joystick[0] != -1, config.joystick[1] != -1); } //// Record @@ -1655,47 +1764,59 @@ void main_engine(void) { //// Record setwrandom(7); - if (!draw_with_vircr_mode) - update_vircr_mode = 0; + all_bitmaps_send_now(); + + network_ping(15); + while (!network_last_ping_done()) { + nopeuskontrolli(); + } + + network_change_game_mode(1); while (flag) { update_key_state(); - if (key[SDLK_PAUSE]) { + if (key && key[SDL_SCANCODE_PAUSE]) { // wait until pause key is released, then pressed and released again - while (key[SDLK_PAUSE]) { // still pressed + while (!key || key[SDL_SCANCODE_PAUSE]) { // still pressed nopeuskontrolli(); + do_all(); update_key_state(); } - while (!key[SDLK_PAUSE]) { // released + while (!key || !key[SDL_SCANCODE_PAUSE]) { // released nopeuskontrolli(); + do_all(); update_key_state(); } - while (key[SDLK_PAUSE]) { // pressed again + while (!key || key[SDL_SCANCODE_PAUSE]) { // pressed again nopeuskontrolli(); + do_all(); update_key_state(); } } // use F4 as an alias for the Pause key // because Pause does not always work reliably // (and is not present on all keyboards) - if (key[SDLK_F4]) { + if (key && key[SDL_SCANCODE_F4]) { // wait until F4 is released, then pressed and released again - while (key[SDLK_F4]) { // still pressed + while (!key || key[SDL_SCANCODE_F4]) { // still pressed nopeuskontrolli(); + do_all(); update_key_state(); } - while (!key[SDLK_F4]) { // released + while (!key || !key[SDL_SCANCODE_F4]) { // released nopeuskontrolli(); + do_all(); update_key_state(); } - while (key[SDLK_F4]) { // pressed again + while (!key || key[SDL_SCANCODE_F4]) { // pressed again nopeuskontrolli(); + do_all(); update_key_state(); } } - if (key[SDLK_ESCAPE]) { + if (key && key[SDL_SCANCODE_ESCAPE]) { flag = 0; mission_interrupted = 1; } @@ -1814,7 +1935,7 @@ void main_engine(void) { solo_success = 1; } - if (key[SDLK_F1] && key[SDLK_F2] && key[SDLK_F3]) { + if (key && key[SDL_SCANCODE_F1] && key[SDL_SCANCODE_F2] && key[SDL_SCANCODE_F3]) { frost->printf(40, 40, "SoloDestRemaining: %d. l=%d\n", solo_dest_remaining, l); } @@ -1863,8 +1984,7 @@ void main_engine(void) { } } - if (!draw_with_vircr_mode) - update_vircr_mode = 1; + network_change_game_mode(0); wait_relase(); mission_re_fly = -1; @@ -1913,14 +2033,17 @@ void do_aftermath(int show_it_all) { int struct_score = 0; int some_score; int aaa_score = 0; - int x, y, n1, n2; + int x, y, n1, n2, exit_flag = 0; int need_for_letter = 0; - char ch; Bitmap *fly, *exit; int x_coord; int best_in_record = 0; int sisennys; int mission_success = 0; + menu_position positions[] = { + { 62, 195, 0 /* playing_solo */ }, { 80, 195, 1 }, { 0, 0, -1 } }; + + positions[0].active = playing_solo; fly = new Bitmap("FLY"); exit = new Bitmap("EXIT"); @@ -1955,8 +2078,6 @@ void do_aftermath(int show_it_all) { fontti->printf(254, 80 + l * 21, "%4d%%", (player_hits[l] * 1000) / (firedi)); } - - do_all(); } if (playing_solo) { @@ -2170,9 +2291,6 @@ void do_aftermath(int show_it_all) { } - do_all(); - - if (tempt < 0) tempt = 0; @@ -2228,7 +2346,7 @@ void do_aftermath(int show_it_all) { } - if (config.player_type[l] == 3 && config.player_number[l] != -1) { + if (config.player_type[l] == 3 && config.player_number[l] >= 0) { roster[config.player_number[l]].multi_mis_flown++; tempt = 0; @@ -2277,27 +2395,10 @@ void do_aftermath(int show_it_all) { l = 1; while (l == 1) { - if (kbhit()) { - if ((ch = getch()) == 27) { - l = 0; - - } - - if (ch == 13) { - l = 2; - if (!need_for_letter) { - if (!mission_success) - mission_re_fly = solo_mission; - else - mission_re_fly = 999; - } - } - - } - - - koords(&x, &y, &n1, &n2); - + menu_keys(&exit_flag, NULL); + if (exit_flag) + l = 0; + menu_mouse(&x, &y, &n1, &n2, positions); if (n1 || n2) { if (playing_solo) { @@ -2376,6 +2477,8 @@ void load_up(void) { fontti = new Font("FONTT"); grid2 = new Font("G2FONT"); grid2->scale(); + foverlay = new Font("GRFONT", 0, 116); + foverlay->scale(); } loading_text("Loading and initializing board-graphics."); @@ -2474,6 +2577,7 @@ void load_up(void) { explox[l][l2] = new Bitmap(1 + l2 * 10, 1 + l * 10, 9, 9, plane1); delete plane1; + wfree(point1); loading_text("Loading AA-MG animations"); @@ -2897,7 +3001,6 @@ void load_up(void) { loading_text("Loading mouse cursor."); cursor = new Bitmap("CURSOR"); - } @@ -3041,7 +3144,7 @@ void load_level(void) { loading_text("Loading levelinfo."); - if (!findparameter("-level")) { + if (findparameter_arg("-level") == NULL) { if (!playing_solo) { sprintf(levelname, "level%d", config.current_multilevel + 1); } else { @@ -3049,8 +3152,8 @@ void load_level(void) { } } else { - sprintf(levelname, parametrit[findparameter("-level")] + 6); - + strncpy(levelname, findparameter_arg("-level"), 80); + levelname[79] = '\0'; } @@ -3558,6 +3661,10 @@ void handle_parameters(void) { loading_text("Sounds disabled."); } + if (findparameter("-1")) { + pixel_multiplier_vga = 1; + } + if (findparameter("-2")) { pixel_multiplier_vga = 2; } @@ -3570,10 +3677,6 @@ void handle_parameters(void) { pixel_multiplier_vga = 4; } - if (findparameter("-2svga")) { - pixel_multiplier_svga = 2; - } - if (findparameter("-fullscreen")) { wantfullscreen = 1; } @@ -3581,23 +3684,15 @@ void handle_parameters(void) { if (findparameter("-nofullscreen")) { wantfullscreen = 0; } - - if (findparameter("-sdldraw")) { - draw_with_vircr_mode = 0; - } } int main(int argc, char *argv[]) { - int x, y, n1, n2; + int x, n1, n2; int laskuri; FILE *faili; Bitmap *lakuva1; - for (laskuri = 0; laskuri < argc; laskuri++) - strcpy(parametrit[laskuri], argv[laskuri]); - - parametri_kpl = argc; - + findparameter_init(argc, argv); if (findparameter("-?") || findparameter("-h") || findparameter("--help") || findparameter("-help")) { printf("Triplane Classic " TRIPLANE_VERSION " - a side-scrolling dogfighting game.\n"); @@ -3701,30 +3796,27 @@ int main(int argc, char *argv[]) { loading_text("\nData loading started."); load_up(); - + if (findparameter("-networkhost")) { + // frost was loaded in load_up() above + network_activate_host((findparameter_arg("-listenaddr") != NULL) + ? findparameter_arg("-listenaddr") + : config.neth_listenaddr, + (findparameter_arg("-listenport") != NULL) + ? atoi(findparameter_arg("-listenport")) + : config.neth_listenport, + (findparameter_arg("-netpassword") != NULL) + ? findparameter_arg("-netpassword") + : config.neth_password, + foverlay); + } if (loading_texts) { printf("\nLoading complete. Press a key to continue."); fflush(stdout); } - n1 = 0; - while (!n1 && !findparameter("-autostart")) { - if (kbhit()) - break; - - koords(&x, &y, &n1, &n2); - - if (n1 || n2) { - wait_mouse_relase(); - break; - } - - } - - while (kbhit() && !findparameter("-autostart")) - getch(); - + if (!findparameter("-autostart")) + wait_press_and_release(); if (sfx_loaded) { sample_alku->right_volume = 0; @@ -3741,22 +3833,10 @@ int main(int argc, char *argv[]) { delete lakuva1; - - while (!kbhit() && !findparameter("-autostart")) { - koords(&x, &y, &n1, &n2); - - if (n1 || n2) { - wait_mouse_relase(); - break; - } - - } + if (!findparameter("-autostart")) + wait_press_and_release(); #endif - while (kbhit() && !findparameter("-autostart")) - getch(); - - loading_text("Loading roster."); load_roster(); @@ -3764,8 +3844,54 @@ int main(int argc, char *argv[]) { if (!findparameter("-debugnographics")) init_vga("PALET5"); - while (kbhit()) - getch(); + wait_mouse_relase(); + + if (findparameter_arg("-netclient") != NULL) { + const char *host = findparameter_arg("-netclient"); + int port = config.netc_port; + char playername[21]; + char password[21]; + + strncpy(playername, config.netc_playername, 20); + playername[20] = 0; + strncpy(password, config.netc_password, 20); + password[20] = 0; + + if (findparameter_arg("-port") != NULL) { + port = atoi(findparameter_arg("-port")); + } + if (findparameter_arg("-netplayer") != NULL) { + strncpy(playername, findparameter_arg("-netplayer"), 20); + playername[20] = 0; + } + if (findparameter_arg("-netpassword") != NULL) { + strncpy(password, findparameter_arg("-netpassword"), 20); + password[20] = 0; + } + + if (findparameter_arg("-netcontrol") != NULL) { + if (config.netc_solo_controls < 0 || + !roster[config.netc_solo_controls].pilotname[0]) + config.netc_solo_controls = 0; + if (!roster[config.netc_solo_controls].pilotname[0]) { + fprintf(stderr, "Please add a pilot to the roster for the -netcontol option!\n"); + } else { + if (strcmp(findparameter_arg("-netcontrol"), "r") == 0) + netclient_activate_controls(0, config.netc_solo_controls); + else if (strcmp(findparameter_arg("-netcontrol"), "b") == 0) + netclient_activate_controls(1, config.netc_solo_controls); + else if (strcmp(findparameter_arg("-netcontrol"), "g") == 0) + netclient_activate_controls(2, config.netc_solo_controls); + else if (strcmp(findparameter_arg("-netcontrol"), "y") == 0) + netclient_activate_controls(3, config.netc_solo_controls); + } + } + + netclient_loop(host, port, playername, password); + + clean_memory(); + return 0; + } main_menu(); save_roster(); @@ -3813,6 +3939,8 @@ int main(int argc, char *argv[]) { save_config(); + network_quit(); + clean_memory(); return 0; diff --git a/src/triplane.h b/src/triplane.h index b55ec2a..b6740fd 100644 --- a/src/triplane.h +++ b/src/triplane.h @@ -119,11 +119,7 @@ extern int struct_heigth[MAX_STRUCTURES]; extern Font *fontti; extern Font *frost; extern Font *grid2; - -//\ Parameter control - -extern char parametrit[40][40]; -extern int parametri_kpl; +extern Font *foverlay; //\ Shots control @@ -172,8 +168,6 @@ extern int player_on_airfield[16]; extern int collision_detect; extern int part_collision_detect; extern int end_game_after; -extern int power_reverse; -extern int power_on_off; extern int loading_texts; extern int solo_mode; extern int aftermath; @@ -320,13 +314,22 @@ extern int main_engine_random_seed; /***************************** Functions **************************************/ +extern int big_warning(const char *message); extern int small_warning(const char *message); -extern int findparameter(const char *jono); extern void kangas_terrain_to_screen(int leftx); extern void main_engine(void); extern void do_aftermath(int show_it_all); extern void clear_level(void); extern void init_player(int l, int pommit = 1); +void set_player_types(void); + +void set_keys_none(void); +void set_keys_from_multiplayer(int country); +void set_keys_from_roster(int country, int player_num); +void get_controls_for_player(int player, + int *down, int *up, int *power, + int *roll, int *guns, int *bombs); + extern void cause_damage(int amount, int plane); extern void do_flags(void); diff --git a/src/util/wutil.cpp b/src/util/wutil.cpp index 73dded9..b96ff72 100644 --- a/src/util/wutil.cpp +++ b/src/util/wutil.cpp @@ -37,6 +37,9 @@ int32_t asinit[ASIN_NRO]; static int trigs_initialized = 0; +static int saved_argc = 0; +static char **saved_argv = NULL; + void setwrandom(int seed) { triplane_srandom(seed); } @@ -239,3 +242,56 @@ int squareroot(int number) { return new_result; } + +void findparameter_init(int argc, char **argv) { + saved_argc = argc; + saved_argv = argv; +} + +int findparameter(const char *jono) { + int laskuri; + + for (laskuri = 1; laskuri < saved_argc; laskuri++) + if (!strncmp(saved_argv[laskuri], jono, strlen(jono))) + return (laskuri); + + return (0); +} + +const char *findparameter_arg(const char *jono) { + int laskuri; + + for (laskuri = 1; laskuri < saved_argc; laskuri++) + if (!strncmp(saved_argv[laskuri], jono, strlen(jono))) + break; + + if (laskuri + 1 < saved_argc) + return saved_argv[laskuri + 1]; + else + return NULL; +} + +// Is ch a normal printable character? +// (used to limit network player names and chat messages) +// should be a subset of the characters accepted by frost->scanf +int printable_char(int ch) { + return (ch >= 32 && ch <= 126); +} + +// Is s an alphanumeric string of length 0 to len-1? +int check_strict_string(const char *s, int len) { + int i; + for (i = 0; i < len && s[i] != '\0'; i++) + if (!isalnum(s[i])) + return 0; + return (i < len); +} + +// Is s a printable string of length 0 to len-1? +int check_printable_string(const char *s, int len) { + int i; + for (i = 0; i < len && s[i] != '\0'; i++) + if (!printable_char(s[i])) + return 0; + return (i < len); +} diff --git a/src/util/wutil.h b/src/util/wutil.h index 118919a..492fef7 100644 --- a/src/util/wutil.h +++ b/src/util/wutil.h @@ -40,4 +40,12 @@ int squareroot(int number); extern int cosinit[361]; extern int sinit[361]; +void findparameter_init(int argc, char **argv); +int findparameter(const char *jono); +const char *findparameter_arg(const char *jono); + +int printable_char(int ch); +int check_strict_string(const char *s, int len); +int check_printable_string(const char *s, int len); + #endif diff --git a/src/world/plane.cpp b/src/world/plane.cpp index 3fd7a17..1d60d19 100644 --- a/src/world/plane.cpp +++ b/src/world/plane.cpp @@ -27,7 +27,7 @@ /* Variables */ -unsigned char controls_up[16], controls_down[16], controls_power[16], controls_power2[16]; +unsigned char controls_up[16], controls_down[16], controls_power[16]; /* Note: 1== enable, 0== disable */ int player_x[16], player_y[16]; int player_speed[16]; diff --git a/src/world/plane.h b/src/world/plane.h index b7ae503..00bb7ff 100644 --- a/src/world/plane.h +++ b/src/world/plane.h @@ -22,7 +22,7 @@ #define PLANE_H /* Note: 1== enabled, 0== disabled */ -extern unsigned char controls_up[16], controls_down[16], controls_power[16], controls_power2[16]; +extern unsigned char controls_up[16], controls_down[16], controls_power[16]; extern int player_x[16], player_y[16]; extern int player_speed[16]; diff --git a/src/world/tripaudio.cpp b/src/world/tripaudio.cpp index f71c545..de9d847 100644 --- a/src/world/tripaudio.cpp +++ b/src/world/tripaudio.cpp @@ -26,6 +26,7 @@ sb_mod_file *triplane_mod; sb_mod_file *aces_mod; +sb_mod_file *national_mod[4]; sb_sample *sample_itexp[3]; sb_sample *sample_bomb[4]; @@ -182,6 +183,10 @@ void load_music(void) { triplane_mod = sdl_load_mod_file("music1"); aces_mod = sdl_load_mod_file("maces"); + national_mod[0] = sdl_load_mod_file("mgerma"); + national_mod[1] = sdl_load_mod_file("mfinla"); + national_mod[2] = sdl_load_mod_file("mengla"); + national_mod[3] = sdl_load_mod_file("mjapan"); } void clear_sfx(void) { diff --git a/src/world/tripaudio.h b/src/world/tripaudio.h index a430834..061fc3b 100644 --- a/src/world/tripaudio.h +++ b/src/world/tripaudio.h @@ -25,6 +25,7 @@ extern sb_mod_file *triplane_mod; extern sb_mod_file *aces_mod; +extern sb_mod_file *national_mod[4]; extern sb_sample *sample_itexp[3]; extern sb_sample *sample_bomb[4];