indexing description: "[ Join Multiplayer Game ]" date: "$Date$" revision: "$Revision$" class BB_JOIN_GAME inherit BB_MENU redefine handle_update_event end EM_SHARED_SUBSYSTEMS export {NONE} all end SHARED_BB_SETTINGS export {NONE} all end EM_NETWORK_HELPER_FUNCTIONS export {ANY} is_valid_ip_string {NONE} all end create make feature -- Initialization make is -- create widgets local button: BB_MENU_BUTTON do init_menu -- create menu items create button.make ("Join Game") button.set_position (550, 530) button.pressed_event.subscribe (agent joingame_button_pressed) add_button (button) create button.make ("Back") button.set_position (50, 530) button.pressed_event.subscribe (agent back_button_pressed) add_button (button) create button.make ("Refresh") button.set_position (550, 200) button.pressed_event.subscribe (agent refresh_button_pressed) add_button (button) create button.make ("Refresh LAN") button.set_position (550, 300) button.pressed_event.subscribe (agent refresh_lan_only_button_pressed) add_button (button) create server_list.make_empty server_list.set_position (20, 200) server_list.set_dimension (500, 200) server_list.set_to_string_agent (agent discovery_response_pretty_printer (?)) add_widget (server_list) create nickname_textbox.make_from_size (Maximum_nickname_length + 3) nickname_textbox.set_position (20, 150) nickname_textbox.text_changed_event.subscribe (agent on_nickname_textbox_changed) nickname_textbox.set_text (bb_settings.nickname) add_widget (nickname_textbox) create nickname_label.make_from_text ("Nickname:") nickname_label.set_font (Standard_ttf_fonts.bitstream_vera_sans (18)) nickname_label.set_foreground_color (create {EM_COLOR}.make_white) nickname_label.set_position (20, 120) nickname_label.resize_to_optimal_dimension add_widget (nickname_label) create status_message.make_from_text ("") status_message.set_font (Standard_ttf_fonts.bitstream_vera_sans (20)) status_message.set_foreground_color (create {EM_COLOR}.make_white) status_message.set_position (50, 450) status_message.resize_to_optimal_dimension add_widget (status_message) create options_panel.make_from_dimension (400, 300) options_panel.set_background_color (create {EM_COLOR}.make_black) options_panel.set_position (175, 175) options_panel.hide add_widget (options_panel) create ok_button.make ("Ok") ok_button.set_font (standard_ttf_fonts.bitstream_vera_sans (22)) ok_button.set_position (240, 425) ok_button.pressed_event.subscribe (agent ok_button_pressed) ok_button.hide add_button (ok_button) create abort_button.make ("Abort") abort_button.set_font (standard_ttf_fonts.bitstream_vera_sans (22)) abort_button.set_position (425, 425) abort_button.pressed_event.subscribe (agent abort_button_pressed) abort_button.hide add_button (abort_button) is_connecting := False -- Setup the scanner client: create scanner.make ("brick_breaker_ce",35701) -- Setup a broadcast group scanner.create_group("broadcast") scanner.create_static_connection(create {EM_INET_SOCKET_ADDRESS}.make_by_ip_string_port("255.255.255.255",Server_port)) scanner.last_created_connection.join (scanner.group ("broadcast")) -- Event subscription scanner.subscribe_by_type_id (scanner.object_types.em_net_server_discovery_response, agent on_server_discovery_response(?)) set_selected_menu_item (1) refresh_button_pressed end join_game (socket_address: EM_INET_SOCKET_ADDRESS) is -- join mulitplayer game do create client.make (Client_port) client.set_server_address (socket_address) client.last_created_connection.set_max_idle_time (11000) client.enable_auto_transmit client.subscribe_by_type_id (client.object_types.em_net_join_response, agent on_successful_join (?)) client.join_timeout_event.subscribe (agent on_join_timeout) client.set_nickname (bb_settings.nickname) client.join is_connecting := True status_message.set_text ("Connecting...") status_message.resize_to_optimal_dimension client.subscribe_by_type_id (client.object_types.bb_mp_options, agent on_options (?)) end feature -- Events on_connecting_failed (unused_agent_argument: DS_LINKED_LIST [EM_NET_CONNECTION]) is -- server did not send the options do client.disable_auto_transmit client.disable_time_sync client.close is_connecting := False status_message.set_text ("Connecting failed") status_message.resize_to_optimal_dimension end on_options (an_options_object: BB_MP_OPTIONS) is local a_label: EM_LABEL file: PLAIN_TEXT_FILE do options := an_options_object create options_panel.make_from_dimension (400, 300) options_panel.set_background_color (create {EM_COLOR}.make_black) options_panel.set_position (175, 175) options_panel.hide add_widget (options_panel) remove_widget (ok_button) remove_widget (abort_button) create file.make ("./levels/multiplayer.ord") -- if player hasn't full level set but the server uses it if not file.exists and options.level_set.is_equal ("Full") then create a_label.make_from_text ("You have to download the full level set %Nto play on this server") a_label.enable_multilined a_label.set_font (Standard_ttf_fonts.bitstream_vera_sans (18)) a_label.set_foreground_color (create {EM_COLOR}.make_with_rgb(255,0,0)) a_label.set_position (20, 80) a_label.resize_to_optimal_dimension options_panel.add_widget (a_label) add_widget (abort_button) abort_button.show else create a_label.make_from_text ("Effect mode: " + options.remote_item_effect_mode) a_label.set_font (Standard_ttf_fonts.bitstream_vera_sans (18)) a_label.set_foreground_color (create {EM_COLOR}.make_white) a_label.set_position (20, 20) a_label.resize_to_optimal_dimension options_panel.add_widget (a_label) create a_label.make_from_text ("Remote effect chance: " + options.remote_item_effect_chance.out + "%%") a_label.set_font (Standard_ttf_fonts.bitstream_vera_sans (18)) a_label.set_foreground_color (create {EM_COLOR}.make_white) a_label.set_position (20, 50) a_label.resize_to_optimal_dimension options_panel.add_widget (a_label) create a_label.make_from_text ("Add block score: " + options.score_for_enemy_block.out) a_label.set_font (Standard_ttf_fonts.bitstream_vera_sans (18)) a_label.set_foreground_color (create {EM_COLOR}.make_white) a_label.set_position (20, 80) a_label.resize_to_optimal_dimension options_panel.add_widget (a_label) create a_label.make_from_text ("Difficulty: " + options.difficulty) a_label.set_font (Standard_ttf_fonts.bitstream_vera_sans (18)) a_label.set_foreground_color (create {EM_COLOR}.make_white) a_label.set_position (20, 110) a_label.resize_to_optimal_dimension options_panel.add_widget (a_label) create a_label.make_from_text ("Win games: " + options.number_of_games_to_win.out) a_label.set_font (Standard_ttf_fonts.bitstream_vera_sans (18)) a_label.set_foreground_color (create {EM_COLOR}.make_white) a_label.set_position (20, 140) a_label.resize_to_optimal_dimension options_panel.add_widget (a_label) create a_label.make_from_text ("Start level: " + options.start_level) a_label.set_font (Standard_ttf_fonts.bitstream_vera_sans (18)) a_label.set_foreground_color (create {EM_COLOR}.make_white) a_label.set_position (20, 170) a_label.resize_to_optimal_dimension options_panel.add_widget (a_label) add_widget (ok_button) add_widget (abort_button) ok_button.show abort_button.show end options_panel.show is_connecting := False status_message.set_text ("Connected") status_message.resize_to_optimal_dimension end on_successful_join (a_join_response: EM_NET_JOIN_RESPONSE) is -- Handle successful join attempt require options_not_void: options /= Void local an_options_request: BB_MP_OPTIONS_REQUEST do if a_join_response.is_accepted then -- request server for the options an_options_request := client.object_types.create_bb_mp_options_request an_options_request.set_resend_time (Resend_time) an_options_request.set_resends_left (Number_of_resends) client.event_2pc_id_manager.set_unique_id_for_object (an_options_request) an_options_request.set_group (client.standard_group) an_options_request.set_timeout_agent (agent on_connecting_failed (?)) an_options_request.publish status_message.set_text ("Requesting options...") status_message.resize_to_optimal_dimension else status_message.set_text ("Game has already started") status_message.resize_to_optimal_dimension client.disable_auto_transmit client.disable_time_sync client.close is_connecting := False end end on_join_timeout is -- Handle join timeout do client.disable_auto_transmit client.disable_time_sync client.close is_connecting := False status_message.set_text ("Connecting failed") status_message.resize_to_optimal_dimension ensure is_not_connecting: not is_connecting end on_server_discovery_response (a_response: EM_NET_SERVER_DISCOVERY_RESPONSE) is -- server discovery response event handling do a_response.set_ping (time.ticks-last_scan_ticks) server_list.put (a_response) end on_nickname_textbox_changed is -- ckeck if nickname is not too long do if nickname_textbox.text.count > Maximum_nickname_length then nickname_textbox.delete_character_left_of_cursor end end on_network_problem (error_code: INTEGER) is -- Handle network problems do io.put_string ("error: ") io.put_integer (error_code) io.put_new_line end on_http_connection_established is -- Handle connection established event. do create http_response.make_empty http.get end on_master_server_data (the_data: STRING) is -- Append data to buffer. do http_response.append_string (the_data) end on_http_connection_failed is -- Handle connection failed event. do is_refresh_in_progress := False status_message.set_text ("Refreshing failed") status_message.resize_to_optimal_dimension end on_http_connection_closed is -- Handle connection closed event. local a_discovery_response: EM_NET_SERVER_DISCOVERY_RESPONSE current_entry: DS_HASH_TABLE[STRING,STRING] do generate_servers from servers.start until servers.after loop current_entry := servers.item_for_iteration if is_current_entry_valid(current_entry) then a_discovery_response := scanner.object_types.create_em_net_server_discovery_response a_discovery_response.set_server_name (current_entry.item("servername")) a_discovery_response.set_game_name (current_entry.item("game")) a_discovery_response.set_ip (convert_ip_string_to_ip(current_entry.item("address"))) a_discovery_response.set_port (current_entry.item("port").to_integer) end on_server_discovery_response (a_discovery_response) servers.forth end is_refresh_in_progress := False status_message.set_text ("") status_message.resize_to_optimal_dimension end joingame_button_pressed is -- join game button pressed do if is_nickname_valid and not is_connecting then bb_settings.set_nickname (nickname_textbox.text) bb_settings.write_user_settings if server_list.has_selected_element then join_game (create {EM_INET_SOCKET_ADDRESS}.make_by_ip_port (server_list.selected_element.ip, server_list.selected_element.port)) else status_message.set_text ("Please select a server") status_message.resize_to_optimal_dimension end elseif not is_nickname_valid then status_message.set_text ("Please choose a valid nickname") status_message.resize_to_optimal_dimension end end back_button_pressed is -- back button pressed do scanner.close if not (client = Void) and client.is_open then client.disable_auto_transmit client.disable_time_sync client.close end set_next_scene (create {BB_MULTIPLAYER}.make) start_next_scene end refresh_button_pressed is -- refresh button pressed do -- looks for lan servers, sets last_scan_ticks and wipes out server_list refresh_lan_only_button_pressed if not is_refresh_in_progress and not is_connecting then is_refresh_in_progress := True status_message.set_text ("Refreshing...") status_message.resize_to_optimal_dimension create http.make http.set_hostname (Masterserver_hostname) http.set_path (Masterserver_path) http.connection_established_event.subscribe (agent on_http_connection_established) http.connection_failed_event.subscribe (agent on_http_connection_failed) http.connection_closed_event.subscribe (agent on_http_connection_closed) http.data_received_event.subscribe (agent on_master_server_data(?)) http.connect end end refresh_lan_only_button_pressed is -- refresh LAN only button pressed local discovery: EM_NET_SERVER_DISCOVERY do if not is_refresh_in_progress and not is_connecting then server_list.wipe_out last_scan_ticks := time.ticks discovery := scanner.object_types.create_em_net_server_discovery discovery.set_game_name (scanner.game_identifier) discovery.set_group (scanner.group ("broadcast")) discovery.publish end end ok_button_pressed is -- ok button was pressed do client.unsubscribe_by_type_id (client.object_types.bb_mp_options, agent on_options (?)) scanner.close set_next_scene (create {BB_WAITING_ROOM_CLIENT}.make (client, options)) start_next_scene end abort_button_pressed is -- abort button was pressed do client.disable_auto_transmit client.disable_time_sync client.close options_panel.hide abort_button.hide ok_button.hide is_connecting := False status_message.set_text ("") status_message.resize_to_optimal_dimension client.unsubscribe_by_type_id (client.object_types.bb_mp_options, agent on_options (?)) end feature -- Status report options: BB_MP_OPTIONS -- options of the selected server feature -- Drawing draw_options is -- draw options require options /= Void do end feature {NONE} -- Implementation is_nickname_valid: BOOLEAN is -- is `nickname_textbox.text' a valid nickname? do Result := nickname_textbox.text.count > 0 and nickname_textbox.text.count < Maximum_nickname_length end handle_update_event is -- Handle outside event. do Precursor if client /= Void then client.process_timed_procedures_in_same_thread end scanner.send_update_to_clients end discovery_response_pretty_printer (a_response: EM_NET_SERVER_DISCOVERY_RESPONSE): STRING is -- turn `a_response' into a nice server description (will be displayed in the serverlist) do Result := "Host: " + a_response.server_name + " Ping: " + a_response.ping.out end generate_servers is -- Generate the server list. local sections: LIST[STRING] keys: ARRAY[STRING] values: ARRAY[STRING] split: INTEGER num_of_servers: INTEGER new_server: DS_HASH_TABLE[STRING, STRING] i, j, index: INTEGER current_value, current_key: STRING do create servers.make num_of_servers := num_of_servers.max_value sections := http_response.split ('&') create keys.make(1, sections.count) create values.make(1, sections.count) from sections.start until sections.after loop if sections.item.has ('=') then split := sections.item.index_of ('=', 1) keys.put (sections.item.substring (1, split - 1), sections.index) values.put (sections.item.substring(split + 1, sections.item.count), sections.index) num_of_servers := num_of_servers.min (values.item (sections.index).occurrences ('|') + 1) end sections.forth end if num_of_servers = num_of_servers.max_value then num_of_servers := 0 end from i := 1 until i > num_of_servers loop create new_server.make (keys.count) from j := 1 until j > keys.count loop index := values.item(j).index_of ('|', 1) current_value := values.item(j) current_key := keys.item(j) if index /= 0 then new_server.put (current_value.substring (1, index - 1), current_key) values.put (current_value.substring (index + 1, current_value.count), j) else new_server.put (current_value, current_key) end j := j + 1 end servers.put_last (new_server) i := i + 1 end end is_current_entry_valid(current_entry: DS_HASH_TABLE [STRING,STRING]): BOOLEAN is -- Is current entry valid? require current_entry_not_void: current_entry /= Void do if current_entry.has ("servername") and current_entry.has ("game") and current_entry.has("address") and current_entry.has ("port") then Result := is_valid_ip_string (current_entry.item ("address")) and current_entry.item ("port").is_integer else Result := False end end feature {NONE} -- Implementation client: EM_NET_CLIENT [BB_MP_OBJECT_TYPES] -- Client server_list: EM_TEXTLIST [EM_NET_SERVER_DISCOVERY_RESPONSE] -- displayed game server list servers: DS_LINKED_LIST[DS_HASH_TABLE[STRING, STRING]] -- used to turn the http string to a list of em_net_server_discovery_response s nickname_textbox: EM_TEXTBOX nickname_label: EM_LABEL scanner: EM_NET_SERVER [BB_MP_OBJECT_TYPES] -- scans for active servers last_scan_ticks: INTEGER -- time of last scan http: EM_HTTP_PROTOCOL -- HTTP Client http_response: STRING -- HTTP response string is_refresh_in_progress: BOOLEAN -- is refreshing of serverlist from masterserver in progress? is_connecting: BOOLEAN -- is currently a connecting process active? status_message: EM_LABEL -- reports whats currently done ok_button: BB_MENU_BUTTON -- button to accept a server abort_button: BB_MENU_BUTTON -- button to abort a server connection options_panel: EM_PANEL -- panel which contains the options end