diff --git a/Makefile b/Makefile --- a/Makefile +++ b/Makefile @@ -69,7 +69,7 @@ # # Paths: # INSTALL: If not set, the game uses the directory of the binary to -# store everything (lang, data, gm, save and openttd.cfg), this is the `old' behaviour. +# store everything (lang, data, gm, save and openttd.cfg), this is the `old' behaviour. # In this case, none of the following paths are used, you also should _not_ # use `make install', but copy the required stuff yourself (or just play out # of you source directory, which should work fine). @@ -83,7 +83,7 @@ # PREFIX: Normally /usr/local # BINARY_DIR: The location of the binary, normally games. (Will be prefixed # with $PREFIX) -# DATA_DIR: The location of the data (lang, data and gm), normally +# DATA_DIR: The location of the data (lang, data and gm), normally # share/games/openttd. (Will be prefixed with $PREFIX) # PERSONAL_DIR: The directory where openttd.cfg and the save folder will be # stored. You cannot use ~ here, define USE_HOMEDIR for that. @@ -157,7 +157,7 @@ endif # this is used if there aren't any makefile.config ifndef CONFIG_INCLUDED # sets network on by default if there aren't any config file -ENABLE_NETWORK:=1 +ENABLE_NETWORK:=1 # paths for make install # disabled as they would break it for some (many?) people if they were default @@ -287,6 +287,7 @@ BASECFLAGS += -g else ifdef PROFILE BASECFLAGS += -pg +LDFLAGS += -pg else # Release mode ifndef MORPHOS @@ -457,9 +458,11 @@ CDEFS += -DMIDI_ARG=\"$(MIDI_ARG)\" endif endif -# Experimental ifdef WITH_NETWORK CDEFS += -DENABLE_NETWORK +ifdef QNX +LIBS += -lsocket +endif ifdef UNIX ifndef OSX ifndef MORPHOS @@ -516,18 +519,21 @@ endif C_SOURCES = \ ai.c ai_build.c ai_new.c ai_pathfinder.c ai_shared.c aircraft_cmd.c \ aircraft_gui.c airport.c airport_gui.c aystar.c bridge_gui.c \ - clear_cmd.c command.c console.c console_cmds.c disaster_cmd.c dock_gui.c dummy_land.c economy.c \ + callback_table.c clear_cmd.c command.c console.c console_cmds.c \ + dedicated.c disaster_cmd.c dock_gui.c dummy_land.c economy.c \ engine.c engine_gui.c fileio.c gfx.c graph_gui.c newgrf.c \ industry_cmd.c industry_gui.c intro_gui.c landscape.c main_gui.c \ minilzo.c misc.c misc_cmd.c misc_gui.c music_gui.c namegen.c network.c \ - network_gui.c news_gui.c oldloader.c order_cmd.c order_gui.c \ - pathfind.c player_gui.c players.c queue.c rail_cmd.c rail_gui.c rev.c \ - road_cmd.c road_gui.c roadveh_cmd.c roadveh_gui.c saveload.c \ - screenshot.c settings.c settings_gui.c ship_cmd.c ship_gui.c \ - smallmap_gui.c sound.c sprite.c spritecache.c station_cmd.c station_gui.c \ - strings.c subsidy_gui.c terraform_gui.c texteff.c town_cmd.c \ - town_gui.c train_cmd.c train_gui.c tree_cmd.c ttd.c tunnelbridge_cmd.c \ - unmovable_cmd.c vehicle.c vehicle_gui.c viewport.c water_cmd.c widget.c window.c + network_client.c network_data.c network_gamelist.c network_gui.c \ + network_server.c network_udp.c news_gui.c oldloader.c order_cmd.c \ + order_gui.c pathfind.c player_gui.c players.c queue.c rail_cmd.c \ + rail_gui.c rev.c road_cmd.c road_gui.c roadveh_cmd.c roadveh_gui.c \ + saveload.c screenshot.c settings.c settings_gui.c ship_cmd.c \ + ship_gui.c smallmap_gui.c sound.c sprite.c spritecache.c station_cmd.c \ + station_gui.c strings.c subsidy_gui.c terraform_gui.c texteff.c \ + town_cmd.c town_gui.c train_cmd.c train_gui.c tree_cmd.c ttd.c \ + tunnelbridge_cmd.c unmovable_cmd.c vehicle.c vehicle_gui.c viewport.c \ + water_cmd.c widget.c window.c CXX_SOURCES = ifdef WITH_SDL @@ -670,8 +676,8 @@ ifeq ($(INSTALL),) is set correctly up - run \"make upgradeconf\") endif -ifeq ($(PREFIX), ) - $(error no prefix set - check makefile.config) +ifeq ($(PREFIX), ) + $(error no prefix set - check makefile.config) endif # We compare against the non prefixed version here, so we won't install # if only the prefix has been set diff --git a/ai_new.c b/ai_new.c --- a/ai_new.c +++ b/ai_new.c @@ -830,7 +830,7 @@ static int AiNew_HowManyVehicles(Player tiles_a_day = RoadVehInfo(i)->max_speed * DAY_TICKS / 256 / 16; // We want a vehicle in a station once a month at least, so, calculate it! // (the * 2 is because we have 2 stations ;)) - amount = ((int)(((float)length / (float)tiles_a_day / 30 * 2))) * 2; + amount = length * 2 * 2 / tiles_a_day / 30; if (amount == 0) amount = 1; return amount; } else if (p->ainew.tbt == AI_TRUCK) { @@ -853,7 +853,7 @@ static int AiNew_HowManyVehicles(Player // We want all the cargo to be gone in a month.. so, we know the cargo it delivers // we know what the vehicle takes with him, and we know the time it takes him // to get back here.. now let's do some math! - amount = (int)(((float)length / (float)tiles_a_day / 30 * 2) * ((float)max_cargo / (float)RoadVehInfo(i)->capacity)); + amount = 2 * length * max_cargo / tiles_a_day / 30 / RoadVehInfo(i)->capacity; amount += 1; return amount; } else { diff --git a/aircraft_gui.c b/aircraft_gui.c --- a/aircraft_gui.c +++ b/aircraft_gui.c @@ -25,7 +25,7 @@ static void DrawAircraftImage(Vehicle *v } } -static void CcBuildAircraft(bool success, uint tile, uint32 p1, uint32 p2) +void CcBuildAircraft(bool success, uint tile, uint32 p1, uint32 p2) { Vehicle *v; diff --git a/airport_gui.c b/airport_gui.c --- a/airport_gui.c +++ b/airport_gui.c @@ -16,7 +16,7 @@ static byte _selected_airport_type; static void ShowBuildAirportPicker(); -static void CcBuildAirport(bool success, uint tile, uint32 p1, uint32 p2) +void CcBuildAirport(bool success, uint tile, uint32 p1, uint32 p2) { if (success) { SndPlayTileFx(SND_1F_SPLAT, tile); diff --git a/bridge_gui.c b/bridge_gui.c --- a/bridge_gui.c +++ b/bridge_gui.c @@ -23,7 +23,7 @@ extern const PalSpriteID _bridge_sprites extern const uint16 _bridge_speeds[MAX_BRIDGES]; extern const StringID _bridge_material[MAX_BRIDGES]; -static void CcBuildBridge(bool success, uint tile, uint32 p1, uint32 p2) +void CcBuildBridge(bool success, uint tile, uint32 p1, uint32 p2) { if (success) SndPlayTileFx(SND_27_BLACKSMITH_ANVIL, tile); } diff --git a/callback_table.c b/callback_table.c new file mode 100644 --- /dev/null +++ b/callback_table.c @@ -0,0 +1,74 @@ +#include "stdafx.h" +#include "ttd.h" +#include "functions.h" + +// If you add a callback for DoCommandP, also add the callback in here +// see below for the full list! +// If you don't do it, it won't work across the network!! + +/* aircraft_gui.c */ +CommandCallback CcBuildAircraft; + +/* airport_gui.c */ +CommandCallback CcBuildAirport; + +/* bridge_gui.c */ +CommandCallback CcBuildBridge; + +/* dock_gui.c */ +CommandCallback CcBuildDocks; +CommandCallback CcBuildCanal; + +/* main_gui.c */ +CommandCallback CcPlaySound10; +CommandCallback CcPlaceSign; +CommandCallback CcTerraform; +//CommandCallback CcDemolish; +CommandCallback CcBuildTown; + +/* rail_gui.c */ +CommandCallback CcPlaySound1E; +CommandCallback CcRailDepot; +CommandCallback CcStation; +CommandCallback CcBuildRailTunnel; + +/* road_gui.c */ +CommandCallback CcPlaySound1D; +CommandCallback CcBuildRoadTunnel; +CommandCallback CcRoadDepot; + +/* roadveh_gui.c */ +CommandCallback CcBuildRoadVeh; + +/* ship_gui.c */ +CommandCallback CcBuildShip; + +/* train_gui.c */ +CommandCallback CcBuildWagon; +CommandCallback CcBuildLoco; + +CommandCallback *_callback_table[] = { + /* 0x00 */ NULL, + /* 0x01 */ CcBuildAircraft, + /* 0x02 */ CcBuildAirport, + /* 0x03 */ CcBuildBridge, + /* 0x04 */ CcBuildCanal, + /* 0x05 */ CcBuildDocks, + /* 0x06 */ CcBuildLoco, + /* 0x07 */ CcBuildRoadVeh, + /* 0x08 */ CcBuildShip, + /* 0x09 */ CcBuildTown, + /* 0x0A */ CcBuildRoadTunnel, + /* 0x0B */ CcBuildRailTunnel, + /* 0x0C */ CcBuildWagon, + /* 0x0D */ CcRoadDepot, + /* 0x0E */ CcRailDepot, + /* 0x0F */ CcPlaceSign, + /* 0x10 */ CcPlaySound10, + /* 0x11 */ CcPlaySound1D, + /* 0x12 */ CcPlaySound1E, + /* 0x13 */ CcStation, + /* 0x14 */ CcTerraform +}; + +const int _callback_table_count = sizeof (_callback_table) / sizeof (*_callback_table); diff --git a/callback_table.h b/callback_table.h new file mode 100644 --- /dev/null +++ b/callback_table.h @@ -0,0 +1,7 @@ +#ifndef CALLBACK_TABLE_H +#define CALLBACK_TABLE_H + +extern CommandCallback *_callback_table[]; +extern const int _callback_table_count; + +#endif diff --git a/command.c b/command.c --- a/command.c +++ b/command.c @@ -4,6 +4,7 @@ #include "gui.h" #include "command.h" #include "player.h" +#include "network.h" #define DEF_COMMAND(yyyy) int32 yyyy(int x, int y, uint32 flags, uint32 p1, uint32 p2) @@ -128,6 +129,7 @@ DEF_COMMAND(CmdSetRoadDriveSide); DEF_COMMAND(CmdSetTownNameType); DEF_COMMAND(CmdChangeDifficultyLevel); +DEF_COMMAND(CmdChangePatchSetting); DEF_COMMAND(CmdStartStopShip); DEF_COMMAND(CmdSellShip); @@ -149,6 +151,7 @@ DEF_COMMAND(CmdCloneOrder); DEF_COMMAND(CmdClearArea); +DEF_COMMAND(CmdGiveMoney); DEF_COMMAND(CmdMoneyCheat); DEF_COMMAND(CmdBuildCanal); DEF_COMMAND(CmdBuildLock); @@ -301,6 +304,8 @@ static CommandProc * const _command_proc CmdBuildManySignals, /* 110 */ //CmdDestroyIndustry, /* 109 */ CmdDestroyCompanyHQ, /* 111 */ + CmdGiveMoney, /* 112 */ + CmdChangePatchSetting, /* 113 */ }; int32 DoCommandByTile(TileIndex tile, uint32 p1, uint32 p2, uint32 flags, uint procc) @@ -386,15 +391,6 @@ bool DoCommandP(TileIndex tile, uint32 p assert(_docommand_recursive == 0); - if (_networking && !(cmd & CMD_NET_INSTANT) && _pause) { - // When the game is paused, and we are in a network game - // we do not allow any commands. This is because - // of some technical reasons - ShowErrorMessage(-1, STR_MULTIPLAYER_PAUSED, x, y); - _docommand_recursive = 0; - return true; - } - _error_message = INVALID_STRING_ID; _error_message_2 = cmd >> 16; _additional_cash_required = 0; @@ -413,7 +409,10 @@ bool DoCommandP(TileIndex tile, uint32 p assert((cmd & 0xFF) < lengthof(_command_proc_table)); proc = _command_proc_table[cmd & 0xFF]; - // this command is a notest command? + // Some commands have a different output in dryrun then the realrun + // e.g.: if you demolish a whole town, the dryrun would say okay. + // but by really destroying, your rating drops and at a certain point + // it will fail. so res and res2 are different // CMD_REMOVE_ROAD: This command has special local authority // restrictions which may cause the test run to fail (the previous // road fragments still stay there and the town won't let you @@ -426,12 +425,10 @@ bool DoCommandP(TileIndex tile, uint32 p (cmd & 0xFF) == CMD_TRAIN_GOTO_DEPOT || (cmd & 0xFF) == CMD_REMOVE_ROAD; - if (_networking && (cmd & CMD_ASYNC)) notest = true; - _docommand_recursive = 1; // cost estimation only? - if (_shift_pressed && _current_player == _local_player && !(cmd & CMD_DONT_NETWORK)) { + if (_shift_pressed && _current_player == _local_player && !(cmd & CMD_NETWORK_COMMAND)) { // estimate the cost. res = proc(x, y, flags, p1, p2); if ((uint32)res >> 16 == 0x8000) { @@ -446,30 +443,26 @@ bool DoCommandP(TileIndex tile, uint32 p } - - // unless the command is a notest command, check if it can be executed. - if (!notest) { + if (!((cmd & CMD_NO_TEST_IF_IN_NETWORK) && _networking)) { // first test if the command can be executed. res = proc(x,y, flags, p1, p2); if ((uint32)res >> 16 == 0x8000) { if (res & 0xFFFF) _error_message = res & 0xFFFF; goto show_error; } - // no money? - if (res != 0 && !CheckPlayerHasMoney(res)) goto show_error; + // no money? Only check if notest is off + if (!notest && res != 0 && !CheckPlayerHasMoney(res)) goto show_error; } - // put the command in a network queue and execute it later? - if (_networking && !(cmd & CMD_DONT_NETWORK)) { - if (!(cmd & CMD_NET_INSTANT)) { - NetworkSendCommand(tile, p1, p2, cmd, callback); - _docommand_recursive = 0; - return true; - } else { - // Instant Command ... Relay and Process then - NetworkSendCommand(tile, p1, p2, cmd, callback); - } +#ifdef ENABLE_NETWORK + // If we are in network, and the command is not from the network + // send it to the command-queue and abort execution + if (_networking && !(cmd & CMD_NETWORK_COMMAND)) { + NetworkSend_Command(tile, p1, p2, cmd, callback); + _docommand_recursive = 0; + return true; } +#endif /* ENABLE_NETWORK */ // update last build coordinate of player. if ( tile != 0 && _current_player < MAX_PLAYERS) DEREF_PLAYER(_current_player)->last_build_coordinate = tile; @@ -478,6 +471,8 @@ bool DoCommandP(TileIndex tile, uint32 p _yearly_expenses_type = 0; res2 = proc(x,y, flags|DC_EXEC, p1, p2); + // If notest is on, it means the result of the test can be different then + // the real command.. so ignore the test if (!notest) { assert(res == res2); // sanity check } else { diff --git a/command.h b/command.h --- a/command.h +++ b/command.h @@ -146,6 +146,8 @@ enum { //CMD_DESTROY_INDUSTRY = 109, CMD_DESTROY_COMPANY_HQ = 111, + CMD_GIVE_MONEY = 112, + CMD_CHANGE_PATCH_SETTING = 113, }; enum { @@ -166,9 +168,8 @@ enum { enum { CMD_AUTO = 0x200, CMD_NO_WATER = 0x400, - CMD_DONT_NETWORK = 0x800, // execute the command without sending it on the network - CMD_ASYNC = 0x1000, // execute the command asynchronously without testing first in networking - CMD_NET_INSTANT = 0x2000, + CMD_NETWORK_COMMAND = 0x800, // execute the command without sending it on the network + CMD_NO_TEST_IF_IN_NETWORK = 0x1000, // When enabled, the command will bypass the no-DC_EXEC round if in network }; //#define return_cmd_error(errcode) do { _error_message=(errcode); return CMD_ERROR; } while(0) diff --git a/console.c b/console.c --- a/console.c +++ b/console.c @@ -10,6 +10,7 @@ #include #include #include "console.h" +#include "network.h" #ifdef WIN32 #include @@ -19,19 +20,15 @@ #define ICON_CMDBUF_SIZE 20 #define ICON_CMDLN_SIZE 255 #define ICON_LINE_HEIGHT 12 - -typedef enum { - ICONSOLE_OPENED, - ICONSOLE_CLOSED -} _iconsole_modes; +#define ICON_RIGHT_BORDERWIDTH 10 +#define ICON_BOTTOM_BORDERWIDTH 12 // ** main console ** // static bool _iconsole_inited; static char* _iconsole_buffer[ICON_BUFFER + 1]; -static char _iconsole_cbuffer[ICON_BUFFER + 1]; +static uint16 _iconsole_cbuffer[ICON_BUFFER + 1]; static char _iconsole_cmdline[ICON_CMDLN_SIZE]; static byte _iconsole_cmdpos; -static _iconsole_modes _iconsole_mode = ICONSOLE_CLOSED; static Window* _iconsole_win = NULL; static byte _iconsole_scroll; @@ -104,14 +101,20 @@ static void IConsoleWndProc(Window* w, W { int i = _iconsole_scroll; int max = (w->height / ICON_LINE_HEIGHT) - 1; + int delta = 0; GfxFillRect(w->left, w->top, w->width, w->height - 1, 0); while ((i > _iconsole_scroll - max) && (_iconsole_buffer[i] != NULL)) { DoDrawString(_iconsole_buffer[i], 5, w->height - (_iconsole_scroll + 2 - i) * ICON_LINE_HEIGHT, _iconsole_cbuffer[i]); i--; } - DoDrawString("]", 5, w->height - ICON_LINE_HEIGHT, _iconsole_color_commands); - DoDrawString(_iconsole_cmdline, 10, w->height - ICON_LINE_HEIGHT, _iconsole_color_commands); + delta = w->width - 10 - GetStringWidth(_iconsole_cmdline) - ICON_RIGHT_BORDERWIDTH; + if (delta > 0) { + DoDrawString("]", 5, w->height - ICON_LINE_HEIGHT, _iconsole_color_commands); + delta = 0; + } + + DoDrawString(_iconsole_cmdline, 10 + delta, w->height - ICON_LINE_HEIGHT, _iconsole_color_commands); break; } case WE_TICK: @@ -119,11 +122,15 @@ static void IConsoleWndProc(Window* w, W if (_icursor_counter > _icursor_rate) { int posx; int posy; + int delta; _icursor_state = !_icursor_state; _cur_dpi = &_screen; - posx = 10 + GetStringWidth(_iconsole_cmdline); + delta = w->width - 10 - GetStringWidth(_iconsole_cmdline) - ICON_RIGHT_BORDERWIDTH; + if (delta > 0) + delta = 0; + posx = 10 + GetStringWidth(_iconsole_cmdline) + delta; posy = w->height - 3; GfxFillRect(posx, posy, posx + 5, posy + 1, _icursor_state ? 14 : 0); _video_driver->make_dirty(posx, posy, 5, 1); @@ -183,10 +190,20 @@ static void IConsoleWndProc(Window* w, W break; case WKC_RETURN: IConsolePrintF(_iconsole_color_commands, "] %s", _iconsole_cmdline); + _iconsole_cmdbufferpos = 19; IConsoleCmdBufferAdd(_iconsole_cmdline); IConsoleCmdExec(_iconsole_cmdline); IConsoleClearCommand(); break; + case WKC_CTRL | WKC_RETURN: + if (_iconsole_mode == ICONSOLE_FULL) { + _iconsole_mode = ICONSOLE_OPENED; + } else { + _iconsole_mode = ICONSOLE_FULL; + } + IConsoleResize(); + MarkWholeScreenDirty(); + break; case WKC_BACKSPACE: if (_iconsole_cmdpos != 0) _iconsole_cmdpos--; _iconsole_cmdline[_iconsole_cmdpos] = 0; @@ -239,9 +256,9 @@ void IConsoleInit(void) } IConsoleStdLibRegister(); #if defined(WITH_REV) - IConsolePrintF(13, "OpenTTD Game Console Revision 4 - %s", _openttd_revision); + IConsolePrintF(13, "OpenTTD Game Console Revision 5 - %s", _openttd_revision); #else - IConsolePrint(13, "OpenTTD Game Console Revision 4"); + IConsolePrint(13, "OpenTTD Game Console Revision 5"); #endif IConsolePrint(12, "---------------------------------"); IConsolePrint(12, "use \"help\" for more info"); @@ -266,9 +283,17 @@ void IConsoleFree(void) void IConsoleResize(void) { - if (_iconsole_win != NULL) { - _iconsole_win->height = _screen.height / 3; - _iconsole_win->width = _screen.width; + switch (_iconsole_mode) { + case ICONSOLE_OPENED: + _iconsole_win->height = _screen.height / 3; + _iconsole_win->width = _screen.width; + break; + case ICONSOLE_FULL: + _iconsole_win->height = _screen.height - ICON_BOTTOM_BORDERWIDTH; + _iconsole_win->width = _screen.width; + break; + default: + break; } } @@ -286,6 +311,11 @@ void IConsoleSwitch(void) _iconsole_win = NULL; _iconsole_mode = ICONSOLE_CLOSED; break; + case ICONSOLE_FULL: + DeleteWindowById(WC_CONSOLE, 0); + _iconsole_win = NULL; + _iconsole_mode = ICONSOLE_CLOSED; + break; } } @@ -331,15 +361,20 @@ void IConsoleCmdBufferNavigate(signed ch _iconsole_cmdpos = strlen(_iconsole_cmdbuffer[i]); } -void IConsolePrint(byte color_code, const char* string) +void IConsolePrint(uint16 color_code, const char* string) { char* _ex; char* _new; - char _exc; - char _newc; + uint16 _exc; + uint16 _newc; char* i; int j; + if (_network_dedicated) { + printf("%s\n", string); + return; + } + if (!_iconsole_inited) return; _newc = color_code; @@ -362,7 +397,7 @@ void IConsolePrint(byte color_code, cons } -void CDECL IConsolePrintF(byte color_code, const char* s, ...) +void CDECL IConsolePrintF(uint16 color_code, const char* s, ...) { va_list va; char buf[1024]; @@ -384,7 +419,7 @@ void CDECL IConsolePrintF(byte color_cod void IConsoleDebug(const char* string) { if (_stdlib_developer > 1) - IConsolePrintF(_iconsole_color_debug, "DEBUG: %s", string); + IConsolePrintF(_iconsole_color_debug, "dbg: %s", string); } void IConsoleError(const char* string) @@ -446,7 +481,7 @@ void IConsoleVarRegister(const char* nam item_new = malloc(sizeof(_iconsole_var)); /* XXX unchecked malloc */ item_new->name = malloc(strlen(name) + 2); /* XXX unchecked malloc */ - sprintf(item_new->name, "*%s", name); + sprintf(item_new->name, "%s", name); item_new->_next = NULL; switch (type) { @@ -454,6 +489,7 @@ void IConsoleVarRegister(const char* nam item_new->data.bool_ = addr; break; case ICONSOLE_VAR_BYTE: + case ICONSOLE_VAR_UINT8: item_new->data.byte_ = addr; break; case ICONSOLE_VAR_UINT16: @@ -507,7 +543,7 @@ void IConsoleVarInsert(_iconsole_var* va if (var->_next != NULL) return; var->name = malloc(strlen(name) + 2); /* XXX unchecked malloc */ - sprintf(var->name, "*%s", name); + sprintf(var->name, "%s", name); item = _iconsole_vars; if (item == NULL) { @@ -540,6 +576,7 @@ void IConsoleVarInsert(_iconsole_var* va item->_malloc = true; break; case ICONSOLE_VAR_BYTE: + case ICONSOLE_VAR_UINT8: item->data.byte_ = malloc(sizeof(*item->data.byte_)); *item->data.byte_ = 0; item->_malloc = true; @@ -607,6 +644,7 @@ void IConsoleVarSetValue(_iconsole_var* *var->data.bool_ = (value != 0); break; case ICONSOLE_VAR_BYTE: + case ICONSOLE_VAR_UINT8: *var->data.byte_ = value; break; case ICONSOLE_VAR_UINT16: @@ -629,6 +667,7 @@ void IConsoleVarSetValue(_iconsole_var* void IConsoleVarDump(const _iconsole_var* var, const char* dump_desc) { + if (var == NULL) return; if (dump_desc == NULL) dump_desc = var->name; switch (var->type) { @@ -638,6 +677,7 @@ void IConsoleVarDump(const _iconsole_var break; break; case ICONSOLE_VAR_BYTE: + case ICONSOLE_VAR_UINT8: IConsolePrintF(_iconsole_color_default, "%s = %u", dump_desc, *var->data.byte_); break; @@ -703,7 +743,10 @@ void IConsoleVarHook(const char* name, _ bool IConsoleVarHookHandle(_iconsole_var* hook_var, _iconsole_hook_types type) { - iconsole_var_hook proc = NULL; + iconsole_var_hook proc; + if (hook_var == NULL) return false; + + proc = NULL; switch (type) { case ICONSOLE_HOOK_BEFORE_CHANGE: proc = hook_var->hook_before_change; @@ -802,6 +845,7 @@ void IConsoleCmdExec(const char* cmdstr) i = 0; c = 0; tokens[c] = tokenstream; + tokentypes[c] = ICONSOLE_VAR_UNKNOWN; while (i < l && c < lengthof(tokens) - 1) { if (cmdstr[i] == '"') { if (longtoken) { @@ -812,9 +856,12 @@ void IConsoleCmdExec(const char* cmdstr) skip_lt_change = true; } else { longtoken = !longtoken; + tokentypes[c] = ICONSOLE_VAR_STRING; } - } else + } else { longtoken = !longtoken; + tokentypes[c] = ICONSOLE_VAR_STRING; + } if (!skip_lt_change) { if (!longtoken) { if (valid_token) { @@ -822,6 +869,7 @@ void IConsoleCmdExec(const char* cmdstr) *tokenstream = '\0'; tokenstream++; tokens[c] = tokenstream; + tokentypes[c] = ICONSOLE_VAR_UNKNOWN; valid_token = false; } } @@ -833,6 +881,7 @@ void IConsoleCmdExec(const char* cmdstr) *tokenstream = '\0'; tokenstream++; tokens[c] = tokenstream; + tokentypes[c] = ICONSOLE_VAR_UNKNOWN; valid_token = false; } } else { @@ -853,28 +902,29 @@ void IConsoleCmdExec(const char* cmdstr) //** interpreting **// for (i = 0; i < c; i++) { - tokentypes[i] = ICONSOLE_VAR_UNKNOWN; if (tokens[i] != NULL && i > 0 && strlen(tokens[i]) > 0) { - if (tokens[i][0] == '*') { + if (IConsoleVarGet((char *)tokens[i]) != NULL) { // change the variable to an pointer if execution_mode != 4 is - // being prepared. execution_mode 4 is used to assign + // being prepared. execution_mode 4 is used to assign // one variables data to another one // [token 0 and 2] if (!((i == 2) && (tokentypes[1] == ICONSOLE_VAR_UNKNOWN) && (strcmp(tokens[1], "<<") == 0))) { - var = IConsoleVarGet(tokens[i]); + // only look for another variable if it isnt an longtoken == string with "" + var = NULL; + if (tokentypes[i]!=ICONSOLE_VAR_STRING) var = IConsoleVarGet(tokens[i]); if (var != NULL) { // pointer to the data --> token - tokens[i] = (char *) var->data.addr; /* XXX: maybe someone finds an cleaner way to do this */ + tokens[i] = (char *) var->data.addr; /* XXX: maybe someone finds an cleaner way to do this */ tokentypes[i] = var->type; } } } - if (tokens[i] != NULL && tokens[i][0] == '@' && tokens[i][1] == '*') { - var = IConsoleVarGet(tokens[i] + 1); + if (tokens[i] != NULL && tokens[i][0] == '@' && (IConsoleVarGet(tokens[i]+1) != NULL)) { + var = IConsoleVarGet(tokens[i]+1); if (var != NULL) { // pointer to the _iconsole_var struct --> token - tokens[i] = (char *) var; /* XXX: maybe someone finds an cleaner way to do this */ + tokens[i] = (char *) var; /* XXX: maybe someone finds an cleaner way to do this */ tokentypes[i] = ICONSOLE_VAR_REFERENCE; } } @@ -960,6 +1010,7 @@ void IConsoleCmdExec(const char* cmdstr) break; } case ICONSOLE_VAR_BYTE: + case ICONSOLE_VAR_UINT8: { if (strcmp(tokens[1], "=") == 0) { if (c == 3) @@ -1088,9 +1139,9 @@ void IConsoleCmdExec(const char* cmdstr) IConsoleError("operation not supported"); break; } - case ICONSOLE_VAR_NONE: - case ICONSOLE_VAR_REFERENCE: - case ICONSOLE_VAR_UNKNOWN: + case ICONSOLE_VAR_NONE: + case ICONSOLE_VAR_REFERENCE: + case ICONSOLE_VAR_UNKNOWN: IConsoleError("operation not supported"); break; } @@ -1140,6 +1191,7 @@ void IConsoleCmdExec(const char* cmdstr) IConsoleVarDump(var, NULL); break; case ICONSOLE_VAR_BYTE: + case ICONSOLE_VAR_UINT8: *var->data.byte_ = *result->data.byte_; IConsoleVarDump(var, NULL); break; diff --git a/console.h b/console.h --- a/console.h +++ b/console.h @@ -7,6 +7,7 @@ typedef enum _iconsole_var_types { ICONSOLE_VAR_NONE, ICONSOLE_VAR_BOOLEAN, ICONSOLE_VAR_BYTE, + ICONSOLE_VAR_UINT8, ICONSOLE_VAR_UINT16, ICONSOLE_VAR_UINT32, ICONSOLE_VAR_INT16, @@ -17,6 +18,12 @@ typedef enum _iconsole_var_types { ICONSOLE_VAR_UNKNOWN } _iconsole_var_types; +typedef enum { + ICONSOLE_FULL, + ICONSOLE_OPENED, + ICONSOLE_CLOSED +} _iconsole_modes; + typedef enum _iconsole_hook_types { ICONSOLE_HOOK_ACCESS, ICONSOLE_HOOK_BEFORE_CHANGE, @@ -80,6 +87,7 @@ VARDEF byte _iconsole_color_error; VARDEF byte _iconsole_color_warning; VARDEF byte _iconsole_color_debug; VARDEF byte _iconsole_color_commands; +VARDEF _iconsole_modes _iconsole_mode; // ** ttd.c functions ** // @@ -100,8 +108,8 @@ void IConsoleCmdBufferAdd(const char* cm void IConsoleCmdBufferNavigate(signed char direction); // ** console output ** // -void IConsolePrint(byte color_code, const char* string); -void CDECL IConsolePrintF(byte color_code, const char* s, ...); +void IConsolePrint(uint16 color_code, const char* string); +void CDECL IConsolePrintF(uint16 color_code, const char* s, ...); void IConsoleDebug(const char* string); void IConsoleError(const char* string); void IConsoleWarning(const char* string); diff --git a/console_cmds.c b/console_cmds.c --- a/console_cmds.c +++ b/console_cmds.c @@ -5,10 +5,10 @@ #include "engine.h" #include "functions.h" #include "variables.h" - -#if defined(WIN32) -# define ENABLE_NETWORK -#endif +#include "network_data.h" +#include "network_client.h" +#include "network_server.h" +#include "command.h" // ** scriptfile handling ** // @@ -39,6 +39,8 @@ static uint32 GetArgumentInteger(const c /* variable and command hooks */ /* **************************** */ +#ifdef ENABLE_NETWORK + DEF_CONSOLE_CMD_HOOK(ConCmdHookNoNetwork) { if (_networking) { @@ -48,26 +50,44 @@ DEF_CONSOLE_CMD_HOOK(ConCmdHookNoNetwork return true; } -#if 0 /* Not used atm */ -DEF_CONSOLE_VAR_HOOK(ConVarHookNoNetwork) -{ - if (_networking) { - IConsoleError("This variable is forbidden in multiplayer."); - return false; - } - return true; -} -#endif - DEF_CONSOLE_VAR_HOOK(ConVarHookNoNetClient) { - if (!_networking_server) { + if (!_network_server) { IConsoleError("This variable only makes sense for a network server."); return false; } return true; } +DEF_CONSOLE_CMD_HOOK(ConCmdHookNoNetClient) +{ + if (!_networking || !_network_server) { + IConsoleError("This command is only available for a network server."); + return false; + } + return true; +} + +DEF_CONSOLE_CMD_HOOK(ConCmdHookNoNetServer) +{ + if (!_networking || _network_server) { + IConsoleError("You can not use this command for you are a network-server."); + return false; + } + return true; +} + +DEF_CONSOLE_CMD_HOOK(ConCmdHookNeedNetwork) +{ + if (!_networking) { + IConsoleError("Not connected. Multiplayer only command."); + return false; + } + return true; +} + +#endif /* ENABLE_NETWORK */ + /* **************************** */ /* reset commands */ /* **************************** */ @@ -100,41 +120,130 @@ DEF_CONSOLE_CMD(ConScrollToTile) return 0; } + // ********************************* // // * Network Core Console Commands * // // ********************************* // #ifdef ENABLE_NETWORK +DEF_CONSOLE_CMD(ConStatus) +{ + const char *status; + int lag; + const ClientState *cs; + const NetworkClientInfo *ci; + FOR_ALL_CLIENTS(cs) { + lag = NetworkCalculateLag(cs); + ci = DEREF_CLIENT_INFO(cs); + + switch (cs->status) { + case STATUS_INACTIVE: + status = "inactive"; + break; + case STATUS_AUTH: + status = "authorized"; + break; + case STATUS_MAP: + status = "loading map"; + break; + case STATUS_DONE_MAP: + status = "done map"; + break; + case STATUS_PRE_ACTIVE: + status = "ready"; + break; + case STATUS_ACTIVE: + status = "active"; + break; + default: + status = "unknown"; + break; + } + IConsolePrintF(8, "Client #%d/%s status: %s frame-lag: %d play-as: %d", + cs->index, ci->client_name, status, lag, ci->client_playas); + } + + return NULL; +} + +DEF_CONSOLE_CMD(ConKick) +{ + NetworkClientInfo *ci; + + if (argc == 2) { + uint32 index = atoi(argv[1]); + if (index == NETWORK_SERVER_INDEX) { + IConsolePrint(_iconsole_color_default, "Silly boy, you can not kick yourself!"); + return NULL; + } + if (index == 0) { + IConsoleError("Invalid Client-ID"); + return NULL; + } + + ci = NetworkFindClientInfoFromIndex(index); + + if (ci != NULL) { + SEND_COMMAND(PACKET_SERVER_ERROR)(NetworkFindClientStateFromIndex(index), NETWORK_ERROR_KICKED); + return NULL; + } else { + IConsoleError("Client-ID not found"); + return NULL; + } + } + + IConsolePrint(_iconsole_color_default, "Unknown usage. Usage: kick . For client-ids, see 'clients'."); + + return NULL; +} + +DEF_CONSOLE_CMD(ConNetworkClients) +{ + NetworkClientInfo *ci; + for (ci = _network_client_info; ci != &_network_client_info[MAX_CLIENT_INFO]; ci++) { + if (ci->client_index != NETWORK_EMPTY_INDEX) { + IConsolePrintF(8,"Client #%d name: %s play-as: %d", ci->client_index, ci->client_name, ci->client_playas); + } + } + + return NULL; +} + DEF_CONSOLE_CMD(ConNetworkConnect) { char* ip; - const char *port = NULL; - const char *player = NULL; + const byte *port = NULL; + const byte *player = NULL; uint16 rport; if (argc<2) return NULL; + if (_networking) { + // We are in network-mode, first close it! + NetworkDisconnect(); + } + ip = argv[1]; - rport = _network_server_port; + rport = NETWORK_DEFAULT_PORT; ParseConnectionString(&player, &port, ip); IConsolePrintF(_iconsole_color_default, "Connecting to %s...", ip); - if (player!=NULL) { + if (player != NULL) { _network_playas = atoi(player); IConsolePrintF(_iconsole_color_default, " player-no: %s", player); } - if (port!=NULL) { + if (port != NULL) { rport = atoi(port); IConsolePrintF(_iconsole_color_default, " port: %s", port); } - NetworkCoreConnectGame(ip, rport); + NetworkClientConnectGame(ip, rport); return NULL; } -#endif +#endif /* ENABLE_NETWORK */ /* ******************************** */ /* script file console commands */ @@ -148,7 +257,7 @@ DEF_CONSOLE_CMD(ConExec) if (argc<2) return NULL; doerror = true; - _script_file = fopen(argv[1], "rb"); + _script_file = fopen(argv[1], "r"); if (_script_file == NULL) { if (argc>2) if (atoi(argv[2])==0) doerror=false; @@ -158,9 +267,11 @@ DEF_CONSOLE_CMD(ConExec) _script_running = true; + fgets(cmd, sizeof(cmd), _script_file); while (!feof(_script_file) && _script_running) { + strtok(cmd, "\r\n"); + IConsoleCmdExec(cmd); fgets(cmd, sizeof(cmd), _script_file); - IConsoleCmdExec(cmd); } _script_running = false; @@ -209,6 +320,19 @@ DEF_CONSOLE_CMD(ConEchoC) return NULL; } +extern void SwitchMode(int new_mode); + +DEF_CONSOLE_CMD(ConNewGame) +{ + _docommand_recursive = 0; + + _random_seeds[0][0] = Random(); + _random_seeds[0][1] = InteractiveRandom(); + + SwitchMode(SM_NEWGAME); + return NULL; +} + DEF_CONSOLE_CMD(ConPrintF) { if (argc < 3) return NULL; @@ -299,15 +423,15 @@ DEF_CONSOLE_CMD(ConHelp) { IConsolePrint(13, " -- console help -- "); IConsolePrint( 1, " variables: [command to list them: list_vars]"); - IConsolePrint( 1, " *temp_string = \"my little \""); + IConsolePrint( 1, " temp_string = \"my little \""); IConsolePrint( 1, ""); IConsolePrint( 1, " commands: [command to list them: list_cmds]"); IConsolePrint( 1, " [command] [\"string argument with spaces\"] [argument 2] ..."); - IConsolePrint( 1, " printf \"%s world\" *temp_string"); + IConsolePrint( 1, " printf \"%s world\" temp_string"); IConsolePrint( 1, ""); IConsolePrint( 1, " command/variable returning a value into an variable:"); - IConsolePrint( 1, " *temp_uint16 << random"); - IConsolePrint( 1, " *temp_uint16 << *temp_uint16_2"); + IConsolePrint( 1, " temp_uint16 << random"); + IConsolePrint( 1, " temp_uint16 << temp_uint16_2"); IConsolePrint( 1, ""); return NULL; } @@ -362,6 +486,131 @@ DEF_CONSOLE_CMD(ConListDumpVariables) return NULL; } +#ifdef ENABLE_NETWORK + +DEF_CONSOLE_CMD(ConSay) +{ + if (argc == 2) { + if (!_network_server) + SEND_COMMAND(PACKET_CLIENT_CHAT)(NETWORK_ACTION_CHAT, DESTTYPE_BROADCAST, 0 /* param does not matter */, argv[1]); + else + NetworkServer_HandleChat(NETWORK_ACTION_CHAT, DESTTYPE_BROADCAST, 0, argv[1], NETWORK_SERVER_INDEX); + } else + IConsolePrint(_iconsole_color_default, "Unknown usage. Usage: say \"\""); + return NULL; +} + +DEF_CONSOLE_CMD(ConSayPlayer) +{ + if (argc == 3) { + if (atoi(argv[1]) < 1 || atoi(argv[1]) > MAX_PLAYERS) { + IConsolePrintF(_iconsole_color_default, "Unknown player. Player range is between 1 and %d.", MAX_PLAYERS); + return NULL; + } + + if (!_network_server) + SEND_COMMAND(PACKET_CLIENT_CHAT)(NETWORK_ACTION_CHAT_PLAYER, DESTTYPE_PLAYER, atoi(argv[1]), argv[2]); + else + NetworkServer_HandleChat(NETWORK_ACTION_CHAT_PLAYER, DESTTYPE_PLAYER, atoi(argv[1]), argv[2], NETWORK_SERVER_INDEX); + } else + IConsolePrint(_iconsole_color_default, "Unknown usage. Usage: say_player \"\""); + return NULL; +} + +DEF_CONSOLE_CMD(ConSayClient) +{ + if (argc == 3) { + if (!_network_server) + SEND_COMMAND(PACKET_CLIENT_CHAT)(NETWORK_ACTION_CHAT_CLIENT, DESTTYPE_CLIENT, atoi(argv[1]), argv[2]); + else + NetworkServer_HandleChat(NETWORK_ACTION_CHAT_CLIENT, DESTTYPE_CLIENT, atoi(argv[1]), argv[2], NETWORK_SERVER_INDEX); + } else + IConsolePrint(_iconsole_color_default, "Unknown usage. Usage: say_client \"\""); + return NULL; +} + +DEF_CONSOLE_CMD(ConSetServerName) { + if (argc == 2) { + strncpy(_network_server_name, argv[1], 40); + IConsolePrintF(_iconsole_color_default, "Server-name changed to '%s'", _network_server_name); + ttd_strlcpy(_network_game_info.server_name, _network_server_name, 40); + } else if (argc == 1) { + IConsolePrintF(_iconsole_color_default, "Current server-name is '%s'", _network_server_name); + IConsolePrint(_iconsole_color_default, " Usage: setservername \"\"."); + } else { + IConsolePrint(_iconsole_color_default, "Unknow usage. Usage: setservername \"\"."); + } + return NULL; +} + +DEF_CONSOLE_CMD(ConClientName) { + NetworkClientInfo *ci; + ci = NetworkFindClientInfoFromIndex(_network_own_client_index); + + if (argc == 2 && ci != NULL) { + if (!_network_server) + SEND_COMMAND(PACKET_CLIENT_SET_NAME)(argv[1]); + else { + if (NetworkFindName(argv[1])) { + NetworkTextMessage(NETWORK_ACTION_NAME_CHANGE, 1, ci->client_name, argv[1]); + ttd_strlcpy(ci->client_name, argv[1], 40); + NetworkUpdateClientInfo(NETWORK_SERVER_INDEX); + } + } + } else { + IConsolePrint(_iconsole_color_default, "With 'name' you can change your network-player name."); + IConsolePrint(_iconsole_color_default, " Usage: name \"\"."); + } + + return NULL; +} + +DEF_CONSOLE_CMD(ConProtect) { + // Protect a company with a password + if (_local_player >= MAX_PLAYERS) { + IConsolePrintF(_iconsole_color_default, "You have to own a company to make use of this command."); + return NULL; + } + if (argc == 2) { + if (strncmp(argv[1], "*", 20) == 0) { + _network_player_info[_local_player].password[0] = '\0'; + } else { + strncpy(_network_player_info[_local_player].password, argv[1], 20); + } + if (!_network_server) + SEND_COMMAND(PACKET_CLIENT_SET_PASSWORD)(_network_player_info[_local_player].password); + IConsolePrintF(_iconsole_color_default, "Company protected with '%s'", _network_player_info[_local_player].password); + } else { + IConsolePrint(_iconsole_color_default, "Protect sets a password on the company, so no-one without the correct password can join."); + IConsolePrint(_iconsole_color_default, " Usage: protect \"\". Use * as to set no password."); + } + + return NULL; +} + +DEF_CONSOLE_CMD(ConSetPassword) { + if (argc == 2) { + // Change server password + if (strncmp(argv[1], "*", 20) == 0) { + _network_game_info.server_password[0] = '\0'; + _network_game_info.use_password = 0; + } else { + strncpy(_network_game_info.server_password, argv[1], 20); + _network_game_info.use_password = 1; + } + IConsolePrintF(_iconsole_color_default, "Game-password changed to '%s'", _network_game_info.server_password); + } else if (argc == 1) { + IConsolePrintF(_iconsole_color_default, "Current game-password is set to '%s'", _network_game_info.server_password); + IConsolePrint(_iconsole_color_default, " Usage: setpassword \"\". Use * as to set no password."); + } else { + IConsolePrint(_iconsole_color_default, "Unknow usage. Usage: setpassword \"\". Use * as to set no password."); + } + + return 0; +} + +#endif /* ENABLE_NETWORK */ + #ifdef _DEBUG /* ****************************************** */ /* debug commands and variables */ @@ -390,7 +639,7 @@ void IConsoleDebugLibRegister() /* console command and variable registration */ /* ****************************************** */ -void IConsoleStdLibRegister() +void IConsoleStdLibRegister(void) { // stdlib extern byte _stdlib_developer; /* XXX extern in .c */ @@ -402,8 +651,9 @@ void IConsoleStdLibRegister() // functions [please add them alphabetically] #ifdef ENABLE_NETWORK IConsoleCmdRegister("connect", ConNetworkConnect); - IConsoleCmdHook("connect", ICONSOLE_HOOK_ACCESS, ConCmdHookNoNetwork); -#endif + IConsoleCmdHook("connect", ICONSOLE_HOOK_ACCESS, ConCmdHookNoNetServer); + IConsoleCmdRegister("clients", ConNetworkClients); +#endif /* ENABLE_NETWORK */ IConsoleCmdRegister("debug_level", ConDebugLevel); IConsoleCmdRegister("dump_vars", ConListDumpVariables); IConsoleCmdRegister("echo", ConEcho); @@ -415,26 +665,50 @@ void IConsoleStdLibRegister() IConsoleCmdRegister("info_var", ConInfoVar); IConsoleCmdRegister("list_cmds", ConListCommands); IConsoleCmdRegister("list_vars", ConListVariables); +#ifdef ENABLE_NETWORK + IConsoleCmdRegister("kick", ConKick); + IConsoleCmdHook("kick", ICONSOLE_HOOK_ACCESS, ConCmdHookNoNetClient); + IConsoleCmdRegister("protect", ConProtect); + IConsoleCmdRegister("name", ConClientName); +#endif + IConsoleCmdRegister("newgame", ConNewGame); IConsoleCmdRegister("printf", ConPrintF); IConsoleCmdRegister("printfc", ConPrintFC); IConsoleCmdRegister("quit", ConExit); IConsoleCmdRegister("random", ConRandom); IConsoleCmdRegister("resetengines", ConResetEngines); +#ifdef ENABLE_NETWORK IConsoleCmdHook("resetengines", ICONSOLE_HOOK_ACCESS, ConCmdHookNoNetwork); +#endif /* ENABLE_NETWORK */ IConsoleCmdRegister("return", ConReturn); +#ifdef ENABLE_NETWORK + IConsoleCmdRegister("say", ConSay); + IConsoleCmdHook("say", ICONSOLE_HOOK_ACCESS, ConCmdHookNeedNetwork); + IConsoleCmdRegister("say_player", ConSayPlayer); + IConsoleCmdHook("say_player", ICONSOLE_HOOK_ACCESS, ConCmdHookNeedNetwork); + IConsoleCmdRegister("say_client", ConSayClient); + IConsoleCmdHook("say_client", ICONSOLE_HOOK_ACCESS, ConCmdHookNeedNetwork); +#endif /* ENABLE_NETWORK */ IConsoleCmdRegister("screenshot", ConScreenShot); IConsoleCmdRegister("script", ConScript); IConsoleCmdRegister("scrollto", ConScrollToTile); +#ifdef ENABLE_NETWORK + IConsoleCmdRegister("setservername", ConSetServerName); + IConsoleCmdHook("setservername", ICONSOLE_HOOK_ACCESS, ConCmdHookNoNetClient); + IConsoleCmdRegister("setpassword", ConSetPassword); + IConsoleCmdHook("setpassword", ICONSOLE_HOOK_ACCESS, ConCmdHookNoNetClient); + IConsoleCmdRegister("status", ConStatus); + IConsoleCmdHook("status", ICONSOLE_HOOK_ACCESS, ConCmdHookNoNetClient); +#endif /* ENABLE_NETWORK */ // variables [please add them alphabeticaly] IConsoleVarRegister("developer", &_stdlib_developer, ICONSOLE_VAR_BYTE); #ifdef ENABLE_NETWORK - IConsoleVarRegister("net_client_timeout", &_network_client_timeout, ICONSOLE_VAR_UINT16); - IConsoleVarHook("*net_client_timeout", ICONSOLE_HOOK_ACCESS, ConVarHookNoNetClient); - IConsoleVarRegister("net_ready_ahead", &_network_ready_ahead, ICONSOLE_VAR_UINT16); + IConsoleVarRegister("net_frame_freq", &_network_frame_freq, ICONSOLE_VAR_UINT8); + IConsoleVarHook("*net_frame_freq", ICONSOLE_HOOK_ACCESS, ConVarHookNoNetClient); IConsoleVarRegister("net_sync_freq", &_network_sync_freq, ICONSOLE_VAR_UINT16); IConsoleVarHook("*net_sync_freq", ICONSOLE_HOOK_ACCESS, ConVarHookNoNetClient); -#endif +#endif /* ENABLE_NETWORK */ } diff --git a/dedicated.c b/dedicated.c new file mode 100644 --- /dev/null +++ b/dedicated.c @@ -0,0 +1,179 @@ +#include "stdafx.h" +#include "ttd.h" +#include "network.h" +#include "hal.h" + +#ifdef ENABLE_NETWORK + +#include "gfx.h" +#include "window.h" +#include "command.h" +#include "console.h" +#ifdef WIN32 +# include /* GetTickCount */ +# include +#endif +#ifdef UNIX +# include /* gettimeofday */ +# include +# include +# define STDIN 0 /* file descriptor for standard input */ +#endif + +// This file handles all dedicated-server in- and outputs + +static void *_dedicated_video_mem; + +static const char *DedicatedVideoStart(char **parm) { + _screen.width = _screen.pitch = _cur_resolution[0]; + _screen.height = _cur_resolution[1]; + _dedicated_video_mem = malloc(_cur_resolution[0]*_cur_resolution[1]); + + _debug_net_level = 6; + _debug_misc_level = 0; + + DEBUG(misc,0)("Loading dedicated server..."); + return NULL; +} +static void DedicatedVideoStop() { free(_dedicated_video_mem); } +static void DedicatedVideoMakeDirty(int left, int top, int width, int height) {} +static bool DedicatedVideoChangeRes(int w, int h) { return false; } + +#ifdef UNIX + +bool InputWaiting() +{ + struct timeval tv; + fd_set readfds; + + tv.tv_sec = 0; + tv.tv_usec = 1; + + FD_ZERO(&readfds); + FD_SET(STDIN, &readfds); + + /* don't care about writefds and exceptfds: */ + select(STDIN+1, &readfds, NULL, NULL, &tv); + + if (FD_ISSET(STDIN, &readfds)) + return true; + else + return false; +} +#else +bool InputWaiting() +{ + return kbhit(); +} +#endif + +static int DedicatedVideoMainLoop() { +#ifndef WIN32 + struct timeval tim; +#else + char input; +#endif + uint32 next_tick; + uint32 cur_ticks; + char input_line[200]; + +#ifdef WIN32 + next_tick = GetTickCount() + 30; +#else + gettimeofday(&tim, NULL); + next_tick = (tim.tv_usec / 1000) + 30 + (tim.tv_sec * 1000); +#endif + + // Load the dedicated server stuff + _is_network_server = true; + _network_dedicated = true; + _switch_mode = SM_NONE; + _network_playas = OWNER_SPECTATOR; + _local_player = OWNER_SPECTATOR; + DoCommandP(0, Random(), InteractiveRandom(), NULL, CMD_GEN_RANDOM_NEW_GAME); + // Done loading, start game! + + if (!_networking) { + DEBUG(net, 1)("Dedicated server could not be launced. Aborting.."); + return ML_QUIT; + } + + while (true) { + InteractiveRandom(); // randomness + +#ifdef UNIX + if (InputWaiting()) { + fgets(input_line, 200, stdin); + // Forget about the final \n (or \r) + strtok(input_line, "\r\n"); + IConsoleCmdExec(input_line); + } +#else + if (InputWaiting()) { + input = getch(); + printf("%c", input); + if (input != '\r') + snprintf(input_line, 200, "%s%c", input_line, input); + else { + printf("\n"); + IConsoleCmdExec(input_line); + sprintf(input_line, ""); + } + } +#endif + + if (_exit_game) return ML_QUIT; + +#ifdef WIN32 + cur_ticks = GetTickCount(); +#else + gettimeofday(&tim, NULL); + cur_ticks = (tim.tv_usec / 1000) + (tim.tv_sec * 1000); +#endif + + if (cur_ticks >= next_tick) { + next_tick += 30; + + GameLoop(); + _screen.dst_ptr = _dedicated_video_mem; + UpdateWindows(); + } + CSleep(1); + } + + return ML_QUIT; +} + + +const HalVideoDriver _dedicated_video_driver = { + DedicatedVideoStart, + DedicatedVideoStop, + DedicatedVideoMakeDirty, + DedicatedVideoMainLoop, + DedicatedVideoChangeRes, +}; + +#else + +static void *_dedicated_video_mem; + +static const char *DedicatedVideoStart(char **parm) { + DEBUG(misc,0)("OpenTTD compiled without network-support, quiting..."); + + return NULL; +} + +static void DedicatedVideoStop() { free(_dedicated_video_mem); } +static void DedicatedVideoMakeDirty(int left, int top, int width, int height) {} +static bool DedicatedVideoChangeRes(int w, int h) { return false; } +static int DedicatedVideoMainLoop() { return ML_QUIT; } + +const HalVideoDriver _dedicated_video_driver = { + DedicatedVideoStart, + DedicatedVideoStop, + DedicatedVideoMakeDirty, + DedicatedVideoMainLoop, + DedicatedVideoChangeRes, +}; + +#endif /* ENABLE_NETWORK */ diff --git a/dock_gui.c b/dock_gui.c --- a/dock_gui.c +++ b/dock_gui.c @@ -13,7 +13,7 @@ static void ShowBuildDocksDepotPicker(); static byte _ship_depot_direction; -static void CcBuildDocks(bool success, uint tile, uint32 p1, uint32 p2) +void CcBuildDocks(bool success, uint tile, uint32 p1, uint32 p2) { if (success) { SndPlayTileFx(SND_02_SPLAT, tile); @@ -21,7 +21,7 @@ static void CcBuildDocks(bool success, u } } -static void CcBuildCanal(bool success, uint tile, uint32 p1, uint32 p2) +void CcBuildCanal(bool success, uint tile, uint32 p1, uint32 p2) { if (success) SndPlayTileFx(SND_02_SPLAT, tile); } diff --git a/docs/Manual.txt b/docs/Manual.txt --- a/docs/Manual.txt +++ b/docs/Manual.txt @@ -90,7 +90,7 @@ Virtually any settings - train numbers, 2.11 Network Play -OpenTTD now supports rudimentary TCP/IP based network play. This is not supported on all platforms. To start a server, use the '-n' CLI switch, and start a client with '-n' and the servers IP adress. The OpenTTD network play runs over port 12345, so you may need to open this on your firewall. +See multiplayer.txt for more info. 2.12 Rail Recycling. diff --git a/docs/console.txt b/docs/console.txt --- a/docs/console.txt +++ b/docs/console.txt @@ -40,13 +40,13 @@ VARIABLES: VARIABLE HANDLING: ------------------ -*developer = 0 -*developer ++ +developer = 0 +developer ++ -*temp_string = test -*temp_string = "my little" +temp_string = test +temp_string = "my little" -printf "%s world" *temp_string +printf "%s world" temp_string --------------------------------------------------- diff --git a/docs/multiplayer.txt b/docs/multiplayer.txt --- a/docs/multiplayer.txt +++ b/docs/multiplayer.txt @@ -18,11 +18,18 @@ 2. Connecting to a Server - select one in the list below the buttons - click on "join game". - - if you want to play over the internet you should have the ip of the game server you want connect to. - - click direct connect + - if you want to play over the internet you should have the ip or hostname of the game server you want connect to. + - click add server - type in the ip address or hostname - if you want to add a port use : - - if you want to connect as an special player use # + + - now you can select a company and press: "Join company", to help that company + - or you can press "Spectate game", to spectate the game + - or you can press "New company", and start your own company (if there are slots free) + + - you see a progressbar how far you are with joining the server. + + - happy playing 3. Connecting to a Server over the Console @@ -33,38 +40,32 @@ 3. Connecting to a Server over the Conso 4. Playing Internet-Games - - since OpenTTD 0.3.4 you can also play internet games over higher latency connections. - - to do this the gameservers sync frequency should be highered to a decent value. - - open the console [on the server] - - type in the following command: - - ] *net_sync_freq = <4-80> - - default value: 4 - - - this is lowering the sync frequency of the server and your game should be less laggy. - - this is a server variable: it has nothing to do with the clients - - - you can also change when the clients ready packet is sent if you still have lags. - - open the console - - type in the following command: - - ] *net_ready_ahead = <1-8> - - default value: 1 - - - in that way your client is sending its "i am ready for next sync" a bit earlier - thats quite good for games where some players have higher latencies than the others. - - this is a client variable: it has nothing to do with the server + - since OpenTTD 0.3.5 the network protocol has been rewritten and is very stable, even over slow connections. + + - it can happen that a connection is that slow, or you have that many clients connected to your server, that your clients start to loose their connection. Some things you can do about it: + + - net_frame_freq: + change it in console with: net_frame_freq = + the number should be between the 0 and 10, not much higher. It indicates the delay between clicking and showing up. The higher, the more you notice it, but the less bandwidth you use. + + - net_sync_freq: + change it in console with: net_sync_freq = + the number should be between the 50 and 1000, not much lower, not much higer. It indicates the time between sync-frames. A sync-frame is a frame which checks if all clients are still in sync. When the value it too high, clients can desync in 1960, but the server detects it in 1970. Not really handy. The lower the value, the more bandwidth it uses. + + NB: changing net_frame_freq has more effect on the bandwidth then net_sync_freq. You should never change net_sync_freq! - - to change the client timeout time - - open the console [on the server] - - type in the following command: - - ] *net_client_timeout = <30-x> - - default value: 300 - - - warning: a too low value will disconnect your clients if they have a short lag - - \ No newline at end of file + +5. Some useful things + + - You can protect your company so nobody else can join uninvited. You do this with opening the console and then enter: protect , where is your password. + + - You can give other players some money via the ClientList (under the 'head' in the mainbar). + + - You can chat with other players via SHIFT+T or via the ClientList + + - Servers can now kick players, so don't make them use it! + + - From 0.3.5, desyncs should not happen anymore + + - From 0.3.5, patch-settings are also synced. You can now play without deleting openttd.cfg, and with, for example, extra large trains enabled. + diff --git a/economy.c b/economy.c --- a/economy.c +++ b/economy.c @@ -15,6 +15,7 @@ #include "network.h" #include "sound.h" #include "engine.h" +#include "network_data.h" void UpdatePlayerHouse(Player *p, uint score) { @@ -139,7 +140,7 @@ int UpdateCompanyRatingAndValue(Player * PlayerEconomyEntry *pee; int numec; int32 min_income; - uint32 max_income; + int32 max_income; numec = min(p->num_valid_stat_ent, 12); if (numec != 0) { @@ -346,6 +347,7 @@ static void PlayersCheckBankrupt(Player int owner; int64 val; + // If the player has money again, it does not go bankrupt if (p->player_money >= 0) { p->quarters_of_bankrupcy = 0; return; @@ -355,43 +357,65 @@ static void PlayersCheckBankrupt(Player owner = p->index; - if (p->quarters_of_bankrupcy == 2) { -year_2: - AddNewsItem( (StringID)(owner + 16), - NEWS_FLAGS(NM_CALLBACK, 0, NT_COMPANY_INFO, DNC_BANKRUPCY),0,0); - - } else if (p->quarters_of_bankrupcy == 3) { - if (IS_HUMAN_PLAYER(owner)) - goto year_2; + switch (p->quarters_of_bankrupcy) { + case 2: + AddNewsItem( (StringID)(owner + 16), + NEWS_FLAGS(NM_CALLBACK, 0, NT_COMPANY_INFO, DNC_BANKRUPCY),0,0); + break; + case 3: { + /* XXX - In multiplayer, should we ask other players if it wants to take + over when it is a human company? -- TrueLight */ + if (IS_HUMAN_PLAYER(owner)) { + AddNewsItem( (StringID)(owner + 16), + NEWS_FLAGS(NM_CALLBACK, 0, NT_COMPANY_INFO, DNC_BANKRUPCY),0,0); + break; + } - val = CalculateCompanyValue(p); - if (val == 0) goto year_4; - - p->bankrupt_value = val; - p->bankrupt_asked = 1 << owner; - p->bankrupt_timeout = 0; - } else if (p->quarters_of_bankrupcy == 4) { -year_4: - DeletePlayerWindows(owner); + // Check if the company has any value.. if not, declare it bankrupt + // right now + val = CalculateCompanyValue(p); + if (val > 0) { + p->bankrupt_value = val; + p->bankrupt_asked = 1 << owner; // Don't ask the owner + p->bankrupt_timeout = 0; + break; + } + // Else, falltrue to case 4... + } + case 4: { + // Close everything the owner has open + DeletePlayerWindows(owner); - if (IS_HUMAN_PLAYER(owner)) { -// what does this code do?? - InitNewsItemStructs(); - DeleteWindowById(WC_NEWS_WINDOW, 0); - } +// Show bankrupt news + SetDParam(0, p->name_1); + SetDParam(1, p->name_2); + AddNewsItem( (StringID)(owner + 16*3), NEWS_FLAGS(NM_CALLBACK, 0, NT_COMPANY_INFO, DNC_BANKRUPCY),0,0); -// Show bankrupt news - SetDParam(0, p->name_1); - SetDParam(1, p->name_2); - AddNewsItem( (StringID)(owner + 16*3), NEWS_FLAGS(NM_CALLBACK, 0, NT_COMPANY_INFO, DNC_BANKRUPCY),0,0); - - if (IS_HUMAN_PLAYER(owner)) { - p->bankrupt_asked = 255; - p->bankrupt_timeout = 0x456; - } else { - p->money64 = p->player_money = 100000000; - ChangeOwnershipOfPlayerItems(owner, 0xFF); // 255 is no owner - p->is_active = false; + // If the player is human, and it is no network play, leave the player playing + if (IS_HUMAN_PLAYER(owner) && !_networking) { + p->bankrupt_asked = 255; + p->bankrupt_timeout = 0x456; + } else { + // If we are the server, make sure it is clear that his player is no + // longer with us! + if (IS_HUMAN_PLAYER(owner) && _network_server) { + NetworkClientInfo *ci; + ci = NetworkFindClientInfoFromIndex(_network_own_client_index); + ci->client_playas = (byte)(OWNER_SPECTATOR + 1); + // Send the new info to all the clients + NetworkUpdateClientInfo(_network_own_client_index); + } + // Make sure the player no longer controls the company + if (IS_HUMAN_PLAYER(owner) && owner == _local_player) { + // Switch the player to spectator.. + _local_player = OWNER_SPECTATOR; + } + // Convert everything the player owns to NO_OWNER + p->money64 = p->player_money = 100000000; + ChangeOwnershipOfPlayerItems(owner, 0xFF); // 255 is no owner + // Register the player as not-active + p->is_active = false; + } } } } @@ -811,11 +835,11 @@ static void FindSubsidyPassengerRoute(Fo fr->distance = (uint)-1; - fr->from = from = DEREF_TOWN(InteractiveRandomRange(_total_towns)); + fr->from = from = DEREF_TOWN(RandomRange(_total_towns)); if (from->xy == 0 || from->population < 400) return; - fr->to = to = DEREF_TOWN(InteractiveRandomRange(_total_towns)); + fr->to = to = DEREF_TOWN(RandomRange(_total_towns)); if (from==to || to->xy == 0 || to->population < 400 || to->pct_pass_transported > 42) return; @@ -830,12 +854,12 @@ static void FindSubsidyCargoRoute(FoundR fr->distance = (uint)-1; - fr->from = i = DEREF_INDUSTRY(InteractiveRandomRange(_total_industries)); + fr->from = i = DEREF_INDUSTRY(RandomRange(_total_industries)); if (i->xy == 0) return; // Randomize cargo type - if (InteractiveRandom()&1 && i->produced_cargo[1] != 0xFF) { + if (Random()&1 && i->produced_cargo[1] != 0xFF) { cargo = i->produced_cargo[1]; trans = i->pct_transported[1]; total = i->total_production[1]; @@ -855,7 +879,7 @@ static void FindSubsidyCargoRoute(FoundR if (cargo == CT_GOODS || cargo == CT_FOOD) { // The destination is a town - Town *t = DEREF_TOWN(InteractiveRandomRange(_total_towns)); + Town *t = DEREF_TOWN(RandomRange(_total_towns)); // Only want big towns if (t->xy == 0 || t->population < 900) @@ -864,7 +888,7 @@ static void FindSubsidyCargoRoute(FoundR fr->to = t; } else { // The destination is an industry - Industry *i2 = DEREF_INDUSTRY(InteractiveRandomRange(_total_industries)); + Industry *i2 = DEREF_INDUSTRY(RandomRange(_total_industries)); // The industry must accept the cargo if (i == i2 || i2->xy == 0 || @@ -943,10 +967,8 @@ static void SubsidyMonthlyHandler() } } - if ((_networking) && (!_networking_server)) return; - // 25% chance to go on - if (ICHANCE16(1,4)) { + if (CHANCE16(1,4)) { // Find a free slot s = _subsidies; while (s->cargo_type != 0xFF) { @@ -972,7 +994,6 @@ static void SubsidyMonthlyHandler() if (!CheckSubsidyDuplicate(s)) { s->age = 0; pair = SetupSubsidyDecodeParam(s, 0); - if (_networking_server) NetworkSendEvent(NET_EVENT_SUBSIDY,sizeof(Subsidy),s); AddNewsItem(STR_2030_SERVICE_SUBSIDY_OFFERED, NEWS_FLAGS(NM_NORMAL, NF_TILE, NT_SUBSIDIES, 0), pair.a, pair.b); modified = true; break; diff --git a/engine.c b/engine.c --- a/engine.c +++ b/engine.c @@ -783,7 +783,7 @@ int32 CmdRenameEngine(int x, int y, uint { StringID str; - str = AllocateName((byte*)_decode_parameters, 0); + str = AllocateNameUnique((byte*)_decode_parameters, 0); if (str == 0) return CMD_ERROR; diff --git a/functions.h b/functions.h --- a/functions.h +++ b/functions.h @@ -1,8 +1,6 @@ #ifndef FUNCTIONS_H #define FUNCTIONS_H -#include "network.h" - /* vehicle.c */ /* window.c */ @@ -96,8 +94,28 @@ void CDECL ShowInfoF(const char *str, .. void NORETURN CDECL error(const char *str, ...); /* ttd.c */ -uint32 Random(); -uint RandomRange(uint max); + +// ************** +// * Warning: DO NOT enable this unless you understand what it does +// * +// * If enabled, in a network game all randoms will be dumped to the +// * stdout if the first client joins (or if you are a client). This +// * is to help finding desync problems. +// * +// * Warning: DO NOT enable this unless you understand what it does +// ************** + +//#define RANDOM_DEBUG + +#ifdef RANDOM_DEBUG + #define Random() DoRandom(__LINE__, __FILE__) + uint32 DoRandom(uint line, char *file); + #define RandomRange(max) DoRandomRange(max, __LINE__, __FILE__) + uint DoRandomRange(uint max, uint line, char *file); +#else + uint32 Random(); + uint RandomRange(uint max); +#endif void InitPlayerRandoms(); @@ -114,6 +132,12 @@ void AddTextEffect(StringID msg, int x, void InitTextEffects(); void DrawTextEffects(DrawPixelInfo *dpi); +void InitTextMessage(); +void DrawTextMessage(); +void AddTextMessage(uint16 color, uint8 duration, const char *message, ...); +void UndrawTextMessage(); +void TextMessageDailyLoop(); + bool AddAnimatedTile(uint tile); void DeleteAnimatedTile(uint tile); void AnimateAnimatedTiles(); @@ -125,46 +149,21 @@ bool CheckBridge_Stuff(byte bridge_type, uint32 GetBridgeLength(TileIndex begin, TileIndex end); int CalcBridgeLenCostFactor(int x); -/* network.c */ typedef void CommandCallback(bool success, uint tile, uint32 p1, uint32 p2); bool DoCommandP(TileIndex tile, uint32 p1, uint32 p2, CommandCallback *callback, uint32 cmd); -void NetworkReceive(); -void NetworkSend(); -void NetworkProcessCommands(); -void NetworkListen(); -void NetworkInitialize(); -void NetworkShutdown(); -void NetworkSendCommand(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback); -void NetworkSendEvent(uint16 type, uint16 data_len, void * data); -void NetworkStartSync(bool fcreset); -void NetworkClose(bool client); -void NetworkSendReadyPacket(); -void NetworkSendSyncPackets(); -void NetworkSendFrameSyncPackets(); -bool NetworkCheckClientReady(); - -void NetworkIPListInit(); - -void NetworkCoreInit(); -void NetworkCoreShutdown(); -void NetworkCoreDisconnect(); -void NetworkCoreLoop(bool incomming); -bool NetworkCoreConnectGame(const byte* b, unsigned short port); -bool NetworkCoreConnectGameStruct(NetworkGameList * item); -bool NetworkCoreStartGame(); - -void NetworkLobbyShutdown(); -void NetworkLobbyInit(); - -void NetworkGameListClear(); -NetworkGameList * NetworkGameListAdd(); -void NetworkGameListFromLAN(); -void NetworkGameListFromInternet(); -NetworkGameList * NetworkGameListItem(uint16 index); - -void NetworkGameFillDefaults(); -void NetworkGameChangeDate(uint16 newdate); +/* network.c */ +void NetworkUDPClose(void); +void NetworkStartUp(); +void NetworkShutDown(void); +void NetworkGameLoop(void); +void NetworkUDPGameLoop(void); +bool NetworkServerStart(void); +bool NetworkClientConnectGame(const byte* host, unsigned short port); +void NetworkQueryServer(const byte* host, unsigned short port, bool game_info); +void NetworkReboot(); +void NetworkDisconnect(); +void NetworkSend_Command(uint32 tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback); /* misc_cmd.c */ void PlaceTreesRandomly(); @@ -180,11 +179,16 @@ void InitializeLandscapeVariables(bool o /* misc.c */ void DeleteName(StringID id); byte *GetName(int id, byte *buff); -StringID AllocateName(const byte *name, byte skip); + +// AllocateNameUnique also tests if the name used is not used anywere else +// and if it is used, it returns an error. +#define AllocateNameUnique(name, skip) RealAllocateName(name, skip, true) +#define AllocateName(name, skip) RealAllocateName(name, skip, false) +StringID RealAllocateName(const byte *name, byte skip, bool check_double); void ConvertDayToYMD(YearMonthDay *ymd, uint16 date); uint ConvertYMDToDay(uint year, uint month, uint day); uint ConvertIntDate(uint date); - +void CSleep(int milliseconds); /* misc functions */ @@ -221,6 +225,10 @@ void ChangeTownRating(Town *t, int add, uint GetRoadBitsByTile(TileIndex tile); int GetTownRadiusGroup(Town *t, uint tile); int32 GetTransportedGoodsIncome(uint num_pieces, uint dist, byte transit_days, byte cargo_type); +void ShowNetworkChatQueryWindow(byte desttype, byte dest); +void ShowNetworkGiveMoneyWindow(byte player); +void ShowNetworkNeedGamePassword(); +void ShowNetworkNeedCompanyPassword(); void ShowRenameSignWindow(SignStruct *ss); void ShowRenameWaypointWindow(Waypoint *cp); int FindFirstBit(uint32 x); diff --git a/gfx.c b/gfx.c --- a/gfx.c +++ b/gfx.c @@ -17,7 +17,8 @@ static byte _string_colorremap[3]; static byte _dirty_blocks[DIRTY_BYTES_PER_LINE * MAX_SCREEN_HEIGHT / 8]; -static void memcpy_pitch(void *d, void *s, int w, int h, int spitch, int dpitch) + +void memcpy_pitch(void *d, void *s, int w, int h, int spitch, int dpitch) { byte *dp = (byte*)d; byte *sp = (byte*)s; @@ -41,6 +42,7 @@ void GfxScroll(int left, int top, int wi if (_cursor.visible) UndrawMouseCursor(); + UndrawTextMessage(); p = _screen.pitch; @@ -254,7 +256,7 @@ enum { /* returns right coordinate */ -int DrawString(int x, int y, uint16 str, byte color) +int DrawString(int x, int y, uint16 str, uint16 color) { GetString(str_buffr, str); assert(strlen(str_buffr) < sizeof(str_buffr) - 1); @@ -262,7 +264,7 @@ int DrawString(int x, int y, uint16 str, } -void DrawStringRightAligned(int x, int y, uint16 str, byte color) +void DrawStringRightAligned(int x, int y, uint16 str, uint16 color) { GetString(str_buffr, str); assert(strlen(str_buffr) < sizeof(str_buffr) - 1); @@ -270,7 +272,7 @@ void DrawStringRightAligned(int x, int y } -int DrawStringCentered(int x, int y, uint16 str, byte color) +int DrawStringCentered(int x, int y, uint16 str, uint16 color) { int w; @@ -283,7 +285,7 @@ int DrawStringCentered(int x, int y, uin return w; } -void DrawStringCenterUnderline(int x, int y, uint16 str, byte color) +void DrawStringCenterUnderline(int x, int y, uint16 str, uint16 color) { int w = DrawStringCentered(x, y, str, color); GfxFillRect(x-(w>>1), y+10, x-(w>>1)+w, y+10, _string_colorremap[1]); @@ -472,12 +474,15 @@ void DrawFrameRect(int left, int top, in } } -int DoDrawString(const byte *string, int x, int y, byte color) { +int DoDrawString(const byte *string, int x, int y, uint16 real_color) { DrawPixelInfo *dpi = _cur_dpi; int base = _stringwidth_base; byte c; + byte color; int xo = x, yo = y; + color = real_color & 0xFF; + if (color != 0xFE) { if (x >= dpi->left + dpi->width || x + _screen.width*2 <= dpi->left || @@ -487,8 +492,13 @@ int DoDrawString(const byte *string, int if (color != 0xFF) { switch_color:; - _string_colorremap[1] = _string_colormap[color].text; - _string_colorremap[2] = _string_colormap[color].shadow; + if (real_color & 0x100) { + _string_colorremap[1] = color; + _string_colorremap[2] = 215; + } else { + _string_colorremap[1] = _string_colormap[color].text; + _string_colorremap[2] = _string_colormap[color].shadow; + } _color_remap_ptr = _string_colorremap; } } @@ -1684,6 +1694,7 @@ void RedrawScreenRect(int left, int top, UndrawMouseCursor(); } } + UndrawTextMessage(); #if defined(_DEBUG) if (_dbg_screen_rect) @@ -1921,9 +1932,18 @@ void ToggleFullScreen(const bool full_sc { _fullscreen = full_screen; /* use preset resolutions, not _screen.height and _screen.width. On windows for example - if Desktop-size is 1280x1024, and gamesize is also 1280x1024, _screen.height will be - only 1000 because of possible start-bar. For this reason you cannot switch to + if Desktop-size is 1280x1024, and gamesize is also 1280x1024, _screen.height will be + only 1000 because of possible start-bar. For this reason you cannot switch to fullscreen mode from this resolution. Use of preset resolution will fix this */ if (!_video_driver->change_resolution(_cur_resolution[0], _cur_resolution[1])) _fullscreen ^= true; // switching resolution failed, put back full_screen to original status } + +uint16 GetDrawStringPlayerColor(byte player) +{ + // Get the color for DrawString-subroutines which matches the color + // of the player + if (player == OWNER_SPECTATOR || player == OWNER_SPECTATOR - 1) + return 1; + return (_color_list[_player_colors[player]].window_color_1b) | 0x100; +} diff --git a/gfx.h b/gfx.h --- a/gfx.h +++ b/gfx.h @@ -42,14 +42,15 @@ typedef struct CursorVars { void RedrawScreenRect(int left, int top, int right, int bottom); void GfxScroll(int left, int top, int width, int height, int xo, int yo); -int DrawStringCentered(int x, int y, uint16 str, byte color); -int DrawString(int x, int y, uint16 str, byte color); -void DrawStringCenterUnderline(int x, int y, uint16 str, byte color); -int DoDrawString(const byte *string, int x, int y, byte color); -void DrawStringRightAligned(int x, int y, uint16 str, byte color); +int DrawStringCentered(int x, int y, uint16 str, uint16 color); +int DrawString(int x, int y, uint16 str, uint16 color); +void DrawStringCenterUnderline(int x, int y, uint16 str, uint16 color); +int DoDrawString(const byte *string, int x, int y, uint16 color); +void DrawStringRightAligned(int x, int y, uint16 str, uint16 color); void GfxFillRect(int left, int top, int right, int bottom, int color); void GfxDrawLine(int left, int top, int right, int bottom, int color); void DrawFrameRect(int left, int top, int right, int bottom, int color, int flags); +uint16 GetDrawStringPlayerColor(byte player); int GetStringWidth(const byte *str); void LoadStringWidthTable(); diff --git a/gui.h b/gui.h --- a/gui.h +++ b/gui.h @@ -93,6 +93,12 @@ void AskForNewGameToStart(); void DrawEditBox(Window *w, int wid); void HandleEditBox(Window *w, int wid); +void BuildFileList(); +void SetFiosType(const byte fiostype); + +/* FIOS_TYPE_FILE, FIOS_TYPE_OLDFILE etc. different colours */ +static const byte _fios_colors[] = {13, 9, 9, 6, 5, 6, 5}; + /* network gui */ void ShowNetworkGameWindow(); diff --git a/hal.h b/hal.h --- a/hal.h +++ b/hal.h @@ -71,6 +71,8 @@ extern const HalMusicDriver _extmidi_mus extern const HalMusicDriver _bemidi_music_driver; #endif +extern const HalVideoDriver _dedicated_video_driver; + enum DriverType { VIDEO_DRIVER = 0, SOUND_DRIVER = 1, @@ -119,6 +121,12 @@ enum { FIOS_TYPE_OLD_SCENARIO = 6, }; + +// Variables to display file lists +FiosItem *_fios_list; +int _fios_num; +int _saveload_mode; + // get the name of an oldstyle savegame void GetOldSaveGameName(char *title, const char *file); // get the name of an oldstyle scenario diff --git a/industry_cmd.c b/industry_cmd.c --- a/industry_cmd.c +++ b/industry_cmd.c @@ -969,7 +969,9 @@ static void MaybePlantFarmField(Industry { uint tile; if (CHANCE16(1,8)) { - tile = TileAddWrap(i->xy, ((i->width>>1) + Random() % 31 - 16), ((i->height>>1) + Random() % 31 - 16)); + int x = (i->width>>1) + Random() % 31 - 16; + int y = (i->height>>1) + Random() % 31 - 16; + tile = TileAddWrap(i->xy, x, y); if (tile != TILE_WRAPPED) PlantFarmField(tile); } @@ -1391,7 +1393,7 @@ static Industry *AllocateIndustry() for(i=_industries; i != endof(_industries); i++) { if (i->xy == 0) { int index = i - _industries; - if (index > _total_industries) _total_industries++; + if (index > _total_industries) _total_industries = index; return i; } } @@ -1476,7 +1478,9 @@ static void DoCreateNewIndustry(Industry if (i->type == IT_FARM || i->type == IT_FARM_2) { tile = i->xy + TILE_XY((i->width >> 1), (i->height >> 1)); for(j=0; j!=50; j++) { - uint new_tile = TileAddWrap(tile, Random() % 31 - 16, Random() % 31 - 16); + int x = Random() % 31 - 16; + int y = Random() % 31 - 16; + uint new_tile = TileAddWrap(tile, x, y); if (new_tile != TILE_WRAPPED) PlantFarmField(new_tile); } @@ -1898,8 +1902,7 @@ static void Load_INDY() _total_industries = 0; while ((index = SlIterateArray()) != -1) { SlObject(DEREF_INDUSTRY(index), _industry_desc); - if (index + 1 > _total_industries) - _total_industries = index + 1; + if (index > _total_industries) _total_industries = index; } } diff --git a/intro_gui.c b/intro_gui.c --- a/intro_gui.c +++ b/intro_gui.c @@ -8,9 +8,9 @@ #include "player.h" #include "command.h" #include "console.h" +#include "network.h" -extern void MakeNewGame(); -extern void StartScenario(); +extern void SwitchMode(int new_mode); /* static void ShowSelectTutorialWindow() @@ -39,11 +39,13 @@ static const Widget _select_game_widgets { WIDGETS_END}, }; +extern void HandleOnEditText(WindowEvent *e); +extern void HandleOnEditTextCancel(); + static void SelectGameWndProc(Window *w, WindowEvent *e) { switch(e->event) { case WE_PAINT: - w->click_state = (w->click_state & ~(0xC0) & ~(0xF << 12)) | (1 << (_new_opt.landscape+12)) | (!_networking?(1<<6):(1<<7)); - w->disabled_state = _networking ? 0x30 : 0; + w->click_state = (w->click_state & ~(0xC0) & ~(0xF << 12)) | (1 << (_new_opt.landscape+12)) | (1<<6); SetDParam(0, STR_6801_EASY + _new_opt.diff_level); DrawWindowWidgets(w); break; @@ -54,17 +56,16 @@ static void SelectGameWndProc(Window *w, case 3: ShowSaveLoadDialog(SLD_LOAD_GAME); break; case 4: ShowPatchesSelection(); break; case 5: DoCommandP(0, InteractiveRandom(), 0, NULL, CMD_CREATE_SCENARIO); break; - case 6: - if (_networking) - DoCommandP(0, 0, 0, NULL, CMD_SET_SINGLE_PLAYER); - break; case 7: +#ifdef ENABLE_NETWORK if (!_network_available) { ShowErrorMessage(-1,STR_NETWORK_ERR_NOTAVAILABLE, 0, 0); } else { ShowNetworkGameWindow(); - ShowErrorMessage(-1, TEMP_STRING_NO_NETWORK, 0, 0); } +#else + ShowErrorMessage(-1,STR_NETWORK_ERR_NOTAVAILABLE, 0, 0); +#endif /* ENABLE_NETWORK */ break; case 8: ShowGameOptions(); break; case 9: ShowGameDifficulty(); break; @@ -79,7 +80,11 @@ static void SelectGameWndProc(Window *w, case WKC_BACKQUOTE: IConsoleSwitch(); break; } break; + + case WE_ON_EDIT_TEXT: HandleOnEditText(e); break; + case WE_ON_EDIT_TEXT_CANCEL: HandleOnEditTextCancel(); break; } + } static const WindowDesc _select_game_desc = { @@ -128,9 +133,7 @@ int32 CmdGenRandomNewGame(int x, int y, _random_seeds[0][0] = p1; _random_seeds[0][1] = p2; - if (_networking) { NetworkStartSync(true); } - - MakeNewGame(); + SwitchMode(SM_NEWGAME); return 0; } @@ -169,9 +172,7 @@ int32 CmdStartScenario(int x, int y, uin _random_seeds[0][0] = p1; _random_seeds[0][1] = p2; - if (_networking) { NetworkStartSync(true); } - - StartScenario(); + SwitchMode(SM_START_SCENARIO); return 0; } diff --git a/lang/english.txt b/lang/english.txt --- a/lang/english.txt +++ b/lang/english.txt @@ -1062,10 +1062,10 @@ STR_CONFIG_PATCHES_CURRENCY :{CURRENC STR_CONFIG_PATCHES_QUERY_CAPT :{WHITE}Change setting value STR_CONFIG_PATCHES_SERVICE_INTERVAL_INCOMPATIBLE :{WHITE}Some or all of the default service interval(s) below are incompatible with chosen setting! 5-90% and 30-800 days are valid -STR_TEMPERATE_LANDSCAPE :temperate landscape -STR_SUB_ARCTIC_LANDSCAPE :sub-arctic landscape -STR_SUB_TROPICAL_LANDSCAPE :sub-tropical landscape -STR_TOYLAND_LANDSCAPE :toyland landscape +STR_TEMPERATE_LANDSCAPE :Temperate landscape +STR_SUB_ARCTIC_LANDSCAPE :Sub-arctic landscape +STR_SUB_TROPICAL_LANDSCAPE :Sub-tropical landscape +STR_TOYLAND_LANDSCAPE :Toyland landscape STR_CHEATS :{WHITE}Cheats STR_CHEATS_TIP :{BLACK}Checkboxes indicate if you have used this cheat before @@ -1184,37 +1184,46 @@ TEMP_AI_ACTIVATED :{WHITE}Warning: ############ network gui strings -TEMP_STRING_NO_NETWORK :{WHITE}Network interface is not yet fully operational!{}Not working items have been disabled. - STR_NETWORK_MULTIPLAYER :{WHITE}Multiplayer -STR_NETWORK_FIND_SERVER :{BLACK}Find server -STR_NETWORK_FIND_SERVER_TIP :{BLACK}Search network for a server -STR_NETWORK_DIRECT_CONNECT :{BLACK}Direct connect -STR_NETWORK_ENTER_IP :{BLACK}Enter the IP address of the server -STR_NETWORK_DIRECT_CONNECT_TIP :{BLACK}Connect to a known IP -STR_NETWORK_START_SERVER :{BLACK}Start server -STR_NETWORK_START_SERVER_TIP :{BLACK}Start an own server - STR_NETWORK_PLAYER_NAME :{BLACK}Player name: STR_NETWORK_ENTER_NAME_TIP :{BLACK}This is the name other players will identify you by - -STR_NETWORK_SELECT_CONNECTION :{BLACK}Select connection type: -STR_NETWORK_CONNECTION_TYPE_TIP :{BLACK}Choose between an internet game or a local area nework game +STR_NETWORK_CONNECTION :{BLACK}Connection: +STR_NETWORK_CONNECTION_TIP :{BLACK}Choose between an internet game or a local area nework game STR_NETWORK_COMBO1 :{BLACK}{SKIP}{SKIP}{STRING} STR_NETWORK_LAN :LAN STR_NETWORK_INTERNET :Internet +STR_NETWORK_START_SERVER :{BLACK}Start server +STR_NETWORK_START_SERVER_TIP :{BLACK}Start an own server + STR_NETWORK_GAME_NAME :{BLACK}Name STR_NETWORK_GAME_NAME_TIP :{BLACK}Name of the game -STR_NETWORK_PLAYERS :{BLACK}#/# -STR_NETWORK_PLAYERS_TIP :{BLACK}Players currently in this game / Maximum number of players -STR_NETWORK_MAP_SIZE :{BLACK}Size -STR_NETWORK_MAP_SIZE_TIP :{BLACK}Size of the map STR_NETWORK_INFO_ICONS_TIP :{BLACK}Language, server version, etc. STR_NETWORK_CLICK_GAME_TO_SELECT :{BLACK}Click a game from the list to select it -STR_NETWORK_PLAYERS_VAL :{BLACK}{COMMA8}/{COMMA8} +STR_NETWORK_FIND_SERVER :{BLACK}Find server +STR_NETWORK_FIND_SERVER_TIP :{BLACK}Search network for a server +STR_NETWORK_ADD_SERVER :{BLACK}Add server +STR_NETWORK_ADD_SERVER_TIP :{BLACK}Adds a server to the list which will always be checked for running games. +STR_NETWORK_ENTER_IP :{BLACK}Enter the address of the host + +STR_NETWORK_CLIENTS_ONLINE :{BLACK}{COMMA16}/{COMMA16} +STR_NETWORK_CLIENTS_CAPTION :{BLACK}Clients +STR_NETWORK_CLIENTS_CAPTION_TIP :{BLACK}Clients online / clients max +STR_NETWORK_GAME_INFO :{SILVER}GAME INFO +STR_ORANGE :{ORANGE}{STRING} +STR_NETWORK_CLIENTS :{SILVER}Clients: {WHITE}{COMMA8} / {COMMA8} +STR_NETWORK_LANGUAGE :{SILVER}Language: {WHITE}{STRING} +STR_NETWORK_TILESET :{SILVER}Tileset: {WHITE}{STRING} +STR_NETWORK_MAP_SIZE :{SILVER}Map size: {WHITE}{COMMA16}x{COMMA16} +STR_NETWORK_SERVER_VERSION :{SILVER}Server version: {WHITE}{STRING} +STR_NETWORK_SERVER_ADDRESS :{SILVER}Server address: {WHITE}{STRING} +STR_NETWORK_START_DATE :{SILVER}Start date: {WHITE}{DATE_SHORT} +STR_NETWORK_CURRENT_DATE :{SILVER}Current date: {WHITE}{DATE_SHORT} +STR_NETWORK_PASSWORD :{SILVER}Password protected! +STR_NETWORK_SERVER_OFFLINE :{SILVER}SERVER OFFLINE +STR_NETWORK_SERVER_FULL :{SILVER}SERVER FULL STR_NETWORK_JOIN_GAME :{BLACK}Join game @@ -1223,20 +1232,25 @@ STR_NETWORK_START_GAME_WINDOW :{WHITE} STR_NETWORK_NEW_GAME_NAME :{BLACK}Game name: STR_NETWORK_NEW_GAME_NAME_TIP :{BLACK}The game name will be displayed to other players in the multiplayer game selection menu -STR_NETWORK_PASSWORD :{BLACK}Password: +STR_NETWORK_SET_PASSWORD :{BLACK}Set password STR_NETWORK_PASSWORD_TIP :{BLACK}Protect your game with a password if you don't want other people to join it STR_NETWORK_SELECT_MAP :{BLACK}Select a map: STR_NETWORK_SELECT_MAP_TIP :{BLACK}Which map do you want to play? -STR_NETWORK_NUMBER_OF_PLAYERS :{BLACK}Number of players: -STR_NETWORK_NUMBER_OF_PLAYERS_TIP :{BLACK}Choose a maximum number of players. Not all slots need to be filled. +STR_NETWORK_NUMBER_OF_CLIENTS :{BLACK}Maximum allowed clients: +STR_NETWORK_NUMBER_OF_CLIENTS_TIP :{BLACK}Choose a maximum number of clients. Not all slots need to be filled. STR_NETWORK_COMBO2 :{BLACK}{SKIP}{SKIP}{SKIP}{SKIP}{SKIP}{SKIP}{SKIP}{STRING} -STR_NETWORK_2_PLAYERS :2 players -STR_NETWORK_3_PLAYERS :3 players -STR_NETWORK_4_PLAYERS :4 players -STR_NETWORK_5_PLAYERS :5 players -STR_NETWORK_6_PLAYERS :6 players -STR_NETWORK_7_PLAYERS :7 players -STR_NETWORK_8_PLAYERS :8 players +STR_NETWORK_2_CLIENTS :2 clients +STR_NETWORK_3_CLIENTS :3 clients +STR_NETWORK_4_CLIENTS :4 clients +STR_NETWORK_5_CLIENTS :5 clients +STR_NETWORK_6_CLIENTS :6 clients +STR_NETWORK_7_CLIENTS :7 clients +STR_NETWORK_8_CLIENTS :8 clients +STR_NETWORK_9_CLIENTS :9 clients +STR_NETWORK_10_CLIENTS :10 clients +STR_NETWORK_LANGUAGE_SPOKEN :{BLACK}Language spoken: +STR_NETWORK_LANGUAGE_TIP :{BLACK}Other players will know which language is spoken on the server. +STR_NETWORK_COMBO3 :{BLACK}{SKIP}{SKIP}{SKIP}{SKIP}{SKIP}{SKIP}{SKIP}{SKIP}{SKIP}{STRING} STR_NETWORK_START_GAME :{BLACK}Start Game STR_NETWORK_START_GAME_TIP :{BLACK}Start a new network game from a random map, or scenario STR_NETWORK_LOAD_GAME :{BLACK}Load Game @@ -1244,17 +1258,61 @@ STR_NETWORK_LOAD_GAME_TIP :{BLACK}Re STR_NETWORK_LOAD_SCENARIO :{BLACK}Load Scenario STR_NETWORK_LOAD_SCENARIO_TIP :{BLACK}Start a new network game from a scenario +############ Leave those lines in this order!! +STR_NETWORK_LANG_ANY :Any +STR_NETWORK_LANG_ENGLISH :English +STR_NETWORK_LANG_GERMAN :German +STR_NETWORK_LANG_FRENCH :French +############ End of leave-in-this-order + STR_NETWORK_GAME_LOBBY :{WHITE}Multiplayer game lobby -STR_NETWORK_SEND :{BLACK}Send -STR_NETWORK_SEND_TIP :{BLACK}Send a message to the other players -STR_NETWORK_COMPANY_NAME :{BLACK}Company name: -STR_NETWORK_COMPANY_NAME_TIP :{BLACK}Change the name of your company. Hit enter to apply changes +STR_NETWORK_PREPARE_TO_JOIN :{BLACK}Preparing to join: {ORANGE}{STRING} +STR_NETWORK_COMPANY_LIST_TIP :{BLACK}A list of all companies currently in this game. You can either join one or start a +STR_NETWORK_NEW_COMPANY :{BLACK}New company +STR_NETWORK_NEW_COMPANY_TIP :{BLACK}Open a new company STR_NETWORK_SPECTATE_GAME :{BLACK}Spectate game STR_NETWORK_SPECTATE_GAME_TIP :{BLACK}Watch the game as a spectator -STR_NETWORK_NEW_COMPANY :{BLACK}New company -STR_NETWORK_NEW_COMPANY_TIP :{BLACK}Open a new company -STR_NETWORK_READY :{BLACK}Ready +STR_NETWORK_JOIN_COMPANY :{BLACK}Join company +STR_NETWORK_JOIN_COMPANY_TIP :{BLACK}Help managing this company +STR_NETWORK_REFRESH :{BLACK}Refresh server +STR_NETWORK_REFRESH_TIP :{BLACK}Refresh the server info + +STR_NETWORK_COMPANY_INFO :{SILVER}COMPANY INFO + +STR_NETWORK_COMPANY_NAME :{SILVER}Company name: {WHITE}{STRING} +STR_NETWORK_INAUGURATION_YEAR :{SILVER}Inauguration: {WHITE}{NUMU16} +STR_NETWORK_VALUE :{SILVER}Company value: {WHITE}{CURRENCY64} +STR_NETWORK_CURRENT_BALANCE :{SILVER}Current balance: {WHITE}{CURRENCY64} +STR_NETWORK_LAST_YEARS_INCOME :{SILVER}Last year's income: {WHITE}{CURRENCY64} +STR_NETWORK_PERFORMANCE :{SILVER}Performance: {WHITE}{NUMU16} + +STR_NETWORK_VEHICLES :{SILVER}Vehicles: {WHITE}{NUMU16} {TRAIN}, {NUMU16} {LORRY}, {NUMU16} {BUS}, {NUMU16} {PLANE}, {NUMU16} {SHIP} +STR_NETWORK_STATIONS :{SILVER}Stations: {WHITE}{NUMU16} {TRAIN}, {NUMU16} {LORRY}, {NUMU16} {BUS}, {NUMU16} {PLANE}, {NUMU16} {SHIP} +STR_NETWORK_PLAYERS :{SILVER}Players: {WHITE}{STRING} + +STR_NETWORK_CONNECTING :{WHITE}Connecting... + +############ Leave those lines in this order!! +STR_NETWORK_CONNECTING_1 :{BLACK}(1/6) Connecting.. +STR_NETWORK_CONNECTING_2 :{BLACK}(2/6) Authorizing.. +STR_NETWORK_CONNECTING_3 :{BLACK}(3/6) Waiting.. +STR_NETWORK_CONNECTING_4 :{BLACK}(4/6) Downloading map.. +STR_NETWORK_CONNECTING_5 :{BLACK}(5/6) Processing data.. + +STR_NETWORK_CONNECTING_SPECIAL_1 :{BLACK}Fetching game info.. +STR_NETWORK_CONNECTING_SPECIAL_2 :{BLACK}Fetching company info.. +############ End of leave-in-this-order +STR_NETWORK_CONNECTING_WAITING :{BLACK}{INT32} client(s) in front of us +STR_NETWORK_CONNECTING_DOWNLOADING :{BLACK}{INT32} / {INT32} kbytes downloaded so far + +STR_NETWORK_DISCONNECT :{BLACK}Disconnect + +STR_NETWORK_CHAT_QUERY_CAPTION :{WHITE}Enter your text message to send +STR_NETWORK_GIVE_MONEY_CAPTION :{WHITE}Enter the amount of money you want to give +STR_NETWORK_NEED_GAME_PASSWORD_CAPTION :{WHITE}Server is protected. Enter password +STR_NETWORK_NEED_COMPANY_PASSWORD_CAPTION :{WHITE}Company is protected. Enter password +STR_NETWORK_CLIENT_LIST :{WHITE}Client List STR_NETWORK_ERR_NOTAVAILABLE :{WHITE} No network devices found or compiled without ENABLE_NETWORK STR_NETWORK_ERR_NOSERVER :{WHITE} Could not find any network games @@ -1262,7 +1320,34 @@ STR_NETWORK_ERR_NOCONNECTION :{WHITE} STR_NETWORK_ERR_DESYNC :{WHITE} Network-Game synchronization failed. STR_NETWORK_ERR_LOSTCONNECTION :{WHITE} Network-Game connection lost. STR_NETWORK_ERR_SAVEGAMEERROR :{WHITE} Could not load server-savegame. +STR_NETWORK_ERR_SERVER_START :{WHITE} Could not start the server. +STR_NETWORK_ERR_CLIENT_START :{WHITE} Could not connect. STR_NETWORK_ERR_TIMEOUT :{WHITE} Connection #{NUMU16} timed out. +STR_NETWORK_ERR_SERVER_ERROR :{WHITE} We made a protocol-error and our connection is closed. +STR_NETWORK_ERR_WRONG_REVISION :{WHITE} The revision of this client does not match the revision of the server. +STR_NETWORK_ERR_WRONG_PASSWORD :{WHITE} Wrong password. +STR_NETWORK_ERR_SERVER_FULL :{WHITE} The server is full +STR_NETWORK_ERR_KICKED :{WHITE} You are kicked out of the server + +STR_NETWORK_ERR_LEFT :has left the game +############ Leave those lines in this order!! +STR_NETWORK_ERR_CLIENT_GENERAL :general error +STR_NETWORK_ERR_CLIENT_DESYNC :desync error +STR_NETWORK_ERR_CLIENT_SAVEGAME :could not load map +STR_NETWORK_ERR_CLIENT_CONNECTION_LOST :connection lost +STR_NETWORK_ERR_CLIENT_PROTOCOL_ERROR :protocol error +STR_NETWORK_ERR_CLIENT_NOT_AUTHORIZED :not authorized +STR_NETWORK_ERR_CLIENT_NOT_EXPECTED :received strange packet +STR_NETWORK_ERR_CLIENT_WRONG_REVISION :wrong revision +STR_NETWORK_ERR_CLIENT_NAME_IN_USE :name already in use +STR_NETWORK_ERR_CLIENT_WRONG_PASSWORD :wrong game-password +STR_NETWORK_ERR_CLIENT_PLAYER_MISMATCH :wrong player-id in DoCommand +STR_NETWORK_ERR_CLIENT_KICKED :kicked by server +############ End of leave-in-this-order +STR_NETWORK_CLIENT_JOINED :has joined the game +STR_NETWORK_GIVE_MONEY :gave you some money ({CURRENCY}) +STR_NETWORK_SERVER_SHUTDOWN :{WHITE} The server closed the session +STR_NETWORK_SERVER_REBOOT :{WHITE} The server is restarting...{}Please wait... ############ end network gui strings diff --git a/macros.h b/macros.h --- a/macros.h +++ b/macros.h @@ -170,7 +170,6 @@ static inline int FindFirstBit2x64(int v #define CHANCE16(a,b) ((uint16)Random() <= (uint16)((65536 * a) / b)) -#define ICHANCE16(a,b) ((uint16)InteractiveRandom() <= (uint16)((65536 * a) / b)) #define CHANCE16R(a,b,r) ((uint16)(r=Random()) <= (uint16)((65536 * a) / b)) #define CHANCE16I(a,b,v) ((uint16)(v) <= (uint16)((65536 * a) / b)) diff --git a/main_gui.c b/main_gui.c --- a/main_gui.c +++ b/main_gui.c @@ -12,6 +12,13 @@ #include "vehicle.h" #include "console.h" #include "sound.h" +#include "network.h" + +#ifdef ENABLE_NETWORK +#include "network_data.h" +#include "network_client.h" +#include "network_server.h" +#endif /* ENABLE_NETWORK */ #include "table/animcursors.h" @@ -35,7 +42,19 @@ extern void GenerateWorld(int mode); extern void GenerateIndustries(); extern void GenerateTowns(); -static void HandleOnEditText(WindowEvent *e) { +extern uint GetCurrentCurrencyRate(); + +void HandleOnEditTextCancel() { + switch(_rename_what) { +#ifdef ENABLE_NETWORK + case 4: + NetworkDisconnect(); + break; +#endif /* ENABLE_NETWORK */ + } +} + +void HandleOnEditText(WindowEvent *e) { byte *b = e->edittext.str; int id; memcpy(_decode_parameters, b, 32); @@ -52,6 +71,36 @@ static void HandleOnEditText(WindowEvent return; DoCommandP(0, id, 0, NULL, CMD_RENAME_WAYPOINT | CMD_MSG(STR_CANT_CHANGE_WAYPOINT_NAME)); break; +#ifdef ENABLE_NETWORK + case 2: + // Speak to.. + if (!_network_server) + SEND_COMMAND(PACKET_CLIENT_CHAT)(NETWORK_ACTION_CHAT + (id & 0xFF), id & 0xFF, (id >> 8) & 0xFF, e->edittext.str); + else + NetworkServer_HandleChat(NETWORK_ACTION_CHAT + (id & 0xFF), id & 0xFF, (id >> 8) & 0xFF, e->edittext.str, NETWORK_SERVER_INDEX); + break; + case 3: { + // Give money + int32 money = atoi(e->edittext.str) / GetCurrentCurrencyRate(); + char msg[100]; + // Give 'id' the money, and substract it from ourself + if (!DoCommandP(0, money, id, NULL, CMD_GIVE_MONEY)) break; + + // Inform the player of this action + SetDParam(0, money); + GetString(msg, STR_NETWORK_GIVE_MONEY); + + if (!_network_server) + SEND_COMMAND(PACKET_CLIENT_CHAT)(NETWORK_ACTION_GIVE_MONEY, DESTTYPE_PLAYER, id + 1, msg); + else + NetworkServer_HandleChat(NETWORK_ACTION_GIVE_MONEY, DESTTYPE_PLAYER, id + 1, msg, NETWORK_SERVER_INDEX); + break; + } + case 4: {// Game-Password and Company-Password + SEND_COMMAND(PACKET_CLIENT_PASSWORD)(id, e->edittext.str); + break; + } +#endif /* ENABLE_NETWORK */ } } @@ -88,9 +137,9 @@ typedef void ToolbarButtonProc(Window *w static void ToolbarPauseClick(Window *w) { - if (_networking && !_networking_server) { return;} // only server can pause the game + if (_networking && !_network_server) { return;} // only server can pause the game - if (DoCommandP(0, _pause?0:1, 0, NULL, CMD_PAUSE | CMD_NET_INSTANT)) + if (DoCommandP(0, _pause?0:1, 0, NULL, CMD_PAUSE)) SndPlayFx(SND_15_BEEP); } @@ -111,7 +160,7 @@ static void MenuClickSettings(int index) case 1: ShowGameDifficulty(); return; case 2: ShowPatchesSelection(); return; case 3: ShowNewgrf(); return; - + case 5: _display_opt ^= DO_SHOW_TOWN_NAMES; MarkWholeScreenDirty(); return; case 6: _display_opt ^= DO_SHOW_STATION_NAMES; MarkWholeScreenDirty(); return; case 7: _display_opt ^= DO_SHOW_SIGNS; MarkWholeScreenDirty(); return; @@ -194,9 +243,20 @@ static void MenuClickFinances(int index) ShowPlayerFinances(index); } +#ifdef ENABLE_NETWORK +extern void ShowClientList(); +#endif /* ENABLE_NETWORK */ + static void MenuClickCompany(int index) { - ShowPlayerCompany(index); + if (_networking && index == 0) { +#ifdef ENABLE_NETWORK + ShowClientList(); +#endif /* ENABLE_NETWORK */ + } else { + if (_networking) index--; + ShowPlayerCompany(index); + } } @@ -270,6 +330,37 @@ static void MenuClickBuildAir(int index) ShowBuildAirToolbar(); } +#ifdef ENABLE_NETWORK +void ShowNetworkChatQueryWindow(byte desttype, byte dest) +{ + _rename_id = desttype + (dest << 8); + _rename_what = 2; + ShowQueryString(STR_EMPTY, STR_NETWORK_CHAT_QUERY_CAPTION, 60, 250, 1, 0); +} + +void ShowNetworkGiveMoneyWindow(byte player) +{ + _rename_id = player; + _rename_what = 3; + ShowQueryString(STR_EMPTY, STR_NETWORK_GIVE_MONEY_CAPTION, 30, 180, 1, 0); +} + +void ShowNetworkNeedGamePassword() +{ + _rename_id = NETWORK_GAME_PASSWORD; + _rename_what = 4; + ShowQueryString(STR_EMPTY, STR_NETWORK_NEED_GAME_PASSWORD_CAPTION, 20, 180, WC_SELECT_GAME, 0); +} + +void ShowNetworkNeedCompanyPassword() +{ + _rename_id = NETWORK_COMPANY_PASSWORD; + _rename_what = 4; + ShowQueryString(STR_EMPTY, STR_NETWORK_NEED_COMPANY_PASSWORD_CAPTION, 20, 180, WC_SELECT_GAME, 0); +} + +#endif /* ENABLE_NETWORK */ + void ShowRenameSignWindow(SignStruct *ss) { _rename_id = ss - _sign_list; @@ -286,7 +377,7 @@ void ShowRenameWaypointWindow(Waypoint * ShowQueryString(STR_WAYPOINT_RAW, STR_EDIT_WAYPOINT_NAME, 30, 180, 1, 0); } -static void CcPlaceSign(bool success, uint tile, uint32 p1, uint32 p2) +void CcPlaceSign(bool success, uint tile, uint32 p1, uint32 p2) { if (success) { ShowRenameSignWindow(_new_sign_struct); @@ -483,6 +574,12 @@ static void UpdatePlayerMenuHeight(Windo num++; } + // Increase one to fit in PlayerList in the menu when + // in network + if (_networking && WP(w,menu_d).main_button == 9) { + num++; + } + if (WP(w,menu_d).item_count != num) { WP(w,menu_d).item_count = num; SetWindowDirty(w); @@ -493,6 +590,8 @@ static void UpdatePlayerMenuHeight(Windo } } +extern void DrawPlayerIcon(int p, int x, int y); + static void PlayerMenuWndProc(Window *w, WindowEvent *e) { switch(e->event) { @@ -510,12 +609,23 @@ static void PlayerMenuWndProc(Window *w, sel = WP(w,menu_d).sel_index; chk = WP(w,menu_d).checked_items; // let this mean gray items. + // 9 = playerlist + if (_networking && WP(w,menu_d).main_button == 9) { + if (sel == 0) { + GfxFillRect(x, y, x + 238, y + 9, 0); + } + DrawString(x + 19, y, STR_NETWORK_CLIENT_LIST, 0x0); + y += 10; + sel--; + } + FOR_ALL_PLAYERS(p) { if (p->is_active) { if (p->index == sel) { - GfxFillRect(x, y, x + 0xEE, y + 9, 0); + GfxFillRect(x, y, x + 238, y + 9, 0); } - DrawSprite( ((p->player_color + 0x307)<<16)+0x82EB, x+2, y+1); + + DrawPlayerIcon(p->index, x + 2, y + 1); SetDParam(0, p->name_1); SetDParam(1, p->name_2); @@ -523,12 +633,13 @@ static void PlayerMenuWndProc(Window *w, color = (byte)((p->index==sel) ? 0xC : 0x10); if (chk&1) color = 14; - DrawString(x+0x13, y, STR_7021, color); + DrawString(x + 19, y, STR_7021, color); y += 10; } chk >>= 1; } + break; } @@ -540,9 +651,15 @@ static void PlayerMenuWndProc(Window *w, } case WE_POPUPMENU_SELECT: { - int index = GetPlayerIndexFromMenu(GetMenuItemIndex(w, e->popupmenu.pt.x, e->popupmenu.pt.y)); + int index = GetMenuItemIndex(w, e->popupmenu.pt.x, e->popupmenu.pt.y); int action_id = WP(w,menu_d).action_id; + // We have a new entry at the top of the list of menu 9 when networking + // so keep that in count + if (!_networking || (WP(w,menu_d).main_button == 9 && index > 0)) { + index = GetPlayerIndexFromMenu(index-1)+1; + } + if (index < 0) { Window *w2 = FindWindowById(WC_MAIN_TOOLBAR,0); if (GetWidgetFromPos(w2, e->popupmenu.pt.x - w2->left, e->popupmenu.pt.y - w2->top) == WP(w,menu_d).main_button) @@ -560,7 +677,15 @@ static void PlayerMenuWndProc(Window *w, case WE_POPUPMENU_OVER: { int index; UpdatePlayerMenuHeight(w); - index = GetPlayerIndexFromMenu(GetMenuItemIndex(w, e->popupmenu.pt.x, e->popupmenu.pt.y)); + index = GetMenuItemIndex(w, e->popupmenu.pt.x, e->popupmenu.pt.y); + + // We have a new entry at the top of the list of menu 9 when networking + // so keep that in count + if (index != -1) { + if (!_networking || (WP(w,menu_d).main_button == 9 && index != 0)) { + index = GetPlayerIndexFromMenu(index-1)+1; + } + } if (index == -1 || index == WP(w,menu_d).sel_index) return; @@ -612,7 +737,10 @@ static Window *PopupMainPlayerToolbMenu( w = AllocateWindow(x, 0x16, 0xF1, 0x52, PlayerMenuWndProc, WC_TOOLBAR_MENU, _player_menu_widgets); w->flags4 &= ~WF_WHITE_BORDER_MASK; WP(w,menu_d).item_count = 0; - WP(w,menu_d).sel_index = _local_player != OWNER_SPECTATOR ? _local_player : 0; + WP(w,menu_d).sel_index = (_local_player != OWNER_SPECTATOR) ? _local_player : 0; + if (_networking && main_button == 9 && _local_player != OWNER_SPECTATOR) { + WP(w,menu_d).sel_index++; + } WP(w,menu_d).action_id = main_button; WP(w,menu_d).main_button = main_button; WP(w,menu_d).checked_items = gray; @@ -988,7 +1116,7 @@ static void AskResetLandscape(uint mode) AllocateWindowDescFront(&_ask_reset_landscape_desc, mode); } -static void CcTerraform(bool success, uint tile, uint32 p1, uint32 p2) +void CcTerraform(bool success, uint tile, uint32 p1, uint32 p2) { if (success) { SndPlayTileFx(SND_1F_SPLAT, tile); @@ -1000,7 +1128,7 @@ static void CcTerraform(bool success, ui static void CommonRaiseLowerBigLand(uint tile, int mode) { int size; - uint h; + byte h; _error_message_2 = mode ? STR_0808_CAN_T_RAISE_LAND_HERE : STR_0809_CAN_T_LOWER_LAND_HERE; @@ -1049,7 +1177,7 @@ static void PlaceProc_LowerBigLand(uint CommonRaiseLowerBigLand(tile, 0); } -//static void CcDemolish(bool success, uint tile, uint32 p1, uint32 p2) +//void CcDemolish(bool success, uint tile, uint32 p1, uint32 p2) //{ // if (success) { //SndPlayTileFx(0x10, tile); @@ -1257,7 +1385,7 @@ static void ToolbarScenGenLand(Window *w AllocateWindowDescFront(&_scen_edit_land_gen_desc, 0); } -static void CcBuildTown(bool success, uint tile, uint32 p1, uint32 p2) +void CcBuildTown(bool success, uint tile, uint32 p1, uint32 p2) { if (success) { SndPlayTileFx(SND_1F_SPLAT, tile); @@ -1713,7 +1841,7 @@ static void MainToolbarWndProc(Window *w case WKC_CTRL | 'S': _make_screenshot = 1; break; case WKC_CTRL | 'G': _make_screenshot = 2; break; case WKC_BACKQUOTE: IConsoleSwitch(); e->keypress.cont=false; break; - case WKC_CTRL | WKC_ALT | 'C': if(!_networking) ShowCheatWindow(); break; + case WKC_CTRL | WKC_ALT | 'C': if (!_networking) ShowCheatWindow(); break; } } break; @@ -2201,6 +2329,12 @@ static void MainWindowWndProc(Window *w, MarkWholeScreenDirty(); break; +#ifdef ENABLE_NETWORK + case 'T' | WKC_SHIFT: + ShowNetworkChatQueryWindow(DESTTYPE_BROADCAST, 0); + break; +#endif /* ENABLE_NETWORK */ + default: return; } @@ -2247,7 +2381,7 @@ void SetupColorsAndInitialWindow() if (_networking) { // if networking, disable fast-forward button w->disabled_state |= (1 << 1); - if (!_networking_server) // if not server, disable pause button + if (!_network_server) // if not server, disable pause button w->disabled_state |= (1 << 0); } diff --git a/makefiledir/Makefile.libdetection b/makefiledir/Makefile.libdetection --- a/makefiledir/Makefile.libdetection +++ b/makefiledir/Makefile.libdetection @@ -1,108 +1,110 @@ -# this file detects what OS and libs the computer have/are running - -# Automatically recognize if building on Win32 -ifdef WINDIR -ifndef UNIX -WIN32:=1 -CYGWIN:=1 -MINGW:=1 -STATIC:=1 -SKIP_STATIC_CHECK:=1 -endif -else -UNIX:=1 -endif - -# Automatically recognize if building on FreeBSD -ifeq ($(shell uname),FreeBSD) -FREEBSD:=1 -endif - -# Automatically recognize if building on MacOSX -ifeq ($(VENDOR), apple) -OSX:=1 -# OSX uses the unix setup too -UNIX:=1 -endif - -# Automatically recognize if building on MorphOS -ifeq ($(shell uname), MorphOS) -MORPHOS:=1 -# MorphOS uses UNIX setup too -UNIX:=1 -endif - -# Automatically recognize if building on BeOS -ifeq ($(shell uname), BeOS) -BEOS:=1 -# BeOS uses UNIX setup too -UNIX:=1 -# Except that in BeOS 5.0 we need to use net_server, not BONE networking -ifeq ($(shell uname -r), 5.0) -BEOS_NET_SERVER:=1 -endif -endif - -# Automatically recognize if building on SunOS/Solaris -ifeq ($(shell uname), SunOS) -SUNOS:=1 -# SunOS uses UNIX setup too -UNIX:=1 -endif - -# FreeBSD uses sdl11 instead of sdl -ifdef FREEBSD -SDL-CONFIG:=sdl11-config -else -SDL-CONFIG:=sdl-config -endif - - -# Library detections -WITH_SDL:=$(shell $(SDL-CONFIG) --version 2>/dev/null) - -# libpng detection -ifdef FREEBSD -# a little hack was needed for FreeBSD because it misses libpng-config -WITH_PNG:=$(shell ls /usr/lib | grep "libpng" 2>/dev/null) $(shell \ -ls /usr/local/lib | grep "libpng" 2>/dev/null) -ifdef WITH_PNG -# makes the flag look nicer in makefile.config -WITH_PNG:=1 -endif -else -WITH_PNG:=$(shell libpng-config --version 2>/dev/null) -endif - -ifdef WITH_PNG -# LibPNG depends on Zlib -WITH_ZLIB:=1 -else -# We go looking for zlib with a little hack -WITH_ZLIB:=$(shell ls /usr/include | grep "zlib.h" 2>/dev/null) \ -$(shell ls /usr/local/include | grep "zlib.h" 2>/dev/null) -ifdef WITH_ZLIB -WITH_ZLIB:=1 -endif -endif - - -# sets the default paths -ifdef UNIX -ifndef OSX -ifndef MORPHOS -ifndef BIN_DIR -#BINARY_DIR:= -#DATA_DIR_PREFIX:= -#INSTALL_DIR:=/usr/local/ -#USE_HOMEDIR:= -endif -endif -endif -endif - -# workaround -# cygwin have problems with libpng, so we will just disable it for now until the problem is solved -ifdef CYGWIN -WITH_PNG:= -endif \ No newline at end of file +# this file detects what OS and libs the computer have/are running + +# Automatically recognize if building on Win32 +ifdef WINDIR +ifndef UNIX +WIN32:=1 +CYGWIN:=1 +MINGW:=1 +STATIC:=1 +SKIP_STATIC_CHECK:=1 +endif +else +UNIX:=1 +endif + +# Automatically recognize if building on FreeBSD +ifeq ($(shell uname),FreeBSD) +FREEBSD:=1 +endif + +# Automatically recognize if building on MacOSX +ifeq ($(VENDOR), apple) +OSX:=1 +# OSX uses the unix setup too +UNIX:=1 +endif + +# Automatically recognize if building on MorphOS +ifeq ($(shell uname), MorphOS) +MORPHOS:=1 +# MorphOS uses UNIX setup too +UNIX:=1 +endif + +# Automatically recognize if building on BeOS +ifeq ($(shell uname), BeOS) +BEOS:=1 +# BeOS uses UNIX setup too +UNIX:=1 +# Except that in BeOS 5.0 we need to use net_server, not BONE networking +ifeq ($(shell uname -r), 5.0) +BEOS_NET_SERVER:=1 +endif +endif + +# Automatically recognize if building on SunOS/Solaris +ifeq ($(shell uname), SunOS) +SUNOS:=1 +# SunOS uses UNIX setup too +UNIX:=1 +endif + +# FreeBSD uses sdl11 instead of sdl +ifdef FREEBSD +SDL-CONFIG:=sdl11-config +else +SDL-CONFIG:=sdl-config +endif + +# Networking, enabled by default +WITH_NETWORK:=1 + +# Library detections +WITH_SDL:=$(shell $(SDL-CONFIG) --version 2>/dev/null) + +# libpng detection +ifdef FREEBSD +# a little hack was needed for FreeBSD because it misses libpng-config +WITH_PNG:=$(shell ls /usr/lib | grep "libpng" 2>/dev/null) $(shell \ +ls /usr/local/lib | grep "libpng" 2>/dev/null) +ifdef WITH_PNG +# makes the flag look nicer in makefile.config +WITH_PNG:=1 +endif +else +WITH_PNG:=$(shell libpng-config --version 2>/dev/null) +endif + +ifdef WITH_PNG +# LibPNG depends on Zlib +WITH_ZLIB:=1 +else +# We go looking for zlib with a little hack +WITH_ZLIB:=$(shell ls /usr/include | grep "zlib.h" 2>/dev/null) \ +$(shell ls /usr/local/include | grep "zlib.h" 2>/dev/null) +ifdef WITH_ZLIB +WITH_ZLIB:=1 +endif +endif + + +# sets the default paths +ifdef UNIX +ifndef OSX +ifndef MORPHOS +ifndef BIN_DIR +#BINARY_DIR:= +#DATA_DIR_PREFIX:= +#INSTALL_DIR:=/usr/local/ +#USE_HOMEDIR:= +endif +endif +endif +endif + +# workaround +# cygwin have problems with libpng, so we will just disable it for now until the problem is solved +ifdef CYGWIN +WITH_PNG:= +endif diff --git a/misc.c b/misc.c --- a/misc.c +++ b/misc.c @@ -5,6 +5,7 @@ #include "gfx.h" #include "assert.h" #include "saveload.h" +#include "network.h" extern void StartupEconomy(); extern void InitNewsItemStructs(); @@ -16,21 +17,24 @@ static inline uint32 ROR(uint32 x, int n return (x >> n) + (x << ((sizeof(x)*8)-n)); } -// For multiplayer, we introduced this new way of random-seeds -// It is player-based, so 2 clients can do 2 commands at the same time -// without the game desyncing. -// It is not used for non-multiplayer games -#ifdef ENABLE_NETWORK - #define PLAYER_SEED_RANDOM +/* XXX - Player-seeds don't seem to be used anymore.. which is a good thing + so I just disabled them for now. If there are no problems, we can remove + it completely! -- TrueLight */ +#undef PLAYER_SEED_RANDOM + +#ifdef RANDOM_DEBUG +#include "network_data.h" + +uint32 DoRandom(uint line, char *file) #else - #undef PLAYER_SEED_RANDOM +uint32 Random() +#endif +{ +#ifdef RANDOM_DEBUG + if (_networking && (DEREF_CLIENT(0)->status != STATUS_INACTIVE || !_network_server)) + printf("Random [%d/%d] %s:%d\n",_frame_counter, _current_player, file, line); #endif -// its for now not used at all because it is still desyncing :( -#undef PLAYER_SEED_RANDOM - -uint32 Random() -{ #ifdef PLAYER_SEED_RANDOM if (_current_player>=MAX_PLAYERS || !_networking) { uint32 s = _random_seeds[0][0]; @@ -41,6 +45,7 @@ uint32 Random() uint32 s = _player_seeds[_current_player][0]; uint32 t = _player_seeds[_current_player][1]; _player_seeds[_current_player][0] = s + ROR(t ^ 0x1234567F, 7); + DEBUG(net, 1)("[NET-Seeds] Player seed called!"); return _player_seeds[_current_player][1] = ROR(s, 3); } #else @@ -51,10 +56,17 @@ uint32 Random() #endif } +#ifdef RANDOM_DEBUG +uint DoRandomRange(uint max, uint line, char *file) +{ + return (uint16)DoRandom(line, file) * max >> 16; +} +#else uint RandomRange(uint max) { return (uint16)Random() * max >> 16; } +#endif uint32 InteractiveRandom() { @@ -75,7 +87,7 @@ void InitPlayerRandoms() for (i=0; i +# include + + static struct Device *TimerBase = NULL; + static struct MsgPort *TimerPort = NULL; + static struct timerequest *TimerRequest = NULL; +#endif // __AMIGA__ + +void CSleep(int milliseconds) +{ + #if defined(WIN32) + Sleep(milliseconds); + #endif + #if defined(UNIX) + #if !defined(__BEOS__) && !defined(__AMIGAOS__) + usleep(milliseconds * 1000); + #endif + #ifdef __BEOS__ + snooze(milliseconds * 1000); + #endif + #if defined(__AMIGAOS__) && !defined(__MORPHOS__) + { + ULONG signals; + ULONG TimerSigBit = 1 << TimerPort->mp_SigBit; + + // send IORequest + TimerRequest->tr_node.io_Command = TR_ADDREQUEST; + TimerRequest->tr_time.tv_secs = (milliseconds * 1000) / 1000000; + TimerRequest->tr_time.tv_micro = (milliseconds * 1000) % 1000000; + SendIO((struct IORequest *)TimerRequest); + + if (!((signals = Wait(TimerSigBit | SIGBREAKF_CTRL_C)) & TimerSigBit) ) { + AbortIO((struct IORequest *)TimerRequest); + } + WaitIO((struct IORequest *)TimerRequest); + } + #endif // __AMIGAOS__ && !__MORPHOS__ + #endif +} + void InitializeClearLand(); void InitializeRail(); void InitializeRailGui(); @@ -160,6 +217,7 @@ void InitializeGame() InitializeCheats(); InitTextEffects(); + InitTextMessage(); InitializeAnimatedTiles(); InitializeLandscapeVariables(false); @@ -171,6 +229,9 @@ void GenerateWorld(int mode) { int i; + // Make sure everything is done via OWNER_NONE + _current_player = OWNER_NONE; + _generating_world = true; InitializeGame(); SetObjectToPlace(1, 0, 0, 0); @@ -252,7 +313,7 @@ static void InitializeNameMgr() memset(_name_array, 0, sizeof(_name_array)); } -StringID AllocateName(const byte *name, byte skip) +StringID RealAllocateName(const byte *name, byte skip, bool check_double) { int free_item = -1; const byte *names; @@ -266,7 +327,7 @@ StringID AllocateName(const byte *name, if (free_item == -1) free_item = i; } else { - if (str_eq(names, name)) { + if (check_double && str_eq(names, name)) { _error_message = STR_0132_CHOSEN_NAME_IN_USE_ALREADY; return 0; } @@ -575,9 +636,9 @@ void IncreaseDate() /* yeah, increse day counter and call various daily loops */ _date++; - NetworkGameChangeDate(_date); + _vehicle_id_ctr_day = 0; - _vehicle_id_ctr_day = 0; + TextMessageDailyLoop(); DisasterDailyLoop(); WaypointsDailyLoop(); @@ -593,8 +654,6 @@ void IncreaseDate() return; _cur_month = ymd.month; -// printf("Month %d, %X\n", ymd.month, _random_seeds[0][0]); - /* yes, call various monthly loops */ if (_game_mode != GM_MENU) { if (HASBIT(_autosave_months[_opt.autosave], _cur_month)) { diff --git a/misc_cmd.c b/misc_cmd.c --- a/misc_cmd.c +++ b/misc_cmd.c @@ -8,6 +8,7 @@ #include "window.h" #include "saveload.h" #include "economy.h" +#include "network.h" /* p1 = player p2 = face @@ -124,7 +125,7 @@ int32 CmdChangeCompanyName(int x, int y, StringID str,old_str; Player *p; - str = AllocateName((byte*)_decode_parameters, 4); + str = AllocateNameUnique((byte*)_decode_parameters, 4); if (str == 0) return CMD_ERROR; @@ -146,7 +147,7 @@ int32 CmdChangePresidentName(int x, int StringID str,old_str; Player *p; - str = AllocateName((byte*)_decode_parameters, 4); + str = AllocateNameUnique((byte*)_decode_parameters, 4); if (str == 0) return CMD_ERROR; @@ -228,7 +229,7 @@ int32 CmdRenameSign(int x, int y, uint32 SignStruct *ss; if (_decode_parameters[0] != 0 && !p2) { - str = AllocateName((byte*)_decode_parameters, 0); + str = AllocateNameUnique((byte*)_decode_parameters, 0); if (str == 0) return CMD_ERROR; @@ -280,6 +281,22 @@ int32 CmdMoneyCheat(int x, int y, uint32 return (int32)p1; } +int32 CmdGiveMoney(int x, int y, uint32 flags, uint32 p1, uint32 p2) +{ + SET_EXPENSES_TYPE(EXPENSES_OTHER); + + if (flags & DC_EXEC) { + // Add money to player + byte old_cp = _current_player; + _current_player = p2; + SubtractMoneyFromPlayer(-(int32)p1); + _current_player = old_cp; + } + + // Subtract money from local-player + return (int32)p1; +} + int32 CmdChangeDifficultyLevel(int x, int y, uint32 flags, uint32 p1, uint32 p2) { if (flags & DC_EXEC) { @@ -289,6 +306,9 @@ int32 CmdChangeDifficultyLevel(int x, in } else { _opt_mod_ptr->diff_level = p2; } + // If we are a network-client, update the difficult setting (if it is open) + if (_networking && !_network_server && FindWindowById(WC_GAME_OPTIONS, 0) != NULL) + memcpy(&_opt_mod_temp, _opt_mod_ptr, sizeof(GameOptions)); InvalidateWindow(WC_GAME_OPTIONS, 0); } return 0; diff --git a/misc_gui.c b/misc_gui.c --- a/misc_gui.c +++ b/misc_gui.c @@ -10,11 +10,12 @@ #include "player.h" #include "town.h" #include "sound.h" +#include "network.h" -#include "hal.h" // Fios items +#include "hal.h" // for file list bool _query_string_active; -static void SetFiosType(const byte fiostype); +void SetFiosType(const byte fiostype); typedef struct LandInfoData { Town *town; @@ -24,7 +25,6 @@ typedef struct LandInfoData { TileDesc td; } LandInfoData; - static void LandInfoWndProc(Window *w, WindowEvent *e) { LandInfoData *lid; @@ -764,6 +764,7 @@ void DrawEditBox(Window *w, int wid) static void QueryStringWndProc(Window *w, WindowEvent *e) { + static bool closed = false; switch(e->event) { case WE_PAINT: { // int x; @@ -779,7 +780,7 @@ static void QueryStringWndProc(Window *w case 3: DeleteWindow(w); break; case 4: press_ok:; - if (str_eq(WP(w,querystr_d).buf, WP(w,querystr_d).buf + MAX_QUERYSTR_LEN)) { + if (str_eq(WP(w,querystr_d).buf, WP(w,querystr_d).buf + MAX_QUERYSTR_LEN) && (WP(w,querystr_d).maxlen & 0x1000) == 0) { DeleteWindow(w); } else { byte *buf = WP(w,querystr_d).buf; @@ -788,6 +789,10 @@ press_ok:; Window *parent; DeleteWindow(w); + + // Mask the edit-box as closed, so we don't send out a CANCEL + closed = true; + parent = FindWindowById(wnd_class, wnd_num); if (parent != NULL) { WindowEvent e; @@ -818,7 +823,20 @@ press_ok:; } } break; + case WE_CREATE: + closed = false; + break; + case WE_DESTROY: + // If the window is not closed yet, it means it still needs to send a CANCEL + if (!closed) { + Window *parent = FindWindowById(WP(w,querystr_d).wnd_class, WP(w,querystr_d).wnd_num); + if (parent != NULL) { + WindowEvent e; + e.event = WE_ON_EDIT_TEXT_CANCEL; + parent->wndproc(parent, &e); + } + } _query_string_active = false; break; } @@ -933,11 +951,7 @@ static const Widget _save_dialog_scen_wi }; -static FiosItem *_fios_list; -static int _fios_num; -static int _saveload_mode; - -static void BuildFileList() +void BuildFileList() { FiosFreeSavegameList(); if(_saveload_mode==SLD_NEW_GAME || _saveload_mode==SLD_LOAD_SCENARIO || _saveload_mode==SLD_SAVE_SCENARIO) @@ -957,9 +971,6 @@ static void DrawFiosTexts() DoDrawString(path, 2, 27, 16); } -/* FIOS_TYPE_FILE, FIOS_TYPE_OLDFILE etc. different colours */ -static const byte _fios_colors[] = {13, 9, 9, 6, 5, 6, 5}; - #if defined(_WIN32) extern int CDECL compare_FiosItems (const void *a, const void *b); #else @@ -1185,7 +1196,7 @@ void ShowSaveLoadDialog(int mode) } // pause is only used in single-player, non-editor mode - if(_game_mode != GM_MENU && !_networking && (_game_mode != GM_EDITOR)) + if(_game_mode != GM_MENU && !_networking && _game_mode != GM_EDITOR) DoCommandP(0, 1, 0, NULL, CMD_PAUSE); BuildFileList(); @@ -1282,7 +1293,7 @@ static void SelectScenarioWndProc(Window } } -static void SetFiosType(const byte fiostype) +void SetFiosType(const byte fiostype) { switch (fiostype) { case FIOS_TYPE_FILE: case FIOS_TYPE_SCENARIO: diff --git a/network.c b/network.c --- a/network.c +++ b/network.c @@ -1,1035 +1,554 @@ #include "stdafx.h" -#include "ttd.h" +#include "network_data.h" + +#ifdef ENABLE_NETWORK + #include "table/strings.h" -#include "gui.h" -#include "command.h" -#include "player.h" -#include "console.h" -#include "economy.h" - -#if defined(WIN32) -# include -# include +#include "network_client.h" +#include "network_server.h" +#include "network_udp.h" +#include "network_gamelist.h" +#include "console.h" /* IConsoleCmdExec */ +#include /* va_list */ -# pragma comment (lib, "ws2_32.lib") -# define ENABLE_NETWORK -# define GET_LAST_ERROR() WSAGetLastError() -# define EWOULDBLOCK WSAEWOULDBLOCK -#endif +// The listen socket for the server +static SOCKET _listensocket; + +// Network copy of patches, so the patches of a client are not fucked up +// after he joined a server +static Patches network_tmp_patches; -#if defined(UNIX) -// Make compatible with WIN32 names -# define SOCKET int -# define INVALID_SOCKET -1 -// we need different defines for MorphOS and AmigaOS -#if !defined(__MORPHOS__) && !defined(__AMIGA__) -# define ioctlsocket ioctl -# define closesocket close -# define GET_LAST_ERROR() errno -#endif -// Need this for FIONREAD on solaris -# define BSD_COMP -# include -# include +// The amount of clients connected +static byte _network_clients_connected = 0; +// The index counter for new clients (is never decreased) +static uint16 _network_client_index = NETWORK_SERVER_INDEX + 1; + +// Function that looks up the CI for a given client-index +NetworkClientInfo *NetworkFindClientInfoFromIndex(uint16 client_index) +{ + NetworkClientInfo *ci; -// Socket stuff -# include -# include -# include -# include -# include -// NetDB -# include + for (ci = _network_client_info; ci != &_network_client_info[MAX_CLIENT_INFO]; ci++) + if (ci->client_index == client_index) + return ci; -# ifndef TCP_NODELAY -# define TCP_NODELAY 0x0001 -# endif + return NULL; +} -#endif - +// Function that looks up the CS for a given client-index +ClientState *NetworkFindClientStateFromIndex(uint16 client_index) +{ + ClientState *cs; -#if defined(__MORPHOS__) || defined(__AMIGA__) -# include -# include // required for Open/CloseLibrary() -# if defined(__MORPHOS__) -# include // FION#? defines -# else // __AMIGA__ -# include -# endif + for (cs = _clients; cs != &_clients[MAX_CLIENT_INFO]; cs++) + if (cs->index == client_index) + return cs; -// make source compatible with bsdsocket.library functions -# define closesocket(s) CloseSocket(s) -# define GET_LAST_ERROR() Errno() -# define ioctlsocket(s,request,status) IoctlSocket((LONG)s,(ULONG)request,(char*)status) - -struct Library *SocketBase = NULL; + return NULL; +} -#if !defined(__MORPHOS__) -// usleep() implementation -#include -#include - -struct Device *TimerBase = NULL; -struct MsgPort *TimerPort = NULL; -struct timerequest *TimerRequest = NULL; -#endif - -#endif /* __MORPHOS__ || __AMIGA__ */ - - -#define SEND_MTU 1460 - -#if defined(ENABLE_NETWORK) +// NetworkGetClientName is a server-safe function to get the name of the client +// if the user did not send it yet, Client # is used. +void NetworkGetClientName(char *client_name, size_t size, ClientState *cs) +{ + NetworkClientInfo *ci = DEREF_CLIENT_INFO(cs); + if (ci->client_name[0] == '\0') + snprintf(client_name, size, "Client #%d", cs->index); + else + snprintf(client_name, size, "%s", ci->client_name); +} -enum { - PACKET_TYPE_WELCOME = 0, - PACKET_TYPE_READY, - PACKET_TYPE_ACK, - PACKET_TYPE_SYNC, - PACKET_TYPE_FSYNC, - PACKET_TYPE_XMIT, - PACKET_TYPE_COMMAND, - PACKET_TYPE_EVENT, -}; - -// sent from client -> server whenever the client wants to exec a command. -// send from server -> client when another player execs a command. -typedef struct CommandPacket { - byte packet_length; - byte packet_type; - uint16 cmd; - uint32 p1,p2; - TileIndex tile; - byte player;// player id, this is checked by the server. - byte when; // offset from the current max_frame value minus 1. this is set by the server. - uint32 dp[8]; -} CommandPacket; +// This puts a text-message to the console, or in the future, the chat-box, +// (to keep it all a bit more general) +void NetworkTextMessage(NetworkAction action, uint16 color, const char *name, const char *str, ...) +{ + char buf[1024]; + va_list va; + const int duration = 10; // Game days the messages stay visible -typedef struct EventPacket { - byte packet_length; - byte packet_type; - byte event_type; - byte data_start; -} EventPacket; - -#define COMMAND_PACKET_BASE_SIZE (sizeof(CommandPacket) - 8 * sizeof(uint32)) - -// sent from server -> client periodically to tell the client about the current tick in the server -// and how far the client may progress. -typedef struct SyncPacket { - byte packet_length; - byte packet_type; - byte frames; // how many more frames may the client execute? this is relative to the old value of max. - byte server; // where is the server currently executing? this is negatively relative to the old value of max. - uint32 random_seed_1; // current random state at server. used to detect out of sync. - uint32 random_seed_2; -} SyncPacket; - -typedef struct FrameSyncPacket { - byte packet_length; - byte packet_type; - byte frames; // where is the server currently executing? this is negatively relative to the old value of max. -} FrameSyncPacket; + va_start(va, str); + vsprintf(buf, str, va); + va_end(va); -// sent from server -> client as an acknowledgement that the server received the command. -// the command will be executed at the current value of "max". -typedef struct AckPacket { - byte packet_length; - byte packet_type; - int16 when; -} AckPacket; - -typedef struct ReadyPacket { - byte packet_length; - byte packet_type; -} ReadyPacket; - -typedef struct FilePacketHdr { - byte packet_length; - byte packet_type; -} FilePacketHdr; + switch (action) { + case NETWORK_ACTION_JOIN_LEAVE: + IConsolePrintF(color, "*** %s %s", name, buf); + AddTextMessage(color, duration, "*** %s %s", name, buf); + break; + case NETWORK_ACTION_GIVE_MONEY: + IConsolePrintF(color, "*** %s %s", name, buf); + AddTextMessage(color, duration, "*** %s %s", name, buf); + break; + case NETWORK_ACTION_CHAT_PLAYER: + IConsolePrintF(color, "[Team] %s: %s", name, buf); + AddTextMessage(color, duration, "[Team] %s: %s", name, buf); + break; + case NETWORK_ACTION_CHAT_CLIENT: + IConsolePrintF(color, "[Private] %s: %s", name, buf); + AddTextMessage(color, duration, "[Private] %s: %s", name, buf); + break; + case NETWORK_ACTION_CHAT_TO_CLIENT: + IConsolePrintF(color, "[Private] To %s: %s", name, buf); + AddTextMessage(color, duration, "[Private] To %s: %s", name, buf); + break; + case NETWORK_ACTION_CHAT_TO_PLAYER: + IConsolePrintF(color, "[Team] To %s: %s", name, buf); + AddTextMessage(color, duration, "[Team] To %s: %s", name, buf); + break; + case NETWORK_ACTION_NAME_CHANGE: + IConsolePrintF(color, "*** %s changed his name to %s", name, buf); + AddTextMessage(color, duration, "*** %s changed his name to %s", name, buf); + break; + default: + IConsolePrintF(color, "[All] %s: %s", name, buf); + AddTextMessage(color, duration, "[All] %s: %s", name, buf); + break; + } +} -// sent from server to client when the client has joined. -typedef struct WelcomePacket { - byte packet_length; - byte packet_type; - uint32 player_seeds[MAX_PLAYERS][2]; - uint32 frames_max; - uint32 frames_srv; - uint32 frames_cnt; -} WelcomePacket; +// Calculate the frame-lag of a client +uint NetworkCalculateLag(const ClientState *cs) +{ + int lag = cs->last_frame_server - cs->last_frame; + // This client has missed his ACK packet after 1 DAY_TICKS.. + // so we increase his lag for every frame that passes! + // The packet can be out by a max of _net_frame_freq + if (cs->last_frame_server + DAY_TICKS + _network_frame_freq < _frame_counter) + lag += _frame_counter - (cs->last_frame_server + DAY_TICKS + _network_frame_freq); -typedef struct Packet Packet; -struct Packet { - Packet *next; // this one has to be the first element. - uint siz; - byte buf[SEND_MTU]; // packet payload -}; - -typedef struct ClientState { - int socket; - bool inactive; // disable sending of commands/syncs to client - bool writable; - bool ready; - uint timeout; - uint xmitpos; - - uint eaten; - Packet *head, **last; - - uint buflen; // receive buffer len - byte buf[1024]; // receive buffer -} ClientState; + return lag; +} -typedef struct QueuedCommand QueuedCommand; -struct QueuedCommand { - QueuedCommand *next; - CommandPacket cp; - CommandCallback *callback; - uint32 cmd; - uint32 frame; -}; - -typedef struct CommandQueue CommandQueue; -struct CommandQueue { - QueuedCommand *head, **last; -}; - -#define MAX_CLIENTS (MAX_PLAYERS + 1) - -// packets waiting to be executed, for each of the players. -// this list is sorted in frame order, so the item on the front will be executed first. -static CommandQueue _command_queue; - -// in the client, this is the list of commands that have not yet been acked. -// when it is acked, it will be moved to the appropriate position at the end of the player queue. -static CommandQueue _ack_queue; +// There was a non-recoverable error, drop back to the main menu with a nice +// error +void NetworkError(StringID error_string) +{ + _switch_mode = SM_MENU; + _switch_mode_errorstr = error_string; +} -static ClientState _clients[MAX_CLIENTS]; -static int _num_clients; - -// keep a history of the 16 most recent seeds to be able to capture out of sync errors. -static uint32 _my_seed_list[16][2]; -static bool _network_ready_sent; -static uint32 _frame_fsync_last; - -typedef struct FutureSeeds { - uint32 frame; - uint32 seed[2]; -} FutureSeeds; +void ClientStartError(char *error) { + DEBUG(net, 0)("[NET] Client could not start network: %s",error); + NetworkError(STR_NETWORK_ERR_CLIENT_START); +} -// remember some future seeds that the server sent to us. -static FutureSeeds _future_seed[8]; -static uint _num_future_seed; - -static SOCKET _listensocket; // tcp socket +void ServerStartError(char *error) { + DEBUG(net, 0)("[NET] Server could not start network: %s",error); + NetworkError(STR_NETWORK_ERR_SERVER_START); +} -static SOCKET _udp_client_socket; // udp server socket -static SOCKET _udp_server_socket; // udp client socket - -typedef struct UDPPacket { - byte command_code; - byte data_len; - byte command_check; - byte data[255]; -} UDPPacket; +void NetworkClientError(byte res, ClientState *cs) { + // First, send a CLIENT_ERROR to the server, so he knows we are + // disconnection (and why!) + NetworkErrorCode errorno; -enum { - NET_UDPCMD_SERVERSEARCH = 1, - NET_UDPCMD_SERVERACTIVE, - NET_UDPCMD_GETSERVERINFO, - NET_UDPCMD_SERVERINFO, -}; + // We just want to close the connection.. + if (res == NETWORK_RECV_STATUS_CLOSE_QUERY) { + cs->quited = true; + CloseClient(cs); + _networking = false; -void NetworkUDPSend(bool client, struct sockaddr_in recv,struct UDPPacket packet); -static void HandleCommandPacket(ClientState *cs, CommandPacket *np); -static void CloseClient(ClientState *cs); -void NetworkSendWelcome(ClientState *cs, bool direct); - -uint32 _network_ip_list[10]; // network ip list - -// this is set to point to the savegame -static byte *_transmit_file; -static size_t _transmit_file_size; - -static FILE *_recv_file; + DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0); + return; + } -/* multi os compatible sleep function */ -void CSleep(int milliseconds) { -#if defined(WIN32) -Sleep(milliseconds); -#endif -#if defined(UNIX) -#if !defined(__BEOS__) && !defined(__MORPHOS__) && !defined(__AMIGAOS__) -usleep(milliseconds*1000); -#endif -#ifdef __BEOS__ -snooze(milliseconds*1000); -#endif -#if defined(__MORPHOS__) -usleep(milliseconds*1000); -#endif -#if defined(__AMIGAOS__) && !defined(__MORPHOS__) -{ - ULONG signals; - ULONG TimerSigBit = 1 << TimerPort->mp_SigBit; + switch(res) { + case NETWORK_RECV_STATUS_DESYNC: errorno = NETWORK_ERROR_DESYNC; break; + case NETWORK_RECV_STATUS_SAVEGAME: errorno = NETWORK_ERROR_SAVEGAME_FAILED; break; + default: errorno = NETWORK_ERROR_GENERAL; + } + // This means we fucked up and the server closed the connection + if (res != NETWORK_RECV_STATUS_SERVER_ERROR && res != NETWORK_RECV_STATUS_SERVER_FULL) { + SEND_COMMAND(PACKET_CLIENT_ERROR)(errorno); - // send IORequest - TimerRequest->tr_node.io_Command = TR_ADDREQUEST; - TimerRequest->tr_time.tv_secs = (milliseconds * 1000) / 1000000; - TimerRequest->tr_time.tv_micro = (milliseconds * 1000) % 1000000; - SendIO((struct IORequest *)TimerRequest); + // Dequeue all commands before closing the socket + NetworkSend_Packets(DEREF_CLIENT(0)); + } - if ( !((signals = Wait(TimerSigBit|SIGBREAKF_CTRL_C)) & TimerSigBit) ) { - AbortIO((struct IORequest *)TimerRequest); - } - WaitIO((struct IORequest *)TimerRequest); -} -#endif // __AMIGAOS__ && !__MORPHOS__ -#endif + _switch_mode = SM_MENU; + CloseClient(cs); + _networking = false; } -////////////////////////////////////////////////////////////////////// - -// ****************************** // -// * Network Error Handlers * // -// ****************************** // - -static void NetworkHandleSaveGameError() -{ - _networking_sync = false; - _networking_queuing = true; - _switch_mode = SM_MENU; - _switch_mode_errorstr = STR_NETWORK_ERR_SAVEGAMEERROR; -} - -static void NetworkHandleConnectionLost() -{ - _networking_sync = false; - _networking_queuing = true; - _switch_mode = SM_MENU; - _switch_mode_errorstr = STR_NETWORK_ERR_LOSTCONNECTION; -} - -static void NetworkHandleDeSync() +// Find all IP-aliases for this host +void NetworkFindIPs(void) { - DEBUG(net, 0) ("NET: error: network sync error at frame %i", _frame_counter); - { - int i; - for (i=15; i>=0; i--) DEBUG(net,0) ("NET frame %i: [0]=%i, [1]=%i",_frame_counter-(i+1),_my_seed_list[i][0],_my_seed_list[i][1]); - for (i=0; i<8; i++) DEBUG(net,0) ("NET frame %i: [0]=%i, [1]=%i",_frame_counter+i,_future_seed[i].seed[0],_future_seed[i].seed[1]); + int i, last; + +#if defined(BEOS_NET_SERVER) /* doesn't have neither getifaddrs or net/if.h */ + /* Based on Andrew Bachmann's netstat+.c. Big thanks to him! */ + int _netstat(int fd, char **output, int verbose); + + int seek_past_header(char **pos, const char *header) { + char *new_pos = strstr(*pos, header); + if (new_pos == 0) { + return B_ERROR; + } + *pos += strlen(header) + new_pos - *pos + 1; + return B_OK; + } + + int output_length; + char *output_pointer = NULL; + char **output; + int sock = socket(AF_INET, SOCK_DGRAM, 0); + i = 0; + + // If something fails, make sure the list is empty + _network_ip_list[0] = 0; + + if (sock < 0) { + DEBUG(net, 0)("Error creating socket!"); + return; + } + + output_length = _netstat(sock, &output_pointer, 1); + if (output_length < 0) { + DEBUG(net, 0)("Error running _netstat!"); + return; } - _networking_sync = false; - _networking_queuing = true; - _switch_mode = SM_MENU; - _switch_mode_errorstr = STR_NETWORK_ERR_DESYNC; -} + + output = &output_pointer; + if (seek_past_header(output, "IP Interfaces:") == B_OK) { + for (;;) { + uint32 n, fields, read; + uint8 i1, i2, i3, i4, j1, j2, j3, j4; + struct in_addr inaddr; + fields = sscanf(*output, "%u: %hhu.%hhu.%hhu.%hhu, netmask %hhu.%hhu.%hhu.%hhu%n", + &n, &i1,&i2,&i3,&i4, &j1,&j2,&j3,&j4, &read); + read += 1; + if (fields != 9) { + break; + } + inaddr.s_addr = htonl((uint32)i1 << 24 | (uint32)i2 << 16 | (uint32)i3 << 8 | (uint32)i4); + if (inaddr.s_addr != 0) { + _network_ip_list[i] = inaddr.s_addr; + i++; + } + if (read < 0) { + break; + } + *output += read; + } + /* XXX - Using either one of these crashes openttd heavily? - wber */ + /*free(output_pointer);*/ + /*free(output);*/ + closesocket(sock); + } +#elif defined(HAVE_GETIFADDRS) + struct ifaddrs *ifap, *ifa; + + // If something fails, make sure the list is empty + _network_ip_list[0] = 0; + + if (getifaddrs(&ifap) != 0) + return; -// ****************************** // -// * TCP Packets and Handlers * // -// ****************************** // + i = 0; + for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL || ifa->ifa_addr->sa_family != AF_INET) + continue; + _network_ip_list[i] = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr; + i++; + } + freeifaddrs(ifap); + +#else /* not HAVE_GETIFADDRS */ + + unsigned long len = 0; + SOCKET sock; + IFREQ ifo[MAX_INTERFACES]; + +#ifndef WIN32 + struct ifconf if_conf; +#endif + + // If something fails, make sure the list is empty + _network_ip_list[0] = 0; + + if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + return; + } -static QueuedCommand *AllocQueuedCommand(CommandQueue *nq) -{ - QueuedCommand *qp = (QueuedCommand*)calloc(sizeof(QueuedCommand), 1); - assert(qp); - *nq->last = qp; - nq->last = &qp->next; - return qp; +#ifdef WIN32 + // On windows it is easy + memset(&ifo[0], 0, sizeof(ifo)); + if ((WSAIoctl(sock, SIO_GET_INTERFACE_LIST, NULL, 0, &ifo[0], sizeof(ifo), &len, NULL, NULL)) != 0) { + closesocket(sock); + return; + } +#else + // On linux a bit harder + if_conf.ifc_len = (sizeof (struct ifreq)) * MAX_INTERFACES; + if_conf.ifc_buf = (char *)&ifo[0]; + if ((ioctl(sock, SIOCGIFCONF, &if_conf)) == -1) { + closesocket(sock); + return; + } + len = if_conf.ifc_len; +#endif /* WIN32 */ + + // Now walk through all IPs and list them + for (i = 0; i < (int)(len / sizeof(IFREQ)); i++) { + // Request IP for this interface +#ifdef WIN32 + _network_ip_list[i] = *(&ifo[i].iiAddress.AddressIn.sin_addr.s_addr); +#else + if ((ioctl(sock, SIOCGIFADDR, &ifo[i])) != 0) { + closesocket(sock); + return; + } + + _network_ip_list[i] = ((struct sockaddr_in *)&ifo[i].ifr_addr)->sin_addr.s_addr; +#endif + } + + closesocket(sock); + +#endif /* not HAVE_GETIFADDRS */ + + _network_ip_list[i] = 0; + last = i - 1; + + DEBUG(net, 3)("Detected IPs:"); + // Now display to the debug all the detected ips + i = 0; + while (_network_ip_list[i] != 0) { + // Also check for non-used ips (127.0.0.1) + if (_network_ip_list[i] == inet_addr("127.0.0.1")) { + // If there is an ip after thisone, put him in here + if (last > i) + _network_ip_list[i] = _network_ip_list[last]; + // Clear the last ip + _network_ip_list[last] = 0; + // And we have 1 ip less + last--; + continue; + } + + DEBUG(net, 3)(" %d) %s", i, inet_ntoa(*(struct in_addr *)&_network_ip_list[i]));//inet_ntoa(inaddr)); + i++; + } } -static void QueueClear(CommandQueue *nq) +// Resolve a hostname to a inet_addr +unsigned long NetworkResolveHost(const char *hostname) { - QueuedCommand *qp; - while ((qp=nq->head)) { - // unlink it. - if (!(nq->head = qp->next)) nq->last = &nq->head; - free(qp); + in_addr_t ip; + + // First try: is it an ip address? + ip = inet_addr(hostname); + + // If not try to resolve the name + if (ip == INADDR_NONE) { + struct hostent *he = gethostbyname(hostname); + if (he == NULL) { + DEBUG(net, 0) ("[NET] Cannot resolve %s", hostname); + } else { + struct in_addr addr = *(struct in_addr *)he->h_addr_list[0]; + DEBUG(net, 1) ("[NET] Resolved %s to %s", hostname, inet_ntoa(addr)); + ip = addr.s_addr; } - nq->last = &nq->head; -} - -static int GetNextSyncFrame() -{ - uint32 newframe; - if (_frame_fsync_last == 0) return -11; - newframe = (_frame_fsync_last + 11); // do not use a multiple of 4 since that screws up sync-packets - return (_frame_counter_max - newframe); - + } + return ip; } -// go through the player queues for each player and see if there are any pending commands -// that should be executed this frame. if there are, execute them. -void NetworkProcessCommands() +// Converts a string to ip/port/player +// Format: IP#player:port +// +// connection_string will be re-terminated to seperate out the hostname, and player and port will +// be set to the player and port strings given by the user, inside the memory area originally +// occupied by connection_string. +void ParseConnectionString(const byte **player, const byte **port, byte *connection_string) { - CommandQueue *nq; - QueuedCommand *qp; - byte old_player; - - // queue mode ? - if (_networking_queuing) - return; - - nq = &_command_queue; - while ( (qp=nq->head) && (!_networking_sync || qp->frame <= _frame_counter)) { - // unlink it. - if (!(nq->head = qp->next)) nq->last = &nq->head; - - if (qp->frame < _frame_counter && _networking_sync) { - DEBUG(net,0) ("warning: !qp->cp.frame < _frame_counter, %d < %d [%d]\n", qp->frame, _frame_counter, _frame_counter_srv+4); - } - - // run the command - old_player = _current_player; - _current_player = qp->cp.player; - memcpy(_decode_parameters, qp->cp.dp, (qp->cp.packet_length - COMMAND_PACKET_BASE_SIZE)); - - DoCommandP(qp->cp.tile, qp->cp.p1, qp->cp.p2, qp->callback, qp->cmd | CMD_DONT_NETWORK); - free(qp); - _current_player = old_player; - } - - if (!_networking_server) { - // remember the random seed so we can check if we're out of sync. - _my_seed_list[_frame_counter & 15][0] = _sync_seed_1; - _my_seed_list[_frame_counter & 15][1] = _sync_seed_2; - - while (_num_future_seed) { - assert(_future_seed[0].frame >= _frame_counter); - if (_future_seed[0].frame != _frame_counter) break; - if (_future_seed[0].seed[0] != _sync_seed_1 ||_future_seed[0].seed[1] != _sync_seed_2) NetworkHandleDeSync(); - memmove(_future_seed, _future_seed + 1, --_num_future_seed * sizeof(FutureSeeds)); + byte *p; + for (p = connection_string; *p != '\0'; p++) { + if (*p == '#') { + *player = p + 1; + *p = '\0'; + } else if (*p == ':') { + *port = p + 1; + *p = '\0'; } } } -// send a packet to a client -static void SendBytes(ClientState *cs, void *bytes, uint len) +// Creates a new client from a socket +// Used both by the server and the client +static ClientState *AllocClient(SOCKET s) { - byte *b = (byte*)bytes; - uint n; - Packet *p; - - assert(len != 0); + ClientState *cs; + NetworkClientInfo *ci; + byte client_no; - // see if there's space in the last packet? - if (!cs->head || (p = (Packet*)cs->last, p->siz == sizeof(p->buf))) - p = NULL; + client_no = 0; - do { - if (!p) { - // need to allocate a new packet buffer. - p = (Packet*)malloc(sizeof(Packet)); + if (_network_server) { + // Can we handle a new client? + if (_network_clients_connected >= MAX_CLIENTS) + return NULL; + + if (_network_game_info.clients_on >= _network_game_info.clients_max) + return NULL; - // insert at the end of the linked list. - *cs->last = p; - cs->last = &p->next; - p->next = NULL; - p->siz = 0; - } + // Register the login + client_no = _network_clients_connected++; + } + + cs = &_clients[client_no]; + memset(cs, 0, sizeof(*cs)); + cs->socket = s; + cs->last_frame = 0; + cs->quited = false; - // copy bytes to packet. - n = minu(sizeof(p->buf) - p->siz, len); - memcpy(p->buf + p->siz, b, n); - p->siz += n; - b += n; - p = NULL; - } while (len -= n); -} + if (_network_server) { + ci = &_network_client_info[client_no]; + memset(ci, 0, sizeof(*ci)); -// send data direct to a client -static void SendDirectBytes(ClientState *cs, void *bytes, uint len) -{ - char *buf = (char*)bytes; - uint n; + cs->index = _network_client_index++; + ci->client_index = cs->index; + ci->join_date = _date; - n = send(cs->socket, buf, len, 0); - if (n == -1) { - int err = GET_LAST_ERROR(); - DEBUG(net, 0) ("NET: %i] send() failed with error %d", _frame_counter, err); - CloseClient(cs); - } + InvalidateWindow(WC_CLIENT_LIST, 0); + } + + return cs; } -// client: -// add it to the client's ack queue, and send the command to the server -// server: -// add it to the server's player queue, and send it to all clients. -void NetworkSendCommand(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback) +// Close a connection +void CloseClient(ClientState *cs) { - int nump; - QueuedCommand *qp; - ClientState *cs; - CommandPacket cp; - - if (!(cmd & CMD_NET_INSTANT)) { - qp = AllocQueuedCommand(_networking_server ? &_command_queue : &_ack_queue); - } else { - qp = (QueuedCommand*)calloc(sizeof(QueuedCommand), 1); - } - qp->cp.packet_type = PACKET_TYPE_COMMAND; - qp->cp.tile = tile; - qp->cp.p1 = p1; - qp->cp.p2 = p2; - qp->cp.cmd = (uint16)cmd; - qp->cp.player = _local_player; - qp->cp.when = 0; - qp->cmd = cmd; - qp->callback = callback; + NetworkClientInfo *ci; + // Socket is already dead + if (cs->socket == INVALID_SOCKET) return; - // so the server knows when to execute it. - qp->frame = _frame_counter_max - GetNextSyncFrame(); - - // calculate the amount of extra bytes. - nump = 8; - while ( nump != 0 && ((uint32*)_decode_parameters)[nump-1] == 0) nump--; - qp->cp.packet_length = COMMAND_PACKET_BASE_SIZE + nump * sizeof(uint32); - if (nump != 0) memcpy(qp->cp.dp, _decode_parameters, nump * sizeof(uint32)); - - cp = qp->cp; - - // convert to little endian - cp.tile = TO_LE16(cp.tile); - cp.p1 = TO_LE32(cp.p1); - cp.p2 = TO_LE32(cp.p2); - cp.cmd = TO_LE16(cp.cmd); - - // send it to the peers - for(cs=_clients; cs->socket != INVALID_SOCKET; cs++) if (!cs->inactive) SendBytes(cs, &cp, cp.packet_length); - - if (cmd & CMD_NET_INSTANT) { - free(qp); - } -} - -void NetworkSendEvent(uint16 type, uint16 data_len, void * data) -{ - EventPacket * ep; - ClientState *cs; + DEBUG(net, 1) ("[NET] Closed client connection"); - // encode the event ... add its data - ep=malloc(data_len+sizeof(EventPacket)-1); - ep->event_type = type; - ep->packet_length = data_len+sizeof(EventPacket)-1; - ep->packet_type = PACKET_TYPE_EVENT; - memcpy(&ep->data_start,data,data_len); - - // send it to the peers - for(cs=_clients; cs->socket != INVALID_SOCKET; cs++) if (!cs->inactive) SendBytes(cs, ep, ep->packet_length); - - // free the temp packet - free(ep); -} + if (!cs->quited && _network_server && cs->status > STATUS_INACTIVE) { + // We did not receive a leave message from this client... + NetworkErrorCode errorno = NETWORK_ERROR_CONNECTION_LOST; + char str1[100], str2[100]; + char client_name[NETWORK_NAME_LENGTH]; + ClientState *new_cs; -// client: -// server sends a command from another player that we should execute. -// put it in the command queue. -// -// server: -// client sends a command that it wants to execute. -// fill the when field so the client knows when to execute it. -// put it in the appropriate player queue. -// send it to all other clients. -// send an ack packet to the actual client. - -static void HandleCommandPacket(ClientState *cs, CommandPacket *np) -{ - QueuedCommand *qp; - ClientState *c; - AckPacket ap; - uint16 cmd; + NetworkGetClientName(client_name, sizeof(client_name), cs); - DEBUG(net, 2) ("NET: %i] cmd size %d", _frame_counter, np->packet_length); - assert(np->packet_length >= COMMAND_PACKET_BASE_SIZE); - - cmd = FROM_LE16(np->cmd); + GetString(str1, STR_NETWORK_ERR_LEFT); + GetString(str2, STR_NETWORK_ERR_CLIENT_GENERAL + errorno); - if (!(cmd & CMD_NET_INSTANT)) { - // put it into the command queue - qp = AllocQueuedCommand(&_command_queue); - } else { - qp = (QueuedCommand*)calloc(sizeof(QueuedCommand), 1); - } - qp->cp = *np; - - qp->frame = _frame_counter_max - GetNextSyncFrame(); + NetworkTextMessage(NETWORK_ACTION_JOIN_LEAVE, 1, client_name, "%s (%s)", str1, str2); - qp->callback = NULL; - - // extra params - memcpy(&qp->cp.dp, np->dp, np->packet_length - COMMAND_PACKET_BASE_SIZE); - - ap.packet_type = PACKET_TYPE_ACK; - ap.when = TO_LE16(GetNextSyncFrame()); - ap.packet_length = sizeof(AckPacket); - DEBUG(net,4)("NET: %i] NewACK: frame=%i %i",_frame_counter, ap.when,_frame_counter_max - GetNextSyncFrame()); - - // send it to the peers - if (_networking_server) { - for(c=_clients; c->socket != INVALID_SOCKET; c++) { - if (c == cs) { - if (!(cmd & CMD_NET_INSTANT)) SendDirectBytes(c, &ap, ap.packet_length); - } else { - if (!cs->inactive) SendBytes(c, &qp->cp, qp->cp.packet_length); + // Inform other clients of this... strange leaving ;) + FOR_ALL_CLIENTS(new_cs) { + if (new_cs->status > STATUS_AUTH && cs != new_cs) { + SEND_COMMAND(PACKET_SERVER_ERROR_QUIT)(new_cs, cs->index, errorno); } } } -// convert from little endian to big endian? -#if defined(TTD_BIG_ENDIAN) - qp->cp.cmd = FROM_LE16(qp->cp.cmd); - qp->cp.tile = FROM_LE16(qp->cp.tile); - qp->cp.p1 = FROM_LE32(qp->cp.p1); - qp->cp.p2 = FROM_LE32(qp->cp.p2); -#endif - - qp->cmd = qp->cp.cmd; - - if (cmd & CMD_NET_INSTANT) { - byte p = _current_player; - _current_player = qp->cp.player; - memcpy(_decode_parameters, qp->cp.dp, (qp->cp.packet_length - COMMAND_PACKET_BASE_SIZE)); - DoCommandP(qp->cp.tile, qp->cp.p1, qp->cp.p2, qp->callback, qp->cmd | CMD_DONT_NETWORK); - free(qp); - _current_player = p; - } -} + closesocket(cs->socket); + cs->writable = false; -static void HandleEventPacket(EventPacket *ep) -{ - switch (ep->event_type) { - case NET_EVENT_SUBSIDY: - RemoteSubsidyAdd((Subsidy *)&ep->data_start); - break; + // Free all pending and partially received packets + while (cs->packet_queue != NULL) { + Packet *p = cs->packet_queue->next; + free(cs->packet_queue); + cs->packet_queue = p; } -} - -// sent from server -> client periodically to tell the client about the current tick in the server -// and how far the client may progress. -static void HandleSyncPacket(SyncPacket *sp) -{ - uint32 s1,s2; - - _frame_counter_srv = _frame_counter_max - sp->server; - _frame_counter_max += sp->frames; - - // reset network ready packet state - _network_ready_sent = false; - - // queueing only? - if (_networking_queuing || _frame_counter == 0) - return; - - s1 = FROM_LE32(sp->random_seed_1); - s2 = FROM_LE32(sp->random_seed_2); - - DEBUG(net, 3) ("NET: %i] sync seeds: 1=%i 2=%i",_frame_counter, sp->random_seed_1, sp->random_seed_2); + free(cs->packet_recv); + cs->packet_recv = NULL; - if (_frame_counter_srv <= _frame_counter) { - // we are ahead of the server check if the seed is in our list. - if (_frame_counter_srv + 16 > _frame_counter) { - // the random seed exists in our array check it. - if (s1 != _my_seed_list[_frame_counter_srv & 0xF][0] || s2 != _my_seed_list[_frame_counter_srv & 0xF][1]) NetworkHandleDeSync(); - } - } else { - // the server's frame has not been executed yet. store the server's seed in a list. - if (_num_future_seed < lengthof(_future_seed)) { - _future_seed[_num_future_seed].frame = _frame_counter_srv; - _future_seed[_num_future_seed].seed[0] = s1; - _future_seed[_num_future_seed].seed[1] = s2; - _num_future_seed++; - } + while (cs->command_queue != NULL) { + CommandPacket *p = cs->command_queue->next; + free(cs->command_queue); + cs->command_queue = p; } -} -static void HandleFSyncPacket(FrameSyncPacket *fsp) -{ - DEBUG(net,3)("NET: %i] FSYNC: srv=%i %i",_frame_counter, fsp->frames,(_frame_counter_max - fsp->frames)); - if (fsp->frames < 1) return; - _frame_fsync_last = _frame_counter_srv = _frame_counter_max - fsp->frames; -} - -// sent from server -> client as an acknowledgement that the server received the command. -// the command will be executed at the current value of "max". -static void HandleAckPacket(AckPacket * ap) -{ - QueuedCommand *q; - // move a packet from the ack queue to the end of this player's queue. - q = _ack_queue.head; - assert(q); - if (!(_ack_queue.head = q->next)) _ack_queue.last = &_ack_queue.head; - q->next = NULL; - - q->frame = (_frame_counter_max - (FROM_LE16(ap->when))); + // Close the gap in the client-list + ci = DEREF_CLIENT_INFO(cs); - *_command_queue.last = q; - _command_queue.last = &q->next; - - DEBUG(net, 2) ("NET %i] ack [frame=%i]",_frame_counter,q->frame); -} - -static void HandleFilePacket(FilePacketHdr *fp) -{ - int n = fp->packet_length - sizeof(FilePacketHdr); - char tempfile[512]; - - sprintf(tempfile, "%s/networkc.tmp", _path.personal_dir); - - if (n == 0) { - assert(_networking_queuing); - assert(!_networking_sync); - // eof - if (_recv_file) { fclose(_recv_file); _recv_file = NULL; } + if (_network_server) { + // We just lost one client :( + if (cs->status > STATUS_INACTIVE) + _network_game_info.clients_on--; + _network_clients_connected--; - // attempt loading the game. - _game_mode = GM_NORMAL; - if (SaveOrLoad(tempfile, SL_LOAD) != SL_OK) { - NetworkCoreDisconnect(); - NetworkHandleSaveGameError(); - return; - } - // sync to server. - _networking_queuing = false; - NetworkStartSync(false); - - if (_network_playas == 0) { - // send a command to make a new player - _local_player = 0; - NetworkSendCommand(0, 0, 0, CMD_PLAYER_CTRL, NULL); - _local_player = OWNER_SPECTATOR; - } else { - // take control over an existing company - if (DEREF_PLAYER(_network_playas-1)->is_active) - _local_player = _network_playas-1; - else - _local_player = OWNER_SPECTATOR; + while ((cs + 1) != DEREF_CLIENT(MAX_CLIENTS) && (cs + 1)->socket != INVALID_SOCKET) { + *cs = *(cs + 1); + *ci = *(ci + 1); + cs++; + ci++; } - } else { - if(!_recv_file) { - _recv_file = fopen(tempfile, "wb"); - if (!_recv_file) error("can't open savefile"); - } - fwrite( (char*)fp + sizeof(*fp), n, 1, _recv_file); - } -} - -static void HandleWelcomePacket(WelcomePacket *wp) -{ - int i; - for (i=0; iplayer_seeds[i][0]); - _player_seeds[i][1] = FROM_LE32(wp->player_seeds[i][1]); - } - if (wp->frames_srv != 0) { - _frame_counter_max = FROM_LE32(wp->frames_max); - _frame_counter_srv = FROM_LE32(wp->frames_srv); - } - if (wp->frames_cnt != 0) { - _frame_counter = FROM_LE32(wp->frames_cnt); - } -} - -static void HandleReadyPacket(ReadyPacket *rp, ClientState *cs) -{ - cs->ready=true; - cs->timeout=_network_client_timeout; - DEBUG(net,1) ("NET: %i] ready packet recv", _frame_counter); -} - - -static void CloseClient(ClientState *cs) -{ - Packet *p, *next; - - DEBUG(net, 1) ("[NET][TCP] closed client connection"); - - assert(cs->socket != INVALID_SOCKET); - - closesocket(cs->socket); - - // free buffers - for(p = cs->head; p; p=next) { - next = p->next; - free(p); + InvalidateWindow(WC_CLIENT_LIST, 0); } - // copy up structs... - while ((cs+1)->socket != INVALID_SOCKET) { - *cs = *(cs+1); - cs++; - } + // Reset the status of the last socket cs->socket = INVALID_SOCKET; - - if (_networking_server) _network_game.players_on--; - - _num_clients--; + cs->status = STATUS_INACTIVE; + cs->index = NETWORK_EMPTY_INDEX; + ci->client_index = NETWORK_EMPTY_INDEX; } -#define NETWORK_BUFFER_SIZE 4096 -static bool ReadPackets(ClientState *cs) -{ - byte network_buffer[NETWORK_BUFFER_SIZE]; - uint pos,size; - unsigned long recv_bytes; - - size = cs->buflen; - - for(;;) { - if (size != 0) memcpy(network_buffer, cs->buf, size); +extern void ShowJoinStatusWindow(); - recv_bytes = recv(cs->socket, (char*)network_buffer + size, sizeof(network_buffer) - size, 0); - if ( recv_bytes == (unsigned long)-1) { - int err = GET_LAST_ERROR(); - if (err == EWOULDBLOCK) break; - DEBUG(net, 0) ("[NET] recv() failed with error %d", err); - CloseClient(cs); - return false; - } - // no more bytes for now? - if (recv_bytes == 0) - break; +// A client wants to connect to a server +bool NetworkConnect(const char *hostname, int port) +{ + SOCKET s; + struct sockaddr_in sin; + + DEBUG(net, 1) ("[NET] Connecting to %s %d", hostname, port); - size += recv_bytes; // number of bytes read. - pos = 0; - while (size >= 2) { - byte *packet = network_buffer + pos; - // whole packet not there yet? - if (size < packet[0]) break; - size -= packet[0]; - pos += packet[0]; - switch(packet[1]) { - case PACKET_TYPE_WELCOME: - HandleWelcomePacket((WelcomePacket *)packet); - break; - case PACKET_TYPE_COMMAND: - HandleCommandPacket(cs, (CommandPacket*)packet); - break; - case PACKET_TYPE_SYNC: - assert(_networking_sync || _networking_queuing); - assert(!_networking_server); - HandleSyncPacket((SyncPacket*)packet); - break; - case PACKET_TYPE_FSYNC: - HandleFSyncPacket((FrameSyncPacket *)packet); - break; - case PACKET_TYPE_ACK: - assert(!_networking_server); - HandleAckPacket((AckPacket*)packet); - break; - case PACKET_TYPE_XMIT: - HandleFilePacket((FilePacketHdr*)packet); - break; - case PACKET_TYPE_READY: - HandleReadyPacket((ReadyPacket*)packet, cs); - break; - case PACKET_TYPE_EVENT: - HandleEventPacket((EventPacket*)packet); - break; - default: - DEBUG (net,0) ("NET: %i] unknown packet type",_frame_counter); - } - } + s = socket(AF_INET, SOCK_STREAM, 0); + if (s == INVALID_SOCKET) { + ClientStartError("socket() failed"); + return false; + } - assert(size < sizeof(cs->buf)); - - memcpy(cs->buf, network_buffer + pos, size); + { // set nodelay /* XXX should this be done at all? */ + #if !defined(BEOS_NET_SERVER) // not implemented on BeOS net_server... + int b = 1; + // The (const char*) cast is needed for windows!! + if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (const char*)&b, sizeof(b)) != 0) + DEBUG(net, 1)("[NET] Setting TCP_NODELAY failed"); + #endif } - cs->buflen = size; + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = NetworkResolveHost(hostname); + sin.sin_port = htons(port); + _network_last_host_ip = sin.sin_addr.s_addr; + + if (connect(s, (struct sockaddr*) &sin, sizeof(sin)) != 0) { + // We failed to connect for which reason what so ever + return false; + } + + { // set nonblocking mode for socket.. + unsigned long blocking = 1; + #if defined(__BEOS__) && defined(BEOS_NET_SERVER) + byte nonblocking = 1; + if (setsockopt(s, SOL_SOCKET, SO_NONBLOCK, &nonblocking, sizeof(blocking)) != 0) + #else + if (ioctlsocket(s, FIONBIO, &blocking) != 0) + #endif + DEBUG(net, 0)("[NET] Setting non-blocking failed"); /* XXX should this be an error? */ + } + + // in client mode, only the first client field is used. it's pointing to the server. + AllocClient(s); + + ShowJoinStatusWindow(); + + memcpy(&network_tmp_patches, &_patches, sizeof(_patches)); return true; } - -static bool SendPackets(ClientState *cs) -{ - Packet *p; - int n; - uint nskip = cs->eaten, nsent = nskip; - - // try sending as much as possible. - for(p=cs->head; p ;p = p->next) { - if (p->siz) { - assert(nskip < p->siz); - - n = send(cs->socket, p->buf + nskip, p->siz - nskip, 0); - if (n == -1) { - int err = GET_LAST_ERROR(); - if (err == EWOULDBLOCK) break; - DEBUG(net, 0) ("[NET] send() failed with error %d", err); - CloseClient(cs); - return false; - } - nsent += n; - // send was not able to send it all? then we assume that the os buffer is full and break. - if (nskip + n != p->siz) - break; - nskip = 0; - } - } - - // nsent bytes in the linked list are not invalid. free as many buffers as possible. - // don't actually free the last buffer. - while (nsent) { - p = cs->head; - assert(p->siz != 0); - - // some bytes of the packet are still unsent. - if ( (int)(nsent - p->siz) < 0) - break; - nsent -= p->siz; - p->siz = 0; - if (p->next) { - cs->head = p->next; - free(p); - } - } - - cs->eaten = nsent; - - return true; -} - -// transmit the file.. -static void SendXmit(ClientState *cs) -{ - uint pos, n; - FilePacketHdr hdr; - int p; - - // if too many unsent bytes left in buffer, don't send more. - if (cs->head && cs->head->next) - return; - - pos = cs->xmitpos - 1; - - p = 20; - do { - // compute size of data to xmit - n = minu(_transmit_file_size - pos, 248); - - hdr.packet_length = n + sizeof(hdr); - hdr.packet_type = PACKET_TYPE_XMIT; - SendBytes(cs, &hdr, sizeof(hdr)); - - if (n == 0) { - pos = -1; // eof - break; - } - SendBytes(cs, _transmit_file + pos, n); - pos += n; - } while (--p); - - cs->xmitpos = pos + 1; - - if (cs->xmitpos == 0) { - NetworkSendWelcome(cs,false); - } - - DEBUG(net, 2) ("[NET] client xmit at %d", pos + 1); -} - -static ClientState *AllocClient(SOCKET s) -{ - ClientState *cs; - - if (_num_clients == MAX_CLIENTS) - return NULL; - - if (_networking_server) _network_game.players_on++; - - cs = &_clients[_num_clients++]; - memset(cs, 0, sizeof(*cs)); - cs->last = &cs->head; - cs->socket = s; - cs->timeout = _network_client_timeout; - return cs; -} - -void NetworkSendReadyPacket() -{ - if ((!_network_ready_sent) && (_frame_counter + _network_ready_ahead >= _frame_counter_max)) { - ReadyPacket rp; - - DEBUG(net,1) ("NET: %i] ready packet sent", _frame_counter); - - rp.packet_type = PACKET_TYPE_READY; - rp.packet_length = sizeof(rp); - SendBytes(_clients, &rp, sizeof(rp)); - _network_ready_sent = true; - } -} - -void NetworkSendSyncPackets() -{ - ClientState *cs; - uint32 new_max; - SyncPacket sp; - - new_max = _frame_counter + (int)_network_sync_freq; - - DEBUG(net,3) ("NET: %i] serv: sync max=%i, seed1=%i, seed2=%i",_frame_counter,new_max,_sync_seed_1,_sync_seed_2); - - sp.packet_length = sizeof(sp); - sp.packet_type = PACKET_TYPE_SYNC; - sp.frames = new_max - _frame_counter_max; - sp.server = _frame_counter_max - _frame_counter; - sp.random_seed_1 = TO_LE32(_sync_seed_1); - sp.random_seed_2 = TO_LE32(_sync_seed_2); - _frame_counter_max = new_max; - - // send it to all the clients and mark them unready - for(cs=_clients;cs->socket != INVALID_SOCKET; cs++) { - cs->ready=false; - SendBytes(cs, &sp, sp.packet_length); - } - -} - -void NetworkSendFrameSyncPackets() -{ - ClientState *cs; - FrameSyncPacket fsp; - if ((_frame_counter + 4) < _frame_counter_max) if ((_frame_fsync_last + 4 < _frame_counter)) { - // this packet mantains some information about on which frame the server is - fsp.frames = _frame_counter_max - _frame_counter; - fsp.packet_type = PACKET_TYPE_FSYNC; - fsp.packet_length = sizeof (FrameSyncPacket); - // send it to all the clients and mark them unready - for(cs=_clients;cs->socket != INVALID_SOCKET; cs++) { - SendBytes(cs, &fsp, fsp.packet_length); - } - _frame_fsync_last = _frame_counter; - } - -} - -void NetworkSendWelcome(ClientState *cs, bool direct) { - WelcomePacket wp; - int i; - wp.packet_type = PACKET_TYPE_WELCOME; - wp.packet_length = sizeof(WelcomePacket); - for (i=0; ibuffer[0] = p->size & 0xFF; + p->buffer[1] = p->size >> 8; + + send(s, p->buffer, p->size, 0); closesocket(s); + + free(p); + continue; } - if (_networking_sync) { - // a new client has connected. it needs a snapshot. - cs->inactive = true; + // a new client has connected. We set him at inactive for now + // maybe he is only requesting server-info. Till he has sent a PACKET_CLIENT_MAP_OK + // the client stays inactive + cs->status = STATUS_INACTIVE; + + { + // Save the IP of the client + NetworkClientInfo *ci; + ci = DEREF_CLIENT_INFO(cs); + ci->client_ip = sin.sin_addr.s_addr; } } - - // when a new client has joined. it needs different information depending on if it's at the game menu or in an active game. - // Game menu: - // - list of players already in the game (name, company name, face, color) - // - list of game settings and patch settings - // Active game: - // - the state of the world (includes player name, company name, player face, player color) - // - list of the patch settings - - // Networking can be in several "states". - // * not sync - games don't need to be in sync, and frame counter is not synced. for example intro screen. all commands are executed immediately. - // * sync - games are in sync -} - -static void SendQueuedCommandsToNewClient(ClientState *cs) -{ - // send the commands in the server queue to the new client. - QueuedCommand *qp; - SyncPacket sp; - uint32 frame; - - DEBUG(net, 2) ("NET: %i] sending queued commands to client",_frame_counter); - - sp.packet_length = sizeof(sp); - sp.packet_type = PACKET_TYPE_SYNC; - sp.random_seed_1 = sp.random_seed_2 = 0; - sp.server = 0; - - frame = _frame_counter; - - for(qp=_command_queue.head; qp; qp = qp->next) { - DEBUG(net, 4) ("NET: %i] sending cmd to be executed at %d (old %d)", _frame_counter, qp->frame, frame); - if (qp->frame > frame) { - assert(qp->frame <= _frame_counter_max); - sp.frames = qp->frame - frame; - frame = qp->frame; - SendBytes(cs, &sp, sizeof(sp)); - } - SendBytes(cs, &qp->cp, qp->cp.packet_length); - } - - if (frame < _frame_counter_max) { - DEBUG(net, 4) ("NET: %i] sending queued sync %d (%d)",_frame_counter, _frame_counter_max, frame); - sp.frames = _frame_counter_max - frame; - SendBytes(cs, &sp, sizeof(sp)); - } - } -bool NetworkCheckClientReady() -{ - bool ready_all = true; - uint16 count = 0; - ClientState *cs; - - for(cs=_clients;cs->socket != INVALID_SOCKET; cs++) { - count++; - ready_all = ready_all && (cs->ready || cs->inactive || (cs->xmitpos>0)); - if (!cs->ready) cs->timeout-=1; - if (cs->timeout == 0) { - SetDParam(0,count); - ShowErrorMessage(-1,STR_NETWORK_ERR_TIMEOUT,0,0); - CloseClient(cs); - } - } - return ready_all; -} - -// ************************** // -// * TCP Networking * // -// ************************** // - -unsigned long NetworkResolveHost(const char *hostname) +// Set up the listen socket for the server +bool NetworkListen(void) { - struct hostent* remotehost; - - if ((hostname[0]<0x30) || (hostname[0]>0x39)) { - // seems to be an hostname [first character is no number] - remotehost = gethostbyname(hostname); - if (remotehost == NULL) { - DEBUG(net, 1) ("[NET][IP] cannot resolve %s", hostname); - return 0; - } else { - DEBUG(net, 1) ("[NET][IP] resolved %s to %s",hostname, inet_ntoa(*(struct in_addr *) remotehost->h_addr_list[0])); - return inet_addr(inet_ntoa(*(struct in_addr *) remotehost->h_addr_list[0])); - } - } else { - // seems to be an ip [first character is a number] - return inet_addr(hostname); - } - -} - -bool NetworkConnect(const char *hostname, int port) -{ - SOCKET s; - struct sockaddr_in sin; - int b; - - DEBUG(net, 1) ("[NET][TCP] Connecting to %s %d", hostname, port); - - s = socket(AF_INET, SOCK_STREAM, 0); - if (s == INVALID_SOCKET) error("socket() failed"); - - b = 1; - setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (const char*)&b, sizeof(b)); - - sin.sin_family = AF_INET; - sin.sin_addr.s_addr = NetworkResolveHost(hostname); - sin.sin_port = htons(port); - - if (connect(s, (struct sockaddr*) &sin, sizeof(sin)) != 0) { - NetworkClose(true); - return false; - } - - // set nonblocking mode for socket.. - { unsigned long blocking = 1; ioctlsocket(s, FIONBIO, &blocking); } - - // in client mode, only the first client field is used. it's pointing to the server. - AllocClient(s); - - // queue packets.. because we're waiting for the savegame. - _networking_queuing = true; - _frame_counter_max = 0; - - return true; -} - -void NetworkListen() -{ - SOCKET ls; struct sockaddr_in sin; int port; port = _network_server_port; - DEBUG(net, 1) ("[NET][TCP] listening on port %d", port); + DEBUG(net, 1) ("[NET] Listening on port %d", port); ls = socket(AF_INET, SOCK_STREAM, 0); - if (ls == INVALID_SOCKET) - error("socket() on listen socket failed"); - - // reuse the socket - { - int reuse = 1; if (setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)) == -1) - error("setsockopt() on listen socket failed"); + if (ls == INVALID_SOCKET) { + ServerStartError("socket() on listen socket failed"); + return false; } - // set nonblocking mode for socket - { unsigned long blocking = 1; ioctlsocket(ls, FIONBIO, &blocking); } + { // reuse the socket + int reuse = 1; + // The (const char*) cast is needed for windows!! + if (setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)) == -1) { + ServerStartError("setsockopt() on listen socket failed"); + return false; + } + } + + { // set nonblocking mode for socket + unsigned long blocking = 1; + #if defined(__BEOS__) && defined(BEOS_NET_SERVER) + byte nonblocking = 1; + if (setsockopt(ls, SOL_SOCKET, SO_NONBLOCK, &nonblocking, sizeof(blocking)) != 0) + #else + if (ioctlsocket(ls, FIONBIO, &blocking) != 0) + #endif + DEBUG(net, 0)("[NET] Setting non-blocking failed"); /* XXX should this be an error? */ + } sin.sin_family = AF_INET; sin.sin_addr.s_addr = 0; sin.sin_port = htons(port); - if (bind(ls, (struct sockaddr*)&sin, sizeof(sin)) != 0) - error("bind() failed"); + if (bind(ls, (struct sockaddr*)&sin, sizeof(sin)) != 0) { + ServerStartError("bind() failed"); + return false; + } - if (listen(ls, 1) != 0) - error("listen() failed"); + if (listen(ls, 1) != 0) { + ServerStartError("listen() failed"); + return false; + } _listensocket = ls; + + return true; +} + +// Close all current connections +void NetworkClose(void) +{ + ClientState *cs; + + FOR_ALL_CLIENTS(cs) { + if (!_network_server) { + SEND_COMMAND(PACKET_CLIENT_QUIT)("leaving"); + NetworkSend_Packets(cs); + } + CloseClient(cs); + } + + if (_network_server) { + // We are a server, also close the listensocket + closesocket(_listensocket); + _listensocket = INVALID_SOCKET; + DEBUG(net, 1) ("[NET] Closed listener"); + NetworkUDPClose(); + } +} + +// Inits the network (cleans sockets and stuff) +void NetworkInitialize(void) +{ + ClientState *cs; + + _local_command_queue = NULL; + + // Clean all client-sockets + for (cs = _clients; cs != &_clients[MAX_CLIENTS]; cs++) { + cs->socket = INVALID_SOCKET; + cs->status = STATUS_INACTIVE; + cs->command_queue = NULL; + } + + // Clean the client_info memory + memset(_network_client_info, 0, sizeof(_network_client_info)); + memset(_network_player_info, 0, sizeof(_network_player_info)); + + _sync_frame = 0; + _network_first_time = true; + + _network_reconnect = 0; + + InitPlayerRandoms(); + + NetworkUDPInitialize(); +} + +// Query a server to fetch his game-info +// If game_info is true, only the gameinfo is fetched, +// else only the client_info is fetched +void NetworkQueryServer(const byte* host, unsigned short port, bool game_info) +{ + if (!_network_available) return; + + NetworkDisconnect(); + + if (game_info) { + NetworkUDPQueryServer(host, port); + return; + } + + NetworkInitialize(); + + _network_server = false; + + // Try to connect + _networking = NetworkConnect(host, port); + +// ttd_strlcpy(_network_last_host, host, sizeof(_network_last_host)); +// _network_last_port = port; + + // We are connected + if (_networking) { + SEND_COMMAND(PACKET_CLIENT_COMPANY_INFO)(); + return; + } + + // No networking, close everything down again + NetworkDisconnect(); +} + +// Used by clients, to connect to a server +bool NetworkClientConnectGame(const byte* host, unsigned short port) +{ + if (!_network_available) return false; + + if (port == 0) return false; + + ttd_strlcpy(_network_last_host, host, sizeof(_network_last_host)); + _network_last_port = port; + + NetworkDisconnect(); + NetworkUDPClose(); + NetworkInitialize(); + + // Try to connect + _networking = NetworkConnect(host, port); + + // We are connected + if (_networking) { + IConsoleCmdExec("exec scripts/on_client.scr 0"); + NetworkClient_Connected(); + } else { + // Connecting failed + NetworkError(STR_NETWORK_ERR_NOCONNECTION); + } + + return _networking; } -void NetworkReceive() +void NetworkInitGameInfo(void) +{ +#if defined(WITH_REV) + extern char _openttd_revision[]; +#else + const char _openttd_revision[] = "norev000"; +#endif + NetworkClientInfo *ci; + + ttd_strlcpy(_network_game_info.server_name, _network_server_name, sizeof(_network_game_info.server_name)); + if (_network_game_info.server_name[0] == '\0') + snprintf(_network_game_info.server_name, sizeof(_network_game_info.server_name), "Unnamed Server"); + + // The server is a client too ;) + if (_network_dedicated) { + _network_game_info.clients_on = 0; + _network_game_info.dedicated = true; + } else { + _network_game_info.clients_on = 1; + _network_game_info.dedicated = false; + } + strncpy(_network_game_info.server_revision, _openttd_revision, sizeof(_network_game_info.server_revision)); + _network_game_info.spectators_on = 0; + _network_game_info.game_date = _date; + _network_game_info.start_date = ConvertIntDate(_patches.starting_date); + _network_game_info.map_width = TILES_X; + _network_game_info.map_height = TILES_Y; + _network_game_info.map_set = _opt.landscape; + + if (_network_game_info.server_password[0] == '\0') { + _network_game_info.use_password = 0; + } else { + _network_game_info.use_password = 1; + } + + // We use _network_client_info[MAX_CLIENT_INFO - 1] to store the server-data in it + // The index is NETWORK_SERVER_INDEX ( = 1) + ci = &_network_client_info[MAX_CLIENT_INFO - 1]; + memset(ci, 0, sizeof(*ci)); + + ci->client_index = NETWORK_SERVER_INDEX; + if (_network_dedicated) + ci->client_playas = OWNER_SPECTATOR; + else + ci->client_playas = _local_player + 1; + strncpy(ci->client_name, _network_player_name, sizeof(ci->client_name)); +} + +bool NetworkServerStart(void) +{ + if (!_network_available) return false; + + NetworkInitialize(); + if (!NetworkListen()) + return false; + + // Try to start UDP-server + _network_udp_server = true; + _network_udp_server = NetworkUDPListen(0, _network_server_port); + + _network_server = true; + _networking = true; + _frame_counter = 0; + _frame_counter_server = 0; + _frame_counter_max = 0; + _network_own_client_index = NETWORK_SERVER_INDEX; + + _network_clients_connected = 0; + + NetworkInitGameInfo(); + + // execute server initialization script + IConsoleCmdExec("exec scripts/on_server.scr 0"); + // if the server is dedicated ... add some other script + if (_network_dedicated) IConsoleCmdExec("exec scripts/on_dedicated.scr 0"); + return true; +} + +// The server is rebooting... +// The only difference with NetworkDisconnect, is the packets that is sent +void NetworkReboot(void) +{ + if (_network_server) { + ClientState *cs; + FOR_ALL_CLIENTS(cs) { + SEND_COMMAND(PACKET_SERVER_NEWGAME)(cs); + NetworkSend_Packets(cs); + } + } + + NetworkClose(); + + // Free all queued commands + while (_local_command_queue != NULL) { + CommandPacket *p = _local_command_queue; + _local_command_queue = _local_command_queue->next; + free(p); + } + + _networking = false; + _network_server = false; +} + +// We want to disconnect from the host/clients +void NetworkDisconnect(void) +{ + if (_network_server) { + ClientState *cs; + FOR_ALL_CLIENTS(cs) { + SEND_COMMAND(PACKET_SERVER_SHUTDOWN)(cs); + NetworkSend_Packets(cs); + } + } + + NetworkClose(); + + // Free all queued commands + while (_local_command_queue != NULL) { + CommandPacket *p = _local_command_queue; + _local_command_queue = _local_command_queue->next; + free(p); + } + + if (_networking && !_network_server) { + memcpy(&_patches, &network_tmp_patches, sizeof(_patches)); + } + + _networking = false; + _network_server = false; +} + +// Receives something from the network +bool NetworkReceive(void) { ClientState *cs; int n; @@ -1243,13 +924,13 @@ void NetworkReceive() FD_ZERO(&read_fd); FD_ZERO(&write_fd); - for(cs=_clients;cs->socket != INVALID_SOCKET; cs++) { + FOR_ALL_CLIENTS(cs) { FD_SET(cs->socket, &read_fd); FD_SET(cs->socket, &write_fd); } // take care of listener port - if (_networking_server) { + if (_network_server) { FD_SET(_listensocket, &read_fd); } @@ -1259,428 +940,257 @@ void NetworkReceive() #else n = WaitSelect(FD_SETSIZE, &read_fd, &write_fd, NULL, &tv, NULL); #endif - if ((n == -1) && (!_networking_server)) NetworkHandleConnectionLost(); + if (n == -1 && !_network_server) NetworkError(STR_NETWORK_ERR_LOSTCONNECTION); // accept clients.. - if (_networking_server && FD_ISSET(_listensocket, &read_fd)) + if (_network_server && FD_ISSET(_listensocket, &read_fd)) NetworkAcceptClients(); // read stuff from clients - for(cs=_clients;cs->socket != INVALID_SOCKET; cs++) { + FOR_ALL_CLIENTS(cs) { cs->writable = !!FD_ISSET(cs->socket, &write_fd); if (FD_ISSET(cs->socket, &read_fd)) { - if (!ReadPackets(cs)) - cs--; + if (_network_server) + NetworkServer_ReadPackets(cs); + else { + byte res; + // The client already was quiting! + if (cs->quited) return false; + if ((res = NetworkClient_ReadPackets(cs)) != NETWORK_RECV_STATUS_OKAY) { + // The client made an error of which we can not recover + // close the client and drop back to main menu + + NetworkClientError(res, cs); + return false; + } + } } } + return true; +} - // if we're a server, and any client needs a snapshot, create a snapshot and send all commands from the server queue to the client. - if (_networking_server && _transmit_file == NULL) { - bool didsave = false; +// This sends all buffered commands (if possible) +static void NetworkSend(void) +{ + ClientState *cs; + FOR_ALL_CLIENTS(cs) { + if (cs->writable) { + NetworkSend_Packets(cs); + + if (cs->status == STATUS_MAP) { + // This client is in the middle of a map-send, call the function for that + SEND_COMMAND(PACKET_SERVER_MAP)(cs); + } + } + } +} - for(cs=_clients;cs->socket != INVALID_SOCKET; cs++) { - if (cs->inactive) { - cs->inactive = false; - // found a client waiting for a snapshot. make a snapshot. - if (!didsave) { - char filename[256]; - sprintf(filename, "%snetwork.tmp", _path.autosave_dir); - didsave = true; - if (SaveOrLoad(filename, SL_SAVE) != SL_OK) error("network savedump failed"); - _transmit_file = ReadFileToMem(filename, &_transmit_file_size, 500000); - if (_transmit_file == NULL) error("network savedump failed to load"); +// Handle the local-command-queue +void NetworkHandleLocalQueue(void) +{ + if (_local_command_queue != NULL) { + CommandPacket *cp; + CommandPacket *cp_prev; + + cp = _local_command_queue; + cp_prev = NULL; + + while (cp != NULL) { + if (_frame_counter > cp->frame) { + // We can execute this command + NetworkExecuteCommand(cp); + + if (cp_prev != NULL) { + cp_prev->next = cp->next; + free(cp); + cp = cp_prev->next; + } else { + // This means we are at our first packet + _local_command_queue = cp->next; + free(cp); + cp = _local_command_queue; } - // and start sending the file.. - cs->xmitpos = 1; - // send queue of commands to client. - SendQueuedCommandsToNewClient(cs); - - NetworkSendWelcome(cs, true); + } else { + // Command is in the future, skip to next + // (commands don't have to be in order in the queue!!) + cp_prev = cp; + cp = cp->next; } } } } -void NetworkSend() + +extern void StateGameLoop(); + +bool NetworkDoClientLoop(void) { - ClientState *cs; - void *free_xmit; + _frame_counter++; - free_xmit = _transmit_file; + NetworkHandleLocalQueue(); + + StateGameLoop(); - // send stuff to all clients - for(cs=_clients;cs->socket != INVALID_SOCKET; cs++) { - if (cs->xmitpos) { - if (cs->writable) - SendXmit(cs); - free_xmit = NULL; - } - if (cs->writable) { - if (!SendPackets(cs)) cs--; + // Check if we are in sync! + if (_sync_frame != 0) { + if (_sync_frame == _frame_counter) { +#ifdef NETWORK_SEND_DOUBLE_SEED + if (_sync_seed_1 != _random_seeds[0][0] || _sync_seed_2 != _random_seeds[0][1]) { +#else + if (_sync_seed_1 != _random_seeds[0][0]) { +#endif + NetworkError(STR_NETWORK_ERR_DESYNC); + DEBUG(net, 0)("[NET] Sync error detected!"); + NetworkClientError(NETWORK_RECV_STATUS_DESYNC, DEREF_CLIENT(0)); + return false; + } + + // If this is the first time we have a sync-frame, we + // need to let the server know that we are ready and at the same + // frame as he is.. so we can start playing! + if (_network_first_time) { + _network_first_time = false; + SEND_COMMAND(PACKET_CLIENT_ACK)(); + } + + _sync_frame = 0; + } else if (_sync_frame < _frame_counter) { + DEBUG(net, 1)("[NET] Missed frame for sync-test (%d / %d)", _sync_frame, _frame_counter); + _sync_frame = 0; } } - // no clients left that xmit the file, free it. - if (free_xmit) { - _transmit_file = NULL; - free(free_xmit); - } + return true; } - -void NetworkInitialize() +// We have to do some UDP checking +void NetworkUDPGameLoop(void) { - ClientState *cs; - - QueueClear(&_command_queue); - QueueClear(&_ack_queue); - _command_queue.last = &_command_queue.head; - _network_game_list = NULL; - - // invalidate all clients - for(cs=_clients; cs != &_clients[MAX_CLIENTS]; cs++) - cs->socket = INVALID_SOCKET; - -} - -void NetworkClose(bool client) -{ - - ClientState *cs; - // invalidate all clients - - for(cs=_clients; cs != &_clients[MAX_CLIENTS]; cs++) if (cs->socket != INVALID_SOCKET) { - CloseClient(cs); - } - - if (!client) { - // if in servermode --> close listener - closesocket(_listensocket); - _listensocket= INVALID_SOCKET; - DEBUG(net, 1) ("[NET][TCP] closed listener on port %i", _network_server_port); + if (_network_udp_server) + NetworkUDPReceive(); + else if (_udp_client_socket != INVALID_SOCKET) { + NetworkUDPReceive(); + if (_network_udp_broadcast > 0) + _network_udp_broadcast--; } } -void NetworkShutdown() +// The main loop called from ttd.c +// Here we also have to do StateGameLoop if needed! +void NetworkGameLoop(void) { - _networking_server = false; - _networking = false; - _networking_sync = false; - _frame_counter = 0; - _frame_counter_max = 0; - _frame_counter_srv = 0; -} + if (!_networking) return; -// switch to synced mode. -void NetworkStartSync(bool fcreset) -{ - DEBUG(net, 3) ("[NET][SYNC] switching to synced game mode"); - _networking_sync = true; - _frame_counter = 0; + if (!NetworkReceive()) return; - if (fcreset) { - _frame_counter_max = 0; - _frame_counter_srv = 0; - _frame_fsync_last = 0; - } - _num_future_seed = 0; - _sync_seed_1 = _sync_seed_2 = 0; - memset(_my_seed_list, 0, sizeof(_my_seed_list)); -} + if (_network_server) { + // We first increase the _frame_counter + _frame_counter++; -// ************************** // -// * UDP Network Extensions * // -// ************************** // + NetworkHandleLocalQueue(); -void NetworkUDPListen(bool client) -{ - SOCKET udp; - struct sockaddr_in sin; - int port; - - if (client) { port = _network_client_port; } else { port = _network_server_port; }; + // Then we make the frame + StateGameLoop(); - DEBUG(net, 1) ("[NET][UDP] listening on port %i", port); - - udp = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - - // this disables network - _network_available = !(udp == INVALID_SOCKET); + _sync_seed_1 = _random_seeds[0][0]; +#ifdef NETWORK_SEND_DOUBLE_SEED + _sync_seed_2 = _random_seeds[0][1]; +#endif - // set nonblocking mode for socket - { unsigned long blocking = 1; ioctlsocket(udp, FIONBIO, &blocking); } - - sin.sin_family = AF_INET; - sin.sin_addr.s_addr = 0; - sin.sin_port = htons(port); - - if (bind(udp, (struct sockaddr*)&sin, sizeof(sin)) != 0) - DEBUG(net, 1) ("[NET][UDP] error: bind failed on port %i", port); - + NetworkServer_Tick(); + } else { + // Client - // enable broadcasting - { unsigned long val=1; setsockopt(udp, SOL_SOCKET, SO_BROADCAST, (char *) &val , sizeof(val)); } - // allow reusing - { unsigned long val=1; setsockopt(udp, SOL_SOCKET, SO_REUSEADDR, (char *) &val , sizeof(val)); } - - if (client) { _udp_client_socket = udp; } else { _udp_server_socket = udp; } ; - -} - -void NetworkUDPClose(bool client) -{ - if (client) { - DEBUG(net, 1) ("[NET][UDP] closed listener on port %i", _network_client_port); - closesocket(_udp_client_socket); - _udp_client_socket = INVALID_SOCKET; + // Make sure we are at the frame were the server is (quick-frames) + if (_frame_counter_server > _frame_counter) { + while (_frame_counter_server > _frame_counter) { + if (!NetworkDoClientLoop()) break; + } } else { - DEBUG(net, 1) ("[NET][UDP] closed listener on port %i", _network_server_port); - closesocket(_udp_server_socket); - _udp_server_socket = INVALID_SOCKET; - }; + // Else, keep on going till _frame_counter_max + if (_frame_counter_max > _frame_counter) { + NetworkDoClientLoop(); + } + } } -void NetworkUDPReceive(bool client) -{ - struct sockaddr_in client_addr; -#ifndef __MORPHOS__ - int client_len; -#else - LONG client_len; // for some reason we need a 'LONG' under MorphOS -#endif - int nbytes; - struct UDPPacket packet; - int packet_len; - - SOCKET udp; - if (client) udp=_udp_client_socket; else udp=_udp_server_socket; - - packet_len = sizeof(packet); - client_len = sizeof(client_addr); - - nbytes = recvfrom(udp, (char *) &packet, packet_len , 0, (struct sockaddr *) &client_addr, &client_len); - if (nbytes>0) { - if (packet.command_code==packet.command_check) switch (packet.command_code) { - - case NET_UDPCMD_SERVERSEARCH: - if (!client) { - packet.command_check=packet.command_code=NET_UDPCMD_SERVERINFO; - memcpy(&packet.data,&_network_game,sizeof(_network_game)); - packet.data_len=sizeof(_network_game); - NetworkUDPSend(client,client_addr, packet); - } - break; - case NET_UDPCMD_GETSERVERINFO: - if (!client) { - packet.command_check=packet.command_code=NET_UDPCMD_SERVERINFO; - memcpy(&packet.data,&_network_game,sizeof(_network_game)); - packet.data_len=sizeof(_network_game); - NetworkUDPSend(client,client_addr, packet); - } - break; - case NET_UDPCMD_SERVERINFO: - if (client) { - NetworkGameList * item; - - item = (NetworkGameList *) NetworkGameListAdd(); - item -> ip = inet_addr(inet_ntoa(client_addr.sin_addr)); - item -> port = ntohs(client_addr.sin_port); - - memcpy(item,&packet.data,packet.data_len); - } - break; - } - } -} - -void NetworkUDPBroadCast(bool client, struct UDPPacket packet) -{ - int i=0, res; - struct sockaddr_in out_addr; - uint32 bcaddr; - byte * bcptr; - - SOCKET udp; - if (client) udp=_udp_client_socket; else udp=_udp_server_socket; - - while (_network_ip_list[i]!=0) { - bcaddr=_network_ip_list[i]; - out_addr.sin_family = AF_INET; - if (client) { out_addr.sin_port = htons(_network_server_port); } else { out_addr.sin_port = htons(_network_client_port); }; - bcptr = (byte *) &bcaddr; - bcptr[3]=255; - out_addr.sin_addr.s_addr = bcaddr; - res=sendto(udp,(char *) &packet,sizeof(packet),0,(struct sockaddr *) &out_addr,sizeof(out_addr)); - if (res==-1) DEBUG(net, 1)("udp: broadcast error: %i",GET_LAST_ERROR()); - i++; - } - -} - -void NetworkUDPSend(bool client, struct sockaddr_in recv,struct UDPPacket packet) -{ - SOCKET udp; - if (client) udp=_udp_client_socket; else udp=_udp_server_socket; - - sendto(udp,(char *) &packet,sizeof(packet),0,(struct sockaddr *) &recv,sizeof(recv)); + NetworkSend(); } - -bool NetworkUDPSearchGame(const byte ** _network_detected_serverip, unsigned short * _network_detected_serverport) -{ - struct UDPPacket packet; - int timeout=3000; - - NetworkGameListClear(); - - DEBUG(net, 0) ("[NET][UDP] searching server"); - *_network_detected_serverip = "255.255.255.255"; - *_network_detected_serverport = 0; - - packet.command_check=packet.command_code=NET_UDPCMD_SERVERSEARCH; - packet.data_len=0; - NetworkUDPBroadCast(true, packet); - while (timeout>=0) { - CSleep(100); - timeout-=100; - NetworkUDPReceive(true); - - if (_network_game_count>0) { - NetworkGameList * item; - item = (NetworkGameList *) NetworkGameListItem(0); - *_network_detected_serverip=inet_ntoa(*(struct in_addr *) &item->ip); - *_network_detected_serverport=item->port; - timeout=-1; - DEBUG(net, 0) ("[NET][UDP] server found on %s", *_network_detected_serverip); - } - - } - - return (*_network_detected_serverport>0); - -} - - -// *************************** // -// * New Network Core System * // -// *************************** // - -void NetworkIPListInit() +// This tries to launch the network for a given OS +void NetworkStartUp(void) { - struct hostent* he = NULL; - char hostname[250]; - uint32 bcaddr; - int i=0; - - gethostname(hostname,250); - DEBUG(net, 2) ("[NET][IP] init for host %s", hostname); - he=gethostbyname((char *) hostname); - - if (he == NULL) { - he = gethostbyname("localhost"); - } - - if (he == NULL) { - bcaddr = inet_addr("127.0.0.1"); - he = gethostbyaddr(inet_ntoa(*(struct in_addr *) &bcaddr), sizeof(bcaddr), AF_INET); - } + DEBUG(net, 3) ("[NET][Core] Starting network..."); + // Network is available + _network_available = true; + _network_dedicated = false; - if (he == NULL) { - DEBUG(net, 2) ("[NET][IP] cannot resolve %s", hostname); - } else { - while(he->h_addr_list[i]) { - bcaddr = inet_addr(inet_ntoa(*(struct in_addr *) he->h_addr_list[i])); - _network_ip_list[i]=bcaddr; - DEBUG(net, 2) ("[NET][IP] add %s",inet_ntoa(*(struct in_addr *) he->h_addr_list[i])); - i++; - } - - } - _network_ip_list[i]=0; + memset(&_network_game_info, 0, sizeof(_network_game_info)); -} - -/* *************************************************** */ + /* XXX - Hard number here, because the strings can currently handle no more + then 10 clients -- TrueLight */ + _network_game_info.clients_max = 10; -void NetworkCoreInit() -{ - DEBUG(net, 3) ("[NET][Core] init()"); - _network_available = true; - _network_client_timeout = 300; - _network_ready_ahead = 1; - - // [win32] winsock startup - + // Let's load the network in windows #if defined(WIN32) { WSADATA wsa; - DEBUG(net, 3) ("[NET][Core] using windows socket library"); + DEBUG(net, 3) ("[NET][Core] Loading windows socket library"); if (WSAStartup(MAKEWORD(2,0), &wsa) != 0) { - DEBUG(net, 3) ("[NET][Core] error: WSAStartup failed"); - _network_available=false; - } + DEBUG(net, 0) ("[NET][Core] Error: WSAStartup failed. Network not available."); + _network_available = false; + return; + } } #else - - // [morphos/amigaos] bsd-socket startup - - #if defined(__MORPHOS__) || defined(__AMIGA__) - { - DEBUG(misc,3) ("[NET][Core] using bsd socket library"); - if (!(SocketBase = OpenLibrary("bsdsocket.library", 4))) { - DEBUG(net, 3) ("[NET][Core] Couldn't open bsdsocket.library version 4."); - _network_available=false; + #if defined(__MORPHOS__) || defined(__AMIGA__) + { + DEBUG(misc,3) ("[NET][Core] Loading bsd socket library"); + if (!(SocketBase = OpenLibrary("bsdsocket.library", 4))) { + DEBUG(net, 0) ("[NET][Core] Error: couldn't open bsdsocket.library version 4. Network not available."); + _network_available = false; + return; } - #if !defined(__MORPHOS__) - // for usleep() implementation (only required for legacy AmigaOS builds) - if ( (TimerPort = CreateMsgPort()) ) { - if ( (TimerRequest = (struct timerequest *) CreateIORequest(TimerPort, sizeof(struct timerequest))) ) { - if ( OpenDevice("timer.device", UNIT_MICROHZ, (struct IORequest *) TimerRequest, 0) == 0 ) { - if ( !(TimerBase = TimerRequest->tr_node.io_Device) ) { - // free ressources... - DEBUG(net, 3) ("[NET][Core] Couldn't initialize timer."); - _network_available=false; + #if defined(__AMIGA__) + // for usleep() implementation (only required for legacy AmigaOS builds) + if ( (TimerPort = CreateMsgPort()) ) { + if ( (TimerRequest = (struct timerequest *) CreateIORequest(TimerPort, sizeof(struct timerequest))) ) { + if ( OpenDevice("timer.device", UNIT_MICROHZ, (struct IORequest *) TimerRequest, 0) == 0 ) { + if ( !(TimerBase = TimerRequest->tr_node.io_Device) ) { + // free ressources... + DEBUG(net, 0) ("[NET][Core] Error: couldn't initialize timer. Network not available."); + _network_available = false; + return; + } } } } + #endif // __AMIGA__ } - #endif - - } - #else - - // [linux/macos] unix-socket startup - - DEBUG(net, 3) ("[NET][Core] using unix socket library"); + #endif // __MORPHOS__ / __AMIGA__ + #endif // WIN32 - #endif - - #endif - - - if (_network_available) { - DEBUG(net, 3) ("[NET][Core] OK: multiplayer available"); - // initiate network ip list - NetworkIPListInit(); - } else - DEBUG(net, 3) ("[NET][Core] FAILED: multiplayer not available"); + NetworkInitialize(); + DEBUG(net, 3) ("[NET][Core] Network online. Multiplayer available."); + NetworkFindIPs(); } -/* *************************************************** */ +// This shuts the network down +void NetworkShutDown(void) +{ + DEBUG(net, 3) ("[NET][Core] Shutting down the network."); -void NetworkCoreShutdown() -{ - DEBUG(net, 3) ("[NET][Core] shutdown()"); + _network_available = false; #if defined(__MORPHOS__) || defined(__AMIGA__) { // free allocated ressources #if !defined(__MORPHOS__) - if (TimerBase) { CloseDevice((struct IORequest *) TimerRequest); } - if (TimerRequest) { DeleteIORequest(TimerRequest); } - if (TimerPort) { DeleteMsgPort(TimerPort); } + if (TimerBase) { CloseDevice((struct IORequest *) TimerRequest); } + if (TimerRequest) { DeleteIORequest(TimerRequest); } + if (TimerPort) { DeleteMsgPort(TimerPort); } #endif if (SocketBase) { @@ -1690,290 +1200,15 @@ void NetworkCoreShutdown() #endif #if defined(WIN32) - { WSACleanup();} + { + WSACleanup(); + } #endif } -/* *************************************************** */ - -void ParseConnectionString(const byte **player, const byte **port, byte *connection_string) -{ - byte c = 0; - while (connection_string[c] != '\0') { - if (connection_string[c] == '#') { - *player = &connection_string[c+1]; - connection_string[c] = '\0'; - } - if (connection_string[c] == ':') { - *port = &connection_string[c+1]; - connection_string[c] = '\0'; - } - c++; - } -} - -bool NetworkCoreConnectGame(const byte* b, unsigned short port) -{ - if (!_network_available) return false; - - if (strcmp(b,"auto")==0) { - // do autodetect - NetworkUDPSearchGame(&b, &port); - } - - if (port==0) { - // autodetection failed - if (_networking_override) NetworkLobbyShutdown(); - ShowErrorMessage(-1, STR_NETWORK_ERR_NOSERVER, 0, 0); - _switch_mode_errorstr = STR_NETWORK_ERR_NOSERVER; - return false; - } - - NetworkInitialize(); - _networking = NetworkConnect(b, port); - if (_networking) { - NetworkLobbyShutdown(); - IConsoleCmdExec("exec scripts/on_client.scr 0"); - } else { - if (_networking_override) - NetworkLobbyShutdown(); - - ShowErrorMessage(-1, STR_NETWORK_ERR_NOCONNECTION,0,0); - _switch_mode_errorstr = STR_NETWORK_ERR_NOCONNECTION; - } - return _networking; -} - -/* *************************************************** */ - -bool NetworkCoreConnectGameStruct(NetworkGameList * item) -{ - return NetworkCoreConnectGame(inet_ntoa(*(struct in_addr *) &item->ip),item->port); -} - -/* *************************************************** */ - -bool NetworkCoreStartGame() -{ - if (!_network_available) return false; - NetworkLobbyShutdown(); - NetworkInitialize(); - NetworkListen(); - NetworkUDPListen(false); - _networking_server = true; - _networking = true; - NetworkGameFillDefaults(); // clears the network game info - _network_game.players_on++; // the serverplayer is online - // execute server initialization script - IConsoleCmdExec("exec scripts/on_server.scr 0"); - return true; -} - -/* *************************************************** */ - -void NetworkCoreDisconnect() -{ - /* terminate server */ - if (_networking_server) { - NetworkUDPClose(false); - NetworkClose(false); - } - - /* terminate client connection */ - else if (_networking) { - NetworkClose(true); - } - - NetworkShutdown(); -} - -/* *************************************************** */ - -void NetworkCoreLoop(bool incomming) -{ - if (incomming) { - // incomming - if ( _udp_client_socket != INVALID_SOCKET ) NetworkUDPReceive(true); - if ( _udp_server_socket != INVALID_SOCKET ) NetworkUDPReceive(false); - - if (_networking) - NetworkReceive(); - - } else { - if ( _udp_client_socket != INVALID_SOCKET ) NetworkUDPReceive(true); - if ( _udp_server_socket != INVALID_SOCKET ) NetworkUDPReceive(false); - - if (_networking) - NetworkSend(); - } -} - -void NetworkLobbyInit() -{ - DEBUG(net, 3) ("[NET][Lobby] init()"); - NetworkUDPListen(true); -} - -void NetworkLobbyShutdown() -{ - DEBUG(net, 3) ("[NET][Lobby] shutdown()"); - NetworkUDPClose(true); -} - - -// ******************************** // -// * Network Game List Extensions * // -// ******************************** // - -void NetworkGameListClear() -{ - NetworkGameList * item; - NetworkGameList * next; - - DEBUG(net, 4) ("[NET][G-List] cleared server list"); - - item = _network_game_list; +#else - while (item != NULL) { - next = (NetworkGameList *) item -> _next; - free (item); - item = next; - } - _network_game_list=NULL; - _network_game_count=0; -} - -NetworkGameList * NetworkGameListAdd() -{ - NetworkGameList * item; - NetworkGameList * before; - - DEBUG(net, 4) ("[NET][G-List] added server to list"); - - item = _network_game_list; - before = item; - while (item != NULL) { - before = item; - item = (NetworkGameList *) item -> _next; - } - - item = malloc(sizeof(NetworkGameList)); - item -> _next = NULL; - - if (before == NULL) { - _network_game_list = item; - } else - before -> _next = item; - - _network_game_count++; - return item; -} - -void NetworkGameListFromLAN() -{ - struct UDPPacket packet; - DEBUG(net, 2) ("[NET][G-List] searching server over lan"); - NetworkGameListClear(); - packet.command_check=packet.command_code=NET_UDPCMD_SERVERSEARCH; - packet.data_len=0; - NetworkUDPBroadCast(true,packet); -} - -void NetworkGameListFromInternet() -{ - DEBUG(net, 2) ("[NET][G-List] searching servers over internet"); - NetworkGameListClear(); - - // **TODO** masterserver communication [internet protocol list] -} - -NetworkGameList * NetworkGameListItem(uint16 index) -{ - NetworkGameList * item; - NetworkGameList * next; - uint16 cnt = 0; - - item = _network_game_list; - - while ((item != NULL) && (cnt != index)) { - next = (NetworkGameList *) item -> _next; - item = next; - cnt++; - } - - return item; -} +void ParseConnectionString(const byte **player, const byte **port, byte *connection_string) {} +void NetworkUpdateClientInfo(uint16 client_index) {} -// *************************** // -// * Network Game Extensions * // -// *************************** // - -void NetworkGameFillDefaults() -{ - NetworkGameInfo * game = &_network_game; - #if defined(WITH_REV) - extern char _openttd_revision[]; - #else - const char _openttd_revision[] = "norev000"; - #endif - - DEBUG(net, 4) ("[NET][G-Info] setting defaults"); - - ttd_strlcpy(game->server_name, "OpenTTD Game", sizeof(game->server_name)); - game->game_password[0]='\0'; - game->map_name[0]='\0'; - ttd_strlcpy(game->server_revision, _openttd_revision, sizeof(game->server_revision)); - game->game_date=0; - - game->map_height=0; - game->map_width=0; - game->map_set=0; - - game->players_max=8; - game->players_on=0; - - game->server_lang=_dynlang.curr; -} - -void NetworkGameChangeDate(uint16 newdate) -{ - if (_networking_server) - _network_game.game_date = newdate; -} - -#else // not ENABLE_NETWORK - -// stubs -void NetworkInitialize() {} -void NetworkShutdown() {} -void NetworkListen() {} -void NetworkConnect(const char *hostname, int port) {} -void NetworkReceive() {} -void NetworkSend() {} -void NetworkSendCommand(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback) {} -void NetworkSendEvent(uint16 type, uint16 data_len, void * data) {}; -void NetworkProcessCommands() {} -void NetworkStartSync(bool fcreset) {} -void NetworkSendReadyPacket() {} -void NetworkSendSyncPackets() {} -void NetworkSendFrameSyncPackets() {} -bool NetworkCheckClientReady() { return true; } -void NetworkCoreInit() { _network_available=false; }; -void NetworkCoreShutdown() {}; -void NetworkCoreDisconnect() {}; -void NetworkCoreLoop(bool incomming) {}; -void ParseConnectionString(const byte **player, const byte **port, byte *connection_string) {}; -bool NetworkCoreConnectGame(const byte* b, unsigned short port) {return false;}; -bool NetworkCoreStartGame() {return false;}; -void NetworkLobbyShutdown() {}; -void NetworkLobbyInit() {}; -void NetworkGameListClear() {}; -NetworkGameList * NetworkGameListAdd() {return NULL;}; -void NetworkGameListFromLAN() {}; -void NetworkGameListFromInternet() {}; -void NetworkGameFillDefaults() {}; -NetworkGameList * NetworkGameListItem(uint16 index) {return NULL;}; -bool NetworkCoreConnectGameStruct(NetworkGameList * item) {return false;}; -void NetworkGameChangeDate(uint16 newdate) {}; - -#endif +#endif /* ENABLE_NETWORK */ diff --git a/network.h b/network.h --- a/network.h +++ b/network.h @@ -1,36 +1,175 @@ #ifndef NETWORK_H #define NETWORK_H +#include "network_core.h" + +#ifdef ENABLE_NETWORK + +// If this line is enable, every frame will have a sync test +// this is not needed in normal games. Normal is like 1 sync in 100 +// frames. You can enable this if you have a lot of desyncs on a certain +// game. +// Remember: both client and server have to be compiled with this +// option enabled to make it to work. If one of the two has it disabled +// nothing will happen. +//#define ENABLE_NETWORK_SYNC_EVERY_FRAME + +// In theory sending 1 of the 2 seeds is enough to check for desyncs +// so in theory, this next define can be left off. +//#define NETWORK_SEND_DOUBLE_SEED + +// How many clients can we have? Like.. MAX_PLAYERS - 1 is the amount of +// players that can really play.. so.. a max of 4 spectators.. gives us.. +// MAX_PLAYERS + 3 +#define MAX_CLIENTS (MAX_PLAYERS + 3) + + +// Do not change this next line. It should _ALWAYS_ be MAX_CLIENTS + 1 +#define MAX_CLIENT_INFO (MAX_CLIENTS + 1) + +#define NETWORK_DISCOVER_PORT 3978 +#define NETWORK_DEFAULT_PORT 3979 + +#define MAX_INTERFACES 9 + + +// How many vehicle/station types we put over the network +#define NETWORK_VEHICLE_TYPES 5 +#define NETWORK_STATION_TYPES 5 + +#define NETWORK_NAME_LENGTH 80 +#define NETWORK_HOSTNAME_LENGTH 80 +#define NETWORK_REVISION_LENGTH 10 +#define NETWORK_PASSWORD_LENGTH 20 +#define NETWORK_PLAYERS_LENGTH 200 + +// This is the struct used by both client and server +// some fields will be empty on the client (like game_password) by default +// and only filled with data a player enters. typedef struct NetworkGameInfo { - char server_name[40]; // name of the game - char server_revision[8]; // server game version - byte server_lang; // langid - byte players_max; // max players allowed on server - byte players_on; // current count of players on server - uint16 game_date; // current date - char game_password[10]; // should fit ... 10 chars - char map_name[40]; // map which is played ["random" for a randomized map] - uint map_width; // map width / 8 - uint map_height; // map height / 8 - byte map_set; // graphical set + char server_name[NETWORK_NAME_LENGTH]; // Server name + char hostname[NETWORK_HOSTNAME_LENGTH]; // Hostname of the server (if any) + char server_revision[NETWORK_REVISION_LENGTH]; // The SVN version number the server is using (e.g.: 'r304') + // It even shows a SVN version in release-version, so + // it is easy to compare if a server is of the correct version + byte server_lang; // Language of the server (we should make a nice table for this) + byte use_password; // Is set to != 0 if it uses a password + char server_password[NETWORK_PASSWORD_LENGTH]; // On the server: the game password, on the client: != "" if server has password + byte clients_max; // Max clients allowed on server + byte clients_on; // Current count of clients on server + byte spectators_on; // How many spectators do we have? + uint16 game_date; // Current date + uint16 start_date; // When the game started + char map_name[NETWORK_NAME_LENGTH]; // Map which is played ["random" for a randomized map] + uint16 map_width; // Map width + uint16 map_height; // Map height + byte map_set; // Graphical set + bool dedicated; // Is this a dedicated server? } NetworkGameInfo; -//typedef struct NetworkGameList; +typedef struct NetworkPlayerInfo { + char company_name[NETWORK_NAME_LENGTH]; // Company name + char password[NETWORK_PASSWORD_LENGTH]; // The password for the player + byte inaugurated_year; // What year the company started in + int64 company_value; // The company value + int64 money; // The amount of money the company has + int64 income; // How much did the company earned last year + uint16 performance; // What was his performance last month? + uint16 num_vehicle[NETWORK_VEHICLE_TYPES]; // How many vehicles are there of this type? + uint16 num_station[NETWORK_STATION_TYPES]; // How many stations are there of this type? + char players[NETWORK_PLAYERS_LENGTH]; // The players that control this company (Name1, name2, ..) +} NetworkPlayerInfo; + +typedef struct NetworkClientInfo { + uint16 client_index; // Index of the client (same as ClientState->index) + char client_name[NETWORK_NAME_LENGTH]; // Name of the client + byte client_lang; // The language of the client + byte client_playas; // As which player is this client playing + uint32 client_ip; // IP-address of the client (so he can be banned) + uint16 join_date; // Gamedate the player has joined +} NetworkClientInfo; typedef struct NetworkGameList { - NetworkGameInfo item; + NetworkGameInfo info; uint32 ip; uint16 port; - struct NetworkGameList * _next; + bool online; // False if the server did not respond (default status) + struct NetworkGameList *next; } NetworkGameList; -enum { - NET_EVENT_SUBSIDY = 0, -}; +typedef enum { + NETWORK_JOIN_STATUS_CONNECTING, + NETWORK_JOIN_STATUS_AUTHORIZING, + NETWORK_JOIN_STATUS_WAITING, + NETWORK_JOIN_STATUS_DOWNLOADING, + NETWORK_JOIN_STATUS_PROCESSING, + + NETWORK_JOIN_STATUS_GETTING_COMPANY_INFO, +} NetworkJoinStatus; + +// language ids for server_lang and client_lang +typedef enum { + NETLANG_ANY = 0, + NETLANG_ENGLISH = 1, + NETLANG_GERMAN = 2, + NETLANG_FRENCH = 3, +} NetworkLanguage; + +VARDEF NetworkGameList *_network_game_list; + +VARDEF NetworkGameInfo _network_game_info; +VARDEF NetworkPlayerInfo _network_player_info[MAX_PLAYERS]; +VARDEF NetworkClientInfo _network_client_info[MAX_CLIENT_INFO]; + +VARDEF char _network_player_name[NETWORK_NAME_LENGTH]; +VARDEF char _network_default_ip[NETWORK_HOSTNAME_LENGTH]; + +VARDEF uint16 _network_own_client_index; + +VARDEF uint32 _frame_counter_server; // The frame_counter of the server, if in network-mode +VARDEF uint32 _frame_counter_max; // To where we may go with our clients + +// networking settings +VARDEF uint32 _network_ip_list[MAX_INTERFACES + 1]; // Network IPs +VARDEF uint16 _network_game_count; -NetworkGameInfo _network_game; -NetworkGameList * _network_game_list; +VARDEF uint16 _network_lobby_company_count; + +VARDEF uint _network_client_port; +VARDEF uint _network_server_port; +VARDEF bool _is_network_server; // Does this client wants to be a network-server? +VARDEF char _network_server_name[NETWORK_NAME_LENGTH]; + +VARDEF uint16 _network_sync_freq; +VARDEF uint8 _network_frame_freq; + +VARDEF uint32 _sync_seed_1, _sync_seed_2; +VARDEF uint32 _sync_frame; +VARDEF bool _network_first_time; +// Vars needed for the join-GUI +VARDEF NetworkJoinStatus _network_join_status; +VARDEF uint8 _network_join_waiting; +VARDEF uint16 _network_join_kbytes; +VARDEF uint16 _network_join_kbytes_total; + +VARDEF char _network_last_host[NETWORK_HOSTNAME_LENGTH]; +VARDEF short _network_last_port; +VARDEF uint32 _network_last_host_ip; +VARDEF uint8 _network_reconnect; + +VARDEF bool _network_udp_server; +VARDEF uint16 _network_udp_broadcast; + +#endif /* ENABLE_NETWORK */ + +// Those variables must always be registered! +VARDEF bool _networking; +VARDEF bool _network_available; // is network mode available? +VARDEF bool _network_server; // network-server is active +VARDEF bool _network_dedicated; // are we a dedicated server? +VARDEF byte _network_playas; // an id to play as.. void ParseConnectionString(const byte **player, const byte **port, byte *connection_string); +void NetworkUpdateClientInfo(uint16 client_index); #endif /* NETWORK_H */ diff --git a/network_client.c b/network_client.c new file mode 100644 --- /dev/null +++ b/network_client.c @@ -0,0 +1,808 @@ +#include "stdafx.h" +#include "network_data.h" + +#ifdef ENABLE_NETWORK + +#include "table/strings.h" +#include "network_client.h" +#include "network_gamelist.h" +#include "command.h" +#include "gfx.h" +#include "window.h" +#include "settings.h" + + +// This file handles all the client-commands + + +// So we don't make too much typos ;) +#define MY_CLIENT DEREF_CLIENT(0) + +static uint32 last_ack_frame; + +void NetworkRecvPatchSettings(Packet *p); + +// ********** +// Sending functions +// DEF_CLIENT_SEND_COMMAND has no parameters +// ********** + +DEF_CLIENT_SEND_COMMAND(PACKET_CLIENT_COMPANY_INFO) +{ + // + // Packet: CLIENT_COMPANY_INFO + // Function: Request company-info (in detail) + // Data: + // + // + Packet *p; + _network_join_status = NETWORK_JOIN_STATUS_GETTING_COMPANY_INFO; + InvalidateWindow(WC_NETWORK_STATUS_WINDOW, 0); + + p = NetworkSend_Init(PACKET_CLIENT_COMPANY_INFO); + NetworkSend_Packet(p, MY_CLIENT); +} + +DEF_CLIENT_SEND_COMMAND(PACKET_CLIENT_JOIN) +{ + // + // Packet: CLIENT_JOIN + // Function: Try to join the server + // Data: + // String: OpenTTD Revision (norev000 if no revision) + // String: Player Name (max NETWORK_NAME_LENGTH) + // uint8: Play as Player id (1..MAX_PLAYERS) + // uint8: Language ID + // + +#if defined(WITH_REV) + extern char _openttd_revision[]; +#else + const char _openttd_revision[] = "norev000"; +#endif + Packet *p; + _network_join_status = NETWORK_JOIN_STATUS_AUTHORIZING; + InvalidateWindow(WC_NETWORK_STATUS_WINDOW, 0); + + p = NetworkSend_Init(PACKET_CLIENT_JOIN); + NetworkSend_string(p, _openttd_revision); + NetworkSend_string(p, _network_player_name); // Player name + NetworkSend_uint8(p, _network_playas); // Password + NetworkSend_uint8(p, NETLANG_ANY); // Language + NetworkSend_Packet(p, MY_CLIENT); +} + +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_PASSWORD)(NetworkPasswordType type, const char *password) +{ + // + // Packet: CLIENT_PASSWORD + // Function: Send a password to the server to authorize + // Data: + // uint8: NetworkPasswordType + // String: Password + // + Packet *p = NetworkSend_Init(PACKET_CLIENT_PASSWORD); + NetworkSend_uint8(p, type); + NetworkSend_string(p, password); + NetworkSend_Packet(p, MY_CLIENT); +} + +DEF_CLIENT_SEND_COMMAND(PACKET_CLIENT_GETMAP) +{ + // + // Packet: CLIENT_GETMAP + // Function: Request the map from the server + // Data: + // + // + + Packet *p = NetworkSend_Init(PACKET_CLIENT_GETMAP); + NetworkSend_Packet(p, MY_CLIENT); +} + +DEF_CLIENT_SEND_COMMAND(PACKET_CLIENT_MAP_OK) +{ + // + // Packet: CLIENT_MAP_OK + // Function: Tell the server that we are done receiving/loading the map + // Data: + // + // + + Packet *p = NetworkSend_Init(PACKET_CLIENT_MAP_OK); + NetworkSend_Packet(p, MY_CLIENT); +} + +DEF_CLIENT_SEND_COMMAND(PACKET_CLIENT_ACK) +{ + // + // Packet: CLIENT_ACK + // Function: Tell the server we are done with this frame + // Data: + // uint32: current FrameCounter of the client + // + + Packet *p = NetworkSend_Init(PACKET_CLIENT_ACK); + + NetworkSend_uint32(p, _frame_counter); + NetworkSend_Packet(p, MY_CLIENT); +} + +// Send a command packet to the server +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_COMMAND)(CommandPacket *cp) +{ + // + // Packet: CLIENT_COMMAND + // Function: Send a DoCommand to the Server + // Data: + // uint8: PlayerID (0..MAX_PLAYERS-1) + // uint32: CommandID (see command.h) + // uint32: P1 (free variables used in DoCommand) + // uint32: P2 + // uint32: Tile + // uint32: decode_params + // 10 times the last one (lengthof(cp->dp)) + // uint8: CallBackID (see callback_table.c) + // + + int i; + Packet *p = NetworkSend_Init(PACKET_CLIENT_COMMAND); + + NetworkSend_uint8(p, cp->player); + NetworkSend_uint32(p, cp->cmd); + NetworkSend_uint32(p, cp->p1); + NetworkSend_uint32(p, cp->p2); + NetworkSend_uint32(p, (uint32)cp->tile); + for (i = 0; i < lengthof(cp->dp); i++) { + NetworkSend_uint32(p, cp->dp[i]); + } + NetworkSend_uint8(p, cp->callback); + + NetworkSend_Packet(p, MY_CLIENT); +} + +// Send a chat-packet over the network +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_CHAT)(NetworkAction action, DestType desttype, int dest, const char *msg) +{ + // + // Packet: CLIENT_CHAT + // Function: Send a chat-packet to the serve + // Data: + // uint8: ActionID (see network_data.h, NetworkAction) + // uint8: Destination Type (see network_data.h, DestType); + // uint8: Destination Player (1..MAX_PLAYERS) + // String: Message (max MAX_TEXT_MSG_LEN) + // + + Packet *p = NetworkSend_Init(PACKET_CLIENT_CHAT); + + NetworkSend_uint8(p, action); + NetworkSend_uint8(p, desttype); + NetworkSend_uint8(p, dest); + NetworkSend_string(p, msg); + NetworkSend_Packet(p, MY_CLIENT); +} + +// Send an error-packet over the network +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_ERROR)(NetworkErrorCode errorno) +{ + // + // Packet: CLIENT_ERROR + // Function: The client made an error and is quiting the game + // Data: + // uint8: ErrorID (see network_data.h, NetworkErrorCode) + // + Packet *p = NetworkSend_Init(PACKET_CLIENT_ERROR); + + NetworkSend_uint8(p, errorno); + NetworkSend_Packet(p, MY_CLIENT); +} + +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_SET_PASSWORD)(const char *password) +{ + // + // Packet: PACKET_CLIENT_SET_PASSWORD + // Function: Set the password for the clients current company + // Data: + // String: Password + // + Packet *p = NetworkSend_Init(PACKET_CLIENT_SET_PASSWORD); + + NetworkSend_string(p, password); + NetworkSend_Packet(p, MY_CLIENT); +} + +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_SET_NAME)(const char *name) +{ + // + // Packet: PACKET_CLIENT_SET_NAME + // Function: Gives the player a new name + // Data: + // String: Name + // + Packet *p = NetworkSend_Init(PACKET_CLIENT_SET_NAME); + + NetworkSend_string(p, name); + NetworkSend_Packet(p, MY_CLIENT); +} + +// Send an quit-packet over the network +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_QUIT)(const char *leavemsg) +{ + // + // Packet: CLIENT_QUIT + // Function: The client is quiting the game + // Data: + // String: leave-message + // + Packet *p = NetworkSend_Init(PACKET_CLIENT_QUIT); + + NetworkSend_string(p, leavemsg); + NetworkSend_Packet(p, MY_CLIENT); +} + + +// ********** +// Receiving functions +// DEF_CLIENT_RECEIVE_COMMAND has parameter: Packet *p +// ********** + +extern bool SafeSaveOrLoad(const char *filename, int mode, int newgm); + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_FULL) +{ + // We try to join a server which is full + _switch_mode_errorstr = STR_NETWORK_ERR_SERVER_FULL; + DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0); + + return NETWORK_RECV_STATUS_SERVER_FULL; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_COMPANY_INFO) +{ + byte company_info_version; + int i; + + company_info_version = NetworkRecv_uint8(p); + + if (company_info_version == 1) { + byte total; + byte current; + + total = NetworkRecv_uint8(p); + _network_lobby_company_count = total; + + // There is no data at all.. + if (total == 0) + return NETWORK_RECV_STATUS_CLOSE_QUERY; + + current = NetworkRecv_uint8(p) - 1; + if (current >= MAX_PLAYERS) + return NETWORK_RECV_STATUS_CLOSE_QUERY; + + NetworkRecv_string(p, _network_player_info[current].company_name, sizeof(_network_player_info[current].company_name)); + _network_player_info[current].inaugurated_year = NetworkRecv_uint8(p); + _network_player_info[current].company_value = NetworkRecv_uint64(p); + _network_player_info[current].money = NetworkRecv_uint64(p); + _network_player_info[current].income = NetworkRecv_uint64(p); + _network_player_info[current].performance = NetworkRecv_uint16(p); + for (i = 0; i < NETWORK_VEHICLE_TYPES; i++) + _network_player_info[current].num_vehicle[i] = NetworkRecv_uint16(p); + for (i = 0; i < NETWORK_STATION_TYPES; i++) + _network_player_info[current].num_station[i] = NetworkRecv_uint16(p); + + NetworkRecv_string(p, _network_player_info[current].players, sizeof(_network_player_info[current].players)); + + InvalidateWindow(WC_NETWORK_WINDOW, 0); + + if (total == current + 1) + // This was the last one + return NETWORK_RECV_STATUS_CLOSE_QUERY; + else + return NETWORK_RECV_STATUS_OKAY; + } + + return NETWORK_RECV_STATUS_CLOSE_QUERY; +} + +// This packet contains info about the client (playas and name) +// as client we save this in NetworkClientInfo, linked via 'index' +// which is always an unique number on a server. +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_CLIENT_INFO) +{ + NetworkClientInfo *ci; + uint16 index = NetworkRecv_uint16(p); + + ci = NetworkFindClientInfoFromIndex(index); + if (ci != NULL) { + byte playas; + char name[NETWORK_NAME_LENGTH]; + + playas = NetworkRecv_uint8(p); + NetworkRecv_string(p, name, sizeof(name)); + + if (playas == ci->client_playas && strcmp(name, ci->client_name) != 0) { + // Client name changed, display the change + NetworkTextMessage(NETWORK_ACTION_NAME_CHANGE, 1, ci->client_name, name); + } else if (playas != ci->client_playas) { + // The player changed from client-player.. + // Do not display that for now + } + + ci->client_playas = playas; + ttd_strlcpy(ci->client_name, name, sizeof(ci->client_name)); + return NETWORK_RECV_STATUS_OKAY; + } + + // We don't have this index yet, find an empty index, and put the data there + ci = NetworkFindClientInfoFromIndex(NETWORK_EMPTY_INDEX); + if (ci != NULL) { + ci->client_index = index; + ci->client_playas = NetworkRecv_uint8(p); + NetworkRecv_string(p, ci->client_name, sizeof(ci->client_name)); + return NETWORK_RECV_STATUS_OKAY; + } + + // Here the program should never ever come..... + return NETWORK_RECV_STATUS_MALFORMED_PACKET; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_ERROR) +{ + NetworkErrorCode error = NetworkRecv_uint8(p); + + if (error == NETWORK_ERROR_NOT_AUTHORIZED || error == NETWORK_ERROR_NOT_EXPECTED || + error == NETWORK_ERROR_PLAYER_MISMATCH) { + // We made an error in the protocol, and our connection is closed.... :( + _switch_mode_errorstr = STR_NETWORK_ERR_SERVER_ERROR; + } else if (error == NETWORK_ERROR_WRONG_REVISION) { + // Wrong revision :( + _switch_mode_errorstr = STR_NETWORK_ERR_WRONG_REVISION; + } else if (error == NETWORK_ERROR_WRONG_PASSWORD) { + // Wrong password + _switch_mode_errorstr = STR_NETWORK_ERR_WRONG_PASSWORD; + } else if (error == NETWORK_ERROR_KICKED) { + _switch_mode_errorstr = STR_NETWORK_ERR_KICKED; + } + + DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0); + + return NETWORK_RECV_STATUS_SERVER_ERROR; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_NEED_PASSWORD) +{ + NetworkPasswordType type; + type = NetworkRecv_uint8(p); + + if (type == NETWORK_GAME_PASSWORD) { + ShowNetworkNeedGamePassword(); + return NETWORK_RECV_STATUS_OKAY; + } else if (type == NETWORK_COMPANY_PASSWORD) { + ShowNetworkNeedCompanyPassword(); + return NETWORK_RECV_STATUS_OKAY; + } + + return NETWORK_RECV_STATUS_MALFORMED_PACKET; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_WELCOME) +{ + _network_own_client_index = NetworkRecv_uint16(p); + + // Start receiving the map + SEND_COMMAND(PACKET_CLIENT_GETMAP)(); + return NETWORK_RECV_STATUS_OKAY; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_WAIT) +{ + _network_join_status = NETWORK_JOIN_STATUS_WAITING; + _network_join_waiting = NetworkRecv_uint8(p); + InvalidateWindow(WC_NETWORK_STATUS_WINDOW, 0); + + // We are put on hold for receiving the map.. we need GUI for this ;) + DEBUG(net, 1)("[NET] The server is currently busy sending the map to someone else.. please hold..." ); + DEBUG(net, 1)("[NET] There are %d clients in front of you", _network_join_waiting); + + return NETWORK_RECV_STATUS_OKAY; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_MAP) +{ + static char filename[256]; + static FILE *file_pointer; + + byte maptype; + + maptype = NetworkRecv_uint8(p); + + // First packet, init some stuff + if (maptype == MAP_PACKET_START) { + // The name for the temp-map + sprintf(filename, "%s%snetwork_client.tmp", _path.autosave_dir, PATHSEP); + + file_pointer = fopen(filename, "wb"); + if (file_pointer == NULL) { + _switch_mode_errorstr = STR_NETWORK_ERR_SAVEGAMEERROR; + return NETWORK_RECV_STATUS_SAVEGAME; + } + + _frame_counter = _frame_counter_server = _frame_counter_max = NetworkRecv_uint32(p); + + _network_join_status = NETWORK_JOIN_STATUS_DOWNLOADING; + _network_join_kbytes = 0; + _network_join_kbytes_total = NetworkRecv_uint32(p) / 1024; + InvalidateWindow(WC_NETWORK_STATUS_WINDOW, 0); + + // The first packet does not contain any more data + return NETWORK_RECV_STATUS_OKAY; + } + + if (maptype == MAP_PACKET_NORMAL) { + // We are still receiving data, put it to the file + fwrite(p->buffer + p->pos, 1, p->size - p->pos, file_pointer); + + _network_join_kbytes = ftell(file_pointer) / 1024; + InvalidateWindow(WC_NETWORK_STATUS_WINDOW, 0); + } + + if (maptype == MAP_PACKET_PATCH) { + NetworkRecvPatchSettings(p); + } + + // Check if this was the last packet + if (maptype == MAP_PACKET_END) { + // We also get, very nice, the player_seeds in this packet + int i; + for (i = 0; i < MAX_PLAYERS; i++) { + _player_seeds[i][0] = NetworkRecv_uint32(p); + _player_seeds[i][1] = NetworkRecv_uint32(p); + } + + fclose(file_pointer); + + _network_join_status = NETWORK_JOIN_STATUS_PROCESSING; + InvalidateWindow(WC_NETWORK_STATUS_WINDOW, 0); + + // The map is done downloading, load it + // Load the map + if (!SafeSaveOrLoad(filename, SL_LOAD, GM_NORMAL)) { + DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0); + _switch_mode_errorstr = STR_NETWORK_ERR_SAVEGAMEERROR; + return NETWORK_RECV_STATUS_SAVEGAME; + } + _opt_mod_ptr = &_opt; + + DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0); + + // Say we received the map and loaded it correctly! + SEND_COMMAND(PACKET_CLIENT_MAP_OK)(); + + if (_network_playas == 0 || _network_playas > MAX_PLAYERS || + !DEREF_PLAYER(_network_playas - 1)->is_active) { + + if (_network_playas == OWNER_SPECTATOR) { + // The client wants to be a spectator.. + _local_player = OWNER_SPECTATOR; + } else { + // send a command to make a new player + _local_player = 0; + NetworkSend_Command(0, 0, 0, CMD_PLAYER_CTRL, NULL); + _local_player = OWNER_SPECTATOR; + } + } else { + // take control over an existing company + _local_player = _network_playas - 1; + } + + // Remeber the player + if (_local_player != OWNER_SPECTATOR) + _network_playas = _local_player + 1; + else + _network_playas = OWNER_SPECTATOR; + } + + return NETWORK_RECV_STATUS_OKAY; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_FRAME) +{ + _frame_counter_server = NetworkRecv_uint32(p); + _frame_counter_max = NetworkRecv_uint32(p); +#ifdef ENABLE_NETWORK_SYNC_EVERY_FRAME + // Test if the server supports this option + // and if we are at the frame the server is + if (p->pos < p->size) { + _sync_frame = _frame_counter_server; + _sync_seed_1 = NetworkRecv_uint32(p); +#ifdef NETWORK_SEND_DOUBLE_SEED + _sync_seed_2 = NetworkRecv_uint32(p); +#endif + } +#endif + DEBUG(net, 7)("[NET] Received FRAME %d",_frame_counter_server); + + // Let the server know that we received this frame correctly + // We do this only once per day, to save some bandwidth ;) + if (!_network_first_time && last_ack_frame < _frame_counter) { + last_ack_frame = _frame_counter + DAY_TICKS; + DEBUG(net,6)("[NET] Sent ACK at %d", _frame_counter); + SEND_COMMAND(PACKET_CLIENT_ACK)(); + } + + return NETWORK_RECV_STATUS_OKAY; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_SYNC) +{ + _sync_frame = NetworkRecv_uint32(p); + _sync_seed_1 = NetworkRecv_uint32(p); +#ifdef NETWORK_SEND_DOUBLE_SEED + _sync_seed_2 = NetworkRecv_uint32(p); +#endif + + return NETWORK_RECV_STATUS_OKAY; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_COMMAND) +{ + int i; + CommandPacket *cp = malloc(sizeof(CommandPacket)); + cp->player = NetworkRecv_uint8(p); + cp->cmd = NetworkRecv_uint32(p); + cp->p1 = NetworkRecv_uint32(p); + cp->p2 = NetworkRecv_uint32(p); + cp->tile = NetworkRecv_uint32(p); + for (i = 0; i < lengthof(cp->dp); i++) + cp->dp[i] = NetworkRecv_uint32(p); + cp->callback = NetworkRecv_uint8(p); + cp->frame = NetworkRecv_uint32(p); + cp->next = NULL; + + // The server did send us this command.. + // queue it in our own queue, so we can handle it in the upcoming frame! + + if (_local_command_queue == NULL) { + _local_command_queue = cp; + } else { + // Find last packet + CommandPacket *c = _local_command_queue; + while (c->next != NULL) c = c->next; + c->next = cp; + } + + return NETWORK_RECV_STATUS_OKAY; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_CHAT) +{ + NetworkAction action = NetworkRecv_uint8(p); + char msg[MAX_TEXT_MSG_LEN]; + NetworkClientInfo *ci, *ci_to; + uint16 index; + char name[NETWORK_NAME_LENGTH]; + + index = NetworkRecv_uint16(p); + NetworkRecv_string(p, msg, MAX_TEXT_MSG_LEN); + + ci_to = NetworkFindClientInfoFromIndex(index); + if (ci_to == NULL) return NETWORK_RECV_STATUS_OKAY; + + if (action == NETWORK_ACTION_CHAT_TO_CLIENT) { + snprintf(name, 80, "%s", ci_to->client_name); + ci = NetworkFindClientInfoFromIndex(_network_own_client_index); + } else if (action == NETWORK_ACTION_CHAT_TO_PLAYER) { + GetString(name, DEREF_PLAYER(ci_to->client_playas-1)->name_1); + ci = NetworkFindClientInfoFromIndex(_network_own_client_index); + } else { + snprintf(name, 80, "%s", ci_to->client_name); + ci = ci_to; + } + + if (ci != NULL) + NetworkTextMessage(action, GetDrawStringPlayerColor(ci->client_playas-1), name, "%s", msg); + return NETWORK_RECV_STATUS_OKAY; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_ERROR_QUIT) +{ + int errorno; + char str1[100], str2[100]; + uint16 index; + NetworkClientInfo *ci; + + index = NetworkRecv_uint16(p); + errorno = NetworkRecv_uint8(p); + + GetString(str1, STR_NETWORK_ERR_LEFT); + GetString(str2, STR_NETWORK_ERR_CLIENT_GENERAL + errorno); + + ci = NetworkFindClientInfoFromIndex(index); + if (ci != NULL) { + NetworkTextMessage(NETWORK_ACTION_JOIN_LEAVE, 1, ci->client_name, "%s (%s)", str1, str2); + + // The client is gone, give the NetworkClientInfo free + ci->client_index = NETWORK_EMPTY_INDEX; + } + + InvalidateWindow(WC_CLIENT_LIST, 0); + + return NETWORK_RECV_STATUS_OKAY; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_QUIT) +{ + char str1[100], str2[100]; + uint16 index; + NetworkClientInfo *ci; + + index = NetworkRecv_uint16(p); + NetworkRecv_string(p, str2, 100); + + GetString(str1, STR_NETWORK_ERR_LEFT); + + ci = NetworkFindClientInfoFromIndex(index); + if (ci != NULL) { + NetworkTextMessage(NETWORK_ACTION_JOIN_LEAVE, 1, ci->client_name, "%s (%s)", str1, str2); + + // The client is gone, give the NetworkClientInfo free + ci->client_index = NETWORK_EMPTY_INDEX; + } else { + DEBUG(net, 0)("[NET] Error - unknown client (%d) is leaving the game", index); + } + + InvalidateWindow(WC_CLIENT_LIST, 0); + + // If we come here it means we could not locate the client.. strange :s + return NETWORK_RECV_STATUS_OKAY; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_JOIN) +{ + char str1[100]; + uint16 index; + NetworkClientInfo *ci; + + index = NetworkRecv_uint16(p); + + GetString(str1, STR_NETWORK_CLIENT_JOINED); + + ci = NetworkFindClientInfoFromIndex(index); + if (ci != NULL) { + NetworkTextMessage(NETWORK_ACTION_JOIN_LEAVE, 1, ci->client_name, "%s", str1); + } + + InvalidateWindow(WC_CLIENT_LIST, 0); + + return NETWORK_RECV_STATUS_OKAY; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_SHUTDOWN) +{ + _switch_mode_errorstr = STR_NETWORK_SERVER_SHUTDOWN; + + return NETWORK_RECV_STATUS_SERVER_ERROR; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_NEWGAME) +{ + // To trottle the reconnects a bit, every clients waits + // his _local_player value before reconnecting + // OWNER_SPECTATOR is currently 255, so to avoid long wait periods + // set the max to 10. + _network_reconnect = min(_local_player + 1, 10); + _switch_mode_errorstr = STR_NETWORK_SERVER_REBOOT; + + return NETWORK_RECV_STATUS_SERVER_ERROR; +} + + + + +// The layout for the receive-functions by the client +typedef NetworkRecvStatus NetworkClientPacket(Packet *p); + +// This array matches PacketType. At an incoming +// packet it is matches against this array +// and that way the right function to handle that +// packet is found. +static NetworkClientPacket* const _network_client_packet[] = { + RECEIVE_COMMAND(PACKET_SERVER_FULL), + NULL, /*PACKET_CLIENT_JOIN,*/ + RECEIVE_COMMAND(PACKET_SERVER_ERROR), + NULL, /*PACKET_CLIENT_COMPANY_INFO,*/ + RECEIVE_COMMAND(PACKET_SERVER_COMPANY_INFO), + RECEIVE_COMMAND(PACKET_SERVER_CLIENT_INFO), + RECEIVE_COMMAND(PACKET_SERVER_NEED_PASSWORD), + NULL, /*PACKET_CLIENT_PASSWORD,*/ + RECEIVE_COMMAND(PACKET_SERVER_WELCOME), + NULL, /*PACKET_CLIENT_GETMAP,*/ + RECEIVE_COMMAND(PACKET_SERVER_WAIT), + RECEIVE_COMMAND(PACKET_SERVER_MAP), + NULL, /*PACKET_CLIENT_MAP_OK,*/ + RECEIVE_COMMAND(PACKET_SERVER_JOIN), + RECEIVE_COMMAND(PACKET_SERVER_FRAME), + RECEIVE_COMMAND(PACKET_SERVER_SYNC), + NULL, /*PACKET_CLIENT_ACK,*/ + NULL, /*PACKET_CLIENT_COMMAND,*/ + RECEIVE_COMMAND(PACKET_SERVER_COMMAND), + NULL, /*PACKET_CLIENT_CHAT,*/ + RECEIVE_COMMAND(PACKET_SERVER_CHAT), + NULL, /*PACKET_CLIENT_SET_PASSWORD,*/ + NULL, /*PACKET_CLIENT_SET_NAME,*/ + NULL, /*PACKET_CLIENT_QUIT,*/ + NULL, /*PACKET_CLIENT_ERROR,*/ + RECEIVE_COMMAND(PACKET_SERVER_QUIT), + RECEIVE_COMMAND(PACKET_SERVER_ERROR_QUIT), + RECEIVE_COMMAND(PACKET_SERVER_SHUTDOWN), + RECEIVE_COMMAND(PACKET_SERVER_NEWGAME), +}; + +// If this fails, check the array above with network_data.h +assert_compile(lengthof(_network_client_packet) == PACKET_END); + +extern const SettingDesc patch_settings[]; + +// This is a TEMPORARY solution to get the patch-settings +// to the client. When the patch-settings are saved in the savegame +// this should be removed!! +void NetworkRecvPatchSettings(Packet *p) +{ + const SettingDesc *item; + + item = patch_settings; + + while (item->name != NULL) { + switch (item->flags) { + case SDT_BOOL: + case SDT_INT8: + case SDT_UINT8: + *(uint8 *)(item->ptr) = NetworkRecv_uint8(p); + break; + case SDT_INT16: + case SDT_UINT16: + *(uint16 *)(item->ptr) = NetworkRecv_uint16(p); + break; + case SDT_INT32: + case SDT_UINT32: + *(uint32 *)(item->ptr) = NetworkRecv_uint32(p); + break; + } + item++; + } +} + +// Is called after a client is connected to the server +void NetworkClient_Connected(void) +{ + // Set the frame-counter to 0 so nothing happens till we are ready + _frame_counter = 0; + _frame_counter_server = 0; + last_ack_frame = 0; + // Request the game-info + SEND_COMMAND(PACKET_CLIENT_JOIN)(); +} + +// Reads the packets from the socket-stream, if available +NetworkRecvStatus NetworkClient_ReadPackets(ClientState *cs) +{ + Packet *p; + NetworkRecvStatus res = NETWORK_RECV_STATUS_OKAY; + + while (res == NETWORK_RECV_STATUS_OKAY && (p = NetworkRecv_Packet(cs, &res)) != NULL) { + byte type = NetworkRecv_uint8(p); + if (type < PACKET_END && _network_client_packet[type] != NULL) { + res = _network_client_packet[type](p); + } else { + res = NETWORK_RECV_STATUS_MALFORMED_PACKET; + DEBUG(net, 0)("[NET][client] Received invalid packet type %d", type); + } + + free(p); + } + + return res; +} + +#endif /* ENABLE_NETWORK */ diff --git a/network_client.h b/network_client.h new file mode 100644 --- /dev/null +++ b/network_client.h @@ -0,0 +1,22 @@ +#ifndef NETWORK_CLIENT_H +#define NETWORK_CLIENT_H + +#ifdef ENABLE_NETWORK + +DEF_CLIENT_SEND_COMMAND(PACKET_CLIENT_GAME_INFO); +DEF_CLIENT_SEND_COMMAND(PACKET_CLIENT_COMPANY_INFO); +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_COMMAND)(CommandPacket *cp); +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_ERROR)(NetworkErrorCode errorno); +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_QUIT)(const char *leavemsg); +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_CHAT)(NetworkAction action, DestType desttype, int dest, const char *msg); +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_PASSWORD)(NetworkPasswordType type, const char *password); +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_SET_PASSWORD)(const char *password); +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_SET_NAME)(const char *name); +DEF_CLIENT_SEND_COMMAND(PACKET_CLIENT_ACK); + +NetworkRecvStatus NetworkClient_ReadPackets(ClientState *cs); +void NetworkClient_Connected(void); + +#endif /* ENABLE_NETWORK */ + +#endif // NETWORK_CLIENT_H diff --git a/network_core.h b/network_core.h new file mode 100644 --- /dev/null +++ b/network_core.h @@ -0,0 +1,83 @@ +#ifndef NETWORK_CORE_H +#define NETWORK_CORE_H + +// Network stuff has many things that needs to be included +// by default. All those things are in this file. + +// ============================= +// Include standard stuff per OS + +// Windows stuff +#if defined(WIN32) +# include +# include +# include +# pragma comment (lib, "ws2_32.lib") +# define ENABLE_NETWORK // On windows, the network is always enabled +# define GET_LAST_ERROR() WSAGetLastError() +# define EWOULDBLOCK WSAEWOULDBLOCK +// Windows has some different names for some types.. +typedef SSIZE_T ssize_t; +typedef unsigned long in_addr_t; +typedef INTERFACE_INFO IFREQ; +#endif // WIN32 + +// UNIX stuff +#if defined(UNIX) +# define SOCKET int +# define INVALID_SOCKET -1 +typedef struct ifreq IFREQ; +# if !defined(__MORPHOS__) && !defined(__AMIGA__) +# define ioctlsocket ioctl +# if !defined(BEOS_NET_SERVER) +# define closesocket close +# endif +# define GET_LAST_ERROR() (errno) +# endif +// Need this for FIONREAD on solaris +# define BSD_COMP + +// Includes needed for UNIX-like systems +# include +# include +# if defined(__BEOS__) && defined(BEOS_NET_SERVER) +# include +# include // snooze() +# include + typedef unsigned long in_addr_t; +# define INADDR_NONE INADDR_BROADCAST +# else +# include +# include +# include +# include +# include +# include +// If for any reason ifaddrs.h does not exist on a system, remove define below +// and an other system will be used to fetch ips from the system +# define HAVE_GETIFADDRS +# endif // BEOS_NET_SERVER +# include +# include +# include +#endif // UNIX + +// MorphOS and Amiga stuff +#if defined(__MORPHOS__) || defined(__AMIGA__) +# include +# include // required for Open/CloseLibrary() +# if defined(__MORPHOS__) +# include // FION#? defines +# else // __AMIGA__ +# include +# endif + +// Make the names compatible +# define closesocket(s) CloseSocket(s) +# define GET_LAST_ERROR() Errno() +# define ioctlsocket(s,request,status) IoctlSocket((LONG)s,(ULONG)request,(char*)status) + + struct Library *SocketBase = NULL; +#endif // __MORPHOS__ || __AMIGA__ + +#endif // NETWORK_CORE_H diff --git a/network_data.c b/network_data.c new file mode 100644 --- /dev/null +++ b/network_data.c @@ -0,0 +1,434 @@ +#include "stdafx.h" +#include "network_data.h" + +// Is the network enabled? +#ifdef ENABLE_NETWORK + +#include "table/strings.h" +#include "network_client.h" +#include "command.h" +#include "callback_table.h" + +// This files handles the send/receive of all packets + +// Create a packet for sending +Packet *NetworkSend_Init(PacketType type) +{ + Packet *packet = malloc(sizeof(Packet)); + // An error is inplace here, because it simply means we ran out of memory. + if (packet == NULL) error("Failed to allocate Packet"); + + // Skip the size so we can write that in before sending the packet + packet->size = sizeof(packet->size); + packet->buffer[packet->size++] = type; + packet->pos = 0; + + return packet; +} + +// The next couple of functions make sure we can send +// uint8, uint16, uint32 and uint64 endian-safe +// over the network. The order it uses is: +// +// 1 2 3 4 +// + +void NetworkSend_uint8(Packet *packet, uint8 data) +{ + assert(packet->size < sizeof(packet->buffer) - sizeof(data)); + packet->buffer[packet->size++] = data & 0xFF; +} + +void NetworkSend_uint16(Packet *packet, uint16 data) +{ + assert(packet->size < sizeof(packet->buffer) - sizeof(data)); + packet->buffer[packet->size++] = data & 0xFF; + packet->buffer[packet->size++] = (data >> 8) & 0xFF; +} + +void NetworkSend_uint32(Packet *packet, uint32 data) +{ + assert(packet->size < sizeof(packet->buffer) - sizeof(data)); + packet->buffer[packet->size++] = data & 0xFF; + packet->buffer[packet->size++] = (data >>= 8) & 0xFF; + packet->buffer[packet->size++] = (data >>= 8) & 0xFF; + packet->buffer[packet->size++] = (data >> 8) & 0xFF; +} + +void NetworkSend_uint64(Packet *packet, uint64 data) +{ + assert(packet->size < sizeof(packet->buffer) - sizeof(data)); + packet->buffer[packet->size++] = data & 0xFF; + packet->buffer[packet->size++] = (data >>= 8) & 0xFF; + packet->buffer[packet->size++] = (data >>= 8) & 0xFF; + packet->buffer[packet->size++] = (data >>= 8) & 0xFF; + packet->buffer[packet->size++] = (data >>= 8) & 0xFF; + packet->buffer[packet->size++] = (data >>= 8) & 0xFF; + packet->buffer[packet->size++] = (data >>= 8) & 0xFF; + packet->buffer[packet->size++] = (data >> 8) & 0xFF; +} + +// Sends a string over the network. It sends out +// the string + '\0'. No size-byte or something. +void NetworkSend_string(Packet *packet, const char* data) +{ + assert(data != NULL); + assert(packet->size < sizeof(packet->buffer) - strlen(data) - 1); + while ((packet->buffer[packet->size++] = *data++) != '\0') {} +} + +// If PacketSize changes of size, you have to change the 2 packet->size +// lines below matching the size of packet->size/PacketSize! +// (line 'packet->buffer[0] = packet->size & 0xFF;' and below) +assert_compile(sizeof(PacketSize) == 2); + +// This function puts the packet in the send-queue and it is send +// as soon as possible +// (that is: the next tick, or maybe one tick later if the +// OS-network-buffer is full) +void NetworkSend_Packet(Packet *packet, ClientState *cs) +{ + Packet *p; + assert(packet != NULL); + + packet->pos = 0; + packet->next = NULL; + + packet->buffer[0] = packet->size & 0xFF; + packet->buffer[1] = packet->size >> 8; + + // Locate last packet buffered for the client + p = cs->packet_queue; + if (p == NULL) { + // No packets yet + cs->packet_queue = packet; + } else { + // Skip to the last packet + while (p->next != NULL) p = p->next; + p->next = packet; + } +} + +// Functions to help NetworkRecv_Packet/NetworkSend_Packet a bit +// A socket can make errors. When that happens +// this handles what to do. +// For clients: close connection and drop back to main-menu +// For servers: close connection and that is it +NetworkRecvStatus CloseConnection(ClientState *cs) +{ + CloseClient(cs); + + // Clients drop back to the main menu + if (!_network_server) { + _switch_mode = SM_MENU; + _networking = false; + _switch_mode_errorstr = STR_NETWORK_ERR_LOSTCONNECTION; + + return NETWORK_RECV_STATUS_CONN_LOST; + } + + return NETWORK_RECV_STATUS_OKAY; +} + +// Sends all the buffered packets out for this client +// it stops when: +// 1) all packets are send (queue is empty) +// 2) the OS reports back that it can not send any more +// data right now (full network-buffer, it happens ;)) +// 3) sending took too long +bool NetworkSend_Packets(ClientState *cs) +{ + ssize_t res; + Packet *p; + + // We can not write to this socket!! + if (!cs->writable) return false; + if (cs->socket == INVALID_SOCKET) return false; + + p = cs->packet_queue; + while (p != NULL) { + res = send(cs->socket, p->buffer + p->pos, p->size - p->pos, 0); + if (res == -1) { + int err = GET_LAST_ERROR(); + if (err != EWOULDBLOCK) { + // Something went wrong.. close client! + DEBUG(net, 0) ("[NET] send() failed with error %d", err); + CloseConnection(cs); + return false; + } + return true; + } + if (res == 0) { + // Client/server has left us :( + CloseConnection(cs); + return false; + } + + p->pos += res; + + // Is this packet sent? + if (p->pos == p->size) { + // Go to the next packet + cs->packet_queue = p->next; + free(p); + p = cs->packet_queue; + } else + return true; + } + + return true; +} + + +// Receiving commands +// Again, the next couple of functions are endian-safe +// see the comment around NetworkSend_uint8 for more info. +uint8 NetworkRecv_uint8(Packet *packet) +{ + return packet->buffer[packet->pos++]; +} + +uint16 NetworkRecv_uint16(Packet *packet) +{ + uint16 n; + n = (uint16)packet->buffer[packet->pos++]; + n += (uint16)packet->buffer[packet->pos++] << 8; + return n; +} + +uint32 NetworkRecv_uint32(Packet *packet) +{ + uint32 n; + n = (uint32)packet->buffer[packet->pos++]; + n += (uint32)packet->buffer[packet->pos++] << 8; + n += (uint32)packet->buffer[packet->pos++] << 16; + n += (uint32)packet->buffer[packet->pos++] << 24; + return n; +} + +uint64 NetworkRecv_uint64(Packet *packet) +{ + uint64 n; + n = (uint64)packet->buffer[packet->pos++]; + n += (uint64)packet->buffer[packet->pos++] << 8; + n += (uint64)packet->buffer[packet->pos++] << 16; + n += (uint64)packet->buffer[packet->pos++] << 24; + n += (uint64)packet->buffer[packet->pos++] << 32; + n += (uint64)packet->buffer[packet->pos++] << 40; + n += (uint64)packet->buffer[packet->pos++] << 48; + n += (uint64)packet->buffer[packet->pos++] << 56; + return n; +} + +// Reads a string till it finds a '\0' in the stream +void NetworkRecv_string(Packet *p, char* buffer, size_t size) +{ + int pos; + pos = p->pos; + while (--size > 0 && pos < p->size && (*buffer++ = p->buffer[pos++]) != '\0') {} + if (size == 0 || pos == p->size) + { + *buffer = '\0'; + // If size was sooner to zero then the string in the stream + // skip till the \0, so the packet can be read out correctly for the rest + while (pos < p->size && p->buffer[pos] != '\0') ++pos; + ++pos; + } + p->pos = pos; +} + +// If PacketSize changes of size, you have to change the 2 packet->size +// lines below matching the size of packet->size/PacketSize! +// (the line: 'p->size = (uint16)p->buffer[0];' and below) +assert_compile(sizeof(PacketSize) == 2); + +Packet *NetworkRecv_Packet(ClientState *cs, NetworkRecvStatus *status) +{ + ssize_t res; + Packet *p; + + *status = NETWORK_RECV_STATUS_OKAY; + + if (cs->socket == INVALID_SOCKET) return NULL; + + if (cs->packet_recv == NULL) { + cs->packet_recv = malloc(sizeof(Packet)); + if (cs->packet_recv == NULL) error("Failed to allocate packet"); + // Set pos to zero! + cs->packet_recv->pos = 0; + cs->packet_recv->size = 0; // Can be ommited, just for safety reasons + } + + p = cs->packet_recv; + + // Read packet size + if (p->pos < sizeof(PacketSize)) { + while (p->pos < sizeof(PacketSize)) { + // Read the size of the packet + res = recv(cs->socket, p->buffer + p->pos, sizeof(PacketSize) - p->pos, 0); + if (res == -1) { + int err = GET_LAST_ERROR(); + if (err != EWOULDBLOCK) { + // Something went wrong.. + if (err != 104) // 104 is Connection Reset by Peer + DEBUG(net, 0) ("[NET] recv() failed with error %d", err); + *status = CloseConnection(cs); + return NULL; + } + // Connection would block, so stop for now + return NULL; + } + if (res == 0) { + // Client/server has left us :( + *status = CloseConnection(cs); + return NULL; + } + p->pos += res; + } + + p->size = (uint16)p->buffer[0]; + p->size += (uint16)p->buffer[1] << 8; + + if (p->size > SEND_MTU) { + *status = CloseConnection(cs); + return NULL; + } + } + + // Read rest of packet + while (p->pos < p->size) { + res = recv(cs->socket, p->buffer + p->pos, p->size - p->pos, 0); + if (res == -1) { + int err = GET_LAST_ERROR(); + if (err != EWOULDBLOCK) { + // Something went wrong.. + if (err != 104) // 104 is Connection Reset by Peer + DEBUG(net, 0) ("[NET] recv() failed with error %d", err); + *status = CloseConnection(cs); + return NULL; + } + // Connection would block + return NULL; + } + if (res == 0) { + // Client/server has left us :( + *status = CloseConnection(cs); + return NULL; + } + + p->pos += res; + } + + // We have a complete packet, return it! + p->pos = 2; + p->next = NULL; // Should not be needed, but who knows... + + // Prepare for receiving a new packet + cs->packet_recv = NULL; + + return p; +} + +// Add a command to the local command queue +void NetworkAddCommandQueue(ClientState *cs, CommandPacket *cp) +{ + CommandPacket *new_cp = malloc(sizeof(CommandPacket)); + + *new_cp = *cp; + + if (cs->command_queue == NULL) + cs->command_queue = new_cp; + else { + CommandPacket *c = cs->command_queue; + while (c->next != NULL) c = c->next; + c->next = new_cp; + } +} + +// If this fails, make sure you change the following line below: +// 'memcpy(qp->dp, _decode_parameters, 10 * sizeof(uint32));' +// Also, in network_data.h, change the size of CommandPacket->dp! +// (this protection is there to make sure in network.h dp is of the right size!) +assert_compile(sizeof(_decode_parameters) == 20 * sizeof(uint32)); + +// Prepare a DoCommand to be send over the network +void NetworkSend_Command(uint32 tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback) +{ + CommandPacket *c = malloc(sizeof(CommandPacket)); + byte temp_callback; + + c->player = _local_player; + c->next = NULL; + c->tile = tile; + c->p1 = p1; + c->p2 = p2; + c->cmd = cmd; + c->callback = 0; + + temp_callback = 0; + + while (temp_callback < _callback_table_count && _callback_table[temp_callback] != callback) + temp_callback++; + if (temp_callback == _callback_table_count) { + DEBUG(net, 0) ("[NET] Unknown callback. (Pointer: %p) No callback sent.", callback); + temp_callback = 0; /* _callback_table[0] == NULL */ + } + + if (_network_server) { + // We are the server, so set the command to be executed next possible frame + c->frame = _frame_counter_max + 1; + } else { + c->frame = 0; // The client can't tell which frame, so just make it 0 + } + + // Copy the _decode_parameters to dp + memcpy(c->dp, _decode_parameters, 20 * sizeof(uint32)); + + if (_network_server) { + // If we are the server, we queue the command in our 'special' queue. + // In theory, we could execute the command right away, but then the + // client on the server can do everything 1 tick faster then others. + // So to keep the game fair, we delay the command with 1 tick + // which gives about the same speed as most clients. + ClientState *cs; + + // And we queue it for delivery to the clients + FOR_ALL_CLIENTS(cs) { + if (cs->status > STATUS_AUTH) { + NetworkAddCommandQueue(cs, c); + } + } + + // Only the server gets the callback, because clients should not get them + c->callback = temp_callback; + if (_local_command_queue == NULL) { + _local_command_queue = c; + } else { + // Find last packet + CommandPacket *cp = _local_command_queue; + while (cp->next != NULL) cp = cp->next; + cp->next = c; + } + + return; + } + + // Clients send their command to the server and forget all about the packet + c->callback = temp_callback; + SEND_COMMAND(PACKET_CLIENT_COMMAND)(c); +} + +// Execute a DoCommand we received from the network +void NetworkExecuteCommand(CommandPacket *cp) +{ + _current_player = cp->player; + memcpy(_decode_parameters, cp->dp, sizeof(cp->dp)); + /* cp->callback is unsigned. so we don't need to do lower bounds checking. */ + if (cp->callback > _callback_table_count) { + DEBUG(net,0) ("[NET] Received out-of-bounds callback! (%d)", cp->callback); + cp->callback = 0; + } + DoCommandP(cp->tile, cp->p1, cp->p2, _callback_table[cp->callback], cp->cmd | CMD_NETWORK_COMMAND); +} + +#endif /* ENABLE_NETWORK */ diff --git a/network_data.h b/network_data.h new file mode 100644 --- /dev/null +++ b/network_data.h @@ -0,0 +1,223 @@ +#ifndef NETWORK_DATA_H +#define NETWORK_DATA_H + +#include "ttd.h" +#include "network.h" + +// Is the network enabled? +#ifdef ENABLE_NETWORK + +#define SEND_MTU 1460 +#define MAX_TEXT_MSG_LEN 1024 /* long long long long sentences :-) */ + +// The client-info-server-index is always 1 +#define NETWORK_SERVER_INDEX 1 +#define NETWORK_EMPTY_INDEX 0 + +// What version of game-info do we use? +#define NETWORK_GAME_INFO_VERSION 1 +// What version of company info is this? +#define NETWORK_COMPANY_INFO_VERSION 1 + +typedef uint16 PacketSize; + +typedef struct Packet { + struct Packet *next; + PacketSize size; + PacketSize pos; + byte buffer[SEND_MTU]; +} Packet; + +typedef struct CommandPacket { + struct CommandPacket *next; + byte player; + uint32 cmd; + uint32 p1; + uint32 p2; + uint32 tile; // Always make it uint32, so it is bigmap compatible + uint32 dp[20]; // decode_params + uint32 frame; // In which frame must this packet be executed? + byte callback; +} CommandPacket; + +typedef enum { + STATUS_INACTIVE, + STATUS_AUTH, // This means that the client is authorized + STATUS_MAP_WAIT, // This means that the client is put on hold because someone else is getting the map + STATUS_MAP, + STATUS_DONE_MAP, + STATUS_PRE_ACTIVE, + STATUS_ACTIVE, +} ClientStatus; + +typedef enum { + MAP_PACKET_START, + MAP_PACKET_NORMAL, + MAP_PACKET_PATCH, + MAP_PACKET_END, +} MapPacket; + +typedef enum { + NETWORK_RECV_STATUS_OKAY, + NETWORK_RECV_STATUS_DESYNC, + NETWORK_RECV_STATUS_SAVEGAME, + NETWORK_RECV_STATUS_CONN_LOST, + NETWORK_RECV_STATUS_MALFORMED_PACKET, + NETWORK_RECV_STATUS_SERVER_ERROR, // The server told us we made an error + NETWORK_RECV_STATUS_SERVER_FULL, + NETWORK_RECV_STATUS_CLOSE_QUERY, // Done quering the server +} NetworkRecvStatus; + +typedef enum { + NETWORK_ERROR_GENERAL, // Try to use thisone like never + + // Signals from clients + NETWORK_ERROR_DESYNC, + NETWORK_ERROR_SAVEGAME_FAILED, + NETWORK_ERROR_CONNECTION_LOST, + NETWORK_ERROR_ILLEGAL_PACKET, + + // Signals from servers + NETWORK_ERROR_NOT_AUTHORIZED, + NETWORK_ERROR_NOT_EXPECTED, + NETWORK_ERROR_WRONG_REVISION, + NETWORK_ERROR_NAME_IN_USE, + NETWORK_ERROR_WRONG_PASSWORD, + NETWORK_ERROR_PLAYER_MISMATCH, // Happens in CLIENT_COMMAND + NETWORK_ERROR_KICKED, +} NetworkErrorCode; + +// Actions that can be used for NetworkTextMessage +typedef enum { + NETWORK_ACTION_JOIN_LEAVE, + NETWORK_ACTION_CHAT, + NETWORK_ACTION_CHAT_PLAYER, + NETWORK_ACTION_CHAT_CLIENT, + NETWORK_ACTION_CHAT_TO_CLIENT, + NETWORK_ACTION_CHAT_TO_PLAYER, + NETWORK_ACTION_GIVE_MONEY, + NETWORK_ACTION_NAME_CHANGE, +} NetworkAction; + +typedef enum { + NETWORK_GAME_PASSWORD, + NETWORK_COMPANY_PASSWORD, +} NetworkPasswordType; + +// To keep the clients all together +typedef struct ClientState { + int socket; + uint16 index; + uint32 last_frame; + uint32 last_frame_server; + byte lag_test; // This byte is used for lag-testing the client + + ClientStatus status; + bool writable; // is client ready to write to? + bool quited; + + Packet *packet_queue; // Packets that are awaiting delivery + Packet *packet_recv; // Partially received packet + + CommandPacket *command_queue; // The command-queue awaiting delivery +} ClientState; + +// What packet types are there +// WARNING: The first 3 packets can NEVER change order again +// it protects old clients from joining newer servers (because SERVER_ERROR +// is the respond to a wrong revision) +typedef enum { + PACKET_SERVER_FULL, + PACKET_CLIENT_JOIN, + PACKET_SERVER_ERROR, + PACKET_CLIENT_COMPANY_INFO, + PACKET_SERVER_COMPANY_INFO, + PACKET_SERVER_CLIENT_INFO, + PACKET_SERVER_NEED_PASSWORD, + PACKET_CLIENT_PASSWORD, + PACKET_SERVER_WELCOME, + PACKET_CLIENT_GETMAP, + PACKET_SERVER_WAIT, + PACKET_SERVER_MAP, + PACKET_CLIENT_MAP_OK, + PACKET_SERVER_JOIN, + PACKET_SERVER_FRAME, + PACKET_SERVER_SYNC, + PACKET_CLIENT_ACK, + PACKET_CLIENT_COMMAND, + PACKET_SERVER_COMMAND, + PACKET_CLIENT_CHAT, + PACKET_SERVER_CHAT, + PACKET_CLIENT_SET_PASSWORD, + PACKET_CLIENT_SET_NAME, + PACKET_CLIENT_QUIT, + PACKET_CLIENT_ERROR, + PACKET_SERVER_QUIT, + PACKET_SERVER_ERROR_QUIT, + PACKET_SERVER_SHUTDOWN, + PACKET_SERVER_NEWGAME, + PACKET_END // Should ALWAYS be on the end of this list!! (period) +} PacketType; + +typedef enum { + DESTTYPE_BROADCAST, + DESTTYPE_PLAYER, + DESTTYPE_CLIENT +} DestType; + +CommandPacket *_local_command_queue; + +SOCKET _udp_client_socket; // udp client socket + +// Here we keep track of the clients +// (and the client uses [0] for his own communication) +ClientState _clients[MAX_CLIENTS]; +#define DEREF_CLIENT(i) (&_clients[i]) +// This returns the NetworkClientInfo from a ClientState +#define DEREF_CLIENT_INFO(cs) (&_network_client_info[cs - _clients]) + +// Macros to make life a bit more easier +#define DEF_CLIENT_RECEIVE_COMMAND(type) NetworkRecvStatus NetworkPacketReceive_ ## type ## _command(Packet *p) +#define DEF_CLIENT_SEND_COMMAND(type) void NetworkPacketSend_ ## type ## _command(void) +#define DEF_CLIENT_SEND_COMMAND_PARAM(type) void NetworkPacketSend_ ## type ## _command +#define DEF_SERVER_RECEIVE_COMMAND(type) void NetworkPacketReceive_ ## type ## _command(ClientState *cs, Packet *p) +#define DEF_SERVER_SEND_COMMAND(type) void NetworkPacketSend_ ## type ## _command(ClientState *cs) +#define DEF_SERVER_SEND_COMMAND_PARAM(type) void NetworkPacketSend_ ## type ## _command + +#define SEND_COMMAND(type) NetworkPacketSend_ ## type ## _command +#define RECEIVE_COMMAND(type) NetworkPacketReceive_ ## type ## _command + +#define FOR_ALL_CLIENTS(cs) for (cs = _clients; cs != &_clients[MAX_CLIENTS] && cs->socket != INVALID_SOCKET; cs++) + +Packet *NetworkSend_Init(PacketType type); +void NetworkSend_uint8(Packet *packet, uint8 data); +void NetworkSend_uint16(Packet *packet, uint16 data); +void NetworkSend_uint32(Packet *packet, uint32 data); +void NetworkSend_uint64(Packet *packet, uint64 data); +void NetworkSend_string(Packet *packet, const char* data); +void NetworkSend_Packet(Packet *packet, ClientState *cs); + +uint8 NetworkRecv_uint8(Packet *packet); +uint16 NetworkRecv_uint16(Packet *packet); +uint32 NetworkRecv_uint32(Packet *packet); +uint64 NetworkRecv_uint64(Packet *packet); +void NetworkRecv_string(Packet *packet, char* buffer, size_t size); +Packet *NetworkRecv_Packet(ClientState *cs, NetworkRecvStatus *status); + +bool NetworkSend_Packets(ClientState *cs); +void NetworkExecuteCommand(CommandPacket *cp); +void NetworkAddCommandQueue(ClientState *cs, CommandPacket *cp); + +// from network.c +void CloseClient(ClientState *cs); +void NetworkTextMessage(NetworkAction action, uint16 color, const char *name, const char *str, ...); +void NetworkGetClientName(char *clientname, size_t size, ClientState *cs); +uint NetworkCalculateLag(const ClientState *cs); +byte NetworkGetCurrentLanguageIndex(); +NetworkClientInfo *NetworkFindClientInfoFromIndex(uint16 client_index); +ClientState *NetworkFindClientStateFromIndex(uint16 client_index); +unsigned long NetworkResolveHost(const char *hostname); + +#endif /* ENABLE_NETWORK */ + +#endif // NETWORK_DATA_H diff --git a/network_gamelist.c b/network_gamelist.c new file mode 100644 --- /dev/null +++ b/network_gamelist.c @@ -0,0 +1,83 @@ +#include "stdafx.h" +#include "network_data.h" + +#ifdef ENABLE_NETWORK + +// +// This file handles the GameList +// Also, it handles the request to a server for data about the server + +extern void UpdateNetworkGameWindow(bool unselect); + +void NetworkGameListClear(void) +{ + NetworkGameList *item; + NetworkGameList *next; + + item = _network_game_list; + + while (item != NULL) { + next = item->next; + free(item); + item = next; + } + _network_game_list = NULL; + _network_game_count = 0; + + UpdateNetworkGameWindow(true); + + DEBUG(net, 4)("[NET][GameList] Cleared list"); +} + +NetworkGameList *NetworkGameListAddItem(uint32 ip, uint16 port) +{ + NetworkGameList *item; + + item = _network_game_list; + if (item != NULL) { + while (item->next != NULL) { + if (item->ip == ip && item->port == port) + return item; + item = item->next; + } + + if (item->ip == ip && item->port == port) + return item; + + item->next = malloc(sizeof(*item)); + item = item->next; + } else { + item = malloc(sizeof(*item)); + _network_game_list = item; + } + + DEBUG(net, 4) ("[NET][GameList] Added server to list"); + + memset(item, 0, sizeof(*item)); + + item->next = NULL; + item->ip = ip; + item->port = port; + _network_game_count++; + + UpdateNetworkGameWindow(false); + + return item; +} + +void NetworkGameListAddQueriedItem(NetworkGameInfo *info, bool server_online) +{ + // We queried a server and now we are going to add it to the list + NetworkGameList *item; + + item = NetworkGameListAddItem(_network_last_host_ip, _network_last_port); + item->online = server_online; + memcpy(&item->info, info, sizeof(NetworkGameInfo)); + ttd_strlcpy(item->info.hostname, _network_last_host, sizeof(item->info.hostname)); + + UpdateNetworkGameWindow(false); +} + +#endif /* ENABLE_NETWORK */ + + diff --git a/network_gamelist.h b/network_gamelist.h new file mode 100644 --- /dev/null +++ b/network_gamelist.h @@ -0,0 +1,8 @@ +#ifndef NETWORK_GAMELIST_H +#define NETWORK_GAMELIST_H + +void NetworkGameListClear(void); +NetworkGameList *NetworkGameListAddItem(uint32 ip, uint16 port); +void NetworkGameListAddQueriedItem(NetworkGameInfo *info, bool server_online); + +#endif /* NETWORK_GAMELIST_H */ diff --git a/network_gui.c b/network_gui.c --- a/network_gui.c +++ b/network_gui.c @@ -1,20 +1,29 @@ #include "stdafx.h" #include "ttd.h" +#include "network.h" +#include "saveload.h" + +#include "hal.h" // for file list + +#ifdef ENABLE_NETWORK + #include "table/strings.h" +#include "network_data.h" #include "window.h" #include "gui.h" #include "gfx.h" #include "command.h" -#include "network.h" +#include "functions.h" +#include "variables.h" +#include "network_server.h" +#include "network_udp.h" #define BGC 5 #define BTC 15 #define MAX_QUERYSTR_LEN 64 static byte _edit_str_buf[MAX_QUERYSTR_LEN*2]; -static void ShowNetworkStartServerWindow(); -#if 0 -static void ShowNetworkLobbyWindow(); -#endif +static void ShowNetworkStartServerWindow(void); +static void ShowNetworkLobbyWindow(void); static byte _selected_field; @@ -24,148 +33,293 @@ static const StringID _connection_types_ INVALID_STRING_ID }; -/* Should be _network_game->players_max but since network is not yet really done -* we'll just use some dummy here -* network.c -->> static NetworkGameInfo _network_game; -*/ -static byte _players_max; -/* Should be ??????????? (something) but since network is not yet really done -* we'll just use some dummy here -*/ static byte _network_connection; -static uint16 _network_game_count_last; + +static StringID _str_map_name, _str_game_name, _str_server_version, _str_server_address; enum { - NET_PRC__OFFSET_TOP_WIDGET = 93, - NET_PRC__SIZE_OF_ROW = 14, + NET_PRC__OFFSET_TOP_WIDGET = 74, + NET_PRC__OFFSET_TOP_WIDGET_COMPANY = 42, + NET_PRC__SIZE_OF_ROW = 14, + NET_PRC__SIZE_OF_ROW_COMPANY = 12, }; -static NetworkGameList *selected_item = NULL; +static NetworkGameList *_selected_item = NULL; +static int8 _selected_company_item = -1; + +#ifdef WITH_REV +extern char _openttd_revision[]; +#endif + +// Truncates a string to max_width (via GetStringWidth) and adds 3 dots +// at the end of the name. +static void NetworkTruncateString(char *name, const int max_width) +{ + char temp[NETWORK_NAME_LENGTH]; + char internal_name[NETWORK_NAME_LENGTH]; + + ttd_strlcpy(internal_name, name, sizeof(internal_name)); + + if (GetStringWidth(internal_name) > max_width) { + // Servername is too long, trunc it! + snprintf(temp, sizeof(temp), "%s...", internal_name); + // Continue to delete 1 char of the string till it is in range + while (GetStringWidth(temp) > max_width) { + internal_name[strlen(internal_name) - 1] = '\0'; + snprintf(temp, sizeof(temp), "%s...", internal_name); + } + ttd_strlcpy(name, temp, sizeof(temp)); + } +} static void NetworkGameWindowWndProc(Window *w, WindowEvent *e) { switch(e->event) { case WE_PAINT: { + if (_selected_item == NULL) + w->disabled_state = (1<<17) | (1<<18); + else if (!_selected_item->online) + w->disabled_state = (1<<17); // Server offline, join button disabled + else if (_selected_item->info.clients_on == _selected_item->info.clients_max) + w->disabled_state = (1<<17); // Server full, join button disabled + else + w->disabled_state = 0; SetDParam(0, 0x00); SetDParam(2, STR_NETWORK_LAN + _network_connection); DrawWindowWidgets(w); - DrawEditBox(w, 6); + DrawEditBox(w, 3); - DrawString(9, 43, STR_NETWORK_PLAYER_NAME, 2); - DrawString(9, 63, STR_NETWORK_SELECT_CONNECTION, 2); + DrawString(9, 23, STR_NETWORK_PLAYER_NAME, 2); + DrawString(9, 43, STR_NETWORK_CONNECTION, 2); - DrawString(15, 82, STR_NETWORK_GAME_NAME, 2); - DrawString(238, 82, STR_NETWORK_PLAYERS, 2); - DrawString(288, 82, STR_NETWORK_MAP_SIZE, 2); + DrawString(15, 63, STR_NETWORK_GAME_NAME, 2); + DrawString(135, 63, STR_NETWORK_CLIENTS_CAPTION, 2); { // draw list of games uint16 y = NET_PRC__OFFSET_TOP_WIDGET + 3; int32 n = 0; - NetworkGameList *cur_item = _network_game_list; + char servername[NETWORK_NAME_LENGTH]; + const NetworkGameList *cur_item = _network_game_list; while (cur_item != NULL) { - if (cur_item == selected_item) - GfxFillRect(11, y - 2, 380, y + 9, 10); // show highlighted item with a different colour +#ifdef WITH_REV + bool compatible = (strncmp(cur_item->info.server_revision, _openttd_revision, 10) == 0); +#else + bool compatible = true; // We have no idea if we are compatible... +#endif + if (strncmp(cur_item->info.server_revision, "norev000", sizeof(cur_item->info.server_revision)) == 0) + compatible = true; - DoDrawString(cur_item->item.server_name, 15, y, 16); // server name + if (cur_item == _selected_item) + GfxFillRect(11, y - 2, 218, y + 9, 10); // show highlighted item with a different colour - SetDParam(0, cur_item->item.players_on); - SetDParam(1, cur_item->item.players_max); - DrawString(238, y, STR_NETWORK_PLAYERS_VAL, 2); // #/# + snprintf(servername, sizeof(servername), "%s", cur_item->info.server_name); + NetworkTruncateString(servername, 110); + DoDrawString(servername, 15, y, 16); // server name - DoDrawString(cur_item->item.map_name, 288, y, 16); // map size - cur_item = cur_item->_next; + SetDParam(0, cur_item->info.clients_on); + SetDParam(1, cur_item->info.clients_max); + DrawString(135, y, STR_NETWORK_CLIENTS_ONLINE, 2); + + // draw red or green icon, depending on compatibility with server. TODO: needs new icons + DrawSprite((SPR_OPENTTD_BASE + 10) | (compatible?0x30d8000:0x30b8000), 185, y); + // draw red or green flag, to show if the server is password protected. TODO: needs new icons + DrawSprite((cur_item->info.use_password)? 0xC12 : 0xC13, 195, y); + + cur_item = cur_item->next; y += NET_PRC__SIZE_OF_ROW; if (++n == w->vscroll.cap) { break;} // max number of games in the window } } + + // right menu + GfxFillRect(252, 23, 468, 65, 157); + if (_selected_item == NULL) { + DrawStringMultiCenter(360, 40, STR_NETWORK_GAME_INFO, 0); + } else if (!_selected_item->online) { + SetDParam(0, _str_game_name); + DrawStringMultiCenter(360, 42, STR_ORANGE, 2); // game name + + DrawStringMultiCenter(360, 110, STR_NETWORK_SERVER_OFFLINE, 2); // server offline + } else { // show game info + uint16 y = 70; + + DrawStringMultiCenter(360, 30, STR_NETWORK_GAME_INFO, 0); + + SetDParam(0, _str_game_name); + DrawStringMultiCenter(360, 42, STR_ORANGE, 2); // game name + + SetDParam(0, _str_map_name); + DrawStringMultiCenter(360, 54, STR_02BD, 2); // map name + + SetDParam(0, _selected_item->info.clients_on); + SetDParam(1, _selected_item->info.clients_max); + DrawString(260, y, STR_NETWORK_CLIENTS, 2); // clients on the server / maximum slots + y+=10; + + SetDParam(0, STR_NETWORK_LANG_ANY+_selected_item->info.server_lang); + DrawString(260, y, STR_NETWORK_LANGUAGE, 2); // server language + y+=10; + + SetDParam(0, STR_TEMPERATE_LANDSCAPE+_selected_item->info.map_set); + DrawString(260, y, STR_NETWORK_TILESET, 2); // tileset + y+=10; + + SetDParam(0, _selected_item->info.map_width); + SetDParam(1, _selected_item->info.map_height); + DrawString(260, y, STR_NETWORK_MAP_SIZE, 2); // map size + y+=10; + + SetDParam(0, _str_server_version); + DrawString(260, y, STR_NETWORK_SERVER_VERSION, 2); // server version + y+=10; + + SetDParam(0, _str_server_address); + DrawString(260, y, STR_NETWORK_SERVER_ADDRESS, 2); // server address + y+=10; + + SetDParam(0, _selected_item->info.start_date); + DrawString(260, y, STR_NETWORK_START_DATE, 2); // start date + y+=10; + + SetDParam(0, _selected_item->info.game_date); + DrawString(260, y, STR_NETWORK_CURRENT_DATE, 2); // current date + y+=10; + + if (_selected_item->info.clients_on == _selected_item->info.clients_max) + // Show: server full, when clients_on == clients_max + DrawStringMultiCenter(360, y, STR_NETWORK_SERVER_FULL, 2); // server full + else if (_selected_item->info.use_password) + DrawStringMultiCenter(360, y, STR_NETWORK_PASSWORD, 2); // password warning + y+=10; + } } break; case WE_CLICK: _selected_field = e->click.widget; switch(e->click.widget) { - case 0: case 15: /* Close 'X' | Cancel button */ + case 0: case 14: /* Close 'X' | Cancel button */ DeleteWindowById(WC_NETWORK_WINDOW, 0); - NetworkLobbyShutdown(); break; - case 3: { /* Find server automaticaly */ - NetworkCoreConnectGame("auto", _network_server_port); - } break; - case 4: { /* Connect via direct ip */ - StringID str; - str = AllocateName((byte*)_decode_parameters, 0); - - ShowQueryString( - str, - STR_NETWORK_ENTER_IP, - 50, // maximum 50 characters OR - 250, // characters up to width 250 pixels, whichever is satisfied first - w->window_class, - w->window_number); - DeleteName(str); - } break; - case 5: /* Start server */ - ShowNetworkStartServerWindow(); - break; - case 7: case 8: /* Connection type */ - ShowDropDownMenu(w, _connection_types_dropdown, _network_connection, 8, 0); // do it for widget 8 + case 4: case 5: /* Connection type */ + ShowDropDownMenu(w, _connection_types_dropdown, _network_connection, 5, 0); // do it for widget 5 return; - case 14: { /* Matrix to show networkgames */ + case 10: { /* Matrix to show networkgames */ uint32 id_v = (e->click.pt.y - NET_PRC__OFFSET_TOP_WIDGET) / NET_PRC__SIZE_OF_ROW; if (id_v >= w->vscroll.cap) { return;} // click out of bounds - id_v += w->vscroll.pos; { NetworkGameList *cur_item = _network_game_list; for (; id_v > 0 && cur_item != NULL; id_v--) - cur_item = cur_item->_next; + cur_item = cur_item->next; + + if (cur_item == NULL) { + // click out of vehicle bounds + _selected_item = NULL; + SetWindowDirty(w); + return; + } + _selected_item = cur_item; - if (cur_item == NULL) { return;} // click out of vehicle bounds + DeleteName(_str_game_name); + DeleteName(_str_map_name); + DeleteName(_str_server_version); + DeleteName(_str_server_address); + if (_selected_item->info.server_name[0] != '\0') + _str_game_name = AllocateName((byte*) _selected_item->info.server_name, 0); + else + _str_game_name = STR_EMPTY; - selected_item = cur_item; + if (_selected_item->info.map_name[0] != '\0') + _str_map_name = AllocateName((byte*) _selected_item->info.map_name, 0); + else + _str_map_name = STR_EMPTY; + + if (_selected_item->info.server_revision[0] != '\0') + _str_server_version = AllocateName((byte*) _selected_item->info.server_revision, 0); + else + _str_server_version = STR_EMPTY; + + if (_selected_item->info.hostname[0] != '\0') + _str_server_address = AllocateName((byte*) _selected_item->info.hostname, 0); + else + _str_server_address = STR_EMPTY; } + SetWindowDirty(w); } break; - case 16: /* Join Game */ - if (selected_item != NULL) - NetworkCoreConnectGameStruct(selected_item); + case 11: /* Find server automatically */ + NetworkUDPSearchGame(); break; - } - break; + case 12: { // Add a server + StringID str = AllocateName((byte*)_network_default_ip, 0); + + ShowQueryString( + str, + STR_NETWORK_ENTER_IP, + 31 | 0x1000, // maximum number of characters OR + 250, // characters up to this width pixels, whichever is satisfied first + w->window_class, + w->window_number); + DeleteName(str); + } break; + case 13: /* Start server */ + ShowNetworkStartServerWindow(); + break; + case 17: /* Join Game */ + if (_selected_item != NULL) { + memcpy(&_network_game_info, &_selected_item->info, sizeof(NetworkGameInfo)); + snprintf(_network_last_host, sizeof(_network_last_host), "%s", inet_ntoa(*(struct in_addr *)&_selected_item->ip)); + _network_last_port = _selected_item->port; + ShowNetworkLobbyWindow(); + } + break; + case 18: // Refresh + if (_selected_item != NULL) { + NetworkQueryServer(_selected_item->info.hostname, _selected_item->port, true); + } + break; + + } break; case WE_DROPDOWN_SELECT: /* we have selected a dropdown item in the list */ _network_connection = e->dropdown.index; switch (_network_connection) { case 0: /* LAN */ - NetworkGameListFromLAN(); +// NetworkGameListFromLAN(); break; case 1: /* Internet */ - NetworkGameListFromInternet(); +// NetworkGameListFromInternet(); break; } - _network_game_count_last = _network_game_count; SetWindowDirty(w); - break; case WE_MOUSELOOP: - if (_selected_field == 6) - HandleEditBox(w, 6); + if (_selected_field == 3) + HandleEditBox(w, 3); break; case WE_KEYPRESS: - if (_selected_field != 6) + if (_selected_field != 3) break; - switch (HandleEditBoxKey(w, 6, e)) { + switch (HandleEditBoxKey(w, 3, e)) { case 1: - HandleButtonClick(w, 9); + HandleButtonClick(w, 10); break; } + + // The name is only allowed when it starts with a letter! + if (_edit_str_buf[0] != '\0' && _edit_str_buf[0] != ' ') + ttd_strlcpy(_network_player_name, _edit_str_buf, lengthof(_network_player_name)); + else + ttd_strlcpy(_network_player_name, "Player", lengthof(_network_player_name)); + break; case WE_ON_EDIT_TEXT: { @@ -175,147 +329,225 @@ static void NetworkGameWindowWndProc(Win const byte *player = NULL; uint16 rport; - rport = _network_server_port; + ttd_strlcpy(_network_default_ip, b, lengthof(_network_default_ip)); + rport = NETWORK_DEFAULT_PORT; ParseConnectionString(&player, &port, b); - if (player!=NULL) _network_playas = atoi(player); - if (port!=NULL) rport = atoi(port); + if (player != NULL) _network_playas = atoi(player); + if (port != NULL) rport = atoi(port); - NetworkCoreConnectGame(b, rport); + NetworkQueryServer(b, rport, true); } } break; - case WE_TICK: { - if (_network_game_count_last != _network_game_count) - SetWindowDirty(w); + case WE_CREATE: { + _selected_item = NULL; } break; - } } static const Widget _network_game_window_widgets[] = { { WWT_CLOSEBOX, BGC, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, -{ WWT_CAPTION, BGC, 10, 399, 0, 13, STR_NETWORK_MULTIPLAYER, STR_NULL}, -{ WWT_IMGBTN, BGC, 0, 399, 14, 199, 0x0, STR_NULL}, +{ WWT_CAPTION, BGC, 11, 479, 0, 13, STR_NETWORK_MULTIPLAYER, STR_NULL}, +{ WWT_IMGBTN, BGC, 0, 479, 14, 214, 0x0, STR_NULL}, + +/* LEFT SIDE */ +{ WWT_IMGBTN, BGC, 90, 230, 22, 33, 0x0, STR_NETWORK_ENTER_NAME_TIP}, -{ WWT_PUSHTXTBTN, BTC, 20, 130, 22, 33, STR_NETWORK_FIND_SERVER, STR_NETWORK_FIND_SERVER_TIP}, -{ WWT_PUSHTXTBTN, BTC, 145, 255, 22, 33, STR_NETWORK_DIRECT_CONNECT, STR_NETWORK_DIRECT_CONNECT_TIP}, -{ WWT_PUSHTXTBTN, BTC, 270, 380, 22, 33, STR_NETWORK_START_SERVER, STR_NETWORK_START_SERVER_TIP}, +{ WWT_6, BGC, 90, 230, 42, 53, STR_NETWORK_COMBO1, STR_NETWORK_CONNECTION_TIP}, +{ WWT_CLOSEBOX, BGC, 219, 229, 43, 52, STR_0225, STR_NETWORK_CONNECTION_TIP}, -{ WWT_IMGBTN, BGC, 250, 394, 42, 53, 0x0, STR_NETWORK_ENTER_NAME_TIP}, +{ WWT_SCROLLBAR, BGC, 220, 230, 62, 185, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST}, -{ WWT_6, BGC, 250, 393, 62, 73, STR_NETWORK_COMBO1, STR_NETWORK_CONNECTION_TYPE_TIP}, -{ WWT_CLOSEBOX, BGC, 382, 392, 63, 72, STR_0225, STR_NETWORK_CONNECTION_TYPE_TIP}, +{ WWT_IMGBTN, BTC, 10, 130, 62, 73, 0x0, STR_NETWORK_GAME_NAME_TIP }, +{ WWT_IMGBTN, BTC, 131, 180, 62, 73, 0x0, STR_NETWORK_CLIENTS_CAPTION_TIP }, +{ WWT_IMGBTN, BTC, 181, 219, 62, 73, 0x0, STR_NETWORK_INFO_ICONS_TIP }, -{ WWT_SCROLLBAR, BGC, 382, 392, 81, 176, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST}, +{ WWT_MATRIX, BGC, 10, 219, 74, 185, 0x801, STR_NETWORK_CLICK_GAME_TO_SELECT}, + +{ WWT_PUSHTXTBTN, BTC, 10, 115, 195, 206, STR_NETWORK_FIND_SERVER, STR_NETWORK_FIND_SERVER_TIP}, +{ WWT_PUSHTXTBTN, BTC, 125, 230, 195, 206, STR_NETWORK_ADD_SERVER, STR_NETWORK_ADD_SERVER_TIP}, +{ WWT_PUSHTXTBTN, BTC, 250, 355, 195, 206, STR_NETWORK_START_SERVER, STR_NETWORK_START_SERVER_TIP}, +{ WWT_PUSHTXTBTN, BTC, 365, 470, 195, 206, STR_012E_CANCEL, STR_NULL}, -{ WWT_IMGBTN, BTC, 10, 231, 81, 92, 0x0, STR_NETWORK_GAME_NAME_TIP }, -{ WWT_IMGBTN, BTC, 232, 281, 81, 92, 0x0, STR_NETWORK_PLAYERS_TIP }, -{ WWT_IMGBTN, BTC, 282, 331, 81, 92, 0x0, STR_NETWORK_MAP_SIZE_TIP }, -{ WWT_IMGBTN, BTC, 332, 381, 81, 92, 0x0, STR_NETWORK_INFO_ICONS_TIP }, +/* RIGHT SIDE */ +{ WWT_IMGBTN, BGC, 250, 470, 22, 185, 0x0, STR_NULL}, +{ WWT_6, BGC, 251, 469, 23, 184, 0x0, STR_NULL}, -{ WWT_MATRIX, BGC, 10, 381, 93, 176, 0x601, STR_NETWORK_CLICK_GAME_TO_SELECT}, +{ WWT_PUSHTXTBTN, BTC, 260, 355, 164, 175, STR_NETWORK_JOIN_GAME, STR_NULL}, +{ WWT_PUSHTXTBTN, BTC, 365, 460, 164, 175, STR_NETWORK_REFRESH, STR_NETWORK_REFRESH_TIP}, -{ WWT_PUSHTXTBTN, BTC, 145, 255, 180, 191, STR_012E_CANCEL, STR_NULL}, -{ WWT_PUSHTXTBTN, BTC, 270, 392, 180, 191, STR_NETWORK_JOIN_GAME, STR_NULL}, { WIDGETS_END}, }; static const WindowDesc _network_game_window_desc = { - WDP_CENTER, WDP_CENTER, 400, 200, + WDP_CENTER, WDP_CENTER, 480, 215, WC_NETWORK_WINDOW,0, - WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS, + WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_RESTORE_DPARAM, _network_game_window_widgets, NetworkGameWindowWndProc, }; +static FiosItem *selected_map = NULL; // to highlight slected map void ShowNetworkGameWindow() { Window *w; DeleteWindowById(WC_NETWORK_WINDOW, 0); - NetworkLobbyInit(); - +// NetworkLobbyInit(); w = AllocateWindowDesc(&_network_game_window_desc); - strcpy(_edit_str_buf, "Your name"); - w->vscroll.cap = 6; - w->disabled_state = (1<<6) | (1<<7) | (1<<8); // disable buttons not yet working - NetworkGameListFromLAN(); // default dropdown item is LAN, so fill that array - - _network_game_count_last = _network_game_count; + ttd_strlcpy(_edit_str_buf, _network_player_name, MAX_QUERYSTR_LEN); + w->vscroll.cap = 8; + w->disabled_state = (1<<4) | (1<<5); // disable buttons not yet working +// NetworkGameListFromLAN(); // default dropdown item is LAN, so fill that array WP(w,querystr_d).caret = 1; WP(w,querystr_d).maxlen = MAX_QUERYSTR_LEN; - WP(w,querystr_d).maxwidth = 240; + WP(w,querystr_d).maxwidth = 120; WP(w,querystr_d).buf = _edit_str_buf; } +// called when a new server is found on the network +void UpdateNetworkGameWindow(bool unselect) +{ + Window *w; + w = FindWindowById(WC_NETWORK_WINDOW, 0); + if (w != NULL) { + if (unselect) + _selected_item = NULL; + w->vscroll.count = _network_game_count; + SetWindowDirty(w); + } +} + static const StringID _players_dropdown[] = { - STR_NETWORK_2_PLAYERS, - STR_NETWORK_3_PLAYERS, - STR_NETWORK_4_PLAYERS, - STR_NETWORK_5_PLAYERS, - STR_NETWORK_6_PLAYERS, - STR_NETWORK_7_PLAYERS, - STR_NETWORK_8_PLAYERS, + STR_NETWORK_2_CLIENTS, + STR_NETWORK_3_CLIENTS, + STR_NETWORK_4_CLIENTS, + STR_NETWORK_5_CLIENTS, + STR_NETWORK_6_CLIENTS, + STR_NETWORK_7_CLIENTS, + STR_NETWORK_8_CLIENTS, + STR_NETWORK_9_CLIENTS, + STR_NETWORK_10_CLIENTS, INVALID_STRING_ID }; +static const StringID _language_dropdown[] = { + STR_NETWORK_LANG_ANY, + STR_NETWORK_LANG_ENGLISH, + STR_NETWORK_LANG_GERMAN, + STR_NETWORK_LANG_FRENCH, + INVALID_STRING_ID +}; + +enum { + NSSWND_START = 64, + NSSWND_ROWSIZE = 12 +}; + static void NetworkStartServerWindowWndProc(Window *w, WindowEvent *e) { switch(e->event) { case WE_PAINT: { + int y = NSSWND_START, pos; + const FiosItem *item; - SetDParam(7, STR_NETWORK_2_PLAYERS + _players_max); + SetDParam(7, STR_NETWORK_2_CLIENTS + _network_game_info.clients_max - 2); + SetDParam(9, STR_NETWORK_LANG_ANY + _network_game_info.server_lang); DrawWindowWidgets(w); - GfxFillRect(11, 63, 237, 168, 0xD7); + GfxFillRect(11, 63, 239, 165, 0xD7); DrawEditBox(w, 3); - DrawEditBox(w, 4); DrawString(10, 22, STR_NETWORK_NEW_GAME_NAME, 2); - DrawString(210, 22, STR_NETWORK_PASSWORD, 2); DrawString(10, 43, STR_NETWORK_SELECT_MAP, 2); - DrawString(260, 63, STR_NETWORK_NUMBER_OF_PLAYERS, 2); + DrawString(260, 63, STR_NETWORK_NUMBER_OF_CLIENTS, 2); + DrawString(260, 105, STR_NETWORK_LANGUAGE_SPOKEN, 2); + // draw list of maps + pos = w->vscroll.pos; + while (pos < _fios_num + 1) { + item = _fios_list + pos - 1; + if (item == selected_map || (pos == 0 && selected_map == NULL)) + GfxFillRect(11, y - 1, 239, y + 10, 155); // show highlighted item with a different colour + + if (pos == 0) DrawString(14, y, STR_4010_GENERATE_RANDOM_NEW_GAME, 9); + else DoDrawString(item->title[0] ? item->title : item->name, 14, y, _fios_colors[item->type] ); + pos++; + y += NSSWND_ROWSIZE; + + if (y >= w->vscroll.cap * NSSWND_ROWSIZE + NSSWND_START) break; + } } break; case WE_CLICK: _selected_field = e->click.widget; switch(e->click.widget) { - case 0: case 12: /* Close 'X' | Cancel button */ + case 0: case 13: /* Close 'X' | Cancel button */ ShowNetworkGameWindow(); break; + case 4: { /* Set password button */ + StringID str; + str = AllocateName(_network_game_info.server_password, 0); + ShowQueryString(str, STR_NETWORK_SET_PASSWORD, 20, 250, w->window_class, w->window_number); + DeleteName(str); + } break; + case 5: { /* Select map */ + int y = (e->click.pt.y - NSSWND_START) / NSSWND_ROWSIZE; + if ((y += w->vscroll.pos) >= w->vscroll.count) + return; + if (y == 0) selected_map = NULL; + else selected_map = _fios_list + y-1; + SetWindowDirty(w); + } break; case 7: case 8: /* Number of Players */ - ShowDropDownMenu(w, _players_dropdown, _players_max, 8, 0); // do it for widget 8 + ShowDropDownMenu(w, _players_dropdown, _network_game_info.clients_max - 2, 8, 0); // do it for widget 8 return; - case 9: /* Start game */ - NetworkCoreStartGame(); - strcpy(_network_game.server_name, WP(w,querystr_d).buf); - //ShowNetworkLobbyWindow(); - DoCommandP(0, 0, 0, NULL, CMD_START_NEW_GAME); + case 9: case 10: /* Language */ + ShowDropDownMenu(w, _language_dropdown, _network_game_info.server_lang, 10, 0); // do it for widget 10 + return; + case 11: /* Start game */ + _is_network_server = true; + ttd_strlcpy(_network_server_name, WP(w,querystr_d).buf, sizeof(_network_server_name)); + if(selected_map==NULL) { // start random new game + DoCommandP(0, Random(), InteractiveRandom(), NULL, CMD_GEN_RANDOM_NEW_GAME); + } else { // load a scenario + char *name; + if ((name = FiosBrowseTo(selected_map)) != NULL) { + SetFiosType(selected_map->type); + strcpy(_file_to_saveload.name, name); + snprintf(_network_game_info.map_name, sizeof(_network_game_info.map_name), "Loaded scenario"); + DeleteWindow(w); + DoCommandP(0, Random(), InteractiveRandom(), NULL, CMD_START_SCENARIO); + } + } break; - case 10: /* Load game */ - NetworkCoreStartGame(); - strcpy(_network_game.server_name, WP(w,querystr_d).buf); - //ShowNetworkLobbyWindow(); + case 12: /* Load game */ + _is_network_server = true; + ttd_strlcpy(_network_server_name, WP(w,querystr_d).buf, sizeof(_network_server_name)); + snprintf(_network_game_info.map_name, sizeof(_network_game_info.map_name), "Loaded game"); + /* XXX - WC_NETWORK_WINDOW should stay, but if it stays, it gets + * copied all the elements of 'load game' and upon closing that, it segfaults */ + DeleteWindowById(WC_NETWORK_WINDOW, 0); ShowSaveLoadDialog(SLD_LOAD_GAME); break; - case 11: /* Load scenario */ - NetworkCoreStartGame(); - strcpy(_network_game.server_name, WP(w,querystr_d).buf); - //ShowNetworkLobbyWindow(); - ShowSaveLoadDialog(SLD_LOAD_SCENARIO); - break; } break; case WE_DROPDOWN_SELECT: /* we have selected a dropdown item in the list */ - _players_max = e->dropdown.index; + switch(e->dropdown.button) { + case 8: + _network_game_info.clients_max = e->dropdown.index + 2; + break; + case 10: + _network_game_info.server_lang = e->dropdown.index; + break; + } SetWindowDirty(w); break; @@ -327,7 +559,7 @@ static void NetworkStartServerWindowWndP break; case WE_KEYPRESS: - if(_selected_field != 3 && _selected_field != 4) + if(_selected_field != 3) break; switch (HandleEditBoxKey(w, _selected_field, e)) { case 1: @@ -336,104 +568,661 @@ static void NetworkStartServerWindowWndP } break; + case WE_ON_EDIT_TEXT: { + byte *b = e->edittext.str; + ttd_strlcpy(_network_game_info.server_password, b, sizeof(_network_game_info.server_password)); + if (_network_game_info.server_password[0] == '\0') { + _network_game_info.use_password = 0; + } else { + _network_game_info.use_password = 1; + } + } break; } } static const Widget _network_start_server_window_widgets[] = { { WWT_CLOSEBOX, BGC, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW }, -{ WWT_CAPTION, BGC, 10, 399, 0, 13, STR_NETWORK_START_GAME_WINDOW, STR_NULL}, +{ WWT_CAPTION, BGC, 11, 399, 0, 13, STR_NETWORK_START_GAME_WINDOW, STR_NULL}, { WWT_IMGBTN, BGC, 0, 399, 14, 199, 0x0, STR_NULL}, -{ WWT_IMGBTN, BGC, 80, 190, 22, 33, 0x0, STR_NETWORK_NEW_GAME_NAME_TIP}, -{ WWT_IMGBTN, BGC, 280, 390, 22, 33, 0x0, STR_NETWORK_PASSWORD_TIP}, +{ WWT_IMGBTN, BGC, 80, 251, 22, 33, 0x0, STR_NETWORK_NEW_GAME_NAME_TIP}, +{ WWT_PUSHTXTBTN, BTC, 270, 380, 22, 33, STR_NETWORK_SET_PASSWORD, STR_NETWORK_PASSWORD_TIP}, -{ WWT_IMGBTN, BGC, 10, 240, 62, 170, 0x0, STR_NETWORK_SELECT_MAP_TIP}, -{ WWT_SCROLLBAR, BGC, 241, 251, 62, 170, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST}, +{ WWT_6, BGC, 10, 251, 62, 166, 0x0, STR_NETWORK_SELECT_MAP_TIP}, +{ WWT_SCROLLBAR, BGC, 240, 250, 63, 165, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST}, -{ WWT_6, BGC, 260, 390, 81, 92, STR_NETWORK_COMBO2, STR_NETWORK_NUMBER_OF_PLAYERS_TIP}, -{ WWT_CLOSEBOX, BGC, 379, 389, 82, 91, STR_0225, STR_NETWORK_NUMBER_OF_PLAYERS_TIP}, +{ WWT_6, BGC, 260, 390, 77, 88, STR_NETWORK_COMBO2, STR_NETWORK_NUMBER_OF_CLIENTS_TIP}, +{ WWT_CLOSEBOX, BGC, 379, 389, 78, 87, STR_0225, STR_NETWORK_NUMBER_OF_CLIENTS_TIP}, -{ WWT_PUSHTXTBTN, BTC, 10, 100, 180, 191, STR_NETWORK_START_GAME, STR_NETWORK_START_GAME_TIP}, -{ WWT_PUSHTXTBTN, BTC, 110, 200, 180, 191, STR_NETWORK_LOAD_GAME, STR_NETWORK_LOAD_GAME_TIP}, -{ WWT_PUSHTXTBTN, BTC, 210, 300, 180, 191, STR_NETWORK_LOAD_SCENARIO, STR_NETWORK_LOAD_SCENARIO_TIP}, -{ WWT_PUSHTXTBTN, BTC, 310, 390, 180, 191, STR_012E_CANCEL, STR_NULL}, +{ WWT_6, BGC, 260, 390, 119, 130, STR_NETWORK_COMBO3, STR_NETWORK_LANGUAGE_TIP}, +{ WWT_CLOSEBOX, BGC, 379, 389, 120, 129, STR_0225, STR_NETWORK_LANGUAGE_TIP}, + +{ WWT_PUSHTXTBTN, BTC, 55, 145, 180, 191, STR_NETWORK_START_GAME, STR_NETWORK_START_GAME_TIP}, +{ WWT_PUSHTXTBTN, BTC, 155, 245, 180, 191, STR_NETWORK_LOAD_GAME, STR_NETWORK_LOAD_GAME_TIP}, +{ WWT_PUSHTXTBTN, BTC, 255, 345, 180, 191, STR_012E_CANCEL, STR_NULL}, { WIDGETS_END}, }; static const WindowDesc _network_start_server_window_desc = { WDP_CENTER, WDP_CENTER, 400, 200, WC_NETWORK_WINDOW,0, - WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS, + WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_RESTORE_DPARAM, _network_start_server_window_widgets, NetworkStartServerWindowWndProc, }; -static void ShowNetworkStartServerWindow() +static void ShowNetworkStartServerWindow(void) { Window *w; DeleteWindowById(WC_NETWORK_WINDOW, 0); w = AllocateWindowDesc(&_network_start_server_window_desc); - strcpy(_edit_str_buf, ""); - w->disabled_state = (1<<4) | (1<<5) | (1<<6) | (1<<7) | (1<<8); // disable buttons not yet working + ttd_strlcpy(_edit_str_buf, _network_server_name, MAX_QUERYSTR_LEN); + + _saveload_mode = SLD_NEW_GAME; + BuildFileList(); + w->vscroll.cap = 10; + w->vscroll.count = _fios_num+1; WP(w,querystr_d).caret = 1; WP(w,querystr_d).maxlen = MAX_QUERYSTR_LEN; - WP(w,querystr_d).maxwidth = 240; + WP(w,querystr_d).maxwidth = 160; WP(w,querystr_d).buf = _edit_str_buf; } -#if 0 static void NetworkLobbyWindowWndProc(Window *w, WindowEvent *e) { switch(e->event) { case WE_PAINT: { + int y = NET_PRC__OFFSET_TOP_WIDGET_COMPANY, pos; + StringID str; - SetDParam(7, STR_NETWORK_2_PLAYERS + _opt_mod_ptr->road_side); + if (_selected_company_item == -1) { + w->disabled_state = (1<<7); + } else + w->disabled_state = 0; + DrawWindowWidgets(w); - GfxFillRect( 11, 31, 239, 239, 0xD7); - GfxFillRect(261, 31, 378, 220, 0xD7); + SetDParam(0, _str_game_name); + DrawString(10, 22, STR_NETWORK_PREPARE_TO_JOIN, 2); + + // draw company list + GfxFillRect(11, 41, 139, 165, 0xD7); + pos = w->vscroll.pos; + while (pos < _network_lobby_company_count) { + if (_selected_company_item == pos) + GfxFillRect(11, y - 1, 139, y + 10, 155); // show highlighted item with a different colour + + DoDrawString(_network_player_info[pos].company_name, 13, y, 2); + + pos++; + y += NET_PRC__SIZE_OF_ROW_COMPANY; + if (pos >= w->vscroll.cap) + break; + } + + // draw info about selected company + DrawStringMultiCenter(270, 48, STR_NETWORK_COMPANY_INFO, 0); + if (_selected_company_item != -1) { // if a company is selected... + // show company info + const uint x = 168; + uint xm; + y = 65; + + str = AllocateName(_network_player_info[_selected_company_item].company_name, 0); + SetDParam(0, str); + DrawString(x, y, STR_NETWORK_COMPANY_NAME, 2); + DeleteName(str); + y += 10; + + SetDParam(0, _network_player_info[_selected_company_item].inaugurated_year + 1920); + DrawString(x, y, STR_NETWORK_INAUGURATION_YEAR, 2); // inauguration year + y += 10; - DrawEditBox(w, 5); - DrawEditBox(w, 7); + SetDParam64(0, _network_player_info[_selected_company_item].company_value); + DrawString(x, y, STR_NETWORK_VALUE, 2); // company value + y += 10; + + SetDParam64(0, _network_player_info[_selected_company_item].money); + DrawString(x, y, STR_NETWORK_CURRENT_BALANCE, 2); // current balance + y += 10; + + SetDParam64(0, _network_player_info[_selected_company_item].income); + DrawString(x, y, STR_NETWORK_LAST_YEARS_INCOME, 2); // last year's income + y += 10; + + SetDParam(0, _network_player_info[_selected_company_item].performance); + DrawString(x, y, STR_NETWORK_PERFORMANCE, 2); // performance + y += 10; - DrawString(10, 255, STR_NETWORK_COMPANY_NAME, 2); + SetDParam(0, _network_player_info[_selected_company_item].num_vehicle[0]); + SetDParam(1, _network_player_info[_selected_company_item].num_vehicle[1]); + SetDParam(2, _network_player_info[_selected_company_item].num_vehicle[2]); + SetDParam(3, _network_player_info[_selected_company_item].num_vehicle[3]); + SetDParam(4, _network_player_info[_selected_company_item].num_vehicle[4]); + DrawString(x, y, STR_NETWORK_VEHICLES, 2); // vehicles + y += 10; - break; - } + SetDParam(0, _network_player_info[_selected_company_item].num_station[0]); + SetDParam(1, _network_player_info[_selected_company_item].num_station[1]); + SetDParam(2, _network_player_info[_selected_company_item].num_station[2]); + SetDParam(3, _network_player_info[_selected_company_item].num_station[3]); + SetDParam(4, _network_player_info[_selected_company_item].num_station[4]); + DrawString(x, y, STR_NETWORK_STATIONS, 2); // stations + y += 10; + + str = AllocateName(_network_player_info[_selected_company_item].players, 0); + SetDParam(0, str); + xm = DrawString(x, y, STR_NETWORK_PLAYERS, 2); // players + DeleteName(str); + y += 10; + } + } break; case WE_CLICK: - _selected_field = e->click.widget; switch(e->click.widget) { - - case 0: // close X - case 13: // cancel button + case 0: case 11: /* Close 'X' | Cancel button */ ShowNetworkGameWindow(); break; + case 3: /* Company list */ + _selected_company_item = (e->click.pt.y - NET_PRC__OFFSET_TOP_WIDGET_COMPANY) / NET_PRC__SIZE_OF_ROW_COMPANY; - } + if (_selected_company_item >= w->vscroll.cap) { + // click out of bounds + _selected_company_item = -1; + SetWindowDirty(w); + return; + } + _selected_company_item += w->vscroll.pos; + if (_selected_company_item >= _network_lobby_company_count) { + _selected_company_item = -1; + SetWindowDirty(w); + return; + } + + SetWindowDirty(w); + break; + case 7: /* Join company */ + if (_selected_company_item != -1) { + _network_playas = _selected_company_item + 1; + NetworkClientConnectGame(_network_last_host, _network_last_port); + } + break; + case 8: /* New company */ + _network_playas = 0; + NetworkClientConnectGame(_network_last_host, _network_last_port); + break; + case 9: /* Spectate game */ + _network_playas = OWNER_SPECTATOR; + NetworkClientConnectGame(_network_last_host, _network_last_port); + break; + case 10: /* Refresh */ + NetworkQueryServer(_network_last_host, _network_last_port, false); + break; + } break; + + case WE_CREATE: + _selected_company_item = -1; + } +} + +static const Widget _network_lobby_window_widgets[] = { +{ WWT_CLOSEBOX, BGC, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW }, +{ WWT_CAPTION, BGC, 11, 399, 0, 13, STR_NETWORK_GAME_LOBBY, STR_NULL}, +{ WWT_IMGBTN, BGC, 0, 399, 14, 209, 0x0, STR_NULL}, + +// company list +{ WWT_6, BGC, 10, 151, 40, 166, 0x0, STR_NETWORK_COMPANY_LIST_TIP}, +{ WWT_SCROLLBAR, BGC, 140, 150, 41, 165, 0x1, STR_0190_SCROLL_BAR_SCROLLS_LIST}, + +// company/player info +{ WWT_IMGBTN, BGC, 158, 389, 38, 165, 0x0, STR_NULL}, +{ WWT_6, BGC, 159, 388, 39, 164, 0x0, STR_NULL}, + +// buttons +{ WWT_PUSHTXTBTN, BTC, 10, 150, 175, 186, STR_NETWORK_JOIN_COMPANY, STR_NETWORK_JOIN_COMPANY_TIP}, +{ WWT_PUSHTXTBTN, BTC, 10, 150, 190, 201, STR_NETWORK_NEW_COMPANY, STR_NETWORK_NEW_COMPANY_TIP}, +{ WWT_PUSHTXTBTN, BTC, 158, 268, 175, 186, STR_NETWORK_SPECTATE_GAME, STR_NETWORK_SPECTATE_GAME_TIP}, +{ WWT_PUSHTXTBTN, BTC, 158, 268, 190, 201, STR_NETWORK_REFRESH, STR_NETWORK_REFRESH_TIP}, +{ WWT_PUSHTXTBTN, BTC, 278, 388, 175, 186, STR_012E_CANCEL, STR_NULL}, + +{ WIDGETS_END}, +}; + +static const WindowDesc _network_lobby_window_desc = { + WDP_CENTER, WDP_CENTER, 400, 210, + WC_NETWORK_WINDOW,0, + WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS, + _network_lobby_window_widgets, + NetworkLobbyWindowWndProc, +}; + + +static void ShowNetworkLobbyWindow(void) +{ + Window *w; + DeleteWindowById(WC_NETWORK_WINDOW, 0); + + _network_lobby_company_count = 0; + + NetworkQueryServer(_network_last_host, _network_last_port, false); + + w = AllocateWindowDesc(&_network_lobby_window_desc); + strcpy(_edit_str_buf, ""); + w->vscroll.pos = 0; + w->vscroll.cap = 8; +} + + + + +// The window below gives information about the connected clients +// and also makes able to give money to them, kick them (if server) +// and stuff like that. + +extern void DrawPlayerIcon(int p, int x, int y); + +// Every action must be of this form +typedef void ClientList_Action_Proc(byte client_no); + +// Max 10 actions per client +#define MAX_CLIENTLIST_ACTION 10 + +// Some standard bullshit.. defines variables ;) +static void ClientListWndProc(Window *w, WindowEvent *e); +static void ClientListPopupWndProc(Window *w, WindowEvent *e); +static byte _selected_clientlist_item = 255; +static byte _selected_clientlist_y = 0; +static uint16 _client_list_popup_height = 0; +static char _clientlist_action[MAX_CLIENTLIST_ACTION][50]; +static ClientList_Action_Proc *_clientlist_proc[MAX_CLIENTLIST_ACTION]; + +enum { + CLNWND_OFFSET = 16, + CLNWND_ROWSIZE = 10 +}; + +static Widget _client_list_widgets[] = { +{ WWT_TEXTBTN, 14, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, +{ WWT_CAPTION, 14, 11, 249, 0, 13, STR_NETWORK_CLIENT_LIST, STR_018C_WINDOW_TITLE_DRAG_THIS}, + +{ WWT_IMGBTN, 14, 0, 249, 14, 14 + CLNWND_ROWSIZE + 1, 0x0, STR_NULL}, +{ WIDGETS_END}, +}; + +static Widget _client_list_popup_widgets[] = { +{ WWT_PANEL, 14, 0, 99, 0, 0, 0, STR_NULL}, +{ WIDGETS_END}, +}; + +static WindowDesc _client_list_desc = { + -1, -1, 250, 1, + WC_CLIENT_LIST,0, + WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET, + _client_list_widgets, + ClientListWndProc +}; + +// Finds the Xth client-info that is active +static NetworkClientInfo *NetworkFindClientInfo(byte client_no) +{ + NetworkClientInfo *ci; + for (ci = _network_client_info; ci != &_network_client_info[MAX_CLIENT_INFO]; ci++) { + // Skip non-active items + if (ci->client_index == NETWORK_EMPTY_INDEX) continue; + if (client_no == 0) return ci; + client_no--; + } + + return NULL; +} + +// Here we start to define the options out of the menu +static void ClientList_Kick(byte client_no) +{ + if (client_no < MAX_PLAYERS) + SEND_COMMAND(PACKET_SERVER_ERROR)(&_clients[client_no], NETWORK_ERROR_KICKED); +} + +/*static void ClientList_Ban(byte client_no) +{ +// TODO +}*/ + +static void ClientList_GiveMoney(byte client_no) +{ + if (NetworkFindClientInfo(client_no) != NULL) + ShowNetworkGiveMoneyWindow(NetworkFindClientInfo(client_no)->client_playas - 1); +} + +static void ClientList_SpeakToClient(byte client_no) +{ + if (NetworkFindClientInfo(client_no) != NULL) + ShowNetworkChatQueryWindow(DESTTYPE_CLIENT, NetworkFindClientInfo(client_no)->client_index); +} + +static void ClientList_SpeakToPlayer(byte client_no) +{ + if (NetworkFindClientInfo(client_no) != NULL) + ShowNetworkChatQueryWindow(DESTTYPE_PLAYER, NetworkFindClientInfo(client_no)->client_playas); +} + +static void ClientList_SpeakToAll(byte client_no) +{ + ShowNetworkChatQueryWindow(DESTTYPE_BROADCAST, 0); +} + +static void ClientList_None(byte client_no) +{ + // No action ;) +} + + + +// Help, a action is clicked! What do we do? +static void HandleClientListPopupClick(byte index, byte clientno) { + // A click on the Popup of the ClientList.. handle the command + if (index < MAX_CLIENTLIST_ACTION && _clientlist_proc[index] != NULL) { + _clientlist_proc[index](clientno); + } +} + +// Finds the amount of clients and set the height correct +static bool CheckClientListHeight(Window *w) +{ + int num = 0; + NetworkClientInfo *ci; - case WE_MOUSELOOP: - if(_selected_field == 5) - { - HandleEditBox(w, 5); - break; + // Should be replaced with a loop through all clients + for (ci = _network_client_info; ci != &_network_client_info[MAX_CLIENT_INFO]; ci++) { + // Skip non-active items + if (ci->client_index == NETWORK_EMPTY_INDEX) continue; + num++; + } + + num *= CLNWND_ROWSIZE; + + // If height is changed + if (_client_list_desc.height != CLNWND_OFFSET + num + 1) { + // XXX - magic unfortunately; (num + 2) has to be one bigger than heigh (num + 1) + _client_list_widgets[2].bottom = _client_list_widgets[2].top + num + 2; + _client_list_desc.height = CLNWND_OFFSET + num + 1; + _client_list_desc.left = w->left; + _client_list_desc.top = w->top; + // Delete the window and reallocate.. else we can not change the height ;) + DeleteWindow(w); + w = AllocateWindowDescFront(&_client_list_desc, 0); + return false; + } + return true; +} + +// Finds the amount of actions in the popup and set the height correct +static void UpdateClientListPopupHeigth(void) { + int i, num = 0; + + // Find the amount of actions + for (i = 0; i < MAX_CLIENTLIST_ACTION; i++) { + if (_clientlist_action[i][0] == '\0') continue; + if (_clientlist_proc[i] == NULL) continue; + num++; + } + + num *= CLNWND_ROWSIZE; + // Set the height + _client_list_popup_height = num + 2; // XXX - magic, has to be one more than the value below (num + 1) + _client_list_popup_widgets[0].bottom = _client_list_popup_widgets[0].top + num + 1; +} + +// Show the popup (action list) +static Window *PopupClientList(Window *w, int client_no, int x, int y) +{ + int i; + NetworkClientInfo *ci; + DeleteWindowById(WC_TOOLBAR_MENU, 0); + + // Clean the current actions + for (i = 0; i < MAX_CLIENTLIST_ACTION; i++) { + _clientlist_action[i][0] = '\0'; + _clientlist_proc[i] = NULL; + } + + // Fill the actions this client has + // Watch is, max 50 chars long! + + ci = NetworkFindClientInfo(client_no); + if (ci == NULL) return NULL; + + i = 0; + if (_network_own_client_index != ci->client_index) { + sprintf(_clientlist_action[i],"Private message"); + _clientlist_proc[i++] = &ClientList_SpeakToClient; + } + + if (ci->client_playas >= 1 && ci->client_playas <= MAX_PLAYERS) { + sprintf(_clientlist_action[i],"Speak to company"); + _clientlist_proc[i++] = &ClientList_SpeakToPlayer; + } + sprintf(_clientlist_action[i],"Speak to all"); + _clientlist_proc[i++] = &ClientList_SpeakToAll; + + if (_network_own_client_index != ci->client_index) { + if (_network_playas >= 1 && _network_playas <= MAX_PLAYERS) { + // We are no spectator + if (ci->client_playas >= 1 && ci->client_playas <= MAX_PLAYERS) { + sprintf(_clientlist_action[i],"Give money"); + _clientlist_proc[i++] = &ClientList_GiveMoney; + } } - if(_selected_field == 7) - { - HandleEditBox(w, 7); - break; + } + + // A server can kick clients (but not hisself) + if (_network_server && _network_own_client_index != ci->client_index) { + sprintf(_clientlist_action[i],"Kick"); + _clientlist_proc[i++] = &ClientList_Kick; + +/* sprintf(clientlist_action[i],"Ban"); + clientlist_proc[i++] = &ClientList_Ban;*/ + } + + if (i == 0) { + sprintf(_clientlist_action[i],"(none)"); + _clientlist_proc[i++] = &ClientList_None; + } + + + // Find the right height for the popup + UpdateClientListPopupHeigth(); + + // Allocate the popup + w = AllocateWindow(x, y, 100, _client_list_popup_height, ClientListPopupWndProc, WC_TOOLBAR_MENU, _client_list_popup_widgets); + w->flags4 &= ~WF_WHITE_BORDER_MASK; + WP(w,menu_d).item_count = 0; + // Save our client + WP(w,menu_d).main_button = client_no; + WP(w,menu_d).sel_index = 0; + // We are a popup + _popup_menu_active = true; + + return w; +} + +// Main handle for the popup +static void ClientListPopupWndProc(Window *w, WindowEvent *e) +{ + switch(e->event) { + case WE_PAINT: { + int i, y, sel; + byte colour; + DrawWindowWidgets(w); + + // Draw the actions + sel = WP(w,menu_d).sel_index; + y = 1; + for (i = 0; i < MAX_CLIENTLIST_ACTION; i++, y += CLNWND_ROWSIZE) { + if (_clientlist_action[i][0] == '\0') continue; + if (_clientlist_proc[i] == NULL) continue; + + if (sel-- == 0) { // Selected item, highlight it + GfxFillRect(1, y, 98, y + CLNWND_ROWSIZE - 1, 0); + colour = 0xC; + } else colour = 0x10; + + DoDrawString(_clientlist_action[i], 4, y, colour); + } + } break; + + case WE_POPUPMENU_SELECT: { + // We selected an action + int index = (e->popupmenu.pt.y - w->top) / CLNWND_ROWSIZE; + + if (index >= 0 && e->popupmenu.pt.y >= w->top) + HandleClientListPopupClick(index, WP(w,menu_d).main_button); + + // Sometimes, because of the bad DeleteWindow-proc, the 'w' pointer is + // invalid after the last functions (mostly because it kills a window + // that is in front of 'w', and because of a silly memmove, the address + // 'w' was pointing to becomes invalid), so we need to refetch + // the right address... + DeleteWindowById(WC_TOOLBAR_MENU, 0); + } break; + + case WE_POPUPMENU_OVER: { + // Our mouse hoovers over an action? Select it! + int index = (e->popupmenu.pt.y - w->top) / CLNWND_ROWSIZE; + + if (index == -1 || index == WP(w,menu_d).sel_index) + return; + + WP(w,menu_d).sel_index = index; + SetWindowDirty(w); + } break; + + } +} + +// Main handle for clientlist +static void ClientListWndProc(Window *w, WindowEvent *e) +{ + switch(e->event) { + case WE_PAINT: { + NetworkClientInfo *ci; + int y, i = 0; + byte colour; + + // Check if we need to reset the height + if (!CheckClientListHeight(w)) break; + + DrawWindowWidgets(w); + + y = CLNWND_OFFSET; + + for (ci = _network_client_info; ci != &_network_client_info[MAX_CLIENT_INFO]; ci++) { + // Skip non-active items + if (ci->client_index == NETWORK_EMPTY_INDEX) continue; + + if (_selected_clientlist_item == i++) { // Selected item, highlight it + GfxFillRect(1, y, 248, y + CLNWND_ROWSIZE - 1, 0); + colour = 0xC; + } else + colour = 0x10; + + if (ci->client_index == NETWORK_SERVER_INDEX) { + DoDrawString("Server", 4, y, colour); + } else + DoDrawString("Client", 4, y, colour); + + // Filter out spectators + if (ci->client_playas > 0 && ci->client_playas <= MAX_PLAYERS) + DrawPlayerIcon(ci->client_playas - 1, 44, y + 1); + + DoDrawString(ci->client_name, 61, y, colour); + + y += CLNWND_ROWSIZE; + } + } break; + + case WE_CLICK: + // Show the popup with option + if (_selected_clientlist_item != 255) { + PopupClientList(w, _selected_clientlist_item, e->click.pt.x + w->left, e->click.pt.y + w->top); } break; - case WE_KEYPRESS: - if(_selected_field != 5 && _selected_field != 7) + case WE_MOUSEOVER: + // -1 means we left the current window + if (e->mouseover.pt.y == -1) { + _selected_clientlist_y = 0; + _selected_clientlist_item = 255; + SetWindowDirty(w); break; - switch (HandleEditBoxKey(w, _selected_field, e)) { - case 1: - HandleButtonClick(w, 12); + } + // It did not change.. no update! + if (e->mouseover.pt.y == _selected_clientlist_y) break; + + // Find the new selected item (if any) + _selected_clientlist_y = e->mouseover.pt.y; + if (e->mouseover.pt.y > CLNWND_OFFSET) { + _selected_clientlist_item = (e->mouseover.pt.y - CLNWND_OFFSET) / CLNWND_ROWSIZE; + } else + _selected_clientlist_item = 255; + + // Repaint + SetWindowDirty(w); + break; + + case WE_DESTROY: case WE_CREATE: + // When created or destroyed, data is reset + _selected_clientlist_item = 255; + _selected_clientlist_y = 0; + break; + } +} + +void ShowClientList() +{ + AllocateWindowDesc(&_client_list_desc); +} + +static void NetworkJoinStatusWindowWndProc(Window *w, WindowEvent *e) +{ + switch(e->event) { + case WE_PAINT: { + uint8 progress; // used for progress bar + DrawWindowWidgets(w); + + DrawStringCentered(125, 35, STR_NETWORK_CONNECTING_1 + _network_join_status, 14); + switch (_network_join_status) { + case NETWORK_JOIN_STATUS_CONNECTING: case NETWORK_JOIN_STATUS_AUTHORIZING: + case NETWORK_JOIN_STATUS_GETTING_COMPANY_INFO: + progress = 10; // first two stages 10% + break; + case NETWORK_JOIN_STATUS_WAITING: + SetDParam(0, _network_join_waiting); + DrawStringCentered(125, 46, STR_NETWORK_CONNECTING_WAITING, 14); + progress = 15; // third stage is 15% + break; + case NETWORK_JOIN_STATUS_DOWNLOADING: + SetDParam(0, _network_join_kbytes); + SetDParam(1, _network_join_kbytes_total); + DrawStringCentered(125, 46, STR_NETWORK_CONNECTING_DOWNLOADING, 14); + /* Fallthrough */ + default: /* Waiting is 15%, so the resting receivement of map is maximum 70% */ + progress = 15 + _network_join_kbytes * (100 - 15) / _network_join_kbytes_total; + } + + /* Draw nice progress bar :) */ + DrawFrameRect(20, 18, (int)((w->width - 20) * progress / 100), 28, 10, 0); + } break; + + case WE_CLICK: + switch(e->click.widget) { + case 0: case 3: /* Close 'X' | Disconnect button */ + NetworkDisconnect(); + ShowNetworkGameWindow(); + DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0); break; } break; @@ -441,56 +1230,28 @@ static void NetworkLobbyWindowWndProc(Wi } } -static const Widget _network_lobby_window_widgets[] = { -{ WWT_CLOSEBOX, BGC, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW }, -{ WWT_CAPTION, BGC, 10, 399, 0, 13, STR_NETWORK_GAME_LOBBY, STR_NULL}, -{ WWT_IMGBTN, BGC, 0, 399, 14, 299, 0x0, STR_NULL}, - -// chat widget -{ WWT_IMGBTN, BGC, 10, 240, 30, 240, 0x0, STR_NULL}, -{ WWT_SCROLLBAR, BGC, 241, 251, 30, 240, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST}, - -// send message prompt -{ WWT_IMGBTN, BGC, 10, 200, 241, 252, 0x0, STR_NETWORK_ENTER_NAME_TIP}, -{ WWT_PUSHTXTBTN, BTC, 201, 251, 241, 252, STR_NETWORK_SEND, STR_NETWORK_SEND_TIP}, - -// company name -{ WWT_IMGBTN, BGC, 100, 251, 254, 265, 0x0, STR_NETWORK_COMPANY_NAME_TIP}, - -// player information -{ WWT_IMGBTN, BGC, 260, 379, 30, 221, 0x0, STR_NULL}, -{ WWT_SCROLLBAR, BGC, 380, 390, 30, 221, 0x1, STR_0190_SCROLL_BAR_SCROLLS_LIST}, - -// buttons -{ WWT_PUSHTXTBTN, BTC, 260, 390, 233, 244, STR_NETWORK_NEW_COMPANY, STR_NETWORK_NEW_COMPANY_TIP}, -{ WWT_PUSHTXTBTN, BTC, 260, 390, 254, 265, STR_NETWORK_SPECTATE_GAME, STR_NETWORK_SPECTATE_GAME_TIP}, - -{ WWT_PUSHTXTBTN, BTC, 80, 180, 280, 291, STR_NETWORK_READY, STR_NULL}, -{ WWT_PUSHTXTBTN, BTC, 220, 320, 280, 291, STR_012E_CANCEL, STR_NULL}, +static const Widget _network_join_status_window_widget[] = { +{ WWT_TEXTBTN, 14, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, +{ WWT_CAPTION, 14, 11, 249, 0, 13, STR_NETWORK_CONNECTING, STR_018C_WINDOW_TITLE_DRAG_THIS}, +{ WWT_IMGBTN, 14, 0, 249, 14, 84, 0x0,STR_NULL}, +{ WWT_PUSHTXTBTN, BTC, 75, 175, 69, 80, STR_NETWORK_DISCONNECT, STR_NULL}, { WIDGETS_END}, }; -static const WindowDesc _network_lobby_window_desc = { - WDP_CENTER, WDP_CENTER, 400, 300, - WC_NETWORK_WINDOW,0, - WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS, - _network_lobby_window_widgets, - NetworkLobbyWindowWndProc, +static const WindowDesc _network_join_status_window_desc = { + WDP_CENTER, WDP_CENTER, 250, 85, + WC_NETWORK_STATUS_WINDOW, 0, + WDF_STD_TOOLTIPS | WDF_DEF_WIDGET, + _network_join_status_window_widget, + NetworkJoinStatusWindowWndProc, }; +void ShowJoinStatusWindow() +{ + DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0); + _network_join_status = NETWORK_JOIN_STATUS_CONNECTING; + AllocateWindowDesc(&_network_join_status_window_desc); +} + -static void ShowNetworkLobbyWindow() -{ - Window *w; - DeleteWindowById(WC_NETWORK_WINDOW, 0); - - w = AllocateWindowDesc(&_network_lobby_window_desc); - strcpy(_edit_str_buf, ""); - - - WP(w,querystr_d).caret = 1; - WP(w,querystr_d).maxlen = MAX_QUERYSTR_LEN; - WP(w,querystr_d).maxwidth = 240; - WP(w,querystr_d).buf = _edit_str_buf; -} -#endif +#endif /* ENABLE_NETWORK */ diff --git a/network_server.c b/network_server.c new file mode 100644 --- /dev/null +++ b/network_server.c @@ -0,0 +1,1359 @@ +#include "stdafx.h" +#include "network_data.h" + +#ifdef ENABLE_NETWORK + +#include "table/strings.h" +#include "network_server.h" +#include "console.h" +#include "command.h" +#include "gfx.h" +#include "vehicle.h" +#include "station.h" +#include "settings.h" + +// This file handles all the server-commands + +void NetworkHandleCommandQueue(ClientState *cs); +void NetworkPopulateCompanyInfo(void); +void NetworkSendPatchSettings(ClientState *cs); + +// Is the network enabled? + +// ********** +// Sending functions +// DEF_SERVER_SEND_COMMAND has parameter: ClientState *cs +// ********** + +DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_CLIENT_INFO)(ClientState *cs, NetworkClientInfo *ci) +{ + // + // Packet: SERVER_CLIENT_INFO + // Function: Sends info about a client + // Data: + // uint16: The index of the client (always unique on a server. 1 = server) + // uint8: As which player the client is playing + // String: The name of the client + // + + Packet *p; + + if (ci->client_index != NETWORK_EMPTY_INDEX) { + p = NetworkSend_Init(PACKET_SERVER_CLIENT_INFO); + NetworkSend_uint16(p, ci->client_index); + NetworkSend_uint8 (p, ci->client_playas); + NetworkSend_string(p, ci->client_name); + + NetworkSend_Packet(p, cs); + } +} + +DEF_SERVER_SEND_COMMAND(PACKET_SERVER_COMPANY_INFO) +{ +// + // Packet: SERVER_COMPANY_INFO + // Function: Sends info about the companies + // Data: + // + + int i; + + Player *player; + Packet *p; + + byte active = 0; + byte current = 0; + + + FOR_ALL_PLAYERS(player) { + if (player->is_active) + active++; + } + + if (active == 0) { + Packet *p = NetworkSend_Init(PACKET_SERVER_COMPANY_INFO); + + NetworkSend_uint8 (p, NETWORK_COMPANY_INFO_VERSION); + NetworkSend_uint8 (p, active); + + NetworkSend_Packet(p, cs); + return; + } + + NetworkPopulateCompanyInfo(); + + FOR_ALL_PLAYERS(player) { + if (!player->is_active) + continue; + + current++; + + p = NetworkSend_Init(PACKET_SERVER_COMPANY_INFO); + + NetworkSend_uint8 (p, NETWORK_COMPANY_INFO_VERSION); + NetworkSend_uint8 (p, active); + NetworkSend_uint8 (p, current); + + NetworkSend_string(p, _network_player_info[player->index].company_name); + NetworkSend_uint8 (p, _network_player_info[player->index].inaugurated_year); + NetworkSend_uint64(p, _network_player_info[player->index].company_value); + NetworkSend_uint64(p, _network_player_info[player->index].money); + NetworkSend_uint64(p, _network_player_info[player->index].income); + NetworkSend_uint16(p, _network_player_info[player->index].performance); + + for (i = 0; i < NETWORK_VEHICLE_TYPES; i++) + NetworkSend_uint16(p, _network_player_info[player->index].num_vehicle[i]); + + for (i = 0; i < NETWORK_STATION_TYPES; i++) + NetworkSend_uint16(p, _network_player_info[player->index].num_station[i]); + + if (_network_player_info[player->index].players[0] == '\0') + NetworkSend_string(p, ""); + else + NetworkSend_string(p, _network_player_info[player->index].players); + + NetworkSend_Packet(p, cs); + } +} + +DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_ERROR)(ClientState *cs, NetworkErrorCode error) +{ + // + // Packet: SERVER_ERROR + // Function: The client made an error + // Data: + // uint8: ErrorID (see network_data.h, NetworkErrorCode) + // + + ClientState *new_cs; + char str1[100], str2[100]; + char client_name[NETWORK_NAME_LENGTH]; + + Packet *p = NetworkSend_Init(PACKET_SERVER_ERROR); + NetworkSend_uint8(p, error); + NetworkSend_Packet(p, cs); + + // Only send when the current client was in game + if (cs->status > STATUS_AUTH) { + NetworkGetClientName(client_name, sizeof(client_name), cs); + + GetString(str1, STR_NETWORK_ERR_LEFT); + GetString(str2, STR_NETWORK_ERR_CLIENT_GENERAL + error); + + DEBUG(net, 2)("[NET] %s made an error (%s) and his connection is closed", client_name, str2); + + NetworkTextMessage(NETWORK_ACTION_JOIN_LEAVE, 1, client_name, "%s (%s)", str1, str2); + + FOR_ALL_CLIENTS(new_cs) { + if (new_cs->status > STATUS_AUTH && new_cs != cs) { + // Some errors we filter to a more general error. Clients don't have to know the real + // reason a joining failed. + if (error == NETWORK_ERROR_NOT_AUTHORIZED || error == NETWORK_ERROR_NOT_EXPECTED || error == NETWORK_ERROR_WRONG_REVISION) + error = NETWORK_ERROR_ILLEGAL_PACKET; + + SEND_COMMAND(PACKET_SERVER_ERROR_QUIT)(new_cs, cs->index, error); + } + } + } else { + DEBUG(net, 2)("[NET] Clientno %d has made an error and his connection is closed", cs->index); + } + + cs->quited = true; + + // Make sure the data get's there before we close the connection + NetworkSend_Packets(cs); + + // The client made a mistake, so drop his connection now! + CloseClient(cs); +} + +DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_NEED_PASSWORD)(ClientState *cs, NetworkPasswordType type) +{ + // + // Packet: SERVER_NEED_PASSWORD + // Function: Indication to the client that the server needs a password + // Data: + // uint8: Type of password + // + + Packet *p = NetworkSend_Init(PACKET_SERVER_NEED_PASSWORD); + NetworkSend_uint8(p, type); + NetworkSend_Packet(p, cs); +} + +DEF_SERVER_SEND_COMMAND(PACKET_SERVER_WELCOME) +{ + // + // Packet: SERVER_WELCOME + // Function: The client is joined and ready to receive his map + // Data: + // uint16: Own ClientID + // + + Packet *p; + ClientState *new_cs; + + // Invalid packet when status is AUTH or higher + if (cs->status >= STATUS_AUTH) + return; + + cs->status = STATUS_AUTH; + _network_game_info.clients_on++; + + p = NetworkSend_Init(PACKET_SERVER_WELCOME); + NetworkSend_uint16(p, cs->index); + NetworkSend_Packet(p, cs); + + // Transmit info about all the active clients + FOR_ALL_CLIENTS(new_cs) { + if (new_cs != cs && new_cs->status > STATUS_AUTH) + SEND_COMMAND(PACKET_SERVER_CLIENT_INFO)(cs, DEREF_CLIENT_INFO(new_cs)); + } + // Also send the info of the server + SEND_COMMAND(PACKET_SERVER_CLIENT_INFO)(cs, NetworkFindClientInfoFromIndex(NETWORK_SERVER_INDEX)); +} + +DEF_SERVER_SEND_COMMAND(PACKET_SERVER_WAIT) +{ + // + // Packet: PACKET_SERVER_WAIT + // Function: The client can not receive the map at the moment because + // someone else is already receiving the map + // Data: + // uint8: Clients awaiting map + // + int waiting = 0; + ClientState *new_cs; + Packet *p; + + // Count how many players are waiting in the queue + FOR_ALL_CLIENTS(new_cs) { + if (new_cs->status == STATUS_MAP_WAIT) + waiting++; + } + + p = NetworkSend_Init(PACKET_SERVER_WAIT); + NetworkSend_uint8(p, waiting); + NetworkSend_Packet(p, cs); +} + +// This sends the map to the client +DEF_SERVER_SEND_COMMAND(PACKET_SERVER_MAP) +{ + // + // Packet: SERVER_MAP + // Function: Sends the map to the client, or a part of it (it is splitted in + // a lot of multiple packets) + // Data: + // uint8: packet-type (MAP_PACKET_START, MAP_PACKET_NORMAL and MAP_PACKET_END) + // if MAP_PACKET_START: + // uint32: The current FrameCounter + // if MAP_PACKET_NORMAL: + // piece of the map (till max-size of packet) + // if MAP_PACKET_END: + // uint32: seed0 of player + // uint32: seed1 of player + // last 2 are repeated MAX_PLAYERS time + // + + char filename[256]; + static FILE *file_pointer; + static uint sent_packets; // How many packets we did send succecfully last time + + if (cs->status < STATUS_AUTH) { + // Illegal call, return error and ignore the packet + SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_NOT_AUTHORIZED); + return; + } + if (cs->status == STATUS_AUTH) { + Packet *p; + + // Make a dump of the current game + sprintf(filename, "%s%snetwork_server.tmp", _path.autosave_dir, PATHSEP); + if (SaveOrLoad(filename, SL_SAVE) != SL_OK) error("network savedump failed"); + + file_pointer = fopen(filename, "rb"); + fseek(file_pointer, 0, SEEK_END); + + // Now send the _frame_counter and how many packets are coming + p = NetworkSend_Init(PACKET_SERVER_MAP); + NetworkSend_uint8(p, MAP_PACKET_START); + NetworkSend_uint32(p, _frame_counter); + NetworkSend_uint32(p, ftell(file_pointer)); + NetworkSend_Packet(p, cs); + + fseek(file_pointer, 0, SEEK_SET); + + sent_packets = 4; // We start with trying 4 packets + + cs->status = STATUS_MAP; + } + + if (cs->status == STATUS_MAP) { + uint i; + int res; + for (i = 0; i < sent_packets; i++) { + Packet *p = NetworkSend_Init(PACKET_SERVER_MAP); + NetworkSend_uint8(p, MAP_PACKET_NORMAL); + res = fread(p->buffer + p->size, 1, SEND_MTU - p->size, file_pointer); + if (ferror(file_pointer)) { + error("Error reading temporary network savegame!"); + } + p->size += res; + NetworkSend_Packet(p, cs); + if (feof(file_pointer)) { + // Done reading! + int i; + Packet *p; + + // XXX - Delete this when patch-settings are saved in-game + NetworkSendPatchSettings(cs); + + p = NetworkSend_Init(PACKET_SERVER_MAP); + NetworkSend_uint8(p, MAP_PACKET_END); + // Send the player_seeds in this packet + for (i = 0; i < MAX_PLAYERS; i++) { + NetworkSend_uint32(p, _player_seeds[i][0]); + NetworkSend_uint32(p, _player_seeds[i][1]); + } + NetworkSend_Packet(p, cs); + + // Set the status to DONE_MAP, no we will wait for the client + // to send it is ready (maybe that happens like never ;)) + cs->status = STATUS_DONE_MAP; + fclose(file_pointer); + + { + ClientState *new_cs; + bool new_map_client = false; + // Check if there is a client waiting for receiving the map + // and start sending him the map + FOR_ALL_CLIENTS(new_cs) { + if (new_cs->status == STATUS_MAP_WAIT) { + // Check if we already have a new client to send the map to + if (!new_map_client) { + // If not, this client will get the map + new_cs->status = STATUS_AUTH; + new_map_client = true; + SEND_COMMAND(PACKET_SERVER_MAP)(new_cs); + } else { + // Else, send the other clients how many clients are in front of them + SEND_COMMAND(PACKET_SERVER_WAIT)(new_cs); + } + } + } + } + + // There is no more data, so break the for + break; + } + } + + // Send all packets (forced) and check if we have send it all + NetworkSend_Packets(cs); + if (cs->packet_queue == NULL) { + // All are sent, increase the sent_packets + sent_packets *= 2; + } else { + // Not everything is sent, decrease the sent_packets + if (sent_packets > 1) sent_packets /= 2; + } + } +} + +DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_JOIN)(ClientState *cs, uint16 client_index) +{ + // + // Packet: SERVER_JOIN + // Function: A client is joined (all active clients receive this after a + // PACKET_CLIENT_MAP_OK) Mostly what directly follows is a + // PACKET_SERVER_CLIENT_INFO + // Data: + // uint16: Client-Index + // + + Packet *p = NetworkSend_Init(PACKET_SERVER_JOIN); + + NetworkSend_uint16(p, client_index); + + NetworkSend_Packet(p, cs); +} + + +DEF_SERVER_SEND_COMMAND(PACKET_SERVER_FRAME) +{ + // + // Packet: SERVER_FRAME + // Function: Sends the current frame-counter to the client + // Data: + // uint32: Frame Counter + // uint32: Frame Counter Max (how far may the client walk before the server?) + // [uint32: general-seed-1] + // [uint32: general-seed-2] + // (last two depends on compile-settings, and are not default settings) + // + + Packet *p = NetworkSend_Init(PACKET_SERVER_FRAME); + NetworkSend_uint32(p, _frame_counter); + NetworkSend_uint32(p, _frame_counter_max); +#ifdef ENABLE_NETWORK_SYNC_EVERY_FRAME + NetworkSend_uint32(p, _sync_seed_1); +#ifdef NETWORK_SEND_DOUBLE_SEED + NetworkSend_uint32(p, _sync_seed_2); +#endif +#endif + NetworkSend_Packet(p, cs); +} + +DEF_SERVER_SEND_COMMAND(PACKET_SERVER_SYNC) +{ + // + // Packet: SERVER_SYNC + // Function: Sends a sync-check to the client + // Data: + // uint32: Frame Counter + // uint32: General-seed-1 + // [uint32: general-seed-2] + // (last one depends on compile-settings, and are not default settings) + // + + Packet *p = NetworkSend_Init(PACKET_SERVER_SYNC); + NetworkSend_uint32(p, _frame_counter); + NetworkSend_uint32(p, _sync_seed_1); + +#ifdef NETWORK_SEND_DOUBLE_SEED + NetworkSend_uint32(p, _sync_seed_2); +#endif + NetworkSend_Packet(p, cs); +} + +DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_COMMAND)(ClientState *cs, CommandPacket *cp) +{ + // + // Packet: SERVER_COMMAND + // Function: Sends a DoCommand to the client + // Data: + // uint8: PlayerID (0..MAX_PLAYERS-1) + // uint32: CommandID (see command.h) + // uint32: P1 (free variables used in DoCommand) + // uint32: P2 + // uint32: Tile + // uint32: decode_params + // 10 times the last one (lengthof(cp->dp)) + // uint8: CallBackID (see callback_table.c) + // uint32: Frame of execution + // + + int i; + Packet *p = NetworkSend_Init(PACKET_SERVER_COMMAND); + + NetworkSend_uint8(p, cp->player); + NetworkSend_uint32(p, cp->cmd); + NetworkSend_uint32(p, cp->p1); + NetworkSend_uint32(p, cp->p2); + NetworkSend_uint32(p, cp->tile); + for (i = 0; i < lengthof(cp->dp); i++) { + NetworkSend_uint32(p, cp->dp[i]); + } + NetworkSend_uint8(p, cp->callback); + NetworkSend_uint32(p, cp->frame); + + NetworkSend_Packet(p, cs); +} + +DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_CHAT)(ClientState *cs, NetworkAction action, uint16 client_index, const char *msg) +{ + // + // Packet: SERVER_CHAT + // Function: Sends a chat-packet to the client + // Data: + // uint8: ActionID (see network_data.h, NetworkAction) + // uint16: Client-index + // String: Message (max MAX_TEXT_MSG_LEN) + // + + Packet *p = NetworkSend_Init(PACKET_SERVER_CHAT); + + NetworkSend_uint8(p, action); + NetworkSend_uint16(p, client_index); + NetworkSend_string(p, msg); + + NetworkSend_Packet(p, cs); +} + +DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_ERROR_QUIT)(ClientState *cs, uint16 client_index, NetworkErrorCode errorno) +{ + // + // Packet: SERVER_ERROR_QUIT + // Function: One of the clients made an error and is quiting the game + // This packet informs the other clients of that. + // Data: + // uint16: Client-index + // uint8: ErrorID (see network_data.h, NetworkErrorCode) + // + + Packet *p = NetworkSend_Init(PACKET_SERVER_ERROR_QUIT); + + NetworkSend_uint16(p, client_index); + NetworkSend_uint8(p, errorno); + + NetworkSend_Packet(p, cs); +} + +DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_QUIT)(ClientState *cs, uint16 client_index, const char *leavemsg) +{ + // + // Packet: SERVER_ERROR_QUIT + // Function: A client left the game, and this packets informs the other clients + // of that. + // Data: + // uint16: Client-index + // String: leave-message + // + + Packet *p = NetworkSend_Init(PACKET_SERVER_QUIT); + + NetworkSend_uint16(p, client_index); + NetworkSend_string(p, leavemsg); + + NetworkSend_Packet(p, cs); +} + +DEF_SERVER_SEND_COMMAND(PACKET_SERVER_SHUTDOWN) +{ + // + // Packet: SERVER_SHUTDOWN + // Function: Let the clients know that the server is closing + // Data: + // + // + + Packet *p = NetworkSend_Init(PACKET_SERVER_SHUTDOWN); + NetworkSend_Packet(p, cs); +} + +DEF_SERVER_SEND_COMMAND(PACKET_SERVER_NEWGAME) +{ + // + // Packet: PACKET_SERVER_NEWGAME + // Function: Let the clients know that the server is loading a new map + // Data: + // + // + + Packet *p = NetworkSend_Init(PACKET_SERVER_NEWGAME); + NetworkSend_Packet(p, cs); +} + +// ********** +// Receiving functions +// DEF_SERVER_RECEIVE_COMMAND has parameter: ClientState *cs, Packet *p +// ********** + +DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_COMPANY_INFO) +{ + SEND_COMMAND(PACKET_SERVER_COMPANY_INFO)(cs); +} + +DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_JOIN) +{ + char name[NETWORK_NAME_LENGTH]; + NetworkClientInfo *ci; + char test_name[NETWORK_NAME_LENGTH]; + byte playas; + NetworkLanguage client_lang; + char client_revision[NETWORK_REVISION_LENGTH]; + + + NetworkRecv_string(p, client_revision, sizeof(client_revision)); + + // Too bad, when WITH_REV is disabled, we can not compare the version. +#if defined(WITH_REV) + // Check if the client has WITH_REV enabled + if (strncmp("norev000", client_revision, sizeof(client_revision)) != 0) { + if (strncmp(_network_game_info.server_revision, client_revision, sizeof(_network_game_info.server_revision)) != 0) { + // Different revisions!! + SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_WRONG_REVISION); + + return; + } + } +#endif + + NetworkRecv_string(p, name, sizeof(name)); + playas = NetworkRecv_uint8(p); + client_lang = NetworkRecv_uint8(p); + + // Check if someone else already has that name + snprintf(test_name, sizeof(test_name), "%s", name); + + if (test_name[0] == '\0') { + // We need a valid name.. make it Player + snprintf(test_name, sizeof(test_name), "Player"); + } + + if (!NetworkFindName(test_name)) { + // We could not create a name for this player + SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_NAME_IN_USE); + return; + } + + ci = DEREF_CLIENT_INFO(cs); + + snprintf(ci->client_name, sizeof(ci->client_name), "%s", test_name); + ci->client_playas = playas; + ci->client_lang = client_lang; + + // We now want a password from the client + // else we do not allow him in! + if (_network_game_info.use_password) + SEND_COMMAND(PACKET_SERVER_NEED_PASSWORD)(cs, NETWORK_GAME_PASSWORD); + else { + if (ci->client_playas <= MAX_PLAYERS && _network_player_info[ci->client_playas - 1].password[0] != '\0') { + SEND_COMMAND(PACKET_SERVER_NEED_PASSWORD)(cs, NETWORK_COMPANY_PASSWORD); + } + else { + SEND_COMMAND(PACKET_SERVER_WELCOME)(cs); + } + } +} + +DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_PASSWORD) +{ + NetworkPasswordType type; + char password[NETWORK_PASSWORD_LENGTH]; + NetworkClientInfo *ci; + + type = NetworkRecv_uint8(p); + NetworkRecv_string(p, password, sizeof(password)); + + if (cs->status == STATUS_INACTIVE && type == NETWORK_GAME_PASSWORD) { + // Check game-password + if (strncmp(password, _network_game_info.server_password, sizeof(password)) != 0) { + // Password is invalid + SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_WRONG_PASSWORD); + return; + } + + ci = DEREF_CLIENT_INFO(cs); + + if (ci->client_playas <= MAX_PLAYERS && _network_player_info[ci->client_playas - 1].password[0] != '\0') { + SEND_COMMAND(PACKET_SERVER_NEED_PASSWORD)(cs, NETWORK_COMPANY_PASSWORD); + return; + } + + // Valid password, allow user + SEND_COMMAND(PACKET_SERVER_WELCOME)(cs); + return; + } else if (cs->status == STATUS_INACTIVE && type == NETWORK_COMPANY_PASSWORD) { + ci = DEREF_CLIENT_INFO(cs); + + if (strncmp(password, _network_player_info[ci->client_playas - 1].password, sizeof(password)) != 0) { + // Password is invalid + SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_WRONG_PASSWORD); + return; + } + + SEND_COMMAND(PACKET_SERVER_WELCOME)(cs); + return; + } + + + SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_NOT_EXPECTED); + return; +} + +DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_GETMAP) +{ + ClientState *new_cs; + + // The client was never joined.. so this is impossible, right? + // Ignore the packet, give the client a warning, and close his connection + if (cs->status < STATUS_AUTH || cs->quited) { + SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_NOT_AUTHORIZED); + return; + } + + // Check if someone else is receiving the map + FOR_ALL_CLIENTS(new_cs) { + if (new_cs->status == STATUS_MAP) { + // Tell the new client to wait + cs->status = STATUS_MAP_WAIT; + SEND_COMMAND(PACKET_SERVER_WAIT)(cs); + return; + } + } + + // We receive a request to upload the map.. give it to the client! + SEND_COMMAND(PACKET_SERVER_MAP)(cs); +} + +DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_MAP_OK) +{ + // Client has the map, now start syncing + if (cs->status == STATUS_DONE_MAP && !cs->quited) { + char client_name[NETWORK_NAME_LENGTH]; + char str[100]; + ClientState *new_cs; + GetString(str, STR_NETWORK_CLIENT_JOINED); + + NetworkGetClientName(client_name, sizeof(client_name), cs); + + NetworkTextMessage(NETWORK_ACTION_JOIN_LEAVE, 1, client_name, str); + + // Mark the client as pre-active, and wait for an ACK + // so we know he is done loading and in sync with us + cs->status = STATUS_PRE_ACTIVE; + NetworkHandleCommandQueue(cs); + SEND_COMMAND(PACKET_SERVER_FRAME)(cs); + SEND_COMMAND(PACKET_SERVER_SYNC)(cs); + + // This is the frame the client receives + // we need it later on to make sure the client is not too slow + cs->last_frame = _frame_counter; + cs->last_frame_server = _frame_counter; + + FOR_ALL_CLIENTS(new_cs) { + if (new_cs->status > STATUS_AUTH) { + SEND_COMMAND(PACKET_SERVER_CLIENT_INFO)(new_cs, DEREF_CLIENT_INFO(cs)); + SEND_COMMAND(PACKET_SERVER_JOIN)(new_cs, cs->index); + } + } + } else { + // Wrong status for this packet, give a warning to client, and close connection + SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_NOT_EXPECTED); + } +} + +DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_COMMAND) +{ + // The client has done a command and wants us to handle it + int i; + byte callback; + ClientState *new_cs; + NetworkClientInfo *ci; + + CommandPacket *cp = malloc(sizeof(CommandPacket)); + + // The client was never joined.. so this is impossible, right? + // Ignore the packet, give the client a warning, and close his connection + if (cs->status < STATUS_DONE_MAP || cs->quited) { + SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_NOT_EXPECTED); + return; + } + + cp->player = NetworkRecv_uint8(p); + cp->cmd = NetworkRecv_uint32(p); + cp->p1 = NetworkRecv_uint32(p); + cp->p2 = NetworkRecv_uint32(p); + cp->tile = NetworkRecv_uint32(p); + for (i = 0; i < lengthof(cp->dp); i++) + cp->dp[i] = NetworkRecv_uint32(p); + + callback = NetworkRecv_uint8(p); + + ci = DEREF_CLIENT_INFO(cs); + // Only CMD_PLAYER_CTRL is always allowed, for the rest, playas needs + // to match the player in the packet + if (cp->cmd != CMD_PLAYER_CTRL && ci->client_playas-1 != cp->player) { + // The player did a command with the wrong player_id.. bad!! + SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_PLAYER_MISMATCH); + return; + } + if (cp->cmd == CMD_PLAYER_CTRL) { + // UGLY! p1 is mis-used to get the client-id in CmdPlayerCtrl + cp->p2 = cs - _clients; + } + + + // The frame can be executed in the same frame as the next frame-packet + // That frame just before that frame is saved in _frame_counter_max + cp->frame = _frame_counter_max + 1; + cp->next = NULL; + + // Queue the command for the clients (are send at the end of the frame + // if they can handle it ;)) + FOR_ALL_CLIENTS(new_cs) { + if (new_cs->status > STATUS_AUTH) { + // Callbacks are only send back to the client who sent them in the + // first place. This filters that out. + if (new_cs != cs) + cp->callback = 0; + else + cp->callback = callback; + NetworkAddCommandQueue(new_cs, cp); + } + } + + cp->callback = 0; + // Queue the command on the server + if (_local_command_queue == NULL) { + _local_command_queue = cp; + } else { + // Find last packet + CommandPacket *c = _local_command_queue; + while (c->next != NULL) c = c->next; + c->next = cp; + } +} + +DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_ERROR) +{ + // This packets means a client noticed an error and is reporting this + // to us. Display the error and report it to the other clients + ClientState *new_cs; + byte errorno = NetworkRecv_uint8(p); + char str1[100], str2[100]; + char client_name[NETWORK_NAME_LENGTH]; + + // The client was never joined.. thank the client for the packet, but ignore it + if (cs->status < STATUS_DONE_MAP || cs->quited) { + cs->quited = true; + return; + } + + NetworkGetClientName(client_name, sizeof(client_name), cs); + + GetString(str1, STR_NETWORK_ERR_LEFT); + GetString(str2, STR_NETWORK_ERR_CLIENT_GENERAL + errorno); + + DEBUG(net, 2)("[NET] %s reported an error and is closing his connection (%s)", client_name, str2); + + NetworkTextMessage(NETWORK_ACTION_JOIN_LEAVE, 1, client_name, "%s (%s)", str1, str2); + + FOR_ALL_CLIENTS(new_cs) { + if (new_cs->status > STATUS_AUTH) { + SEND_COMMAND(PACKET_SERVER_ERROR_QUIT)(new_cs, cs->index, errorno); + } + } + + cs->quited = true; +} + +DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_QUIT) +{ + // The client wants to leave. Display this and report it to the other + // clients. + ClientState *new_cs; + char str1[100], str2[100]; + char client_name[NETWORK_NAME_LENGTH]; + + // The client was never joined.. thank the client for the packet, but ignore it + if (cs->status < STATUS_DONE_MAP || cs->quited) { + cs->quited = true; + return; + } + + NetworkRecv_string(p, str2, 100); + + NetworkGetClientName(client_name, sizeof(client_name), cs); + + GetString(str1, STR_NETWORK_ERR_LEFT); + + NetworkTextMessage(NETWORK_ACTION_JOIN_LEAVE, 1, client_name, "%s (%s)", str1, str2); + + FOR_ALL_CLIENTS(new_cs) { + if (new_cs->status > STATUS_AUTH) { + SEND_COMMAND(PACKET_SERVER_QUIT)(new_cs, cs->index, str2); + } + } + + cs->quited = true; +} + +DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_ACK) +{ + // The client received the frame, make note of it + cs->last_frame = NetworkRecv_uint32(p); + // With those 2 values we can calculate the lag realtime + cs->last_frame_server = _frame_counter; + + // The client is now really active + if (cs->status == STATUS_PRE_ACTIVE) + cs->status = STATUS_ACTIVE; +} + + + +void NetworkServer_HandleChat(NetworkAction action, DestType desttype, int dest, const char *msg, byte from_index) +{ + ClientState *cs; + NetworkClientInfo *ci, *ci_own, *ci_to; + + switch (desttype) { + case DESTTYPE_CLIENT: + if (dest == 1) { + ci = NetworkFindClientInfoFromIndex(from_index); + if (ci != NULL) + NetworkTextMessage(action, GetDrawStringPlayerColor(ci->client_playas-1), ci->client_name, "%s", msg); + } else { + FOR_ALL_CLIENTS(cs) { + if (cs->index == dest) { + SEND_COMMAND(PACKET_SERVER_CHAT)(cs, action, from_index, msg); + break; + } + } + } + + // Display the message locally (so you know you have sent it) + if (from_index != dest) { + if (from_index == 1) { + ci = NetworkFindClientInfoFromIndex(from_index); + ci_to = NetworkFindClientInfoFromIndex(dest); + if (ci != NULL && ci_to != NULL) + NetworkTextMessage(NETWORK_ACTION_CHAT_TO_CLIENT, GetDrawStringPlayerColor(ci->client_playas-1), ci_to->client_name, "%s", msg); + } else { + FOR_ALL_CLIENTS(cs) { + if (cs->index == from_index) { + SEND_COMMAND(PACKET_SERVER_CHAT)(cs, NETWORK_ACTION_CHAT_TO_CLIENT, dest, msg); + break; + } + } + } + } + break; + case DESTTYPE_PLAYER: { + bool show_local = true; // If this is false, the message is already displayed + // on the client who did sent it. + FOR_ALL_CLIENTS(cs) { + ci = DEREF_CLIENT_INFO(cs); + if (ci->client_playas == dest) { + SEND_COMMAND(PACKET_SERVER_CHAT)(cs, action, from_index, msg); + if (cs->index == from_index) + show_local = false; + } + } + ci = NetworkFindClientInfoFromIndex(from_index); + ci_own = NetworkFindClientInfoFromIndex(NETWORK_SERVER_INDEX); + if (ci != NULL && ci_own != NULL && ci_own->client_playas == dest) { + NetworkTextMessage(action, GetDrawStringPlayerColor(ci->client_playas-1), ci->client_name, "%s", msg); + if (from_index == NETWORK_SERVER_INDEX) + show_local = false; + } + + // Display the message locally (so you know you have sent it) + if (ci != NULL && show_local) { + if (from_index == NETWORK_SERVER_INDEX) { + char name[NETWORK_NAME_LENGTH]; + GetString(name, DEREF_PLAYER(ci->client_playas-1)->name_1); + NetworkTextMessage(NETWORK_ACTION_CHAT_TO_PLAYER, GetDrawStringPlayerColor(ci->client_playas-1), name, "%s", msg); + } else { + FOR_ALL_CLIENTS(cs) { + if (cs->index == from_index) { + SEND_COMMAND(PACKET_SERVER_CHAT)(cs, NETWORK_ACTION_CHAT_TO_PLAYER, from_index, msg); + } + } + } + } + } + break; + default: + DEBUG(net, 0)("[NET][Server] Received unknown destination type %d. Doing broadcast instead.\n"); + /* fall-through to next case */ + case DESTTYPE_BROADCAST: + FOR_ALL_CLIENTS(cs) { + SEND_COMMAND(PACKET_SERVER_CHAT)(cs, action, from_index, msg); + } + ci = NetworkFindClientInfoFromIndex(from_index); + if (ci != NULL) + NetworkTextMessage(action, GetDrawStringPlayerColor(ci->client_playas-1), ci->client_name, "%s", msg); + break; + } +} + +DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_CHAT) +{ + NetworkAction action = NetworkRecv_uint8(p); + DestType desttype = NetworkRecv_uint8(p); + int dest = NetworkRecv_uint8(p); + char msg[MAX_TEXT_MSG_LEN]; + + NetworkRecv_string(p, msg, MAX_TEXT_MSG_LEN); + + NetworkServer_HandleChat(action, desttype, dest, msg, cs->index); +} + +DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_SET_PASSWORD) +{ + char password[NETWORK_PASSWORD_LENGTH]; + NetworkClientInfo *ci; + + NetworkRecv_string(p, password, sizeof(password)); + ci = DEREF_CLIENT_INFO(cs); + + if (ci->client_playas <= MAX_PLAYERS) { + ttd_strlcpy(_network_player_info[ci->client_playas - 1].password, password, sizeof(_network_player_info[ci->client_playas - 1].password)); + } +} + +DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_SET_NAME) +{ + char name[NETWORK_NAME_LENGTH]; + NetworkClientInfo *ci; + + NetworkRecv_string(p, name, sizeof(name)); + ci = DEREF_CLIENT_INFO(cs); + + if (ci != NULL) { + // Display change + if (NetworkFindName(name)) { + NetworkTextMessage(NETWORK_ACTION_NAME_CHANGE, 1, ci->client_name, name); + ttd_strlcpy(ci->client_name, name, sizeof(ci->client_name)); + NetworkUpdateClientInfo(ci->client_index); + } + } +} + +// The layout for the receive-functions by the server +typedef void NetworkServerPacket(ClientState *cs, Packet *p); + + +// This array matches PacketType. At an incoming +// packet it is matches against this array +// and that way the right function to handle that +// packet is found. +static NetworkServerPacket* const _network_server_packet[] = { + NULL, /*PACKET_SERVER_FULL,*/ + RECEIVE_COMMAND(PACKET_CLIENT_JOIN), + NULL, /*PACKET_SERVER_ERROR,*/ + RECEIVE_COMMAND(PACKET_CLIENT_COMPANY_INFO), + NULL, /*PACKET_SERVER_COMPANY_INFO,*/ + NULL, /*PACKET_SERVER_CLIENT_INFO,*/ + NULL, /*PACKET_SERVER_NEED_PASSWORD,*/ + RECEIVE_COMMAND(PACKET_CLIENT_PASSWORD), + NULL, /*PACKET_SERVER_WELCOME,*/ + RECEIVE_COMMAND(PACKET_CLIENT_GETMAP), + NULL, /*PACKET_SERVER_WAIT,*/ + NULL, /*PACKET_SERVER_MAP,*/ + RECEIVE_COMMAND(PACKET_CLIENT_MAP_OK), + NULL, /*PACKET_SERVER_JOIN,*/ + NULL, /*PACKET_SERVER_FRAME,*/ + NULL, /*PACKET_SERVER_SYNC,*/ + RECEIVE_COMMAND(PACKET_CLIENT_ACK), + RECEIVE_COMMAND(PACKET_CLIENT_COMMAND), + NULL, /*PACKET_SERVER_COMMAND,*/ + RECEIVE_COMMAND(PACKET_CLIENT_CHAT), + NULL, /*PACKET_SERVER_CHAT,*/ + RECEIVE_COMMAND(PACKET_CLIENT_SET_PASSWORD), + RECEIVE_COMMAND(PACKET_CLIENT_SET_NAME), + RECEIVE_COMMAND(PACKET_CLIENT_QUIT), + RECEIVE_COMMAND(PACKET_CLIENT_ERROR), + NULL, /*PACKET_SERVER_QUIT,*/ + NULL, /*PACKET_SERVER_ERROR_QUIT,*/ + NULL, /*PACKET_SERVER_SHUTDOWN,*/ + NULL, /*PACKET_SERVER_NEWGAME,*/ +}; + +// If this fails, check the array above with network_data.h +assert_compile(lengthof(_network_server_packet) == PACKET_END); + + +extern const SettingDesc patch_settings[]; + +// This is a TEMPORARY solution to get the patch-settings +// to the client. When the patch-settings are saved in the savegame +// this should be removed!! +void NetworkSendPatchSettings(ClientState *cs) +{ + const SettingDesc *item; + Packet *p = NetworkSend_Init(PACKET_SERVER_MAP); + NetworkSend_uint8(p, MAP_PACKET_PATCH); + // Now send all the patch-settings in a pretty order.. + + item = patch_settings; + + while (item->name != NULL) { + switch (item->flags) { + case SDT_BOOL: + case SDT_INT8: + case SDT_UINT8: + NetworkSend_uint8(p, *(uint8 *)item->ptr); + break; + case SDT_INT16: + case SDT_UINT16: + NetworkSend_uint16(p, *(uint16 *)item->ptr); + break; + case SDT_INT32: + case SDT_UINT32: + NetworkSend_uint32(p, *(uint32 *)item->ptr); + break; + } + item++; + } + + NetworkSend_Packet(p, cs); +} + +// This update the company_info-stuff +void NetworkPopulateCompanyInfo(void) +{ + char password[NETWORK_PASSWORD_LENGTH]; + Player *p; + Vehicle *v; + Station *s; + ClientState *cs; + NetworkClientInfo *ci; + int i; + + FOR_ALL_PLAYERS(p) { + if (!p->is_active) { + memset(&_network_player_info[p->index], 0, sizeof(NetworkPlayerInfo)); + continue; + } + + // Clean the info but not the password + ttd_strlcpy(password, _network_player_info[p->index].password, sizeof(password)); + memset(&_network_player_info[p->index], 0, sizeof(NetworkPlayerInfo)); + ttd_strlcpy(_network_player_info[p->index].password, password, sizeof(_network_player_info[p->index].password)); + + // Grap the company name + GetString(_network_player_info[p->index].company_name, p->name_1); + + // Check the income + if (_cur_year - 1 == p->inaugurated_year) + // The player is here just 1 year, so display [2], else display[1] + for (i = 0; i < 13; i++) + _network_player_info[p->index].income -= p->yearly_expenses[2][i]; + else + for (i = 0; i < 13; i++) + _network_player_info[p->index].income -= p->yearly_expenses[1][i]; + + // Set some general stuff + _network_player_info[p->index].inaugurated_year = p->inaugurated_year; + _network_player_info[p->index].company_value = p->old_economy[0].company_value; + _network_player_info[p->index].money = p->money64; + _network_player_info[p->index].performance = p->old_economy[0].performance_history; + } + + // Go through all vehicles and count the type of vehicles + FOR_ALL_VEHICLES(v) { + if (v->owner < MAX_PLAYERS) + switch (v->type) { + case VEH_Train: + if (v->subtype == 0) + _network_player_info[v->owner].num_vehicle[0]++; + break; + case VEH_Road: + if (v->cargo_type != CT_PASSENGERS) + _network_player_info[v->owner].num_vehicle[1]++; + else + _network_player_info[v->owner].num_vehicle[2]++; + break; + case VEH_Aircraft: + if (v->subtype <= 2) + _network_player_info[v->owner].num_vehicle[3]++; + break; + case VEH_Ship: + _network_player_info[v->owner].num_vehicle[4]++; + break; + } + } + + // Go through all stations and count the types of stations + FOR_ALL_STATIONS(s) { + if (s->owner < MAX_PLAYERS) { + if ((s->facilities & FACIL_TRAIN)) + _network_player_info[s->owner].num_station[0]++; + if ((s->facilities & FACIL_TRUCK_STOP)) + _network_player_info[s->owner].num_station[1]++; + if ((s->facilities & FACIL_BUS_STOP)) + _network_player_info[s->owner].num_station[2]++; + if ((s->facilities & FACIL_AIRPORT)) + _network_player_info[s->owner].num_station[3]++; + if ((s->facilities & FACIL_DOCK)) + _network_player_info[s->owner].num_station[4]++; + } + } + + ci = NetworkFindClientInfoFromIndex(NETWORK_SERVER_INDEX); + // Register local player (if not dedicated) + if (ci != NULL && _local_player < MAX_PLAYERS) { + snprintf(_network_player_info[ci->client_playas-1].players, sizeof(_network_player_info[ci->client_playas-1].players), "%s", ci->client_name); + } + + FOR_ALL_CLIENTS(cs) { + char client_name[NETWORK_NAME_LENGTH]; + char temp[NETWORK_PLAYERS_LENGTH]; + + NetworkGetClientName(client_name, sizeof(client_name), cs); + + ci = DEREF_CLIENT_INFO(cs); + if (ci != NULL && ci->client_playas <= MAX_PLAYERS) { + if (_network_player_info[ci->client_playas-1].players[0] == '\0') + snprintf(_network_player_info[ci->client_playas-1].players, sizeof(_network_player_info[ci->client_playas-1].players), "%s", client_name); + else { + snprintf(temp, sizeof(temp), "%s, %s", _network_player_info[ci->client_playas-1].players, client_name); + snprintf(_network_player_info[ci->client_playas-1].players, sizeof(_network_player_info[ci->client_playas-1].players), "%s", temp); + } + } + } +} + +// Send a packet to all clients with updated info about this client_index +void NetworkUpdateClientInfo(uint16 client_index) +{ + ClientState *cs; + NetworkClientInfo *ci; + + ci = NetworkFindClientInfoFromIndex(client_index); + + FOR_ALL_CLIENTS(cs) { + SEND_COMMAND(PACKET_SERVER_CLIENT_INFO)(cs, ci); + } +} + +// This function changes new_name to a name that is unique (by adding #1 ...) +// and it returns true if that succeeded. +bool NetworkFindName(char new_name[NETWORK_NAME_LENGTH]) +{ + ClientState *new_cs; + NetworkClientInfo *ci; + bool found_name = false; + byte number = 0; + char original_name[NETWORK_NAME_LENGTH]; + + // We use NETWORK_NAME_LENGTH in here, because new_name is really a pointer + ttd_strlcpy(original_name, new_name, NETWORK_NAME_LENGTH); + + while (!found_name) { + found_name = true; + FOR_ALL_CLIENTS(new_cs) { + ci = DEREF_CLIENT_INFO(new_cs); + if (strncmp(ci->client_name, new_name, NETWORK_NAME_LENGTH) == 0) { + // Name already in use + found_name = false; + break; + } + } + // Check if it is the same as the server-name + ci = NetworkFindClientInfoFromIndex(NETWORK_SERVER_INDEX); + if (ci != NULL) { + if (strncmp(ci->client_name, new_name, NETWORK_NAME_LENGTH) == 0) { + // Name already in use + found_name = false; + } + } + + if (!found_name) { + // Try a new name ( #1, #2, and so on) + + // Stop if we tried for more then 50 times.. + if (number++ > 50) break; + snprintf(new_name, NETWORK_NAME_LENGTH, "%s #%d", original_name, number); + } + } + + return found_name; +} + +// Reads a packet from the stream +bool NetworkServer_ReadPackets(ClientState *cs) +{ + Packet *p; + NetworkRecvStatus res; + while((p = NetworkRecv_Packet(cs, &res)) != NULL) { + byte type = NetworkRecv_uint8(p); + if (type < PACKET_END && _network_server_packet[type] != NULL) + _network_server_packet[type](cs, p); + else + DEBUG(net, 0)("[NET][Server] Received invalid packet type %d", type); + free(p); + } + + return true; +} + +// Handle the local command-queue +void NetworkHandleCommandQueue(ClientState *cs) { + if (cs->command_queue != NULL) { + CommandPacket *cp; + CommandPacket *cp_prev; + + cp = cs->command_queue; + cp_prev = NULL; + + while (cp != NULL) { + SEND_COMMAND(PACKET_SERVER_COMMAND)(cs, cp); + + if (cp_prev != NULL) { + cp_prev->next = cp->next; + free(cp); + cp = cp_prev->next; + } else { + // This means we are at our first packet + cs->command_queue = cp->next; + free(cp); + cp = cs->command_queue; + } + } + } +} + +// This is called every tick if this is a _network_server +void NetworkServer_Tick(void) +{ +#ifndef ENABLE_NETWORK_SYNC_EVERY_FRAME + static uint32 last_sync_frame = 0; +#endif + ClientState *cs; + bool send_frame = false; + + // Update max-frame-counter + if (_frame_counter > _frame_counter_max) { + _frame_counter_max = _frame_counter + _network_frame_freq; + send_frame = true; + } + + // Now we are done with the frame, inform the clients that they can + // do their frame! + FOR_ALL_CLIENTS(cs) { + // Check if the speed of the client is what we can expect from a client + if (cs->status == STATUS_ACTIVE) { + // 1 lag-point per day + int lag = NetworkCalculateLag(cs) / DAY_TICKS; + if (lag > 0) { + if (lag > 3) { + // Client did still not report in after 4 game-day, drop him + // (that is, the 3 of above, + 1 before any lag is counted) + IConsolePrintF(_iconsole_color_error,"Client #%d is dropped because the client did not respond for more then 4 game-days", cs->index); + CloseClient(cs); + continue; + } + + // Report once per time we detect the lag + if (cs->lag_test == 0) { + IConsolePrintF(_iconsole_color_warning,"[%d] Client #%d is slow, try increasing *net_frame_freq to a higher value!", _frame_counter, cs->index); + cs->lag_test = 1; + } + } else { + cs->lag_test = 0; + } + } + + + // Check if we can send command, and if we have anything in the queue + if (cs->status > STATUS_DONE_MAP) { + NetworkHandleCommandQueue(cs); + } + + // Do we need to send the new frame-packet? + if (send_frame && cs->status == STATUS_ACTIVE) { + SEND_COMMAND(PACKET_SERVER_FRAME)(cs); + } +#ifndef ENABLE_NETWORK_SYNC_EVERY_FRAME + // Is it time to send a sync-packet to all clients? + if (last_sync_frame + _network_sync_freq < _frame_counter) { + SEND_COMMAND(PACKET_SERVER_SYNC)(cs); + } +#endif + } + +#ifndef ENABLE_NETWORK_SYNC_EVERY_FRAME + // Update the last_sync_frame if needed! + if (last_sync_frame + _network_sync_freq < _frame_counter) { + last_sync_frame = _frame_counter; + } +#endif +} + +#endif /* ENABLE_NETWORK */ diff --git a/network_server.h b/network_server.h new file mode 100644 --- /dev/null +++ b/network_server.h @@ -0,0 +1,20 @@ +#ifndef NETWORK_SERVER_H +#define NETWORK_SERVER_H + +#ifdef ENABLE_NETWORK + +DEF_SERVER_SEND_COMMAND(PACKET_SERVER_MAP); +DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_ERROR_QUIT)(ClientState *cs, uint16 client_index, NetworkErrorCode errorno); +DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_ERROR)(ClientState *cs, NetworkErrorCode error); +DEF_SERVER_SEND_COMMAND(PACKET_SERVER_SHUTDOWN); +DEF_SERVER_SEND_COMMAND(PACKET_SERVER_NEWGAME); + +bool NetworkFindName(char new_name[NETWORK_NAME_LENGTH]); +void NetworkServer_HandleChat(NetworkAction action, DestType desttype, int dest, const char *msg, byte from_index); + +bool NetworkServer_ReadPackets(ClientState *cs); +void NetworkServer_Tick(); + +#endif /* ENABLE_NETWORK */ + +#endif // NETWORK_SERVER_H diff --git a/network_udp.c b/network_udp.c new file mode 100644 --- /dev/null +++ b/network_udp.c @@ -0,0 +1,368 @@ +#include "stdafx.h" +#include "network_data.h" + +#ifdef ENABLE_NETWORK + +#include "network_gamelist.h" + +extern void UpdateNetworkGameWindow(bool unselect); + +// +// This file handles all the LAN-stuff +// Stuff like: +// - UDP search over the network +// + +typedef enum { + PACKET_UDP_FIND_SERVER, + PACKET_UDP_SERVER_RESPONSE, + PACKET_UDP_END +} PacketUDPType; + +static SOCKET _udp_server_socket; // udp server socket + +#define DEF_UDP_RECEIVE_COMMAND(type) void NetworkPacketReceive_ ## type ## _command(Packet *p, struct sockaddr_in *client_addr) +void NetworkSendUDP_Packet(Packet *p, struct sockaddr_in *recv); + +DEF_UDP_RECEIVE_COMMAND(PACKET_UDP_FIND_SERVER) +{ + Packet *packet; + // Just a fail-safe.. should never happen + if (!_network_udp_server) + return; + + packet = NetworkSend_Init(PACKET_UDP_SERVER_RESPONSE); + + // Update some game_info + _network_game_info.game_date = _date; + _network_game_info.map_set = _opt.landscape; + + NetworkSend_uint8 (packet, NETWORK_GAME_INFO_VERSION); + NetworkSend_string(packet, _network_game_info.server_name); + NetworkSend_string(packet, _network_game_info.server_revision); + NetworkSend_uint8 (packet, _network_game_info.server_lang); + NetworkSend_uint8 (packet, _network_game_info.use_password); + NetworkSend_uint8 (packet, _network_game_info.clients_max); + NetworkSend_uint8 (packet, _network_game_info.clients_on); + NetworkSend_uint8 (packet, _network_game_info.spectators_on); + NetworkSend_uint16(packet, _network_game_info.game_date); + NetworkSend_uint16(packet, _network_game_info.start_date); + NetworkSend_string(packet, _network_game_info.map_name); + NetworkSend_uint16(packet, _network_game_info.map_width); + NetworkSend_uint16(packet, _network_game_info.map_height); + NetworkSend_uint8 (packet, _network_game_info.map_set); + NetworkSend_uint8 (packet, _network_game_info.dedicated); + + // Let the client know that we are here + NetworkSendUDP_Packet(packet, client_addr); + + free(packet); + + DEBUG(net, 2)("[NET][UDP] Queried from %s", inet_ntoa(client_addr->sin_addr)); +} + +DEF_UDP_RECEIVE_COMMAND(PACKET_UDP_SERVER_RESPONSE) +{ + NetworkGameList *item; + byte game_info_version; + + // Just a fail-safe.. should never happen + if (_network_udp_server) + return; + + game_info_version = NetworkRecv_uint8(p); + + // Find next item + item = NetworkGameListAddItem(inet_addr(inet_ntoa(client_addr->sin_addr)), ntohs(client_addr->sin_port)); + + if (game_info_version == 1) { + NetworkRecv_string(p, item->info.server_name, sizeof(item->info.server_name)); + NetworkRecv_string(p, item->info.server_revision, sizeof(item->info.server_revision)); + item->info.server_lang = NetworkRecv_uint8(p); + item->info.use_password = NetworkRecv_uint8(p); + item->info.clients_max = NetworkRecv_uint8(p); + item->info.clients_on = NetworkRecv_uint8(p); + item->info.spectators_on = NetworkRecv_uint8(p); + item->info.game_date = NetworkRecv_uint16(p); + item->info.start_date = NetworkRecv_uint16(p); + NetworkRecv_string(p, item->info.map_name, sizeof(item->info.map_name)); + item->info.map_width = NetworkRecv_uint16(p); + item->info.map_height = NetworkRecv_uint16(p); + item->info.map_set = NetworkRecv_uint8(p); + item->info.dedicated = NetworkRecv_uint8(p); + + if (item->info.hostname[0] == '\0') + snprintf(item->info.hostname, sizeof(item->info.hostname), "%s", inet_ntoa(client_addr->sin_addr)); + } + + item->online = true; + + UpdateNetworkGameWindow(false); +} + + +// The layout for the receive-functions by UDP +typedef void NetworkUDPPacket(Packet *p, struct sockaddr_in *client_addr); + +static NetworkUDPPacket* const _network_udp_packet[] = { + RECEIVE_COMMAND(PACKET_UDP_FIND_SERVER), + RECEIVE_COMMAND(PACKET_UDP_SERVER_RESPONSE), +}; + +// If this fails, check the array above with network_data.h +assert_compile(lengthof(_network_udp_packet) == PACKET_UDP_END); + + +void NetworkHandleUDPPacket(Packet *p, struct sockaddr_in *client_addr) +{ + byte type; + + type = NetworkRecv_uint8(p); + + if (type < PACKET_UDP_END && _network_udp_packet[type] != NULL) { + _network_udp_packet[type](p, client_addr); + } else { + DEBUG(net, 0)("[NET][UDP] Received invalid packet type %d", type); + } +} + + +// Send a packet over UDP +void NetworkSendUDP_Packet(Packet *p, struct sockaddr_in *recv) +{ + SOCKET udp; + int res; + + // Find the correct socket + if (_network_udp_server) + udp = _udp_server_socket; + else + udp = _udp_client_socket; + + // Put the length in the buffer + p->buffer[0] = p->size & 0xFF; + p->buffer[1] = p->size >> 8; + + // Send the buffer + res = sendto(udp, p->buffer, p->size, 0, (struct sockaddr *)recv, sizeof(*recv)); + + // Check for any errors, but ignore it for the rest + if (res == -1) { + DEBUG(net, 1)("[NET][UDP] Send error: %i", GET_LAST_ERROR()); + } +} + +// Start UDP listener +bool NetworkUDPListen(uint32 host, uint16 port) +{ + struct sockaddr_in sin; + SOCKET udp; + + // Make sure sockets are closed + if (_network_udp_server) + closesocket(_udp_server_socket); + else + closesocket(_udp_client_socket); + + udp = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (udp == INVALID_SOCKET) { + DEBUG(net, 1)("[NET][UDP] Failed to start UDP support"); + return false; + } + + // set nonblocking mode for socket + { + unsigned long blocking = 1; + ioctlsocket(udp, FIONBIO, &blocking); + } + + sin.sin_family = AF_INET; + // Listen on all IPs + sin.sin_addr.s_addr = host; + sin.sin_port = htons(port); + + if (bind(udp, (struct sockaddr*)&sin, sizeof(sin)) != 0) { + DEBUG(net, 1) ("[NET][UDP] error: bind failed on port %i", port); + return false; + } + + // enable broadcasting + // allow reusing + { + unsigned long val = 1; + setsockopt(udp, SOL_SOCKET, SO_BROADCAST, (char *) &val , sizeof(val)); + val = 1; + setsockopt(udp, SOL_SOCKET, SO_REUSEADDR, (char *) &val , sizeof(val)); + } + + if (_network_udp_server) + _udp_server_socket = udp; + else + _udp_client_socket = udp; + + DEBUG(net, 1)("[NET][UDP] Listening on port %d", port); + + return true; +} + +// Close UDP connection +void NetworkUDPClose(void) +{ + DEBUG(net, 1) ("[NET][UDP] Closed listener"); + + if (_network_udp_server) { + closesocket(_udp_server_socket); + _udp_server_socket = INVALID_SOCKET; + _network_udp_server = false; + _network_udp_broadcast = 0; + } else { + closesocket(_udp_client_socket); + _udp_client_socket = INVALID_SOCKET; + _network_udp_broadcast = 0; + } +} + +// Receive something on UDP level +void NetworkUDPReceive(void) +{ + struct sockaddr_in client_addr; +#ifndef __MORPHOS__ + int client_len; +#else + LONG client_len; // for some reason we need a 'LONG' under MorphOS +#endif + int nbytes; + static Packet *p = NULL; + int packet_len; + SOCKET udp; + + if (_network_udp_server) + udp = _udp_server_socket; + else + udp = _udp_client_socket; + + // If p is NULL, malloc him.. this prevents unneeded mallocs + if (p == NULL) + p = malloc(sizeof(Packet)); + + packet_len = sizeof(p->buffer); + client_len = sizeof(client_addr); + + // Try to receive anything + nbytes = recvfrom(udp, p->buffer, packet_len, 0, (struct sockaddr *)&client_addr, &client_len); + + // We got some bytes.. just asume we receive the whole packet + if (nbytes > 0) { + // Get the size of the buffer + p->size = (uint16)p->buffer[0]; + p->size += (uint16)p->buffer[1] << 8; + // Put the position on the right place + p->pos = 2; + p->next = NULL; + + // Handle the packet + NetworkHandleUDPPacket(p, &client_addr); + + // Free the packet + free(p); + p = NULL; + } +} + +// Broadcast to all ips +void NetworkUDPBroadCast(void) +{ + int i; + struct sockaddr_in out_addr; + byte *bcptr; + uint32 bcaddr; + Packet *p; + + // Init the packet + p = NetworkSend_Init(PACKET_UDP_FIND_SERVER); + + // Go through all the ips on this pc + i = 0; + while (_network_ip_list[i] != 0) { + bcaddr = _network_ip_list[i]; + bcptr = (byte *)&bcaddr; + // Make the address a broadcast address + bcptr[3] = 255; + + DEBUG(net, 6)("[NET][UDP] Broadcasting to %s", inet_ntoa(*(struct in_addr *)&bcaddr)); + + out_addr.sin_family = AF_INET; + out_addr.sin_port = htons(_network_server_port); + out_addr.sin_addr.s_addr = bcaddr; + + NetworkSendUDP_Packet(p, &out_addr); + + i++; + } + + free(p); +} + +// Find all servers +void NetworkUDPSearchGame(void) +{ + // We are still searching.. + if (_network_udp_broadcast > 0) + return; + + // No UDP-socket yet.. + if (_udp_client_socket == INVALID_SOCKET) + if (!NetworkUDPListen(0, 0)) + return; + + DEBUG(net, 0)("[NET][UDP] Searching server"); + + NetworkUDPBroadCast(); + _network_udp_broadcast = 300; // Stay searching for 300 ticks +} + +void NetworkUDPQueryServer(const byte* host, unsigned short port) +{ + struct sockaddr_in out_addr; + Packet *p; + NetworkGameList *item; + char hostname[NETWORK_HOSTNAME_LENGTH]; + + // No UDP-socket yet.. + if (_udp_client_socket == INVALID_SOCKET) + if (!NetworkUDPListen(0, 0)) + return; + + ttd_strlcpy(hostname, host, sizeof(hostname)); + + out_addr.sin_family = AF_INET; + out_addr.sin_port = htons(port); + out_addr.sin_addr.s_addr = NetworkResolveHost(host); + + // Clear item in gamelist + item = NetworkGameListAddItem(inet_addr(inet_ntoa(out_addr.sin_addr)), ntohs(out_addr.sin_port)); + memset(&item->info, 0, sizeof(item->info)); + snprintf(item->info.server_name, sizeof(item->info.server_name), "%s", hostname); + snprintf(item->info.hostname, sizeof(item->info.hostname), "%s", hostname); + item->online = false; + + // Init the packet + p = NetworkSend_Init(PACKET_UDP_FIND_SERVER); + + NetworkSendUDP_Packet(p, &out_addr); + + free(p); + + UpdateNetworkGameWindow(false); +} + +void NetworkUDPInitialize(void) +{ + _udp_client_socket = INVALID_SOCKET; + _udp_server_socket = INVALID_SOCKET; + + _network_udp_server = false; + _network_udp_broadcast = 0; +} + +#endif /* ENABLE_NETWORK */ diff --git a/network_udp.h b/network_udp.h new file mode 100644 --- /dev/null +++ b/network_udp.h @@ -0,0 +1,10 @@ +#ifndef NETWORK_LAN_H +#define NETWORK_LAN_H + +void NetworkUDPInitialize(void); +bool NetworkUDPListen(uint32 host, uint16 port); +void NetworkUDPReceive(void); +void NetworkUDPSearchGame(void); +void NetworkUDPQueryServer(const byte* host, unsigned short port); + +#endif /* NETWORK_LAN_H */ diff --git a/order_cmd.c b/order_cmd.c --- a/order_cmd.c +++ b/order_cmd.c @@ -325,7 +325,7 @@ void RestoreVehicleOrders(Vehicle *v, Ba DoCommandP(0, v->index, 0, NULL, CMD_NAME_VEHICLE); } - DoCommandP(0, v->index, bak->orderindex|(bak->service_interval<<16) , NULL, CMD_RESTORE_ORDER_INDEX | CMD_ASYNC); + DoCommandP(0, v->index, bak->orderindex|(bak->service_interval<<16) , NULL, CMD_RESTORE_ORDER_INDEX); os = bak->order; if (os[0] == 0xFFFF) { @@ -333,9 +333,13 @@ void RestoreVehicleOrders(Vehicle *v, Ba return; } + // CMD_NO_TEST_IF_IN_NETWORK is used here, because CMD_INSERT_ORDER checks if the + // order number is one more then the current amount of orders, and because + // in network the commands are queued before send, the second insert always + // fails in test mode. By bypassing the test-mode, that no longer is a problem. ind = 0; while ((ord = *os++) != 0) { - if (!DoCommandP(0, v->index + (ind << 16), ord, NULL, CMD_INSERT_ORDER | CMD_ASYNC)) + if (!DoCommandP(0, v->index + (ind << 16), ord, NULL, CMD_INSERT_ORDER | CMD_NO_TEST_IF_IN_NETWORK)) break; ind++; } diff --git a/player_gui.c b/player_gui.c --- a/player_gui.c +++ b/player_gui.c @@ -490,6 +490,9 @@ static void PlayerCompanyWndProc(Window dis = 0; if (GetAmountOwnedBy(p, 0xFF) == 0) dis |= 1 << 9; + // Also disable the buy button if 25% is not-owned by someone + // and the player is not an AI + if (GetAmountOwnedBy(p, 0xFF) == 1 && !p->is_ai) dis |= 1 << 9; if (GetAmountOwnedBy(p, _local_player) == 0) dis |= 1 << 10; w->disabled_state = dis; @@ -501,7 +504,8 @@ static void PlayerCompanyWndProc(Window DrawPlayerVehiclesAmount(w->window_number); DrawString(110,48, STR_7006_COLOR_SCHEME, 0); - DrawSprite((p->player_color<<16) + 0x3078C19, 215,49); + // Draw company-colour bus (0xC19) + DrawSprite(PLAYER_SPRITE_COLOR(p->index) + 0x8C19, 215, 49); DrawPlayerFace(p->face, p->player_color, 2, 16); diff --git a/players.c b/players.c --- a/players.c +++ b/players.c @@ -11,6 +11,7 @@ #include "command.h" #include "ai.h" #include "sound.h" +#include "network.h" extern void StartupEconomy(); @@ -489,6 +490,7 @@ Player *DoStartupNewPlayer(bool is_ai) InvalidateWindow(WC_GRAPH_LEGEND, 0); InvalidateWindow(WC_TOOLBAR_MENU, 0); + InvalidateWindow(WC_CLIENT_LIST, 0); return p; } @@ -538,7 +540,7 @@ void OnTick_Players() _cur_player_tick_index = (_cur_player_tick_index + 1) % MAX_PLAYERS; if (p->name_1 != 0) GenerateCompanyName(p); - if (_game_mode != GM_MENU && !--_next_competitor_start) { + if (!_networking && _game_mode != GM_MENU && !--_next_competitor_start) { MaybeStartNewPlayer(); } } @@ -636,12 +638,44 @@ int32 CmdPlayerCtrl(int x, int y, uint32 if (!(flags & DC_EXEC)) return 0; + _current_player = OWNER_NONE; + switch(p1 & 0xff) { case 0: // make new player p = DoStartupNewPlayer(false); - if (_local_player == OWNER_SPECTATOR && p != NULL) { - _local_player = p->index; - MarkWholeScreenDirty(); + if (p != NULL) { + if (_local_player == OWNER_SPECTATOR) { + _local_player = p->index; + MarkWholeScreenDirty(); + } +#ifdef ENABLE_NETWORK + if (_network_server) { + NetworkClientInfo *ci; + // UGLY! p2 is mis-used to fetch the client-id + ci = &_network_client_info[p2]; + ci->client_playas = p->index + 1; + NetworkUpdateClientInfo(ci->client_index); + + if (ci->client_playas != 0 && ci->client_playas <= MAX_PLAYERS) { + memcpy(_decode_parameters, ci->client_name, 32); + /* XXX - What are the consequents of this? It is needed, but is it bad? */ + _docommand_recursive = 0; + DoCommandP(0, ci->client_playas-1, 0, NULL, CMD_CHANGE_PRESIDENT_NAME | CMD_MSG(STR_700D_CAN_T_CHANGE_PRESIDENT)); + } + } else { + _network_playas = p->index + 1; + } + } else { + if (_network_server) { + NetworkClientInfo *ci; + // UGLY! p2 is mis-used to fetch the client-id + ci = &_network_client_info[p2]; + ci->client_playas = OWNER_SPECTATOR; + NetworkUpdateClientInfo(ci->client_index); + } else { + _network_playas = OWNER_SPECTATOR; + } +#endif /* ENABLE_NETWORK */ } break; case 1: // make new ai player diff --git a/rail_cmd.c b/rail_cmd.c --- a/rail_cmd.c +++ b/rail_cmd.c @@ -826,7 +826,7 @@ int32 CmdRenameWaypoint(int x, int y, ui StringID str; if (_decode_parameters[0] != 0) { - str = AllocateName((byte*)_decode_parameters, 0); + str = AllocateNameUnique((byte*)_decode_parameters, 0); if (str == 0) return CMD_ERROR; if (flags & DC_EXEC) { diff --git a/rail_gui.c b/rail_gui.c --- a/rail_gui.c +++ b/rail_gui.c @@ -31,7 +31,7 @@ static void ShowStationBuilder(); typedef void OnButtonClick(Window *w); -static void CcPlaySound1E(bool success, uint tile, uint32 p1, uint32 p2) +void CcPlaySound1E(bool success, uint tile, uint32 p1, uint32 p2) { if (success) SndPlayTileFx(SND_20_SPLAT_2, tile); } @@ -91,7 +91,7 @@ static const uint16 _place_depot_extra[1 }; -static void CcDepot(bool success, uint tile, uint32 p1, uint32 p2) +void CcRailDepot(bool success, uint tile, uint32 p1, uint32 p2) { if (success) { int dir = p2; @@ -111,7 +111,7 @@ static void CcDepot(bool success, uint t static void PlaceRail_Depot(uint tile) { - DoCommandP(tile, _cur_railtype, _build_depot_direction, CcDepot, + DoCommandP(tile, _cur_railtype, _build_depot_direction, CcRailDepot, CMD_BUILD_TRAIN_DEPOT | CMD_AUTO | CMD_NO_WATER | CMD_MSG(STR_100E_CAN_T_BUILD_TRAIN_DEPOT)); } @@ -124,7 +124,7 @@ static void PlaceRail_Waypoint(uint tile } } -static void CcStation(bool success, uint tile, uint32 p1, uint32 p2) +void CcStation(bool success, uint tile, uint32 p1, uint32 p2) { if (success) { SndPlayTileFx(SND_20_SPLAT_2, tile); @@ -180,7 +180,7 @@ static void PlaceRail_Bridge(uint tile) VpStartPlaceSizing(tile, VPM_X_OR_Y); } -static void CcBuildTunnel(bool success, uint tile, uint32 p1, uint32 p2) +void CcBuildRailTunnel(bool success, uint tile, uint32 p1, uint32 p2) { if (success) { SndPlayTileFx(SND_20_SPLAT_2, tile); @@ -192,7 +192,7 @@ static void CcBuildTunnel(bool success, static void PlaceRail_Tunnel(uint tile) { - DoCommandP(tile, _cur_railtype, 0, CcBuildTunnel, + DoCommandP(tile, _cur_railtype, 0, CcBuildRailTunnel, CMD_BUILD_TUNNEL | CMD_AUTO | CMD_MSG(STR_5016_CAN_T_BUILD_TUNNEL_HERE)); } diff --git a/road_gui.c b/road_gui.c --- a/road_gui.c +++ b/road_gui.c @@ -20,7 +20,7 @@ static byte _place_road_flag; static byte _road_depot_orientation; static byte _road_station_picker_orientation; -static void CcPlaySound1D(bool success, uint tile, uint32 p1, uint32 p2) +void CcPlaySound1D(bool success, uint tile, uint32 p1, uint32 p2) { if (success) SndPlayTileFx(SND_1F_SPLAT, tile); } @@ -43,7 +43,7 @@ static void PlaceRoad_Bridge(uint tile) } -static void CcBuildTunnel(bool success, uint tile, uint32 p1, uint32 p2) +void CcBuildRoadTunnel(bool success, uint tile, uint32 p1, uint32 p2) { if (success) { SndPlayTileFx(SND_20_SPLAT_2, tile); @@ -55,7 +55,7 @@ static void CcBuildTunnel(bool success, static void PlaceRoad_Tunnel(uint tile) { - DoCommandP(tile, 0x200, 0, CcBuildTunnel, CMD_BUILD_TUNNEL | CMD_AUTO | CMD_MSG(STR_5016_CAN_T_BUILD_TUNNEL_HERE)); + DoCommandP(tile, 0x200, 0, CcBuildRoadTunnel, CMD_BUILD_TUNNEL | CMD_AUTO | CMD_MSG(STR_5016_CAN_T_BUILD_TUNNEL_HERE)); } static void BuildRoadOutsideStation(uint tile, int direction) @@ -68,7 +68,7 @@ static void BuildRoadOutsideStation(uint } } -static void CcDepot(bool success, uint tile, uint32 p1, uint32 p2) +void CcRoadDepot(bool success, uint tile, uint32 p1, uint32 p2) { if (success) { SndPlayTileFx(SND_1F_SPLAT, tile); @@ -79,17 +79,17 @@ static void CcDepot(bool success, uint t static void PlaceRoad_Depot(uint tile) { - DoCommandP(tile, _road_depot_orientation, 0, CcDepot, CMD_BUILD_ROAD_DEPOT | CMD_AUTO | CMD_NO_WATER | CMD_MSG(STR_1807_CAN_T_BUILD_ROAD_VEHICLE)); + DoCommandP(tile, _road_depot_orientation, 0, CcRoadDepot, CMD_BUILD_ROAD_DEPOT | CMD_AUTO | CMD_NO_WATER | CMD_MSG(STR_1807_CAN_T_BUILD_ROAD_VEHICLE)); } static void PlaceRoad_BusStation(uint tile) { - DoCommandP(tile, _road_station_picker_orientation, 0, CcDepot, CMD_BUILD_BUS_STATION | CMD_AUTO | CMD_NO_WATER | CMD_MSG(STR_1808_CAN_T_BUILD_BUS_STATION)); + DoCommandP(tile, _road_station_picker_orientation, 0, CcRoadDepot, CMD_BUILD_BUS_STATION | CMD_AUTO | CMD_NO_WATER | CMD_MSG(STR_1808_CAN_T_BUILD_BUS_STATION)); } static void PlaceRoad_TruckStation(uint tile) { - DoCommandP(tile, _road_station_picker_orientation, 0, CcDepot, CMD_BUILD_TRUCK_STATION | CMD_AUTO | CMD_NO_WATER | CMD_MSG(STR_1809_CAN_T_BUILD_TRUCK_STATION)); + DoCommandP(tile, _road_station_picker_orientation, 0, CcRoadDepot, CMD_BUILD_TRUCK_STATION | CMD_AUTO | CMD_NO_WATER | CMD_MSG(STR_1809_CAN_T_BUILD_TRUCK_STATION)); } static void PlaceRoad_DemolishArea(uint tile) diff --git a/roadveh_gui.c b/roadveh_gui.c --- a/roadveh_gui.c +++ b/roadveh_gui.c @@ -368,7 +368,7 @@ static void DrawNewRoadVehWindow(Window } } -static void CcBuildRoadVeh(bool success, uint tile, uint32 p1, uint32 p2) +void CcBuildRoadVeh(bool success, uint tile, uint32 p1, uint32 p2) { Vehicle *v; diff --git a/scripts/on_client.scr b/scripts/on_client.scr --- a/scripts/on_client.scr +++ b/scripts/on_client.scr @@ -1,2 +1,1 @@ echo "Setting default network client settings..." -*net_ready_ahead = 1 \ No newline at end of file diff --git a/scripts/on_dedicated.scr b/scripts/on_dedicated.scr new file mode 100644 --- /dev/null +++ b/scripts/on_dedicated.scr @@ -0,0 +1,3 @@ +echo "Setting dedicated network server settings..." +setpassword "*" +setservername "My example dedicated gameserver" diff --git a/scripts/on_server.scr b/scripts/on_server.scr --- a/scripts/on_server.scr +++ b/scripts/on_server.scr @@ -1,3 +1,3 @@ echo "Setting default network server settings..." -*net_sync_freq = 4 -*net_client_timeout = 300 \ No newline at end of file +net_sync_freq = 100 +net_frame_freq = 0 diff --git a/sdl.c b/sdl.c --- a/sdl.c +++ b/sdl.c @@ -8,6 +8,7 @@ #include #include "player.h" #include "hal.h" +#include "network.h" #ifdef UNIX #include @@ -607,6 +608,7 @@ static int SdlVideoMainLoop(void) } else { SDL_CALL SDL_Delay(1); _screen.dst_ptr = _sdl_screen->pixels; + DrawTextMessage(); DrawMouseCursor(); DrawSurfaceToScreen(); } diff --git a/settings.c b/settings.c --- a/settings.c +++ b/settings.c @@ -1,38 +1,12 @@ #include "stdafx.h" #include "ttd.h" #include "sound.h" - -enum SettingDescType { - SDT_INTX, // must be 0 - SDT_ONEOFMANY, - SDT_MANYOFMANY, - SDT_BOOLX, - SDT_STRING, - SDT_STRINGBUF, - SDT_INTLIST, - - SDT_INT8 = 0 << 4, - SDT_UINT8 = 1 << 4, - SDT_INT16 = 2 << 4, - SDT_UINT16 = 3 << 4, - SDT_INT32 = 4 << 4, - SDT_UINT32 = 5 << 4, - SDT_CALLBX = 6 << 4, - - SDT_UINT = SDT_UINT32, - SDT_INT = SDT_INT32, - - SDT_NOSAVE = 1 << 8, - - SDT_CALLB = SDT_INTX | SDT_CALLBX, - - SDT_BOOL = SDT_BOOLX | SDT_UINT8, -}; +#include "network.h" +#include "settings.h" typedef struct IniFile IniFile; typedef struct IniItem IniItem; typedef struct IniGroup IniGroup; -typedef struct SettingDesc SettingDesc; typedef struct MemoryPool MemoryPool; static void pool_init(MemoryPool **pool); @@ -322,15 +296,6 @@ static void ini_free(IniFile *ini) pool_free(&ini->pool); } -struct SettingDesc { - const char *name; - int flags; - const void *def; - void *ptr; - const void *b; - -}; - static int lookup_oneofmany(const char *many, const char *one, int onelen) { const char *s; @@ -532,7 +497,7 @@ static const void *string_to_val(const S return NULL; } -static void load_setting_desc(IniFile *ini, const SettingDesc *desc, const void *grpname, void *base) +static void load_setting_desc(IniFile *ini, const SettingDesc *desc, const void *grpname) { IniGroup *group_def = ini_getgroup(ini, grpname, -1), *group; IniItem *item; @@ -559,8 +524,6 @@ static void load_setting_desc(IniFile *i // get ptr to array ptr = desc->ptr; - if ( (uint32)ptr < 0x10000) - ptr = (byte*)base + (uint32)ptr; switch(desc->flags & 0xF) { // all these are stored in the same way @@ -603,7 +566,7 @@ static void load_setting_desc(IniFile *i } } -static void save_setting_desc(IniFile *ini, const SettingDesc *desc, const void *grpname, void *base) +static void save_setting_desc(IniFile *ini, const SettingDesc *desc, const void *grpname) { IniGroup *group_def = NULL, *group; IniItem *item; @@ -633,8 +596,6 @@ static void save_setting_desc(IniFile *i // get ptr to array ptr = desc->ptr; - if ( (uint32)ptr < 0x10000) - ptr = (byte*)base + (uint32)ptr; if (item->value != NULL) { // check if the value is the same as the old value @@ -726,13 +687,13 @@ static void save_setting_desc(IniFile *i //*************************** static const SettingDesc music_settings[] = { - {"playlist", SDT_UINT8, (void*)0, (void*)offsetof(MusicFileSettings, playlist), NULL}, - {"music_vol", SDT_UINT8, (void*)128, (void*)offsetof(MusicFileSettings, music_vol), NULL}, - {"effect_vol",SDT_UINT8, (void*)128, (void*)offsetof(MusicFileSettings, effect_vol), NULL}, - {"custom_1", SDT_INTLIST | SDT_UINT8 | lengthof(msf.custom_1) << 16, NULL, (void*)offsetof(MusicFileSettings, custom_1), NULL}, - {"custom_2", SDT_INTLIST | SDT_UINT8 | lengthof(msf.custom_2) << 16, NULL, (void*)offsetof(MusicFileSettings, custom_2), NULL}, - {"playing", SDT_BOOL, (void*)true, (void*)offsetof(MusicFileSettings, btn_down), NULL}, - {"shuffle", SDT_BOOL, (void*)false, (void*)offsetof(MusicFileSettings, shuffle), NULL}, + {"playlist", SDT_UINT8, (void*)0, &msf.playlist, NULL}, + {"music_vol", SDT_UINT8, (void*)128, &msf.music_vol, NULL}, + {"effect_vol",SDT_UINT8, (void*)128, &msf.effect_vol, NULL}, + {"custom_1", SDT_INTLIST | SDT_UINT8 | lengthof(msf.custom_1) << 16, NULL, &msf.custom_1, NULL}, + {"custom_2", SDT_INTLIST | SDT_UINT8 | lengthof(msf.custom_2) << 16, NULL, &msf.custom_2, NULL}, + {"playing", SDT_BOOL, (void*)true, &msf.btn_down, NULL}, + {"shuffle", SDT_BOOL, (void*)false, &msf.shuffle, NULL}, {NULL, 0, NULL, NULL, NULL} }; @@ -760,13 +721,19 @@ static const SettingDesc misc_settings[] {NULL, 0, NULL, NULL, NULL} }; +#ifdef ENABLE_NETWORK static const SettingDesc network_settings[] = { - {"port", SDT_UINT | SDT_NOSAVE, (void*)3978, &_network_client_port, NULL}, - {"server_port", SDT_UINT | SDT_NOSAVE, (void*)3979, &_network_server_port, NULL}, - {"sync_freq", SDT_UINT16 | SDT_NOSAVE, (void*)4, &_network_sync_freq, NULL}, - {"ahead_frames", SDT_UINT16 | SDT_NOSAVE, (void*)5, &_network_ahead_frames, NULL}, + {"port", SDT_UINT | SDT_NOSAVE, (void*)NETWORK_DISCOVER_PORT, &_network_client_port, NULL}, + {"server_port", SDT_UINT | SDT_NOSAVE, (void*)NETWORK_DEFAULT_PORT, &_network_server_port, NULL}, + {"sync_freq", SDT_UINT16 | SDT_NOSAVE, (void*)100, &_network_sync_freq, NULL}, + {"frame_freq", SDT_UINT8 | SDT_NOSAVE, (void*)0, &_network_frame_freq, NULL}, + {"player_name", SDT_STRINGBUF | (lengthof(_network_player_name) << 16), NULL, &_network_player_name, NULL}, + {"server_password", SDT_STRINGBUF | (lengthof(_network_game_info.server_password) << 16), NULL, &_network_game_info.server_password, NULL}, + {"server_name", SDT_STRINGBUF | (lengthof(_network_server_name) << 16), NULL, &_network_server_name, NULL}, + {"connect_to_ip", SDT_STRINGBUF | (lengthof(_network_default_ip) << 16), NULL, &_network_default_ip, NULL}, {NULL, 0, NULL, NULL, NULL} }; +#endif /* ENABLE_NETWORK */ static const SettingDesc debug_settings[] = { {"savedump_path", SDT_STRINGBUF | (lengthof(_savedump_path)<<16) | SDT_NOSAVE, NULL, _savedump_path, NULL}, @@ -778,126 +745,141 @@ static const SettingDesc debug_settings[ static const SettingDesc gameopt_settings[] = { - {"diff_level", SDT_UINT8, (void*)9, (void*)offsetof(GameOptions, diff_level), NULL}, - {"diff_custom", SDT_INTLIST | SDT_UINT32 | (sizeof(GameDifficulty)/4) << 16, NULL, (void*)offsetof(GameOptions, diff), NULL}, - {"currency", SDT_UINT8 | SDT_ONEOFMANY, (void*)21, (void*)offsetof(GameOptions, currency), "GBP|USD|FF|DM|YEN|PT|FT|ZL|ATS|BEF|DKK|FIM|GRD|CHF|NLG|ITL|SEK|RUR|CZK|ISK|NOK|EUR|ROL" }, - {"distances", SDT_UINT8 | SDT_ONEOFMANY, (void*)1, (void*)offsetof(GameOptions, kilometers), "imperial|metric" }, - {"town_names", SDT_UINT8 | SDT_ONEOFMANY, (void*)0, (void*)offsetof(GameOptions, town_name), "english|french|german|american|latin|silly|swedish|dutch|finnish|polish|slovakish|hungarian|romanian|czech" }, - {"landscape", SDT_UINT8 | SDT_ONEOFMANY, (void*)0, (void*)offsetof(GameOptions, landscape), "normal|hilly|desert|candy" }, - {"autosave", SDT_UINT8 | SDT_ONEOFMANY, (void*)1, (void*)offsetof(GameOptions, autosave), "off|monthly|quarterly|half year|yearly" }, - {"road_side", SDT_UINT8 | SDT_ONEOFMANY, (void*)1, (void*)offsetof(GameOptions, road_side), "left|right" }, + {"diff_level", SDT_UINT8, (void*)9, &_new_opt.diff_level, NULL}, + {"diff_custom", SDT_INTLIST | SDT_UINT32 | (sizeof(GameDifficulty)/4) << 16, NULL, &_new_opt.diff, NULL}, + {"currency", SDT_UINT8 | SDT_ONEOFMANY, (void*)21, &_new_opt.currency, "GBP|USD|FF|DM|YEN|PT|FT|ZL|ATS|BEF|DKK|FIM|GRD|CHF|NLG|ITL|SEK|RUR|CZK|ISK|NOK|EUR|ROL" }, + {"distances", SDT_UINT8 | SDT_ONEOFMANY, (void*)1, &_new_opt.kilometers, "imperial|metric" }, + {"town_names", SDT_UINT8 | SDT_ONEOFMANY, (void*)0, &_new_opt.town_name, "english|french|german|american|latin|silly|swedish|dutch|finnish|polish|slovakish|hungarian|romanian|czech" }, + {"landscape", SDT_UINT8 | SDT_ONEOFMANY, (void*)0, &_new_opt.landscape, "normal|hilly|desert|candy" }, + {"autosave", SDT_UINT8 | SDT_ONEOFMANY, (void*)1, &_new_opt.autosave, "off|monthly|quarterly|half year|yearly" }, + {"road_side", SDT_UINT8 | SDT_ONEOFMANY, (void*)1, &_new_opt.road_side, "left|right" }, {NULL, 0, NULL, NULL, NULL} }; -static const SettingDesc patch_settings[] = { - {"vehicle_speed", SDT_BOOL, (void*)true, (void*)offsetof(Patches, vehicle_speed), NULL}, - {"build_on_slopes", SDT_BOOL, (void*)true, (void*)offsetof(Patches, build_on_slopes), NULL}, - {"mammoth_trains", SDT_BOOL, (void*)true, (void*)offsetof(Patches, mammoth_trains), NULL}, - {"join_stations", SDT_BOOL, (void*)true, (void*)offsetof(Patches, join_stations), NULL}, - {"station_spread", SDT_UINT8, (void*)12, (void*)offsetof(Patches, station_spread), NULL}, - {"full_load_any", SDT_BOOL, (void*)true, (void*)offsetof(Patches, full_load_any), NULL}, - {"improved_load", SDT_BOOL, (void*)false, (void*)offsetof(Patches, improved_load), NULL}, - {"order_review_system", SDT_UINT8, (void*)2, (void*)offsetof(Patches, order_review_system), NULL}, - - {"inflation", SDT_BOOL, (void*)true, (void*)offsetof(Patches, inflation), NULL}, - {"selectgoods", SDT_BOOL, (void*)true, (void*)offsetof(Patches, selectgoods), NULL}, - {"longbridges", SDT_BOOL, (void*)false, (void*)offsetof(Patches, longbridges), NULL}, - {"gotodepot", SDT_BOOL, (void*)true, (void*)offsetof(Patches, gotodepot), NULL}, - - {"build_rawmaterial_ind", SDT_BOOL, (void*)false, (void*)offsetof(Patches, build_rawmaterial_ind),NULL}, - {"multiple_industry_per_town",SDT_BOOL, (void*)false, (void*)offsetof(Patches, multiple_industry_per_town), NULL}, - {"same_industry_close", SDT_BOOL, (void*)false, (void*)offsetof(Patches, same_industry_close), NULL}, - - {"lost_train_days", SDT_UINT16, (void*)180, (void*)offsetof(Patches, lost_train_days), NULL}, - {"train_income_warn", SDT_BOOL, (void*)true, (void*)offsetof(Patches, train_income_warn), NULL}, - - {"status_long_date", SDT_BOOL, (void*)true, (void*)offsetof(Patches, status_long_date), NULL}, - {"signal_side", SDT_BOOL, (void*)true, (void*)offsetof(Patches, signal_side), NULL}, - {"show_finances", SDT_BOOL, (void*)true, (void*)offsetof(Patches, show_finances), NULL}, - - {"new_nonstop", SDT_BOOL, (void*)false, (void*)offsetof(Patches, new_nonstop), NULL}, - {"roadveh_queue", SDT_BOOL, (void*)false, (void*)offsetof(Patches, roadveh_queue), NULL}, - - {"autoscroll", SDT_BOOL, (void*)false, (void*)offsetof(Patches, autoscroll), NULL}, - {"errmsg_duration", SDT_UINT8, (void*)5, (void*)offsetof(Patches, errmsg_duration), NULL}, - {"snow_line_height", SDT_UINT8, (void*)7, (void*)offsetof(Patches, snow_line_height), NULL}, - - {"bribe", SDT_BOOL, (void*)false, (void*)offsetof(Patches, bribe), NULL}, - {"new_depot_finding", SDT_BOOL, (void*)false, (void*)offsetof(Patches, new_depot_finding), NULL}, - - {"nonuniform_stations", SDT_BOOL, (void*)false, (void*)offsetof(Patches, nonuniform_stations), NULL}, - {"always_small_airport",SDT_BOOL, (void*)false, (void*)offsetof(Patches, always_small_airport), NULL}, - {"realistic_acceleration",SDT_BOOL, (void*)false, (void*)offsetof(Patches, realistic_acceleration), NULL}, - - {"toolbar_pos", SDT_UINT8, (void*)0, (void*)offsetof(Patches, toolbar_pos), NULL}, - {"window_snap_radius", SDT_UINT8, (void*)10, (void*)offsetof(Patches, window_snap_radius), NULL}, +// The player-based settings (are not send over the network) +// Not everything can just be added to this list. For example, service_interval +// can not be done, because every client assigns the service_interval value to the +// v->service_interval, meaning that every client assigns his value to the interval. +// If the setting was player-based, that would mean that vehicles could deside on +// different moments that they are heading back to a service depot, causing desyncs +// on a massive scale. +// Short, you can only add settings that does stuff for the screen, GUI, that kind +// of stuff. +static const SettingDesc patch_player_settings[] = { + {"vehicle_speed", SDT_BOOL, (void*)true, &_patches.vehicle_speed, NULL}, - {"max_trains", SDT_UINT8, (void*)80, (void*)offsetof(Patches, max_trains), NULL}, - {"max_roadveh", SDT_UINT8, (void*)80, (void*)offsetof(Patches, max_roadveh), NULL}, - {"max_aircraft", SDT_UINT8, (void*)40, (void*)offsetof(Patches, max_aircraft), NULL}, - {"max_ships", SDT_UINT8, (void*)50, (void*)offsetof(Patches, max_ships), NULL}, - - {"servint_ispercent", SDT_BOOL, (void*)false, (void*)offsetof(Patches, servint_ispercent), NULL}, - {"servint_trains", SDT_UINT16, (void*)150, (void*)offsetof(Patches, servint_trains), NULL}, - {"servint_roadveh", SDT_UINT16, (void*)150, (void*)offsetof(Patches, servint_roadveh), NULL}, - {"servint_ships", SDT_UINT16, (void*)360, (void*)offsetof(Patches, servint_ships), NULL}, - {"servint_aircraft", SDT_UINT16, (void*)100, (void*)offsetof(Patches, servint_aircraft), NULL}, - - {"autorenew", SDT_BOOL, (void*)false, (void*)offsetof(Patches, autorenew), NULL}, - {"autorenew_months", SDT_INT16, (void*)-6, (void*)offsetof(Patches, autorenew_months), NULL}, - {"autorenew_money", SDT_INT32, (void*)100000,(void*)offsetof(Patches, autorenew_money), NULL}, - - {"new_pathfinding", SDT_BOOL, (void*)false, (void*)offsetof(Patches, new_pathfinding), NULL}, - {"pf_maxlength", SDT_UINT16, (void*)512, (void*)offsetof(Patches, pf_maxlength), NULL}, - {"pf_maxdepth", SDT_UINT8, (void*)16, (void*)offsetof(Patches, pf_maxdepth), NULL}, - + {"lost_train_days", SDT_UINT16, (void*)180, &_patches.lost_train_days, NULL}, + {"train_income_warn", SDT_BOOL, (void*)true, &_patches.train_income_warn, NULL}, + {"order_review_system", SDT_UINT8, (void*)2, &_patches.order_review_system, NULL}, - {"ai_disable_veh_train",SDT_BOOL, (void*)false, (void*)offsetof(Patches, ai_disable_veh_train), NULL}, - {"ai_disable_veh_roadveh",SDT_BOOL, (void*)false, (void*)offsetof(Patches, ai_disable_veh_roadveh), NULL}, - {"ai_disable_veh_aircraft",SDT_BOOL,(void*)false, (void*)offsetof(Patches, ai_disable_veh_aircraft),NULL}, - {"ai_disable_veh_ship", SDT_BOOL, (void*)false, (void*)offsetof(Patches, ai_disable_veh_ship), NULL}, - {"starting_date", SDT_UINT32, (void*)1950, (void*)offsetof(Patches, starting_date), NULL}, - - {"colored_news_date", SDT_UINT32, (void*)2000, (void*)offsetof(Patches, colored_news_date), NULL}, - - {"bridge_pillars", SDT_BOOL, (void*)true, (void*)offsetof(Patches, bridge_pillars), NULL}, - {"invisible_trees", SDT_BOOL, (void*)false, (void*)offsetof(Patches, invisible_trees), NULL}, - - {"keep_all_autosave", SDT_BOOL, (void*)false, (void*)offsetof(Patches, keep_all_autosave), NULL}, - - {"extra_dynamite", SDT_BOOL, (void*)false, (void*)offsetof(Patches, extra_dynamite), NULL}, + {"status_long_date", SDT_BOOL, (void*)true, &_patches.status_long_date, NULL}, + {"show_finances", SDT_BOOL, (void*)true, &_patches.show_finances, NULL}, + {"autoscroll", SDT_BOOL, (void*)false, &_patches.autoscroll, NULL}, + {"errmsg_duration", SDT_UINT8, (void*)5, &_patches.errmsg_duration, NULL}, + {"toolbar_pos", SDT_UINT8, (void*)0, &_patches.toolbar_pos, NULL}, + {"keep_all_autosave", SDT_BOOL, (void*)false, &_patches.keep_all_autosave, NULL}, - {"never_expire_vehicles",SDT_BOOL, (void*)false, (void*)offsetof(Patches, never_expire_vehicles),NULL}, - {"extend_vehicle_life", SDT_UINT8, (void*)0, (void*)offsetof(Patches, extend_vehicle_life), NULL}, - - {"auto_euro", SDT_BOOL, (void*)true, (void*)offsetof(Patches, auto_euro), NULL}, - - {"serviceathelipad", SDT_BOOL, (void*)true, (void*)offsetof(Patches, serviceathelipad), NULL}, - {"smooth_economy", SDT_BOOL, (void*)false, (void*)offsetof(Patches, smooth_economy), NULL}, - {"dist_local_authority",SDT_UINT8, (void*)20, (void*)offsetof(Patches, dist_local_authority), NULL}, - - {"wait_oneway_signal", SDT_UINT8, (void*)15, (void*)offsetof(Patches, wait_oneway_signal), NULL}, - {"wait_twoway_signal", SDT_UINT8, (void*)41, (void*)offsetof(Patches, wait_twoway_signal), NULL}, - - {"ainew_active", SDT_BOOL, (void*)false, (void*)offsetof(Patches, ainew_active), NULL}, - - {"drag_signals_density",SDT_UINT8, (void*)4, (void*)offsetof(Patches, drag_signals_density), NULL}, + {"bridge_pillars", SDT_BOOL, (void*)true, &_patches.bridge_pillars, NULL}, + {"invisible_trees", SDT_BOOL, (void*)false, &_patches.invisible_trees, NULL}, + {"drag_signals_density",SDT_UINT8, (void*)4, &_patches.drag_signals_density, NULL}, {NULL, 0, NULL, NULL, NULL} }; -typedef void SettingDescProc(IniFile *ini, const SettingDesc *desc, const void *grpname, void *base); +// Non-static, needed in network_server.c +const SettingDesc patch_settings[] = { + {"build_on_slopes", SDT_BOOL, (void*)true, &_patches.build_on_slopes, NULL}, + {"mammoth_trains", SDT_BOOL, (void*)true, &_patches.mammoth_trains, NULL}, + {"join_stations", SDT_BOOL, (void*)true, &_patches.join_stations, NULL}, + {"station_spread", SDT_UINT8, (void*)12, &_patches.station_spread, NULL}, + {"full_load_any", SDT_BOOL, (void*)true, &_patches.full_load_any, NULL}, + + {"inflation", SDT_BOOL, (void*)true, &_patches.inflation, NULL}, + {"selectgoods", SDT_BOOL, (void*)true, &_patches.selectgoods, NULL}, + {"longbridges", SDT_BOOL, (void*)true, &_patches.longbridges, NULL}, + {"gotodepot", SDT_BOOL, (void*)true, &_patches.gotodepot, NULL}, + + {"build_rawmaterial_ind", SDT_BOOL, (void*)false, &_patches.build_rawmaterial_ind,NULL}, + {"multiple_industry_per_town",SDT_BOOL, (void*)false, &_patches.multiple_industry_per_town, NULL}, + {"same_industry_close", SDT_BOOL, (void*)false, &_patches.same_industry_close, NULL}, + + {"signal_side", SDT_BOOL, (void*)true, &_patches.signal_side, NULL}, + + {"new_nonstop", SDT_BOOL, (void*)false, &_patches.new_nonstop, NULL}, + {"roadveh_queue", SDT_BOOL, (void*)true, &_patches.roadveh_queue, NULL}, + {"window_snap_radius", SDT_UINT8, (void*)10, &_patches.window_snap_radius, NULL}, + + {"snow_line_height", SDT_UINT8, (void*)7, &_patches.snow_line_height, NULL}, + + {"bribe", SDT_BOOL, (void*)true, &_patches.bribe, NULL}, + {"new_depot_finding", SDT_BOOL, (void*)false, &_patches.new_depot_finding, NULL}, + + {"nonuniform_stations", SDT_BOOL, (void*)true, &_patches.nonuniform_stations, NULL}, + {"always_small_airport",SDT_BOOL, (void*)false, &_patches.always_small_airport, NULL}, + {"realistic_acceleration",SDT_BOOL, (void*)false, &_patches.realistic_acceleration, NULL}, + + {"max_trains", SDT_UINT8, (void*)80, &_patches.max_trains, NULL}, + {"max_roadveh", SDT_UINT8, (void*)80, &_patches.max_roadveh, NULL}, + {"max_aircraft", SDT_UINT8, (void*)40, &_patches.max_aircraft, NULL}, + {"max_ships", SDT_UINT8, (void*)50, &_patches.max_ships, NULL}, + + {"servint_ispercent", SDT_BOOL, (void*)false, &_patches.servint_ispercent, NULL}, + {"servint_trains", SDT_UINT16, (void*)150, &_patches.servint_trains, NULL}, + {"servint_roadveh", SDT_UINT16, (void*)150, &_patches.servint_roadveh, NULL}, + {"servint_ships", SDT_UINT16, (void*)360, &_patches.servint_ships, NULL}, + {"servint_aircraft", SDT_UINT16, (void*)100, &_patches.servint_aircraft, NULL}, + + {"autorenew", SDT_BOOL, (void*)false, &_patches.autorenew, NULL}, + {"autorenew_months", SDT_INT16, (void*)-6, &_patches.autorenew_months, NULL}, + {"autorenew_money", SDT_INT32, (void*)100000,&_patches.autorenew_money, NULL}, + + {"new_pathfinding", SDT_BOOL, (void*)true, &_patches.new_pathfinding, NULL}, + {"pf_maxlength", SDT_UINT16, (void*)512, &_patches.pf_maxlength, NULL}, + {"pf_maxdepth", SDT_UINT8, (void*)16, &_patches.pf_maxdepth, NULL}, + + + {"ai_disable_veh_train",SDT_BOOL, (void*)false, &_patches.ai_disable_veh_train, NULL}, + {"ai_disable_veh_roadveh",SDT_BOOL, (void*)false, &_patches.ai_disable_veh_roadveh, NULL}, + {"ai_disable_veh_aircraft",SDT_BOOL,(void*)false, &_patches.ai_disable_veh_aircraft,NULL}, + {"ai_disable_veh_ship", SDT_BOOL, (void*)false, &_patches.ai_disable_veh_ship, NULL}, + {"starting_date", SDT_UINT32, (void*)1950, &_patches.starting_date, NULL}, + + {"colored_news_date", SDT_UINT32, (void*)2000, &_patches.colored_news_date, NULL}, + + {"extra_dynamite", SDT_BOOL, (void*)false, &_patches.extra_dynamite, NULL}, + + {"never_expire_vehicles",SDT_BOOL, (void*)false, &_patches.never_expire_vehicles,NULL}, + {"extend_vehicle_life", SDT_UINT8, (void*)0, &_patches.extend_vehicle_life, NULL}, + + {"auto_euro", SDT_BOOL, (void*)true, &_patches.auto_euro, NULL}, + + {"serviceathelipad", SDT_BOOL, (void*)true, &_patches.serviceathelipad, NULL}, + {"smooth_economy", SDT_BOOL, (void*)true, &_patches.smooth_economy, NULL}, + {"dist_local_authority",SDT_UINT8, (void*)20, &_patches.dist_local_authority, NULL}, + + {"wait_oneway_signal", SDT_UINT8, (void*)15, &_patches.wait_oneway_signal, NULL}, + {"wait_twoway_signal", SDT_UINT8, (void*)41, &_patches.wait_twoway_signal, NULL}, + + {"ainew_active", SDT_BOOL, (void*)false, &_patches.ainew_active, NULL}, + + {NULL, 0, NULL, NULL, NULL} +}; + +typedef void SettingDescProc(IniFile *ini, const SettingDesc *desc, const void *grpname); static void HandleSettingDescs(IniFile *ini, SettingDescProc *proc) { - proc(ini, misc_settings, "misc", NULL); - proc(ini, win32_settings, "win32", NULL); - proc(ini, network_settings, "network", NULL); - proc(ini, music_settings, "music", &msf); - proc(ini, gameopt_settings, "gameopt", &_new_opt); - proc(ini, patch_settings, "patches", &_patches); + proc(ini, misc_settings, "misc"); + proc(ini, win32_settings, "win32"); +#ifdef ENABLE_NETWORK + proc(ini, network_settings, "network"); +#endif /* ENABLE_NETWORK */ + proc(ini, music_settings, "music"); + proc(ini, gameopt_settings, "gameopt"); + proc(ini, patch_settings, "patches"); + proc(ini, patch_player_settings, "patches"); - proc(ini, debug_settings, "debug", NULL); + proc(ini, debug_settings, "debug"); } static void LoadGrfSettings(IniFile *ini) diff --git a/settings.h b/settings.h new file mode 100644 --- /dev/null +++ b/settings.h @@ -0,0 +1,39 @@ +#ifndef SETTINGS_H +#define SETTINGS_H + +enum SettingDescType { + SDT_INTX, // must be 0 + SDT_ONEOFMANY, + SDT_MANYOFMANY, + SDT_BOOLX, + SDT_STRING, + SDT_STRINGBUF, + SDT_INTLIST, + + SDT_INT8 = 0 << 4, + SDT_UINT8 = 1 << 4, + SDT_INT16 = 2 << 4, + SDT_UINT16 = 3 << 4, + SDT_INT32 = 4 << 4, + SDT_UINT32 = 5 << 4, + SDT_CALLBX = 6 << 4, + + SDT_UINT = SDT_UINT32, + SDT_INT = SDT_INT32, + + SDT_NOSAVE = 1 << 8, + + SDT_CALLB = SDT_INTX | SDT_CALLBX, + + SDT_BOOL = SDT_BOOLX | SDT_UINT8, +}; + +typedef struct SettingDesc { + const char *name; + int flags; + const void *def; + void *ptr; + const void *b; +} SettingDesc; + +#endif /* SETTINGS_H */ diff --git a/settings_gui.c b/settings_gui.c --- a/settings_gui.c +++ b/settings_gui.c @@ -8,6 +8,7 @@ #include "engine.h" #include "screenshot.h" #include "newgrf.h" +#include "network.h" static uint32 _difficulty_click_a; static uint32 _difficulty_click_b; @@ -295,8 +296,6 @@ static inline bool GetBitAndShift(uint32 return (x&1) != 0; } -static GameOptions _opt_mod_temp; - static const int16 _default_game_diff[3][GAME_DIFFICULTY_NUM] = { {2, 2, 1, 3, 300, 2, 0, 2, 0, 1, 2, 0, 1, 0, 0, 0, 0, 0}, {4, 1, 1, 2, 150, 3, 1, 3, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1}, @@ -327,20 +326,29 @@ static void GameDifficultyWndProc(Window w->click_state = (1 << 4) << _opt_mod_temp.diff_level; w->disabled_state = (_game_mode != GM_NORMAL) ? 0 : (1 << 4) | (1 << 5) | (1 << 6) | (1 << 7); + // Disable save-button in multiplayer (and if client) + if (_networking && !_network_server) + w->disabled_state |= (1 << 10); DrawWindowWidgets(w); click_a = _difficulty_click_a; click_b = _difficulty_click_b; + /* XXX - This is most likely the worst way I have ever seen + to disable some buttons and to enable others. + What the value means, is this: + if bit1 is enabled, setting 1 is disabled + then it is shifted to the left, and the story + repeats.... + -- TrueLight */ disabled = _game_mode == GM_NORMAL ? 0x383E : 0; - // XXX x = 0; y = 32; for (i = 0; i != GAME_DIFFICULTY_NUM; i++) { DrawFrameRect(x+5, y+1, x+5+9, y+9, 3, GetBitAndShift(&click_a)?0x20:0); DrawFrameRect(x+15, y+1, x+15+9, y+9, 3, GetBitAndShift(&click_b)?0x20:0); - if (GetBitAndShift(&disabled)) { + if (GetBitAndShift(&disabled) || (_networking && !_network_server)) { int color = 0x8000 | _color_list[3].unk2; GfxFillRect(x+6, y+2, x+6+8, y+9, color); GfxFillRect(x+16, y+2, x+16+8, y+9, color); @@ -367,6 +375,10 @@ static void GameDifficultyWndProc(Window int val; const GameSettingData *info; + // Don't allow clients to make any changes + if (_networking && !_network_server) + return; + x = e->click.pt.x - 5; if (!IS_INT_INSIDE(x, 0, 21)) return; @@ -563,19 +575,21 @@ enum { PF_0ISDIS = 1, PF_NOCOMMA = 2, PF_MULTISTRING = 4, + PF_PLAYERBASED = 8, // This has to match the entries that are in settings.c, patch_player_settings }; static const PatchEntry _patches_ui[] = { - {PE_BOOL, 0, STR_CONFIG_PATCHES_VEHICLESPEED, &_patches.vehicle_speed, 0, 0, 0, NULL}, - {PE_BOOL, 0, STR_CONFIG_PATCHES_LONGDATE, &_patches.status_long_date, 0, 0, 0, NULL}, - {PE_BOOL, 0, STR_CONFIG_PATCHES_SHOWFINANCES, &_patches.show_finances, 0, 0, 0, NULL}, - {PE_BOOL, 0, STR_CONFIG_PATCHES_AUTOSCROLL, &_patches.autoscroll, 0, 0, 0, NULL}, + {PE_BOOL, PF_PLAYERBASED, STR_CONFIG_PATCHES_VEHICLESPEED, &_patches.vehicle_speed, 0, 0, 0, NULL}, + {PE_BOOL, PF_PLAYERBASED, STR_CONFIG_PATCHES_LONGDATE, &_patches.status_long_date, 0, 0, 0, NULL}, + {PE_BOOL, PF_PLAYERBASED, STR_CONFIG_PATCHES_SHOWFINANCES, &_patches.show_finances, 0, 0, 0, NULL}, + {PE_BOOL, PF_PLAYERBASED, STR_CONFIG_PATCHES_AUTOSCROLL, &_patches.autoscroll, 0, 0, 0, NULL}, - {PE_UINT8, 0, STR_CONFIG_PATCHES_ERRMSG_DURATION, &_patches.errmsg_duration, 0, 20, 1, NULL}, + {PE_UINT8, PF_PLAYERBASED, STR_CONFIG_PATCHES_ERRMSG_DURATION, &_patches.errmsg_duration, 0, 20, 1, NULL}, {PE_UINT8, PF_MULTISTRING, STR_CONFIG_PATCHES_TOOLBAR_POS, &_patches.toolbar_pos, 0, 2, 1, &v_PositionMainToolbar}, - {PE_UINT8, PF_0ISDIS, STR_CONFIG_PATCHES_SNAP_RADIUS, &_patches.window_snap_radius, 1, 32, 1, NULL}, - {PE_BOOL, 0, STR_CONFIG_PATCHES_INVISIBLE_TREES, &_patches.invisible_trees, 0, 1, 1, &InvisibleTreesActive}, + {PE_UINT8, PF_MULTISTRING | PF_PLAYERBASED, STR_CONFIG_PATCHES_TOOLBAR_POS, &_patches.toolbar_pos, 0, 2, 1, &v_PositionMainToolbar}, + {PE_UINT8, PF_0ISDIS, STR_CONFIG_PATCHES_SNAP_RADIUS, &_patches.window_snap_radius, 1, 32, 1, NULL}, + {PE_BOOL, PF_PLAYERBASED, STR_CONFIG_PATCHES_INVISIBLE_TREES, &_patches.invisible_trees, 0, 1, 1, &InvisibleTreesActive}, }; static const PatchEntry _patches_construction[] = { @@ -585,7 +599,7 @@ static const PatchEntry _patches_constru {PE_BOOL, 0, STR_CONFIG_PATCHES_SIGNALSIDE, &_patches.signal_side, 0, 0, 0, NULL}, {PE_BOOL, 0, STR_CONFIG_PATCHES_SMALL_AIRPORTS, &_patches.always_small_airport, 0, 0, 0, NULL}, - {PE_UINT8, 0, STR_CONFIG_PATCHES_DRAG_SIGNALS_DENSITY, &_patches.drag_signals_density, 1, 20, 1, NULL}, + {PE_UINT8, PF_PLAYERBASED, STR_CONFIG_PATCHES_DRAG_SIGNALS_DENSITY, &_patches.drag_signals_density, 1, 20, 1, NULL}, }; @@ -597,11 +611,11 @@ static const PatchEntry _patches_vehicle {PE_BOOL, 0, STR_CONFIG_PATCHES_NEW_DEPOT_FINDING,&_patches.new_depot_finding, 0, 0, 0, NULL}, {PE_BOOL, 0, STR_CONFIG_PATCHES_NEW_TRAIN_PATHFIND, &_patches.new_pathfinding, 0, 0, 0, NULL}, - {PE_BOOL, 0, STR_CONFIG_PATCHES_WARN_INCOME_LESS, &_patches.train_income_warn, 0, 0, 0, NULL}, - {PE_UINT8, PF_MULTISTRING, STR_CONFIG_PATCHES_ORDER_REVIEW,&_patches.order_review_system,0,2, 1, NULL}, + {PE_BOOL, PF_PLAYERBASED, STR_CONFIG_PATCHES_WARN_INCOME_LESS, &_patches.train_income_warn, 0, 0, 0, NULL}, + {PE_UINT8, PF_MULTISTRING | PF_PLAYERBASED, STR_CONFIG_PATCHES_ORDER_REVIEW,&_patches.order_review_system,0,2, 1, NULL}, {PE_BOOL, 0, STR_CONFIG_PATCHES_NEVER_EXPIRE_VEHICLES, &_patches.never_expire_vehicles,0,0,0, NULL}, - {PE_UINT16, PF_0ISDIS, STR_CONFIG_PATCHES_LOST_TRAIN_DAYS, &_patches.lost_train_days, 180,720, 60, NULL}, + {PE_UINT16, PF_0ISDIS | PF_PLAYERBASED, STR_CONFIG_PATCHES_LOST_TRAIN_DAYS, &_patches.lost_train_days, 180,720, 60, NULL}, {PE_BOOL, 0, STR_CONFIG_PATCHES_AUTORENEW_VEHICLE,&_patches.autorenew, 0, 0, 0, NULL}, {PE_INT16, 0, STR_CONFIG_PATCHES_AUTORENEW_MONTHS, &_patches.autorenew_months, -12, 12, 1, NULL}, {PE_CURRENCY, 0, STR_CONFIG_PATCHES_AUTORENEW_MONEY,&_patches.autorenew_money, 0, 2000000, 100000, NULL}, @@ -728,8 +742,7 @@ static void WritePE(const PatchEntry *pe *(int32*)pe->variable = val; break; - case PE_CURRENCY: val /= GetCurrentCurrencyRate(); - if ((int64)val > (int64)pe->max) + case PE_CURRENCY: if ((int64)val > (int64)pe->max) *(int64*)pe->variable = (int64)pe->max; else if ((int64)val < (int64)pe->min) *(int64*)pe->variable = (int64)pe->min; @@ -762,12 +775,24 @@ static void PatchesSelectionWndProc(Wind page = &_patches_page[WP(w,def_d).data_1]; for(i=0,pe=page->entries; i!=page->num; i++,pe++) { bool disabled = false; + bool editable = true; + // We do not allow changes of some items when we are a client in a networkgame + if (!(pe->flags & PF_PLAYERBASED) && _networking && !_network_server) + editable = false; if (pe->type == PE_BOOL) { - DrawFrameRect(x+5, y+1, x+15+9, y+9, (*(bool*)pe->variable)?6:4, (*(bool*)pe->variable)?0x20:0); + if (editable) + DrawFrameRect(x+5, y+1, x+15+9, y+9, (*(bool*)pe->variable)?6:4, (*(bool*)pe->variable)?0x20:0); + else + DrawFrameRect(x+5, y+1, x+15+9, y+9, (*(bool*)pe->variable)?7:9, (*(bool*)pe->variable)?0x20:0); SetDParam(0, *(bool*)pe->variable ? STR_CONFIG_PATCHES_ON : STR_CONFIG_PATCHES_OFF); } else { DrawFrameRect(x+5, y+1, x+5+9, y+9, 3, clk == i*2+1 ? 0x20 : 0); DrawFrameRect(x+15, y+1, x+15+9, y+9, 3, clk == i*2+2 ? 0x20 : 0); + if (!editable) { + int color = 0x8000 | _color_list[3].unk2; + GfxFillRect(x+6, y+2, x+6+8, y+9, color); + GfxFillRect(x+16, y+2, x+16+8, y+9, color); + } DrawStringCentered(x+10, y+1, STR_6819, 0); DrawStringCentered(x+20, y+1, STR_681A, 0); @@ -816,6 +841,9 @@ static void PatchesSelectionWndProc(Wind x = e->click.pt.x - 5; if (x < 0) return; + if (!(pe->flags & PF_PLAYERBASED) && _networking && !_network_server) + return; + if (x < 21) { // clicked on the icon on the left side. Either scroller or bool on/off int32 val = ReadPE(pe), oval = val; @@ -859,7 +887,17 @@ static void PatchesSelectionWndProc(Wind break; } if (val != oval) { - WritePE(pe, val); + // To make patch-changes network-safe + if (pe->type == PE_CURRENCY) { + val /= GetCurrentCurrencyRate(); + } + // If an item is playerbased, we do not send it over the network (if any) + if (pe->flags & PF_PLAYERBASED) { + WritePE(pe, val); + } else { + // Else we do + DoCommandP(0, (byte)WP(w,def_d).data_1 + ((byte)btn << 8), val, NULL, CMD_CHANGE_PATCH_SETTING); + } SetWindowDirty(w); if (pe->click_proc != NULL) // call callback function @@ -892,7 +930,18 @@ static void PatchesSelectionWndProc(Wind if (*e->edittext.str) { const PatchPage *page = &_patches_page[WP(w,def_d).data_1]; const PatchEntry *pe = &page->entries[WP(w,def_d).data_3]; - WritePE(pe, atoi(e->edittext.str)); + int32 val; + val = atoi(e->edittext.str); + if (pe->type == PE_CURRENCY) { + val /= GetCurrentCurrencyRate(); + } + // If an item is playerbased, we do not send it over the network (if any) + if (pe->flags & PF_PLAYERBASED) { + WritePE(pe, val); + } else { + // Else we do + DoCommandP(0, (byte)WP(w,def_d).data_1 + ((byte)WP(w,def_d).data_3 << 8), val, NULL, CMD_CHANGE_PATCH_SETTING); + } SetWindowDirty(w); if (pe->click_proc != NULL) // call callback function @@ -907,6 +956,25 @@ static void PatchesSelectionWndProc(Wind } } +int32 CmdChangePatchSetting(int x, int y, uint32 flags, uint32 p1, uint32 p2) +{ + const PatchPage *page; + const PatchEntry *pe; + + if (flags & DC_EXEC) { + page = &_patches_page[(byte)p1]; + if (page == NULL) return 0; + pe = &page->entries[(byte)(p1 >> 8)]; + if (pe == NULL) return 0; + + WritePE(pe, (int32)p2); + + InvalidateWindow(WC_GAME_OPTIONS, 0); + } + + return 0; +} + static const Widget _patches_selection_widgets[] = { { WWT_CLOSEBOX, 10, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, { WWT_CAPTION, 10, 11, 369, 0, 13, STR_CONFIG_PATCHES_CAPTION, STR_018C_WINDOW_TITLE_DRAG_THIS}, @@ -952,7 +1020,7 @@ static void NewgrfWndProc(Window *w, Win struct GRFFile *c = _first_grffile; DrawWindowWidgets(w); - + if (_first_grffile == NULL) { // no grf sets installed DrawStringMultiCenter(140, 210, STR_NEWGRF_NO_FILES_INSTALLED, 250); break; @@ -963,13 +1031,13 @@ static void NewgrfWndProc(Window *w, Win if (i >= w->vscroll.pos) { // draw files according to scrollbar position bool h = (_sel_grffile==c); // show highlighted item with a different background and highlighted text - if(h) GfxFillRect(1, y + 1, 267, y + 12, 156); + if(h) GfxFillRect(1, y + 1, 267, y + 12, 156); // XXX - will be grf name later - DoDrawString(c->filename, 25, y + 2, h ? 0xC : 0x10); + DoDrawString(c->filename, 25, y + 2, h ? 0xC : 0x10); DrawSprite(SPRITE_PALETTE(0x2EB | 0x30b8000), 5, y + 3); y += NEWGRF_WND_PROC_ROWSIZE; } - + c = c->next; if (++i == w->vscroll.cap + w->vscroll.pos) break; // stop after displaying 12 items } @@ -982,7 +1050,7 @@ static void NewgrfWndProc(Window *w, Win // draw filename x = DrawString(5, 199, STR_NEWGRF_FILENAME, 0); DoDrawString(_sel_grffile->filename, x + 2, 199, 0x01); - + // draw grf id x = DrawString(5, 209, STR_NEWGRF_GRF_ID, 0); snprintf(_userstring, USERSTRING_LEN, "%08X", _sel_grffile->grfid); diff --git a/ship_gui.c b/ship_gui.c --- a/ship_gui.c +++ b/ship_gui.c @@ -301,7 +301,7 @@ static void ShowShipDetailsWindow(Vehicl w->caption_color = v->owner; } -static void CcBuildShip(bool success, uint tile, uint32 p1, uint32 p2) +void CcBuildShip(bool success, uint tile, uint32 p1, uint32 p2) { Vehicle *v; if (!success) return; diff --git a/station_cmd.c b/station_cmd.c --- a/station_cmd.c +++ b/station_cmd.c @@ -2415,7 +2415,7 @@ int32 CmdRenameStation(int x, int y, uin StringID str,old_str; Station *st; - str = AllocateName((byte*)_decode_parameters, 6); + str = AllocateNameUnique((byte*)_decode_parameters, 6); if (str == 0) return CMD_ERROR; @@ -2654,7 +2654,7 @@ static int32 ClearTile_Station(uint tile void InitializeStations() { int i; - + memset(_stations, 0, sizeof(_stations)); for(i = 0; i != lengthof(_stations); i++) _stations[i].index=i; diff --git a/stdafx.h b/stdafx.h --- a/stdafx.h +++ b/stdafx.h @@ -64,8 +64,8 @@ # define inline _inline # define CDECL _cdecl # define NOT_REACHED() _assume(0) -# define snprintf _snprintf -# define vsnprintf _vsnprintf +int snprintf(char *str, size_t size, const char *format, ...); +int vsnprintf(char *str, size_t size, const char *format, va_list ap); # undef TTD_ALIGNMENT_4 # undef TTD_ALIGNMENT_2 # define GCC_PACK @@ -97,6 +97,10 @@ typedef unsigned int uint32; #if !defined(UNIX) && !defined(__CYGWIN__) typedef unsigned int uint; #endif +// Not defined in QNX Neutrino (6.x) +#if defined(__QNXNTO__) + typedef unsigned int uint; +#endif #ifndef __BEOS__ typedef signed char int8; @@ -175,6 +179,7 @@ assert_compile(sizeof(uint8) == 1); #define GetString OTTD_GetString #define DrawString OTTD_DrawString #define Random OTTD_Random +#define CloseConnection OTTD_CloseConnection #endif #endif // !defined(_STDAFX_H) diff --git a/texteff.c b/texteff.c --- a/texteff.c +++ b/texteff.c @@ -3,6 +3,9 @@ #include "gfx.h" #include "viewport.h" #include "saveload.h" +#include "hal.h" +#include "console.h" +#include /* va_list */ typedef struct TextEffect { StringID string_id; @@ -12,9 +15,187 @@ typedef struct TextEffect { uint32 params_2; } TextEffect; +#define MAX_TEXTMESSAGE_LENGTH 250 + +typedef struct TextMessage { + char message[MAX_TEXTMESSAGE_LENGTH]; + uint16 color; + uint16 end_date; +} TextMessage; + +#define MAX_CHAT_MESSAGES 10 static TextEffect _text_effect_list[30]; +static TextMessage _text_message_list[MAX_CHAT_MESSAGES]; TileIndex _animated_tile_list[256]; + +int _textmessage_width = 0; +bool _textmessage_dirty = true; +bool _textmessage_visible = false; + +const int _textmessage_box_left = 10; // Pixels from left +const int _textmessage_box_y = 150; // Height of box +const int _textmessage_box_bottom = 20; // Pixels from bottom +const int _textmessage_box_max_width = 400; // Max width of box + +static byte _textmessage_backup[150*400]; // (y * max_width) + +extern void memcpy_pitch(void *d, void *s, int w, int h, int spitch, int dpitch); + +// Duration is in game-days +void AddTextMessage(uint16 color, uint8 duration, const char *message, ...) +{ + int i; + char buf[1024]; + char buf2[MAX_TEXTMESSAGE_LENGTH]; + va_list va; + int length; + + va_start(va, message); + vsprintf(buf, message, va); + va_end(va); + + if ((color & 0xFF) == 0xC9) color = 0x1CA; + + length = MAX_TEXTMESSAGE_LENGTH; + snprintf(buf2, length, "%s", buf); + while (GetStringWidth(buf2) > _textmessage_width - 9) { + snprintf(buf2, --length, "%s", buf); + } + + for (i = 0; i < MAX_CHAT_MESSAGES; i++) { + if (_text_message_list[i].message[0] == '\0') { + // Empty spot + snprintf(_text_message_list[i].message, MAX_TEXTMESSAGE_LENGTH, "%s", buf2); + _text_message_list[i].color = color; + _text_message_list[i].end_date = _date + duration; + + _textmessage_dirty = true; + return; + } + } + + // We did not found a free spot, trash the first one, and add to the end + memmove(&_text_message_list[0], &_text_message_list[1], sizeof(TextMessage) * (MAX_CHAT_MESSAGES - 1)); + snprintf(_text_message_list[MAX_CHAT_MESSAGES - 1].message, MAX_TEXTMESSAGE_LENGTH, "%s", buf2); + _text_message_list[MAX_CHAT_MESSAGES - 1].color = color; + _text_message_list[i].end_date = _date + duration; + + _textmessage_dirty = true; +} + +void InitTextMessage() +{ + int i; + for (i = 0; i < MAX_CHAT_MESSAGES; i++) { + _text_message_list[i].message[0] = '\0'; + } + + _textmessage_width = _textmessage_box_max_width; +} + +// Hide the textbox +void UndrawTextMessage() +{ + if (_textmessage_visible) { + // Sometimes we also need to hide the cursor + // This is because both textmessage and the cursor take a shot of the + // screen before drawing. + // Now the textmessage takes his shot and paints his data before the cursor + // does, so in the shot of the cursor is the screen-data of the textmessage + // included when the cursor hangs somewhere over the textmessage. To + // avoid wrong repaints, we undraw the cursor in that case, and everything + // looks nicely ;) + // (and now hope this story above makes sense to you ;)) + + if (_cursor.visible) { + if (_cursor.draw_pos.x + _cursor.draw_size.x >= _textmessage_box_left && + _cursor.draw_pos.x <= _textmessage_box_left + _textmessage_width && + _cursor.draw_pos.y + _cursor.draw_size.y >= _screen.height - _textmessage_box_bottom - _textmessage_box_y && + _cursor.draw_pos.y <= _screen.height - _textmessage_box_bottom) { + UndrawMouseCursor(); + } + } + + _textmessage_visible = false; + // Put our 'shot' back to the screen + memcpy_pitch( + _screen.dst_ptr + _textmessage_box_left + (_screen.height-_textmessage_box_bottom-_textmessage_box_y) * _screen.pitch, + _textmessage_backup, + _textmessage_width, _textmessage_box_y, _textmessage_width, _screen.pitch); + + // And make sure it is updated next time + _video_driver->make_dirty(_textmessage_box_left, _screen.height-_textmessage_box_bottom-_textmessage_box_y, _textmessage_width, _textmessage_box_y); + + _textmessage_dirty = true; + } +} + +// Check if a message is expired every day +void TextMessageDailyLoop() +{ + int i = 0; + while (i < MAX_CHAT_MESSAGES) { + if (_text_message_list[i].message[0] == '\0') break; + if (_date > _text_message_list[i].end_date) { + memmove(&_text_message_list[i], &_text_message_list[i+1], sizeof(TextMessage) * ((MAX_CHAT_MESSAGES - 1) - i)); + _text_message_list[MAX_CHAT_MESSAGES - 1].message[0] = '\0'; + i--; + _textmessage_dirty = true; + } + i++; + } +} + +// Draw the textmessage-box +void DrawTextMessage() +{ + int i, j; + bool has_message; + + if (!_textmessage_dirty) + return; + + // First undraw if needed + UndrawTextMessage(); + + if (_iconsole_mode == ICONSOLE_FULL) + return; + + has_message = false; + for ( i = 0; i < MAX_CHAT_MESSAGES; i++) { + if (_text_message_list[i].message[0] == '\0') break; + has_message = true; + } + if (!has_message) return; + + // Make a copy of the screen as it is before painting (for undraw) + memcpy_pitch( + _textmessage_backup, + _screen.dst_ptr + _textmessage_box_left + (_screen.height-_textmessage_box_bottom-_textmessage_box_y) * _screen.pitch, + _textmessage_width, _textmessage_box_y, _screen.pitch, _textmessage_width); + + // Switch to _screen painting + _cur_dpi = &_screen; + + j = 0; + // Paint the messages + for (i = MAX_CHAT_MESSAGES - 1; i >= 0; i--) { + if (_text_message_list[i].message[0] == '\0') continue; + j++; + GfxFillRect(_textmessage_box_left, _screen.height-_textmessage_box_bottom-j*13-2, _textmessage_box_left+_textmessage_width - 1, _screen.height-_textmessage_box_bottom-j*13+10, /* black, but with some alpha */ 0x4322); + + DoDrawString(_text_message_list[i].message, _textmessage_box_left + 2, _screen.height - _textmessage_box_bottom - j * 13 - 1, 0x10); + DoDrawString(_text_message_list[i].message, _textmessage_box_left + 3, _screen.height - _textmessage_box_bottom - j * 13, _text_message_list[i].color); + } + + // Make sure the data is updated next flush + _video_driver->make_dirty(_textmessage_box_left, _screen.height-_textmessage_box_bottom-_textmessage_box_y, _textmessage_width, _textmessage_box_y); + + _textmessage_visible = true; + _textmessage_dirty = false; +} + static void MarkTextEffectAreaDirty(TextEffect *te) { MarkAllViewportsDirty( diff --git a/town_cmd.c b/town_cmd.c --- a/town_cmd.c +++ b/town_cmd.c @@ -13,6 +13,7 @@ #include "saveload.h" #include "economy.h" #include "gui.h" +#include "network.h" enum { TOWN_HAS_CHURCH = 0x02, @@ -928,7 +929,7 @@ static Town *AllocateTown() Town *t; FOR_ALL_TOWNS(t) { if (t->xy == 0) { - _total_towns++; + if (t->index > _total_towns) _total_towns = t->index; return t; } } @@ -1342,7 +1343,7 @@ int32 CmdRenameTown(int x, int y, uint32 StringID str; Town *t = DEREF_TOWN(p1); - str = AllocateName((byte*)_decode_parameters, 4); + str = AllocateNameUnique((byte*)_decode_parameters, 4); if (str == 0) return CMD_ERROR; @@ -1910,7 +1911,7 @@ static void Load_TOWN() while ((index = SlIterateArray()) != -1) { Town *t = DEREF_TOWN(index); SlObject(t, _town_desc); - _total_towns++; + if (index > _total_towns) _total_towns = index; } } diff --git a/train_gui.c b/train_gui.c --- a/train_gui.c +++ b/train_gui.c @@ -15,7 +15,7 @@ int _traininfo_vehicle_pitch = 0; -static void CcBuildWagon(bool success, uint tile, uint32 p1, uint32 p2) +void CcBuildWagon(bool success, uint tile, uint32 p1, uint32 p2) { Vehicle *v,*found; @@ -43,7 +43,7 @@ static void CcBuildWagon(bool success, u } } -static void CcBuildLoco(bool success, uint tile, uint32 p1, uint32 p2) +void CcBuildLoco(bool success, uint tile, uint32 p1, uint32 p2) { Vehicle *v; diff --git a/ttd.c b/ttd.c --- a/ttd.c +++ b/ttd.c @@ -24,6 +24,7 @@ #include "ai.h" #include "console.h" #include "screenshot.h" +#include "network.h" #include @@ -53,7 +54,7 @@ extern void HalGameLoop(); uint32 _pixels_redrawn; bool _dbg_screen_rect; -bool disable_computer; +bool disable_computer; // We should get ride of this thing.. is only used for a debug-cheat static byte _os_version = 0; void CDECL error(const char *s, ...) { @@ -299,17 +300,21 @@ static void showhelp() p = strecpy(buf, "Command line options:\n" - " -v drv = Set video driver (see below)\n" - " -s drv = Set sound driver (see below)\n" - " -m drv = Set music driver (see below)\n" - " -r res = Set resolution (for instance 800x600)\n" - " -h = Display this help text\n" - " -t date= Set starting date\n" - " -d dbg = Debug mode\n" - " -l lng = Select Language\n" - " -e = Start Editor\n" - " -g = Start new game immediately (can optionally take a game to load)\n" - " -G seed= Set random seed\n" + " -v drv = Set video driver (see below)\n" + " -s drv = Set sound driver (see below)\n" + " -m drv = Set music driver (see below)\n" + " -r res = Set resolution (for instance 800x600)\n" + " -h = Display this help text\n" + " -t date = Set starting date\n" + " -d [dbg] = Debug mode\n" + " -l lng = Select Language\n" + " -e = Start Editor\n" + " -g [savegame] = Start new/save game immediately\n" + " -G seed = Set random seed\n" + " -n [ip#player:port] = Start networkgame\n" + " -D = Start dedicated server\n" + " -i = Ignore wrong grf\n" + " -p #player = Player as #player (deprecated) (network only)\n" ); for(i=0; i!=lengthof(_driver_classes); i++,dc++) { @@ -474,11 +479,40 @@ void ParseResolution(int res[2], char *s res[1] = strtoul(t + 1, NULL, 0); } +void LoadIntroGame() +{ + char filename[256]; + _game_mode = GM_MENU; + _display_opt &= ~DO_TRANS_BUILDINGS; // don't make buildings transparent in intro + + _opt_mod_ptr = &_new_opt; + GfxLoadSprites(); + LoadStringWidthTable(); + + // Setup main window + InitWindowSystem(); + SetupColorsAndInitialWindow(); + + // Generate a world. + sprintf(filename, "%sopntitle.dat", _path.data_dir); + if (SaveOrLoad(filename, SL_LOAD) != SL_OK) + GenerateWorld(1); // if failed loading, make empty world. + + _opt.currency = _new_opt.currency; + + _pause = 0; + _local_player = 0; + MarkWholeScreenDirty(); + + // Play main theme + if (_music_driver->is_song_playing()) ResetMusic(); +} + int ttd_main(int argc, char* argv[]) { MyGetOptData mgo; int i; - int network = 0; + bool network = false; char *network_conn = NULL; char *language = NULL; char musicdriver[16], sounddriver[16], videodriver[16]; @@ -486,25 +520,31 @@ int ttd_main(int argc, char* argv[]) uint startdate = -1; _ignore_wrong_grf = false; musicdriver[0] = sounddriver[0] = videodriver[0] = 0; - _networking_override=false; _game_mode = GM_MENU; _switch_mode = SM_MENU; _switch_mode_errorstr = INVALID_STRING_ID; - MyGetOptInit(&mgo, argc-1, argv+1, "m:s:v:hn::l:eit:d::r:g::G:cp:"); + // The last param of the following function means this: + // a letter means: it accepts that param (e.g.: -h) + // a ':' behind it means: it need a param (e.g.: -m) + // a '::' behind it means: it can optional have a param (e.g.: -d) + MyGetOptInit(&mgo, argc-1, argv+1, "m:s:v:hDn::l:eit:d::r:g::G:p:"); while ((i = MyGetOpt(&mgo)) != -1) { switch(i) { case 'm': ttd_strlcpy(musicdriver, mgo.opt, sizeof(musicdriver)); break; case 's': ttd_strlcpy(sounddriver, mgo.opt, sizeof(sounddriver)); break; case 'v': ttd_strlcpy(videodriver, mgo.opt, sizeof(videodriver)); break; + case 'D': { + sprintf(musicdriver,"null"); + sprintf(sounddriver,"null"); + sprintf(videodriver,"dedicated"); + } break; case 'n': { - network = 1; - _networking_override=true; - if (mgo.opt) { + network = true; + if (mgo.opt) + // Optional, you can give an IP network_conn = mgo.opt; - network++; - } else network_conn = NULL; } break; @@ -536,7 +576,8 @@ int ttd_main(int argc, char* argv[]) break; case 'p': { int i = atoi(mgo.opt); - if (IS_INT_INSIDE(i, 0, MAX_PLAYERS)) _network_playas = i + 1; + // Play as an other player in network games + if (IS_INT_INSIDE(i, 1, MAX_PLAYERS)) _network_playas = i; break; } case -2: @@ -556,9 +597,6 @@ int ttd_main(int argc, char* argv[]) if (resolution[0]) { _cur_resolution[0] = resolution[0]; _cur_resolution[1] = resolution[1]; } if (startdate != -1) _patches.starting_date = startdate; - // initialize network-core - NetworkCoreInit(); - // enumerate language files InitializeLanguagePacks(); @@ -586,6 +624,11 @@ int ttd_main(int argc, char* argv[]) MusicLoop(); _savegame_sort_order = 1; // default sorting of savegames is by date, newest first +#ifdef ENABLE_NETWORK + // initialize network-core + NetworkStartUp(); +#endif /* ENABLE_NETWORK */ + // Default difficulty level _opt_mod_ptr = &_new_opt; @@ -593,27 +636,45 @@ int ttd_main(int argc, char* argv[]) if (_opt_mod_ptr->diff_level == 9) SetDifficultyLevel(0, _opt_mod_ptr); - if ((network) && (_network_available)) { - NetworkLobbyInit(); - if (network_conn!=NULL) { - NetworkCoreConnectGame(network_conn,_network_server_port); - } else { - NetworkCoreConnectGame("auto",_network_server_port); - } - } - // initialize the ingame console IConsoleInit(); InitPlayerRandoms(); +#ifdef ENABLE_NETWORK + if ((network) && (_network_available)) { + if (network_conn != NULL) { + const byte *port = NULL; + const byte *player = NULL; + uint16 rport; + + rport = NETWORK_DEFAULT_PORT; + + ParseConnectionString(&player, &port, network_conn); + + if (player != NULL) _network_playas = atoi(player); + if (port != NULL) rport = atoi(port); + + LoadIntroGame(); + _switch_mode = SM_NONE; + NetworkClientConnectGame(network_conn, rport); + } else { +// NetworkCoreConnectGame("auto", _network_server_port); + } + } +#endif /* ENABLE_NETWORK */ + while (_video_driver->main_loop() == ML_SWITCHDRIVER) {} IConsoleFree(); +#ifdef ENABLE_NETWORK if (_network_available) { - // shutdown network-core - NetworkCoreShutdown(); - } + // Shut down the network and close any open connections + NetworkDisconnect(); + NetworkUDPClose(); + NetworkShutDown(); + } +#endif /* ENABLE_NETWORK */ _video_driver->stop(); _music_driver->stop(); @@ -638,35 +699,6 @@ static void ShowScreenshotResult(bool b) } -static void LoadIntroGame() -{ - char filename[256]; - _game_mode = GM_MENU; - _display_opt &= ~DO_TRANS_BUILDINGS; // don't make buildings transparent in intro - - _opt_mod_ptr = &_new_opt; - GfxLoadSprites(); - LoadStringWidthTable(); - - // Setup main window - InitWindowSystem(); - SetupColorsAndInitialWindow(); - - // Generate a world. - sprintf(filename, "%sopntitle.dat", _path.data_dir); - if (SaveOrLoad(filename, SL_LOAD) != SL_OK) - GenerateWorld(1); // if failed loading, make empty world. - - _opt.currency = _new_opt.currency; - - _pause = 0; - _local_player = 0; - MarkWholeScreenDirty(); - - // Play main theme - if (_music_driver->is_song_playing()) ResetMusic(); -} - void MakeNewGame() { _game_mode = GM_NORMAL; @@ -686,10 +718,15 @@ void MakeNewGame() // Randomize world GenerateWorld(0); - // Create a single player - DoStartupNewPlayer(false); + // In a dedicated server, the server does not play + if (_network_dedicated) { + _local_player = OWNER_SPECTATOR; + } else { + // Create a single player + DoStartupNewPlayer(false); - _local_player = 0; + _local_player = 0; + } MarkWholeScreenDirty(); } @@ -766,7 +803,7 @@ void StartScenario() MarkWholeScreenDirty(); } -static bool SafeSaveOrLoad(const char *filename, int mode, int newgm) +bool SafeSaveOrLoad(const char *filename, int mode, int newgm) { byte ogm = _game_mode; int r; @@ -788,25 +825,55 @@ static bool SafeSaveOrLoad(const char *f return true; } -static void SwitchMode(int new_mode) +void SwitchMode(int new_mode) { _in_state_game_loop = true; +#ifdef ENABLE_NETWORK + // If we are saving something, the network stays in his current state + if (new_mode != SM_SAVE) { + // If the network is active, make it not-active + if (_networking) { + if (_network_server && (new_mode == SM_LOAD || new_mode == SM_NEWGAME)) { + NetworkReboot(); + NetworkUDPClose(); + } else { + NetworkDisconnect(); + NetworkUDPClose(); + } + } + + // If we are a server, we restart the server + if (_is_network_server) { + // But not if we are going to the menu + if (new_mode != SM_MENU) { + NetworkServerStart(); + } else { + // This client no longer wants to be a network-server + _is_network_server = false; + } + } + } +#endif /* ENABLE_NETWORK */ + switch(new_mode) { case SM_EDITOR: // Switch to scenario editor MakeNewEditorWorld(); break; case SM_NEWGAME: - if (_networking) { NetworkStartSync(true); } // UGLY HACK HACK HACK + if (_network_server) + snprintf(_network_game_info.map_name, 40, "Random"); MakeNewGame(); break; + case SM_START_SCENARIO: + StartScenario(); + break; + normal_load: case SM_LOAD: { // Load game - if (_networking) { NetworkStartSync(true); } // UGLY HACK HACK HACK - _error_message = INVALID_STRING_ID; if (!SafeSaveOrLoad(_file_to_saveload.name, _file_to_saveload.mode, GM_NORMAL)) { ShowErrorMessage(_error_message, STR_4009_GAME_LOAD_FAILED, 0, 0); @@ -814,6 +881,8 @@ normal_load: _opt_mod_ptr = &_opt; _local_player = 0; DoCommandP(0, 0, 0, NULL, CMD_PAUSE); // decrease pause counter (was increased from opening load dialog) + if (_network_server) + snprintf(_network_game_info.map_name, 40, "Loaded game"); } break; } @@ -836,6 +905,9 @@ normal_load: _generating_world = false; // delete all stations owned by a player DeleteAllPlayerStations(); + + if (_network_server) + snprintf(_network_game_info.map_name, 40, "Loaded scenario"); } else ShowErrorMessage(INVALID_STRING_ID, STR_4009_GAME_LOAD_FAILED, 0, 0); @@ -844,10 +916,6 @@ normal_load: case SM_MENU: // Switch to game menu - - if ((_networking) && (!_networking_override)) NetworkCoreDisconnect(); - _networking_override=false; - LoadIntroGame(); break; @@ -877,18 +945,17 @@ normal_load: // The state must not be changed from anywhere // but here. // That check is enforced in DoCommand. -static void StateGameLoop() +void StateGameLoop() { // dont execute the state loop during pause if (_pause) return; _in_state_game_loop = true; - _frame_counter++; - - // store the random seed to be able to detect out of sync errors - _sync_seed_1 = _random_seeds[0][0]; - _sync_seed_2 = _random_seeds[0][1]; - if (_networking) disable_computer=true; + // _frame_counter is increased somewhere else when in network-mode + // Sidenote: _frame_counter is ONLY used for _savedump in non-MP-games + // Should that not be deleted? If so, the next 2 lines can also be deleted + if (!_networking) + _frame_counter++; if (_savedump_path[0] && (uint)_frame_counter >= _savedump_first && (uint)(_frame_counter -_savedump_first) % _savedump_freq == 0 ) { char buf[100]; @@ -915,13 +982,16 @@ static void StateGameLoop() CallVehicleTicks(); CallLandscapeTick(); - if (!disable_computer) + // To bad the AI does not work in multiplayer, because states are not saved + // perfectly + if (!disable_computer && !_networking) RunOtherPlayersLoop(); CallWindowTickEvent(); NewsLoop(); _current_player = p; } + _in_state_game_loop = false; } @@ -992,43 +1062,25 @@ void GameLoop() _timer_counter+=8; CursorTick(); - // incomming packets - NetworkCoreLoop(true); +#ifdef ENABLE_NETWORK + // Check for UDP stuff + NetworkUDPGameLoop(); - if (_networking_sync) { - // client: make sure client's time is synched to the server by running frames quickly up to where the server is. - if (!_networking_server) { - while (_frame_counter < _frame_counter_srv) { - NetworkCoreLoop(true); - StateGameLoop(); - NetworkProcessCommands(); // need to process queue to make sure that packets get executed. - NetworkCoreLoop(false); - } - // client: don't exceed the max count told by the server - if (_frame_counter < _frame_counter_max) { - StateGameLoop(); - NetworkProcessCommands(); - } - // client: send the ready packet - NetworkSendReadyPacket(); - } else { - // server: work on to the frame max - if (_frame_counter < _frame_counter_max) { - StateGameLoop(); - NetworkProcessCommands(); // to check if we got any new commands belonging to the current frame before we increase it. - NetworkSendFrameSyncPackets(); - } - // server: wait until all clients were ready for going on - if (_frame_counter == _frame_counter_max) { - if (NetworkCheckClientReady()) NetworkSendSyncPackets(); - } + if (_networking) { + // Multiplayer + NetworkGameLoop(); + } else { + if (_network_reconnect > 0 && --_network_reconnect == 0) { + // This means that we want to reconnect to the last host + // We do this here, because it means that the network is really closed + NetworkClientConnectGame(_network_last_host, _network_last_port); } - } else { - // server/client/standalone: not synced --> state game loop + // Singleplayer StateGameLoop(); - // server/client: process queued network commands - if (_networking) NetworkProcessCommands(); } +#else + StateGameLoop(); +#endif /* ENABLE_NETWORK */ if (!_pause && _display_opt&DO_FULL_ANIMATION) DoPaletteAnimations(); @@ -1038,10 +1090,6 @@ void GameLoop() MouseLoop(); - // send outgoing packets. - NetworkCoreLoop(false); - - if (_game_mode != GM_MENU) MusicLoop(); } @@ -1174,7 +1222,9 @@ bool AfterLoadGame(uint version) // If Load Scenario / New (Scenario) Game is used, // a player does not exist yet. So create one here. - if (!_players[0].is_active) + // 1 exeption: network-games. Those can have 0 players + // But this exeption is not true for network_servers! + if (!_players[0].is_active && (!_networking || (_networking && _network_server))) DoStartupNewPlayer(false); DoZoomInOutWindow(ZOOM_NONE, w); // update button status diff --git a/ttd.h b/ttd.h --- a/ttd.h +++ b/ttd.h @@ -80,7 +80,7 @@ enum SwitchModes { SM_SAVE = 5, SM_GENRANDLAND = 6, SM_LOAD_SCENARIO = 9, - + SM_START_SCENARIO = 10, }; enum MapTileTypes { @@ -436,6 +436,8 @@ enum { WC_PERFORMANCE_DETAIL = 0x46, WC_CONSOLE = 0x47, WC_EXTRA_VIEW_PORT = 0x48, + WC_CLIENT_LIST = 0x49, + WC_NETWORK_STATUS_WINDOW = 0x4A, }; diff --git a/ttd.vcproj b/ttd.vcproj --- a/ttd.vcproj +++ b/ttd.vcproj @@ -268,6 +268,9 @@ RelativePath=".\aystar.c"> + + @@ -303,6 +306,9 @@ RelativePath=".\console_cmds.c"> + + @@ -570,6 +576,21 @@ RelativePath=".\newgrf.c"> + + + + + + + + + + @@ -1148,6 +1169,24 @@ RelativePath=".\newgrf.h"> + + + + + + + + + + + + u.special.unk2 != 0) { - v->spritenum = (byte)((Random()&3)+1); + v->spritenum = (byte)((InteractiveRandom()&3)+1); } else { v->spritenum = 6; } @@ -1104,7 +1110,7 @@ again: } if (*b == 0x81) { - if (v->z_pos > 180 || CHANCE16(1,96)) { + if (v->z_pos > 180 || CHANCE16I(1,96, InteractiveRandom())) { v->spritenum = 5; SndPlayVehicleFx(SND_2F_POP, v); } @@ -1399,7 +1405,7 @@ int32 CmdNameVehicle(int x, int y, uint3 if (!CheckOwnership(v->owner)) return CMD_ERROR; - str = AllocateName((byte*)_decode_parameters, 2); + str = AllocateNameUnique((byte*)_decode_parameters, 2); if (str == 0) return CMD_ERROR; diff --git a/win32.c b/win32.c --- a/win32.c +++ b/win32.c @@ -11,6 +11,7 @@ #include #include #include +#include "network.h" #define SMART_PALETTE_ANIM @@ -719,6 +720,7 @@ static int Win32GdiMainLoop() Sleep(1); GdiFlush(); _screen.dst_ptr = _wnd.buffer_bits; + DrawTextMessage(); DrawMouseCursor(); } } @@ -1805,6 +1807,7 @@ const DriverDesc _video_driver_descs[] = {"sdl", "SDL Video Driver", &_sdl_video_driver, 1}, #endif {"win32", "Win32 GDI Video Driver", &_win32_video_driver, Windows_NT3_51}, + { "dedicated", "Dedicated Video Driver", &_dedicated_video_driver, 0}, {NULL} }; @@ -1946,12 +1949,12 @@ void CreateConsole() // redirect unbuffered STDIN, STDOUT, STDERR to the console #if !defined(__CYGWIN__) *stdout = *_fdopen( _open_osfhandle((long)hand, _O_TEXT), "w" ); - *stdin = *_fdopen(_open_osfhandle((long)GetStdHandle(STD_INPUT_HANDLE), _O_TEXT), "w" ); + *stdin = *_fdopen(_open_osfhandle((long)GetStdHandle(STD_INPUT_HANDLE), _O_TEXT), "r" ); *stderr = *_fdopen(_open_osfhandle((long)GetStdHandle(STD_ERROR_HANDLE), _O_TEXT), "w" ); #else // open_osfhandle is not in cygwin *stdout = *fdopen(1, "w" ); - *stdin = *fdopen(0, "w" ); + *stdin = *fdopen(0, "r" ); *stderr = *fdopen(2, "w" ); #endif @@ -2052,3 +2055,21 @@ void DeterminePaths() CreateDirectory(_path.scenario_dir, NULL); } +int snprintf(char *str, size_t size, const char *format, ...) +{ + va_list ap; + int ret; + + va_start(ap, format); + ret = vsnprintf(str, size, format, ap); + va_end(ap); + return ret; +} + +int vsnprintf(char *str, size_t size, const char *format, va_list ap) +{ + int ret; + ret = _vsnprintf(str, size, format, ap); + if (ret < 0) str[size - 1] = '\0'; + return ret; +} diff --git a/window.c b/window.c --- a/window.c +++ b/window.c @@ -170,6 +170,7 @@ void DrawOverlappedWindow(Window *w, int void CallWindowEventNP(Window *w, int event) { WindowEvent e; + e.event = event; w->wndproc(w, &e); } @@ -691,7 +692,40 @@ static bool HandlePopupMenu() return false; } -static bool HandleWindowDragging() +bool HandleMouseOver() +{ + Window *w; + WindowEvent e; + static Window *last_w = NULL; + + w = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y); + + // We changed window, put a MOUSEOVER event to the last window + if (last_w && last_w != w) { + e.event = WE_MOUSEOVER; + e.mouseover.pt.x = -1; + e.mouseover.pt.y = -1; + if (last_w->wndproc) + last_w->wndproc(last_w, &e); + } + last_w = w; + + if (w) { + // send an event in client coordinates. + e.event = WE_MOUSEOVER; + e.mouseover.pt.x = _cursor.pos.x - w->left; + e.mouseover.pt.y = _cursor.pos.y - w->top; + if (w->widget != NULL) { + e.mouseover.widget = GetWidgetFromPos(w, e.mouseover.pt.x, e.mouseover.pt.y); + } + w->wndproc(w, &e); + } + + // Mouseover never stops execution + return true; +} + +bool HandleWindowDragging() { Window *w; // Get out immediately if no window is being dragged at all. @@ -1083,6 +1117,9 @@ void MouseLoop() if (!HandleViewportScroll()) return; + if (!HandleMouseOver()) + return; + x = _cursor.pos.x; y = _cursor.pos.y; @@ -1188,6 +1225,7 @@ void UpdateWindows() if (w->viewport != NULL) UpdateViewportPosition(w); } + DrawTextMessage(); // Redraw mouse cursor in case it was hidden DrawMouseCursor(); } diff --git a/window.h b/window.h --- a/window.h +++ b/window.h @@ -55,6 +55,12 @@ union WindowEvent { struct { byte event; + Point pt; + int widget; + } mouseover; + + struct { + byte event; bool cont; // continue the search? (default true) byte ascii; // 8-bit ASCII-value of the key uint16 keycode;// untranslated key (including shift-state) @@ -303,6 +309,8 @@ enum WindowEvents { WE_RCLICK = 17, WE_KEYPRESS = 18, WE_CREATE = 19, + WE_MOUSEOVER = 20, + WE_ON_EDIT_TEXT_CANCEL = 21, };