Rev 140 | Rev 171 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
| Rev | Author | Line No. | Line | 
|---|---|---|---|
| 1 | pmbaty | 1 | // network.cpp | 
        
| 2 | |||
| 3 | #include "common.h" | 
        ||
| 4 | |||
| 5 | |||
| 6 | // handy definitions | 
        ||
| 7 | #define REACH_NEXT_FIELD_ELSE(field,action) \ | 
        ||
| 8 | { \ | 
        ||
| 9 |    while (*(field) && !iswspace (*(field))) (field)++; if (*(field) == 0) action; \ | 
        ||
| 10 |    while (*(field) && iswspace (*(field))) (field)++; if (*(field) == 0) action; \ | 
        ||
| 11 | } | 
        ||
| 12 | #define LOCATE_REPLY_END_FROM_START(end,start) \ | 
        ||
| 13 | { \ | 
        ||
| 14 |    (end) = wcschr ((start), L'\n'); \ | 
        ||
| 15 |    while (((end) != NULL) && ((end)[1] == L'\\')) (end) = wcschr (&(end)[1], L'\n'); /* find the first line feed that is NOT followed by a backslash */ \ | 
        ||
| 16 |    if ((end) != NULL) (end)++; /* skip it */ \ | 
        ||
| 17 |    else (end) = (start) + wcslen (start); \ | 
        ||
| 18 | } | 
        ||
| 19 | #define ERASE_FROM_TO(beginning,end) \ | 
        ||
| 20 | { \ | 
        ||
| 21 |    while ((((end) != NULL) && ((beginning) < (end))) || (*(beginning) != 0)) \ | 
        ||
| 22 |       *(beginning) = L' ', (beginning)++; /* replace all the area with spaces */ \ | 
        ||
| 23 | } | 
        ||
| 24 | #define IS_FIELD_PRESENT(field_var,string) \ | 
        ||
| 25 |    (((field_var) = wcsstr (player->recvbuffer, (string))) != NULL) /* this text is present */ | 
        ||
| 26 | #define IS_FIELD_PRESENT_AT_BEGINNING_OF_LINE(field_var,string) \ | 
        ||
| 27 |    ((((field_var) = wcsstr (player->recvbuffer, (string))) != NULL) /* this text is present... */ \ | 
        ||
| 28 |     && (ReachBeginningOfCurrentLine (player->recvbuffer, (field_var)) == (field_var))) /* ... at the beginning of a line */ | 
        ||
| 29 | |||
| 30 | |||
| 31 | |||
| 32 | // prototypes of local functions | 
        ||
| 33 | static void ReadNickname (wchar_t *nickname, size_t nickname_size, wchar_t *from_string);  | 
        ||
| 34 | static void ReadGamename (wchar_t *gamename, size_t gamename_size, wchar_t *from_string);  | 
        ||
| 35 | static void ReadSpannedLine (wchar_t *outstring, size_t outstring_size, wchar_t *multiline_string);  | 
        ||
| 36 | |||
| 37 | |||
| 38 | void EvaluateServerReply_MOTD (player_t *player)  | 
        ||
| 39 | { | 
        ||
| 40 |    // this function parses a network reply and evaluates it, deciding what to do | 
        ||
| 41 | |||
| 42 |    // is the MOTD already filled ? | 
        ||
| 43 | if (server_motd[0] != 0)  | 
        ||
| 44 | return; // if so, this reply can't be a MOTD so just return  | 
        ||
| 45 | |||
| 46 |    // read all received text as part of the MOTD | 
        ||
| 47 | wcscpy_s (server_motd, WCHAR_SIZEOF (server_motd), player->recvbuffer);  | 
        ||
| 48 | |||
| 49 |    // do we want the MOTD to be displayed ? | 
        ||
| 50 | if (options.network.want_servermessages && options.network.want_motdonconnect)  | 
        ||
| 51 | Window_MOTD (); // display MOTD window if required  | 
        ||
| 52 | |||
| 53 | Player_SendBuffer_Add (player, 1000, L"style 12\n"); // set the style 12 (computer-friendly board display)  | 
        ||
| 54 | Player_SendBuffer_Add (player, 1000, L"who\n"); // send the players update request  | 
        ||
| 55 | Player_SendBuffer_Add (player, 1000, L"sought all\n"); // send the sought games update request  | 
        ||
| 56 | if (options.network.want_publicchat)  | 
        ||
| 57 | Player_SendBuffer_Add (player, 1000, L"inchannel\n"); // send the chatter channels update request  | 
        ||
| 58 | |||
| 59 | return; // finished evaluating the MOTD  | 
        ||
| 60 | } | 
        ||
| 61 | |||
| 62 | |||
| 63 | void EvaluateServerReply_Announcement (player_t *player)  | 
        ||
| 64 | { | 
        ||
| 65 |    // this function parses a network reply and evaluates it, deciding what to do | 
        ||
| 66 | |||
| 67 |    // Announcement format: | 
        ||
| 68 |    //RECEIVED:[ | 
        ||
| 69 |    // | 
        ||
| 70 |    //    **ANNOUNCEMENT** from relay: FICS is relaying the 11th Delhi International | 
        ||
| 71 |    //\   Open Category A 2013 - Round 3, the 24th Villa de Roquetas International  | 
        ||
| 72 |    //\   Open 2013 - Round 8 and the 18th Bosnjaci International Open 2013 - Round  | 
        ||
| 73 |    //\   5. To find more about Relay type "tell relay help" | 
        ||
| 74 |    //] | 
        ||
| 75 | |||
| 76 | static wchar_t announcement_text[1024];  | 
        ||
| 77 | |||
| 78 | wchar_t *announcement_start;  | 
        ||
| 79 | wchar_t *announcement_end;  | 
        ||
| 80 | wchar_t *field_start;  | 
        ||
| 81 | |||
| 82 |    // are both of the possible announcement headers NOT present ? | 
        ||
| 83 | if (!IS_FIELD_PRESENT (announcement_start, L"\n\n **ANNOUNCEMENT** from ")  | 
        ||
| 84 | && !IS_FIELD_PRESENT (announcement_start, L"\n\n **UNREG ANNOUNCEMENT** from "))  | 
        ||
| 85 | return; // if so, this reply can't be an announcement notification so just return  | 
        ||
| 86 | |||
| 87 |    // this reply is indeed an announcement ; find where it ends | 
        ||
| 88 | LOCATE_REPLY_END_FROM_START (announcement_end, announcement_start + 2);  | 
        ||
| 89 | |||
| 90 |    // are we concerned about server messages ? | 
        ||
| 91 | if (options.network.want_servermessages)  | 
        ||
| 92 |    { | 
        ||
| 93 | field_start = wcsstr (announcement_start, L": "); // reach the first colon+space, that's where the announcement starts  | 
        ||
| 94 | if (field_start != NULL)  | 
        ||
| 95 |       { | 
        ||
| 96 | field_start += 2; // skip colon and space  | 
        ||
| 97 | ReadSpannedLine (announcement_text, WCHAR_SIZEOF (announcement_text), field_start); // now format the announcement text well  | 
        ||
| 98 | Scene_AddAnnouncement (&the_scene, announcement_text); // and put it in place  | 
        ||
| 99 |       } | 
        ||
| 100 |    } | 
        ||
| 101 | |||
| 102 |    // now erase the announcement from the recvbuffer, so that it cannot be misinterpreted by further parsing | 
        ||
| 103 | ERASE_FROM_TO (announcement_start, announcement_end);  | 
        ||
| 104 | |||
| 105 | return; // finished evaluating this announcement  | 
        ||
| 106 | } | 
        ||
| 107 | |||
| 108 | |||
| 109 | void EvaluateServerReply_ChannelMessage (player_t *player)  | 
        ||
| 110 | { | 
        ||
| 111 |    // this function parses a network reply and evaluates it, deciding what to do | 
        ||
| 112 | |||
| 113 | static wchar_t channelmessage_text[1024];  | 
        ||
| 114 | |||
| 115 | wchar_t nickname[32];  | 
        ||
| 116 | wchar_t channelname[64];  | 
        ||
| 117 | unsigned long rgbx_color;  | 
        ||
| 118 | wchar_t *channelmessage_start;  | 
        ||
| 119 | wchar_t *channelmessage_end;  | 
        ||
| 120 | wchar_t *field_start;  | 
        ||
| 121 | wchar_t *field_stop;  | 
        ||
| 122 | int channel_index;  | 
        ||
| 123 | int channel_number;  | 
        ||
| 124 | |||
| 125 |    // is the discriminatives bit for a channel message NOT present ? | 
        ||
| 126 | if (!IS_FIELD_PRESENT (field_stop, L"): "))  | 
        ||
| 127 | return; // if so, this reply can't be a channel message so just return  | 
        ||
| 128 | |||
| 129 |    // reach beginning of current line | 
        ||
| 130 | channelmessage_start = ReachBeginningOfCurrentLine (player->recvbuffer, field_stop);  | 
        ||
| 131 | |||
| 132 |    // locate the first space in line (i.e. where the message text is supposed to start) | 
        ||
| 133 | field_start = wcschr (channelmessage_start, L' ');  | 
        ||
| 134 | if (field_start != NULL)  | 
        ||
| 135 | field_start++; // skip it  | 
        ||
| 136 | |||
| 137 |    // is the first space BEFORE the discirminative bit ? | 
        ||
| 138 | if (field_start < field_stop)  | 
        ||
| 139 | return; // if so, this reply can't be a channel message so just return  | 
        ||
| 140 | |||
| 141 | while ((field_stop > player->recvbuffer) && (*field_stop != L'('))  | 
        ||
| 142 | field_stop--; // parse the string backwards to find the channel index  | 
        ||
| 143 | if (*field_stop != L'(')  | 
        ||
| 144 | return; // drop bogus replies  | 
        ||
| 145 | channel_number = _wtoi (&field_stop[1]); // read channel number  | 
        ||
| 146 | if (channel_number == 0)  | 
        ||
| 147 | return; // if what's between the parentheses is not a number, this reply can't be a channel message so just return  | 
        ||
| 148 | |||
| 149 |    // this reply is indeed a channel message ; find where it ends | 
        ||
| 150 | LOCATE_REPLY_END_FROM_START (channelmessage_end, channelmessage_start);  | 
        ||
| 151 | |||
| 152 |    // are we concerned about channel messages ? | 
        ||
| 153 | if (options.network.want_publicchat)  | 
        ||
| 154 |    { | 
        ||
| 155 | ReadNickname (nickname, WCHAR_SIZEOF (nickname), channelmessage_start); // get the nickname  | 
        ||
| 156 | ReadSpannedLine (channelmessage_text, WCHAR_SIZEOF (channelmessage_text), field_start); // get the message  | 
        ||
| 157 | |||
| 158 | channel_number = _wtoi (&field_stop[1]); // and read channel number  | 
        ||
| 159 | for (channel_index = 0; channel_index < chatterchannel_count; channel_index++)  | 
        ||
| 160 | if (chatterchannels[channel_index].id == channel_number)  | 
        ||
| 161 |          { | 
        ||
| 162 | if (chatterchannels[channel_index].theme[0] != 0)  | 
        ||
| 163 |             { | 
        ||
| 164 | wcscpy_s (channelname, WCHAR_SIZEOF (channelname), chatterchannels[channel_index].theme);  | 
        ||
| 165 | rgbx_color = chatterchannels[channel_index].color;  | 
        ||
| 166 |             } | 
        ||
| 167 | break; // break as soon as we find the channel's name and copy it if it exists  | 
        ||
| 168 |          } | 
        ||
| 169 | if (channelname[0] == 0)  | 
        ||
| 170 |       { | 
        ||
| 171 | swprintf_s (channelname, WCHAR_SIZEOF (channelname), L"%s %d", LOCALIZE (L"ChatterChannels_ColumnChannelNumber"), channel_number); // if it hasn't been filled, use the number  | 
        ||
| 172 | rgbx_color = RGBA_TO_RGBACOLOR (17, 181, 205, 0); // default channel color  | 
        ||
| 173 |       } | 
        ||
| 174 | |||
| 175 |       // add CC reply | 
        ||
| 176 | Scene_AddCCReply (&the_scene, nickname, channelname, rgbx_color, channelmessage_text);  | 
        ||
| 177 |    } | 
        ||
| 178 | |||
| 179 |    // now erase the channel message from the recvbuffer, so that it cannot be misinterpreted by further parsing | 
        ||
| 180 | ERASE_FROM_TO (channelmessage_start, channelmessage_end);  | 
        ||
| 181 | |||
| 182 | return; // finished evaluating this channel message  | 
        ||
| 183 | } | 
        ||
| 184 | |||
| 185 | |||
| 186 | void EvaluateServerReply_PrivateMessage (player_t *player)  | 
        ||
| 187 | { | 
        ||
| 188 |    // this function parses a network reply and evaluates it, deciding what to do | 
        ||
| 189 | |||
| 190 | static wchar_t privatemessage_text[16384];  | 
        ||
| 191 | |||
| 192 | wchar_t nickname[32];  | 
        ||
| 193 | wchar_t *privatemessage_start;  | 
        ||
| 194 | wchar_t *privatemessage_end;  | 
        ||
| 195 | wchar_t *field_start;  | 
        ||
| 196 | wchar_t *first_space;  | 
        ||
| 197 | int player_index;  | 
        ||
| 198 | |||
| 199 |    // is the private message discriminative bit NOT present ? | 
        ||
| 200 |    // private messages have " tells you: " just after the first space | 
        ||
| 201 | if (!IS_FIELD_PRESENT (field_start, L" tells you: "))  | 
        ||
| 202 | return; // if so, this reply can't be a private message so just return  | 
        ||
| 203 | |||
| 204 |    // look up for the last line feed and the first space of the line | 
        ||
| 205 | privatemessage_start = ReachBeginningOfCurrentLine (player->recvbuffer, field_start);  | 
        ||
| 206 | first_space = wcschr (privatemessage_start, L' ');  | 
        ||
| 207 | |||
| 208 |    // is the first space BEFORE the discirminative bit ? | 
        ||
| 209 | if ((first_space != NULL) && (first_space < field_start))  | 
        ||
| 210 | return; // if so, this reply can't be a channel message so just return  | 
        ||
| 211 | |||
| 212 |    // this reply is indeed a private message ; find where it ends | 
        ||
| 213 | LOCATE_REPLY_END_FROM_START (privatemessage_end, privatemessage_start + 2);  | 
        ||
| 214 | |||
| 215 | ReadNickname (nickname, WCHAR_SIZEOF (nickname), privatemessage_start); // get username  | 
        ||
| 216 | |||
| 217 |    // is it NOT RoboAdmin OR do we care about server messages ? | 
        ||
| 218 | if ((_wcsicmp (L"ROBOadmin", nickname) != 0) || options.network.want_servermessages)  | 
        ||
| 219 |    { | 
        ||
| 220 |       // see if this nickname exists in the list of connected players ; if not, refresh list | 
        ||
| 221 | for (player_index = 0; player_index < onlineplayer_count; player_index++)  | 
        ||
| 222 | if (wcscmp (nickname, onlineplayers[player_index].nickname) == 0)  | 
        ||
| 223 | break; // break as soon as we find it  | 
        ||
| 224 | |||
| 225 |       // have we NOT found it ? | 
        ||
| 226 | if (player_index == onlineplayer_count)  | 
        ||
| 227 | Player_SendBuffer_Add (player, 1000, L"who\n"); // if so, request a players list refresh  | 
        ||
| 228 | |||
| 229 | field_start += 12; // skip string break and reach the next colon, that's where the PM starts  | 
        ||
| 230 | ReadSpannedLine (privatemessage_text, WCHAR_SIZEOF (privatemessage_text), field_start); // now format the PM text well  | 
        ||
| 231 | |||
| 232 |       // find or create our interlocutor structure and append the chat text in it | 
        ||
| 233 | Interlocutor_Chat (Interlocutor_FindOrCreate (nickname), nickname, false, privatemessage_text);  | 
        ||
| 234 |    } | 
        ||
| 235 | |||
| 236 |    // now erase the private message from the recvbuffer, so that it cannot be misinterpreted by further parsing | 
        ||
| 237 | ERASE_FROM_TO (privatemessage_start, privatemessage_end);  | 
        ||
| 238 | |||
| 239 | return; // finished evaluating this private message  | 
        ||
| 240 | } | 
        ||
| 241 | |||
| 242 | |||
| 243 | void EvaluateServerReply_Finger (player_t *player)  | 
        ||
| 244 | { | 
        ||
| 245 |    // this function parses a network reply and evaluates it, deciding what to do | 
        ||
| 246 | |||
| 247 | wchar_t line_buffer[256];  | 
        ||
| 248 | wchar_t month_str[5];  | 
        ||
| 249 | wchar_t dayhrminsec[8];  | 
        ||
| 250 | wchar_t nickname[32];  | 
        ||
| 251 |    gamestylerating_t gs; | 
        ||
| 252 | playercard_t *playercard;  | 
        ||
| 253 | player_t *local_player;  | 
        ||
| 254 | wchar_t *finger_start;  | 
        ||
| 255 | wchar_t *finger_end;  | 
        ||
| 256 | wchar_t *field_start;  | 
        ||
| 257 | wchar_t *fingerdata_start;  | 
        ||
| 258 | wchar_t *string_pointer;  | 
        ||
| 259 | int onlineplayer_index;  | 
        ||
| 260 | int days;  | 
        ||
| 261 | int hours;  | 
        ||
| 262 | int minutes;  | 
        ||
| 263 | int number;  | 
        ||
| 264 | |||
| 265 |    // is it a NEGATIVE finger reply ? "'gue$tpm' is not a valid handle." | 
        ||
| 266 | if (IS_FIELD_PRESENT (field_start, L"' is not a valid handle."))  | 
        ||
| 267 |    { | 
        ||
| 268 | field_start = ReachBeginningOfCurrentLine (player->recvbuffer, field_start); // look up for the last line feed  | 
        ||
| 269 | if (field_start[0] == L'\'') // first character must be an apostrophe  | 
        ||
| 270 |       { | 
        ||
| 271 | ReadNickname (nickname, WCHAR_SIZEOF (nickname), &field_start[1]); // read username  | 
        ||
| 272 | |||
| 273 |          // test again with the complete finger reply header. Are we SURE it is a finger reply ? | 
        ||
| 274 | if (wcscmp (&field_start[1 + wcslen (nickname)], L"' is not a valid handle.") == 0)  | 
        ||
| 275 |          { | 
        ||
| 276 |             // find or create our player card structure | 
        ||
| 277 | playercard = PlayerCard_FindOrCreate (nickname);  | 
        ||
| 278 | playercard->doesnt_exist = true; // mark it as non-existing  | 
        ||
| 279 | playercard->update_dialog = true; // and tell the dialog to update itself  | 
        ||
| 280 | |||
| 281 | return; // finished evaluating this finger reply  | 
        ||
| 282 |          } | 
        ||
| 283 |       } | 
        ||
| 284 |    } | 
        ||
| 285 | |||
| 286 |    // is it a NEGATIVE finger reply ? "Ambiguous name guestp:" | 
        ||
| 287 | if (IS_FIELD_PRESENT_AT_BEGINNING_OF_LINE (field_start, L"Ambiguous name "))  | 
        ||
| 288 |    { | 
        ||
| 289 | ReadNickname (nickname, WCHAR_SIZEOF (nickname), &field_start[15]); // read username  | 
        ||
| 290 | |||
| 291 |       // find or create our player card structure | 
        ||
| 292 | playercard = PlayerCard_FindOrCreate (nickname);  | 
        ||
| 293 | playercard->doesnt_exist = true; // mark it as non-existing  | 
        ||
| 294 | playercard->update_dialog = true; // and tell the dialog to update itself  | 
        ||
| 295 | |||
| 296 | return; // finished evaluating this finger reply  | 
        ||
| 297 |    } | 
        ||
| 298 | |||
| 299 |    // is it a NEGATIVE finger reply ? "There is no player matching the name guestpm." | 
        ||
| 300 | if (IS_FIELD_PRESENT_AT_BEGINNING_OF_LINE (field_start, L"There is no player matching the name "))  | 
        ||
| 301 |    { | 
        ||
| 302 | ReadNickname (nickname, WCHAR_SIZEOF (nickname), &field_start[37]); // read username  | 
        ||
| 303 | |||
| 304 |       // find or create our player card structure | 
        ||
| 305 | playercard = PlayerCard_FindOrCreate (nickname);  | 
        ||
| 306 | playercard->doesnt_exist = true; // mark it as non-existing  | 
        ||
| 307 | playercard->update_dialog = true; // and tell the dialog to update itself  | 
        ||
| 308 | |||
| 309 | return; // finished evaluating this finger reply  | 
        ||
| 310 |    } | 
        ||
| 311 | |||
| 312 |    // is it a POSITIVE finger reply ? "Finger of guestpm:" | 
        ||
| 313 | if (IS_FIELD_PRESENT_AT_BEGINNING_OF_LINE (field_start, L"Finger of "))  | 
        ||
| 314 |    { | 
        ||
| 315 | ReadNickname (nickname, WCHAR_SIZEOF (nickname), &field_start[10]); // get username  | 
        ||
| 316 | |||
| 317 |       // find or create our player card structure | 
        ||
| 318 | playercard = PlayerCard_FindOrCreate (nickname);  | 
        ||
| 319 | playercard->got_reply = true; // remember it has actual data  | 
        ||
| 320 | |||
| 321 |       // when we have it, update username | 
        ||
| 322 | wcscpy_s (playercard->nickname, WCHAR_SIZEOF (playercard->nickname), nickname);  | 
        ||
| 323 | |||
| 324 |       // find local player and see whether this player card is ours | 
        ||
| 325 | local_player = Player_FindByType (PLAYER_HUMAN);  | 
        ||
| 326 | if ((local_player != NULL) && (_wcsicmp (nickname, local_player->name) == 0))  | 
        ||
| 327 | playercard->is_own = true; // remember this player card is ours  | 
        ||
| 328 | |||
| 329 |       /////////////////////////////////////////////////////////////////////// | 
        ||
| 330 |       // Finger: parse the connection data (On for: n days n hours n minutes) | 
        ||
| 331 | |||
| 332 |       // reach the next double line-break, that's where connection data starts | 
        ||
| 333 | fingerdata_start = wcsstr (field_start, L"\n\n");  | 
        ||
| 334 | if (fingerdata_start == NULL)  | 
        ||
| 335 | return; // nothing in finger reply ; reply evaluated  | 
        ||
| 336 | |||
| 337 | fingerdata_start += 2; // skip them  | 
        ||
| 338 | wcsgets (line_buffer, WCHAR_SIZEOF (line_buffer), fingerdata_start); // copy that line into a buffer for easy parsing  | 
        ||
| 339 | |||
| 340 |       // is the player currently online ? | 
        ||
| 341 | if (wcsncmp (line_buffer, L"On for: ", 8) == 0)  | 
        ||
| 342 |       { | 
        ||
| 343 | field_start = &line_buffer[8]; // skip the "On for: " text  | 
        ||
| 344 | |||
| 345 |          // according to the data presentation, read and convert in the right form | 
        ||
| 346 | if (swscanf_s (field_start, L"%d %*s %d %*s %d %*s Idle: %d %s", &days, &hours, &minutes, &number, dayhrminsec, WCHAR_SIZEOF (dayhrminsec)) == 5)  | 
        ||
| 347 |          { | 
        ||
| 348 | playercard->minutes_online = (days * 60 * 24) + (hours * 60) + minutes; // days, hours, minutes  | 
        ||
| 349 | playercard->seconds_idle = number * (dayhrminsec[0] == L'd' ? 60 * 60 * 24 : (dayhrminsec[0] == L'h' ? 60 * 60 : (dayhrminsec[0] == L'm' ? 60 : 1)));  | 
        ||
| 350 |          } | 
        ||
| 351 | else if (swscanf_s (field_start, L"%d %*s %d %*s Idle: %d %s", &hours, &minutes, &number, dayhrminsec, WCHAR_SIZEOF (dayhrminsec)) == 4)  | 
        ||
| 352 |          { | 
        ||
| 353 | playercard->minutes_online = (hours * 60) + minutes; // hours, minutes  | 
        ||
| 354 | playercard->seconds_idle = number * (dayhrminsec[0] == L'd' ? 60 * 60 * 24 : (dayhrminsec[0] == L'h' ? 60 * 60 : (dayhrminsec[0] == L'm' ? 60 : 1)));  | 
        ||
| 355 |          } | 
        ||
| 356 | else if (swscanf_s (field_start, L"%*d secs Idle: %d %s", &number, dayhrminsec, WCHAR_SIZEOF (dayhrminsec)) == 2)  | 
        ||
| 357 |          { | 
        ||
| 358 | playercard->minutes_online = 1; // less than one minute, round to 1  | 
        ||
| 359 | playercard->seconds_idle = number * (dayhrminsec[0] == L'd' ? 60 * 60 * 24 : (dayhrminsec[0] == L'h' ? 60 * 60 : (dayhrminsec[0] == L'm' ? 60 : 1)));  | 
        ||
| 360 |          } | 
        ||
| 361 | else if (swscanf_s (field_start, L"%d %*s Idle: %d %s", &minutes, &number, dayhrminsec, WCHAR_SIZEOF (dayhrminsec)) == 3)  | 
        ||
| 362 |          { | 
        ||
| 363 | playercard->minutes_online = minutes; // just minutes  | 
        ||
| 364 | playercard->seconds_idle = number * (dayhrminsec[0] == L'd' ? 60 * 60 * 24 : (dayhrminsec[0] == L'h' ? 60 * 60 : (dayhrminsec[0] == L'm' ? 60 : 1)));  | 
        ||
| 365 |          } | 
        ||
| 366 | |||
| 367 | playercard->update_dialog = true; // remember to update dialog  | 
        ||
| 368 |       } | 
        ||
| 369 | |||
| 370 |       // else has player already disconnected ? | 
        ||
| 371 | else if (wcsncmp (line_buffer, L"Last disconnected: ", 19) == 0)  | 
        ||
| 372 |       { | 
        ||
| 373 | field_start = &line_buffer[19]; // skip the "Last disconnected: " text  | 
        ||
| 374 | |||
| 375 |          // read and convert the data in the right form | 
        ||
| 81 | pmbaty | 376 | if (swscanf_s (field_start, L"%*s %s %d, %*d:%*d %*s %d", month_str, WCHAR_SIZEOF (month_str), &days, &number) == 3)  | 
        
| 377 |          { | 
        ||
| 378 | playercard->disconnection_day = days; // reuse the "days" variable, which is an int, and we need an int in swscanf_s()  | 
        ||
| 1 | pmbaty | 379 | playercard->disconnection_month = MonthStringToNumber (month_str); // convert month from string to number  | 
        
| 81 | pmbaty | 380 | playercard->disconnection_year = number; // reuse the "number" variable, which is an int, and we need an int in swscanf_s()  | 
        
| 381 |          } | 
        ||
| 1 | pmbaty | 382 | |
| 383 | playercard->update_dialog = true; // remember to update dialog  | 
        ||
| 384 |       } | 
        ||
| 385 | |||
| 386 |       // else player has never connected | 
        ||
| 387 |       else | 
        ||
| 388 |       { | 
        ||
| 389 | playercard->disconnection_day = 0; // 0 everywhere means the player has never connected  | 
        ||
| 390 | playercard->disconnection_month = 0;  | 
        ||
| 391 | playercard->disconnection_year = 0;  | 
        ||
| 392 | |||
| 393 | playercard->update_dialog = true; // remember to update dialog  | 
        ||
| 394 |       } | 
        ||
| 395 | |||
| 396 |       //////////////////////////////////////////////////////////// | 
        ||
| 397 |       // Finger: parse the status data (playing game N:aaa vs bbb) | 
        ||
| 398 | |||
| 399 |       // see if this player is currently playing a game | 
        ||
| 400 | field_start = wcsstr (fingerdata_start, L"\n(playing game ");  | 
        ||
| 401 | if (field_start != NULL)  | 
        ||
| 402 |       { | 
        ||
| 403 | field_start += 15; // skip the "\n(playing game " substring  | 
        ||
| 404 | |||
| 405 |          // update player activity in opponents list | 
        ||
| 406 | for (onlineplayer_index = 0; onlineplayer_index < onlineplayer_count; onlineplayer_index++)  | 
        ||
| 407 | if (wcscmp (onlineplayers[onlineplayer_index].nickname, playercard->nickname) == 0)  | 
        ||
| 408 |             { | 
        ||
| 409 |                // if this player is NOT involved in a tournament... | 
        ||
| 410 | if (onlineplayers[onlineplayer_index].handlestatus != HANDLESTATUS_INTOURNAMENT)  | 
        ||
| 411 |                { | 
        ||
| 412 | if (playercard->seconds_idle < 5 * 60)  | 
        ||
| 413 | onlineplayers[onlineplayer_index].handlestatus = HANDLESTATUS_INGAME; // update status (playing)  | 
        ||
| 414 |                   else | 
        ||
| 415 | onlineplayers[onlineplayer_index].handlestatus = HANDLESTATUS_INACTIVEORBUSY; // update status (idle)  | 
        ||
| 416 |                } | 
        ||
| 417 | break; // stop searching as soon as player is found  | 
        ||
| 418 |             } | 
        ||
| 419 | |||
| 420 |          // scan the game number and name | 
        ||
| 421 | swscanf_s (field_start, L"%d: %[^)]", &playercard->game_played, playercard->game_name, WCHAR_SIZEOF (playercard->game_name));  | 
        ||
| 422 | playercard->update_dialog = true; // remember to update dialog  | 
        ||
| 423 |       } | 
        ||
| 424 | |||
| 425 |       // see if this player is currently playing a game | 
        ||
| 426 | field_start = wcsstr (fingerdata_start, L"\n(examining game ");  | 
        ||
| 427 | if (field_start != NULL)  | 
        ||
| 428 |       { | 
        ||
| 429 | field_start += 17; // skip the "\n(examining game " substring  | 
        ||
| 430 | |||
| 431 |          // update player activity in opponents list | 
        ||
| 432 | for (onlineplayer_index = 0; onlineplayer_index < onlineplayer_count; onlineplayer_index++)  | 
        ||
| 433 | if (wcscmp (onlineplayers[onlineplayer_index].nickname, playercard->nickname) == 0)  | 
        ||
| 434 |             { | 
        ||
| 435 |                // if this player is NOT involved in a tournament... | 
        ||
| 436 | if (onlineplayers[onlineplayer_index].handlestatus != HANDLESTATUS_INTOURNAMENT)  | 
        ||
| 437 |                { | 
        ||
| 438 | if (playercards->seconds_idle < 5 * 60)  | 
        ||
| 439 | onlineplayers[onlineplayer_index].handlestatus = HANDLESTATUS_EXAMININGAGAME; // update status (studying)  | 
        ||
| 440 |                   else | 
        ||
| 441 | onlineplayers[onlineplayer_index].handlestatus = HANDLESTATUS_INACTIVEORBUSY; // update status (idle)  | 
        ||
| 442 |                } | 
        ||
| 443 | break; // stop searching as soon as player is found  | 
        ||
| 444 |             } | 
        ||
| 445 | |||
| 446 |          // scan the game number and name | 
        ||
| 447 | swscanf_s (field_start, L"%d: %[^)]", &playercard->game_played, playercard->game_name, WCHAR_SIZEOF (playercard->game_name));  | 
        ||
| 448 | playercard->update_dialog = true; // remember to update dialog  | 
        ||
| 449 |       } | 
        ||
| 450 | |||
| 451 |       ////////////////////////////////////////// | 
        ||
| 452 |       // Finger: parse the game statistics array | 
        ||
| 453 | |||
| 454 |       // see if this player has game statistics, find it and jump to the first line | 
        ||
| 455 | field_start = wcsstr (fingerdata_start, L" rating RD win loss draw total best\n");  | 
        ||
| 456 | if (field_start != NULL)  | 
        ||
| 457 |       { | 
        ||
| 458 | field_start += 64; // skip the rating table headers and its carriage return  | 
        ||
| 459 | |||
| 460 |          // game statistics start here. Read line per line... | 
        ||
| 461 | string_pointer = field_start; // start at the beginning of the line  | 
        ||
| 462 | while ((string_pointer = wcsgets (line_buffer, WCHAR_SIZEOF (line_buffer), string_pointer)) != NULL)  | 
        ||
| 463 |          { | 
        ||
| 464 | if (line_buffer[0] == 0)  | 
        ||
| 465 | continue; // skip empty lines  | 
        ||
| 466 | |||
| 467 | memset (&gs, 0, sizeof (gs)); // reset all statistics we're about to read  | 
        ||
| 468 | |||
| 469 |             // does it look like a valid game statistics line ? | 
        ||
| 470 | if ((swscanf_s (line_buffer, L"%s %d %f %d %d %d %d", gs.name, WCHAR_SIZEOF (gs.name), &gs.rating, &gs.rd, &gs.win_count, &gs.loss_count, &gs.draw_count, &gs.total_matches) == 7)  | 
        ||
| 471 | || (swscanf_s (line_buffer, L"%s ---- %f %d %d %d %d", gs.name, WCHAR_SIZEOF (gs.name), &gs.rd, &gs.win_count, &gs.loss_count, &gs.draw_count, &gs.total_matches) == 6))  | 
        ||
| 472 |             { | 
        ||
| 473 |                // reallocate space to hold one game style rating more in this player card | 
        ||
| 474 | playercard->gamestyleratings = (gamestylerating_t *) SAFE_realloc (playercard->gamestyleratings, playercard->gamestylerating_count, playercard->gamestylerating_count + 1, sizeof (gamestylerating_t), false);  | 
        ||
| 475 | memcpy (&playercard->gamestyleratings[playercard->gamestylerating_count], &gs, sizeof (gamestylerating_t)); // copy data  | 
        ||
| 476 | playercard->gamestylerating_count++; // we know now one game style rating more for this player card  | 
        ||
| 477 |             } | 
        ||
| 478 |          } | 
        ||
| 479 | |||
| 480 | playercard->update_dialog = true; // remember to update dialog  | 
        ||
| 481 |       } | 
        ||
| 482 | |||
| 483 |       ////////////////////////////////////// | 
        ||
| 484 |       // Finger: parse the personal messages | 
        ||
| 485 | |||
| 486 |       // see if this player has personal data, find it and jump to the first line | 
        ||
| 487 | field_start = wcsstr (fingerdata_start, L"\n\n 1:");  | 
        ||
| 488 | if (field_start != NULL)  | 
        ||
| 489 |       { | 
        ||
| 490 | field_start += 2; // skip the two carriage returns  | 
        ||
| 491 | finger_start = field_start;  | 
        ||
| 492 | |||
| 493 |          // personal data starts here. Read line per line... | 
        ||
| 494 | string_pointer = field_start; // start at the beginning of the line  | 
        ||
| 495 | while ((string_pointer = wcsgets (line_buffer, WCHAR_SIZEOF (line_buffer), string_pointer)) != NULL)  | 
        ||
| 496 |          { | 
        ||
| 497 | if (line_buffer[0] == 0)  | 
        ||
| 498 | continue; // skip empty lines  | 
        ||
| 499 | |||
| 500 |             // does it look like a valid personal finger data line ? | 
        ||
| 501 | if ((wcslen (line_buffer) > 4) && (wcsncmp (&line_buffer[2], L": ", 2) == 0))  | 
        ||
| 502 | PlayerCard_AppendPersonalData (playercard, &line_buffer[4]); // if so, append it to player's personal data  | 
        ||
| 503 | else if (wcsncmp (line_buffer, L"\\ ", 3) == 0)  | 
        ||
| 504 |             { | 
        ||
| 505 | if (playercard->fingertext_length > 1)  | 
        ||
| 506 |                { | 
        ||
| 507 | playercard->fingertext[playercard->fingertext_length - 1] = 0; // chop off the last carriage return in finger text  | 
        ||
| 508 | playercard->fingertext_length--; // UGLY: now size no longer reflects allocated space  | 
        ||
| 509 |                } | 
        ||
| 510 | PlayerCard_AppendPersonalData (playercard, &line_buffer[3]); // ...and append it to player's personal data  | 
        ||
| 511 |             } | 
        ||
| 512 |          } | 
        ||
| 513 | |||
| 514 |          // now erase that text from the recvbuffer, so that it cannot be misinterpreted by further parsing | 
        ||
| 515 | LOCATE_REPLY_END_FROM_START (finger_end, finger_start);  | 
        ||
| 516 | ERASE_FROM_TO (finger_start, finger_end);  | 
        ||
| 517 | |||
| 518 | playercard->update_dialog = true; // remember to update dialog  | 
        ||
| 519 |       } | 
        ||
| 520 | |||
| 521 | return; // finished evaluating this finger reply  | 
        ||
| 522 |    } | 
        ||
| 523 | |||
| 524 | return; // this was not a reply we could be concerned about  | 
        ||
| 525 | } | 
        ||
| 526 | |||
| 527 | |||
| 528 | void EvaluateServerReply_Seek (player_t *player)  | 
        ||
| 529 | { | 
        ||
| 530 |    // this function parses a network reply and evaluates it, deciding what to do | 
        ||
| 531 | |||
| 532 | wchar_t *field_start;  | 
        ||
| 533 | |||
| 534 |    // are the two bits of the seek notification sentence not present ? | 
        ||
| 535 | if (!IS_FIELD_PRESENT (field_start, L") seeking ") || !IS_FIELD_PRESENT (field_start, L"\" to respond)"))  | 
        ||
| 536 | return; // if so, this reply can't be a seek notification so just return  | 
        ||
| 537 | |||
| 538 |    // only refresh the sought games list if we're displaying it | 
        ||
| 539 | if (IsWindow (hSoughtWnd) && (lastsought_time + 5.0f < current_time))  | 
        ||
| 540 | Player_SendBuffer_Add (player, 1000, L"sought all\n"); // send the sought games update request  | 
        ||
| 541 | |||
| 542 | return; // finished evaluating this seek notification  | 
        ||
| 543 | } | 
        ||
| 544 | |||
| 545 | |||
| 546 | void EvaluateServerReply_Challenge (player_t *player)  | 
        ||
| 547 | { | 
        ||
| 548 |    // this function parses a network reply and evaluates it, deciding what to do | 
        ||
| 549 | |||
| 550 |    challenge_t chal; | 
        ||
| 551 | wchar_t *field_start;  | 
        ||
| 552 | |||
| 553 |    // is the challenge line header NOT present ? | 
        ||
| 554 | if (!IS_FIELD_PRESENT_AT_BEGINNING_OF_LINE (field_start, L"Challenge: "))  | 
        ||
| 555 | return; // if so, this reply can't be a challenge notification so just return  | 
        ||
| 556 | |||
| 557 |    // challenges can appear as | 
        ||
| 558 |    // Challenge: IOEO (1370) pmbaty (----) unrated blitz 2 12. | 
        ||
| 559 |    // Challenge: IOEO (1370) [black] pmbaty (----) unrated blitz 2 12. | 
        ||
| 560 | |||
| 561 | field_start += 11; // skip the "Challenge: " substring  | 
        ||
| 562 | ReadNickname (chal.challenger, WCHAR_SIZEOF (chal.challenger), field_start); // read challenger nickname  | 
        ||
| 563 | REACH_NEXT_FIELD_ELSE (field_start, return);  | 
        ||
| 564 | if (*field_start == L'(')  | 
        ||
| 565 | field_start++; // skip the leading parenthesis  | 
        ||
| 566 | if ((*field_start != L'-') && (*field_start != L'+'))  | 
        ||
| 567 | chal.challenger_level = _wtoi (field_start); // read player rating  | 
        ||
| 568 |    else | 
        ||
| 569 | chal.challenger_level = 0;  | 
        ||
| 570 | REACH_NEXT_FIELD_ELSE (field_start, return);  | 
        ||
| 571 | if (_wcsnicmp (field_start, L"[black]", 7) == 0)  | 
        ||
| 572 |    { | 
        ||
| 573 | chal.color = COLOR_BLACK; // opponent wants to play black  | 
        ||
| 574 | REACH_NEXT_FIELD_ELSE (field_start, return);  | 
        ||
| 575 |    } | 
        ||
| 576 | else if (_wcsnicmp (field_start, L"[white]", 7) == 0)  | 
        ||
| 577 |    { | 
        ||
| 578 | chal.color = COLOR_WHITE; // opponent wants to play white  | 
        ||
| 579 | REACH_NEXT_FIELD_ELSE (field_start, return);  | 
        ||
| 580 |    } | 
        ||
| 581 |    else | 
        ||
| 582 | chal.color = COLOR_UNSPECIFIED; // opponent has no preference over which color he wants to play  | 
        ||
| 583 | REACH_NEXT_FIELD_ELSE (field_start, return); // skip our name  | 
        ||
| 584 | REACH_NEXT_FIELD_ELSE (field_start, return); // skip our rating  | 
        ||
| 585 | chal.is_rated = (*field_start == L'r' ? true : false); // read whether it is rated or not  | 
        ||
| 586 | REACH_NEXT_FIELD_ELSE (field_start, return);  | 
        ||
| 587 | ReadGamename (chal.game_type, WCHAR_SIZEOF (chal.game_type), field_start); // read game type  | 
        ||
| 588 | REACH_NEXT_FIELD_ELSE (field_start, return);  | 
        ||
| 589 | chal.initial_time = (float) _wtoi (field_start); // read initial time  | 
        ||
| 590 | REACH_NEXT_FIELD_ELSE (field_start, return);  | 
        ||
| 591 | chal.increment = (float) _wtoi (field_start); // read increment  | 
        ||
| 592 | |||
| 593 |    // is this variant unsupported ? | 
        ||
| 594 | if ((_wcsicmp (chal.game_type, L"Untimed") != 0) && (_wcsicmp (chal.game_type, L"Standard") != 0)  | 
        ||
| 595 | && (_wcsicmp (chal.game_type, L"Blitz") != 0) && (_wcsicmp (chal.game_type, L"Lightning") != 0))  | 
        ||
| 596 | Player_SendBuffer_Add (player, 1000, L"decline %s\n", chal.challenger); // automatically decline all unsupported games  | 
        ||
| 597 |    else | 
        ||
| 598 |    { | 
        ||
| 599 |       // supported variant. Display a message box for the user to choose whether to accept or decline. | 
        ||
| 600 | |||
| 601 |       // request a player list update before displaying the dialog box | 
        ||
| 602 | if (lastonlineplayers_time + 5.0f < current_time)  | 
        ||
| 603 | Player_SendBuffer_Add (player, 1000, L"who\n");  | 
        ||
| 604 | |||
| 605 |       // print a notification in this player's chat window | 
        ||
| 606 | Interlocutor_Notify (Interlocutor_FindOrCreate (chal.challenger), LOCALIZE (L"Chat_InvitationReceived"), chal.challenger);  | 
        ||
| 607 | |||
| 608 |       // find or create our challenge structure and update its data | 
        ||
| 609 | Challenge_UpdateData (Challenge_FindOrCreate (chal.challenger), &chal);  | 
        ||
| 610 |    } | 
        ||
| 611 | |||
| 612 | return; // finished evaluating this challenge notification  | 
        ||
| 613 | } | 
        ||
| 614 | |||
| 615 | |||
| 616 | void EvaluateServerReply_ChallengeAccepted (player_t *player)  | 
        ||
| 617 | { | 
        ||
| 618 |    // this function parses a network reply and evaluates it, deciding what to do | 
        ||
| 619 | |||
| 620 | wchar_t nickname[32];  | 
        ||
| 621 | wchar_t *line_start;  | 
        ||
| 622 | wchar_t *field_start;  | 
        ||
| 623 | interlocutor_t *interlocutor;  | 
        ||
| 624 | int player_index;  | 
        ||
| 625 | |||
| 626 |    // is the challenge accepted notification discriminative bit NOT present ? | 
        ||
| 627 | if (!IS_FIELD_PRESENT (field_start, L" accepts the match offer."))  | 
        ||
| 628 | return; // if so, this reply can't be a challenge reply notification so just return  | 
        ||
| 629 | |||
| 630 | line_start = ReachBeginningOfCurrentLine (player->recvbuffer, field_start); // look up for the last line feed  | 
        ||
| 631 | ReadNickname (nickname, WCHAR_SIZEOF (nickname), line_start); // get username  | 
        ||
| 632 | |||
| 633 |    // see if this nickname exists in the list of connected players ; if not, refresh list | 
        ||
| 634 | for (player_index = 0; player_index < onlineplayer_count; player_index++)  | 
        ||
| 635 | if (wcscmp (nickname, onlineplayers[player_index].nickname) == 0)  | 
        ||
| 636 | break; // break as soon as we find it  | 
        ||
| 637 | |||
| 638 |    // have we NOT found it ? | 
        ||
| 639 | if (player_index == onlineplayer_count)  | 
        ||
| 640 | Player_SendBuffer_Add (player, 1000, L"who\n"); // if so, request a players list refresh  | 
        ||
| 641 | |||
| 642 |    // send a notification to this player's chat window | 
        ||
| 643 | interlocutor = Interlocutor_FindOrCreate (nickname);  | 
        ||
| 644 | Interlocutor_Notify (interlocutor, LOCALIZE (L"Chat_InvitationAcceptedByOther"), nickname);  | 
        ||
| 645 | if (IsWindow (interlocutor->hWnd))  | 
        ||
| 646 | ShowWindow (interlocutor->hWnd, SW_MINIMIZE); // minimize chat window immediately  | 
        ||
| 647 | |||
| 648 | return; // finished evaluating this challenge reply notification  | 
        ||
| 649 | } | 
        ||
| 650 | |||
| 651 | |||
| 652 | void EvaluateServerReply_ChallengeDeclined (player_t *player)  | 
        ||
| 653 | { | 
        ||
| 654 |    // this function parses a network reply and evaluates it, deciding what to do | 
        ||
| 655 | |||
| 656 | wchar_t nickname[32];  | 
        ||
| 657 | wchar_t *line_start;  | 
        ||
| 658 | wchar_t *field_start;  | 
        ||
| 659 | int player_index;  | 
        ||
| 660 | |||
| 661 |    // is the challenge declined notification discriminative bit NOT present ? | 
        ||
| 662 | if (!IS_FIELD_PRESENT (field_start, L" declines the match offer."))  | 
        ||
| 663 | return; // if so, this reply can't be a challenge reply notification so just return  | 
        ||
| 664 | |||
| 665 | line_start = ReachBeginningOfCurrentLine (player->recvbuffer, field_start); // look up for the last line feed  | 
        ||
| 666 | ReadNickname (nickname, WCHAR_SIZEOF (nickname), line_start); // get username  | 
        ||
| 667 | |||
| 668 |    // see if this nickname exists in the list of connected players ; if not, refresh list | 
        ||
| 669 | for (player_index = 0; player_index < onlineplayer_count; player_index++)  | 
        ||
| 670 | if (wcscmp (nickname, onlineplayers[player_index].nickname) == 0)  | 
        ||
| 671 | break; // break as soon as we find it  | 
        ||
| 672 | |||
| 673 |    // have we NOT found it ? | 
        ||
| 674 | if (player_index == onlineplayer_count)  | 
        ||
| 675 | Player_SendBuffer_Add (player, 1000, L"who\n"); // if so, request a players list refresh  | 
        ||
| 676 | |||
| 677 |    // send a notification to this player's chat window | 
        ||
| 678 | Interlocutor_Notify (Interlocutor_FindOrCreate (nickname), LOCALIZE (L"Chat_InvitationDeclinedByOther"), nickname);  | 
        ||
| 679 | |||
| 680 | return; // finished evaluating this challenge reply notification  | 
        ||
| 681 | } | 
        ||
| 682 | |||
| 683 | |||
| 684 | void EvaluateServerReply_Takeback (player_t *player)  | 
        ||
| 685 | { | 
        ||
| 686 |    // this function parses a network reply and evaluates it, deciding what to do | 
        ||
| 687 | |||
| 688 | wchar_t *field_start;  | 
        ||
| 689 | wchar_t *field_stop;  | 
        ||
| 690 | int howmany_halfmoves;  | 
        ||
| 691 | |||
| 692 |    // is the challenge declined notification discriminative bit NOT present ? | 
        ||
| 693 | if (!IS_FIELD_PRESENT (field_start, L" would like to take back ") || !IS_FIELD_PRESENT (field_stop, L" half move(s)"))  | 
        ||
| 694 | return; // if so, this reply can't be a challenge reply notification so just return  | 
        ||
| 695 | |||
| 696 |    // read the numbre of half moves the opponent reclaims | 
        ||
| 697 | swscanf_s (field_start, L" would like to take back %d ", &howmany_halfmoves);  | 
        ||
| 698 | |||
| 699 |    // send a notification to the local player's chat window | 
        ||
| 700 | Interlocutor_Notify (Interlocutor_FindOrCreate (player->name), LOCALIZE (L"Chat_TakebackRequestReceived"), player->name, howmany_halfmoves);  | 
        ||
| 701 | DialogBox_Takeback (howmany_halfmoves); // and fire up a modal dialog box to ask confirmation to the local player  | 
        ||
| 702 | |||
| 703 | return; // finished evaluating this challenge reply notification  | 
        ||
| 704 | } | 
        ||
| 705 | |||
| 706 | |||
| 707 | void EvaluateServerReply_TakebackDeclinedByOther (player_t *player)  | 
        ||
| 708 | { | 
        ||
| 709 |    // this function parses a network reply and evaluates it, deciding what to do | 
        ||
| 710 | |||
| 711 | wchar_t *field_start;  | 
        ||
| 712 | |||
| 713 |    // is the takeback declined notification discriminative bit NOT present ? | 
        ||
| 714 | if (!IS_FIELD_PRESENT (field_start, L" declines the takeback request."))  | 
        ||
| 715 | return; // if so, this reply can't be a takeback reply notification so just return  | 
        ||
| 716 | |||
| 717 |    // send a notification to the local player's chat window | 
        ||
| 718 | Interlocutor_Notify (Interlocutor_FindOrCreate (player->name), LOCALIZE (L"Chat_TakebackRefused"), player->name);  | 
        ||
| 719 | |||
| 720 | return; // finished evaluating this challenge reply notification  | 
        ||
| 721 | } | 
        ||
| 722 | |||
| 723 | |||
| 724 | void EvaluateServerReply_TakebackDeclinedByYou (player_t *player)  | 
        ||
| 725 | { | 
        ||
| 726 |    // this function parses a network reply and evaluates it, deciding what to do | 
        ||
| 727 | |||
| 728 | wchar_t *field_start;  | 
        ||
| 729 | |||
| 730 |    // is the takeback declined notification discriminative bit NOT present ? | 
        ||
| 731 | if (!IS_FIELD_PRESENT_AT_BEGINNING_OF_LINE (field_start, L"You decline the takeback request from "))  | 
        ||
| 732 | return; // if so, this reply can't be a takeback reply notification so just return  | 
        ||
| 733 | |||
| 734 |    // send a notification to the local player's chat window | 
        ||
| 735 | Interlocutor_Notify (Interlocutor_FindOrCreate (player->name), LOCALIZE (L"Chat_TakebackRefused"), player->name);  | 
        ||
| 736 | |||
| 737 | return; // finished evaluating this challenge reply notification  | 
        ||
| 738 | } | 
        ||
| 739 | |||
| 740 | |||
| 741 | void EvaluateServerReply_PlayNotAllowed (player_t *player)  | 
        ||
| 742 | { | 
        ||
| 743 |    // this function parses a network reply and evaluates it, deciding what to do | 
        ||
| 744 | |||
| 745 | wchar_t *field_start;  | 
        ||
| 746 | |||
| 747 |    // is the play reply notification discriminative bit NOT present ? | 
        ||
| 748 | if (!IS_FIELD_PRESENT_AT_BEGINNING_OF_LINE (field_start, L"Only registered players can play rated games."))  | 
        ||
| 749 | return; // if so, this reply can't be a play reply notification so just return  | 
        ||
| 750 | |||
| 751 |    // display a message box for the player to know that his opponent refuses to play | 
        ||
| 752 | messagebox.hWndParent = (IsWindow (hSoughtWnd) ? hSoughtWnd : hMainWnd);  | 
        ||
| 753 | wcscpy_s (messagebox.title, WCHAR_SIZEOF (messagebox.title), LOCALIZE (L"PlayReply_ServerReply"));  | 
        ||
| 754 | swprintf_s (messagebox.text, WCHAR_SIZEOF (messagebox.text), LOCALIZE (L"PlayReply_OnlyRegisteredCanPlayRated"));  | 
        ||
| 755 | messagebox.flags = MB_ICONINFORMATION | MB_OK;  | 
        ||
| 756 | DialogBox_Message (&messagebox); // display a modeless error message box  | 
        ||
| 757 | |||
| 758 | return; // finished evaluating this play reply notification  | 
        ||
| 759 | } | 
        ||
| 760 | |||
| 761 | |||
| 762 | void EvaluateServerReply_PlayUnexistent (player_t *player)  | 
        ||
| 763 | { | 
        ||
| 764 |    // this function parses a network reply and evaluates it, deciding what to do | 
        ||
| 765 | |||
| 766 | wchar_t *field_start;  | 
        ||
| 767 | |||
| 768 |    // is the play reply notification discriminative bit NOT present ? | 
        ||
| 769 | if (!IS_FIELD_PRESENT_AT_BEGINNING_OF_LINE (field_start, L"That seek is not available."))  | 
        ||
| 770 | return; // if so, this reply can't be a play reply so just return  | 
        ||
| 771 | |||
| 772 |    // display a message box for the player to know that his opponent refuses to play | 
        ||
| 773 | messagebox.hWndParent = (IsWindow (hSoughtWnd) ? hSoughtWnd : hMainWnd);  | 
        ||
| 774 | wcscpy_s (messagebox.title, WCHAR_SIZEOF (messagebox.title), LOCALIZE (L"PlayReply_ServerReply"));  | 
        ||
| 775 | swprintf_s (messagebox.text, WCHAR_SIZEOF (messagebox.text), LOCALIZE (L"PlayReply_UnexistentSeek"));  | 
        ||
| 776 | messagebox.flags = MB_ICONINFORMATION | MB_OK;  | 
        ||
| 777 | DialogBox_Message (&messagebox); // display a modeless error message box  | 
        ||
| 778 | |||
| 779 | return; // finished evaluating this play reply notification  | 
        ||
| 780 | } | 
        ||
| 781 | |||
| 782 | |||
| 783 | void EvaluateServerReply_PlayWrongRating (player_t *player)  | 
        ||
| 784 | { | 
        ||
| 785 |    // this function parses a network reply and evaluates it, deciding what to do | 
        ||
| 786 | |||
| 787 | wchar_t *field_start;  | 
        ||
| 788 | |||
| 789 |    // is the play reply notification discriminative bit NOT present ? | 
        ||
| 790 | if (!IS_FIELD_PRESENT_AT_BEGINNING_OF_LINE (field_start, L"Your rating does not qualify for this seek."))  | 
        ||
| 791 | return; // if so, this reply can't be a play reply so just return  | 
        ||
| 792 | |||
| 793 |    // display a message box for the player to know that his opponent refuses to play | 
        ||
| 794 | messagebox.hWndParent = (IsWindow (hSoughtWnd) ? hSoughtWnd : hMainWnd);  | 
        ||
| 795 | wcscpy_s (messagebox.title, WCHAR_SIZEOF (messagebox.title), LOCALIZE (L"PlayReply_ServerReply"));  | 
        ||
| 796 | swprintf_s (messagebox.text, WCHAR_SIZEOF (messagebox.text), LOCALIZE (L"PlayReply_WrongRating"));  | 
        ||
| 797 | messagebox.flags = MB_ICONINFORMATION | MB_OK;  | 
        ||
| 798 | DialogBox_Message (&messagebox); // display a modeless error message box  | 
        ||
| 799 | |||
| 800 | return; // finished evaluating this play reply notification  | 
        ||
| 801 | } | 
        ||
| 802 | |||
| 803 | |||
| 804 | void EvaluateServerReply_ChannelsAndMembers (player_t *player)  | 
        ||
| 805 | { | 
        ||
| 806 |    // this function parses a network reply and evaluates it, deciding what to do | 
        ||
| 807 | |||
| 808 |    chatterchannel_t cc; | 
        ||
| 809 | wchar_t *field_start;  | 
        ||
| 810 | wchar_t *field_stop;  | 
        ||
| 811 | wchar_t *string_pointer;  | 
        ||
| 812 | wchar_t *big_buffer;  | 
        ||
| 813 | player_t *local_player;  | 
        ||
| 814 | int previouslyselected_channelid;  | 
        ||
| 815 | int naturallanguagechannel_index;  | 
        ||
| 816 | int chatterchannel_index;  | 
        ||
| 817 | int cctheme_length;  | 
        ||
| 818 | int char_index;  | 
        ||
| 819 | |||
| 820 |    // is the channels and members header bit NOT present ? | 
        ||
| 821 | if (!IS_FIELD_PRESENT_AT_BEGINNING_OF_LINE (field_start, L"Channel "))  | 
        ||
| 822 | return; // if so, this reply can't be a channels and members list so just return  | 
        ||
| 823 | |||
| 824 | local_player = Player_FindByType (PLAYER_HUMAN); // get a pointer to the human player  | 
        ||
| 825 | |||
| 826 |    // is a chatter channel selected ? | 
        ||
| 827 | if (selected_chatterchannel != NULL)  | 
        ||
| 828 | previouslyselected_channelid = selected_chatterchannel->id; // save its ID  | 
        ||
| 829 |    else | 
        ||
| 830 | previouslyselected_channelid = -1; // -1 will instruct us to select a default chatter channel  | 
        ||
| 831 | |||
| 832 |    // for each chatter channel we know already... | 
        ||
| 833 | for (chatterchannel_index = 0; chatterchannel_index < chatterchannel_count; chatterchannel_index++)  | 
        ||
| 834 |    { | 
        ||
| 835 | SAFE_free ((void **) &chatterchannels[chatterchannel_index].members); // for each channel, free its members array  | 
        ||
| 836 | chatterchannels[chatterchannel_index].member_count = 0;  | 
        ||
| 837 |    } | 
        ||
| 838 | SAFE_free ((void **) &chatterchannels); // free the chatter channel list we know  | 
        ||
| 839 | chatterchannel_count = 0; // reset the chatter channel count  | 
        ||
| 840 | naturallanguagechannel_index = -1; // reset the natural language channel index  | 
        ||
| 841 | |||
| 842 |    // linearize the string | 
        ||
| 843 | string_pointer = field_start;  | 
        ||
| 844 | while (string_pointer[1] != 0)  | 
        ||
| 845 |    { | 
        ||
| 846 | if ((string_pointer[0] == L'\n') && (string_pointer[1] == L'\\'))  | 
        ||
| 847 | string_pointer[0] = string_pointer[1] = L' '; // replace every newline followed by a backslash by two spaces  | 
        ||
| 848 | string_pointer++;  | 
        ||
| 849 |    } | 
        ||
| 850 | |||
| 851 |    // mallocate space for a big buffer | 
        ||
| 852 | big_buffer = (wchar_t *) SAFE_malloc (1024 * 1024, sizeof (wchar_t), false);  | 
        ||
| 853 | |||
| 854 |    // read line per line | 
        ||
| 855 | string_pointer = ReachBeginningOfCurrentLine (player->recvbuffer, field_start); // start at the first character  | 
        ||
| 856 | while ((string_pointer = wcsgets (big_buffer, 1024 * 1024, string_pointer)) != NULL)  | 
        ||
| 857 |    { | 
        ||
| 858 | if (big_buffer[0] == 0)  | 
        ||
| 859 | break; // if it's an empty line, then the channel list is finished  | 
        ||
| 860 | |||
| 861 |       // now parse the chatter channel data | 
        ||
| 862 | field_start = big_buffer;  | 
        ||
| 863 | memset (&cc, 0, sizeof (cc));  | 
        ||
| 864 | |||
| 865 | REACH_NEXT_FIELD_ELSE (field_start, continue);  | 
        ||
| 866 | cc.id = _wtoi (field_start); // read chatter channel id  | 
        ||
| 867 | |||
| 868 | REACH_NEXT_FIELD_ELSE (field_start, continue);  | 
        ||
| 869 | if (*field_start == L'"')  | 
        ||
| 870 |       { | 
        ||
| 871 | field_start++; // skip the quote  | 
        ||
| 872 | field_stop = field_start;  | 
        ||
| 873 | while (*field_stop && (*field_stop != L'"'))  | 
        ||
| 874 | field_stop++; // reach the next quote  | 
        ||
| 875 | if (*field_stop == 0)  | 
        ||
| 876 | continue; // discard bogus lines  | 
        ||
| 877 | *field_stop = 0; // break the string here  | 
        ||
| 878 | wcscpy_s (cc.theme, WCHAR_SIZEOF (cc.theme), field_start); // copy theme  | 
        ||
| 879 | cctheme_length = wcslen (cc.theme);  | 
        ||
| 880 | for (char_index = 0; char_index < cctheme_length; char_index++)  | 
        ||
| 881 | if (cc.theme[char_index] == L'_')  | 
        ||
| 882 | cc.theme[char_index] = L' '; // convert underscores to spaces  | 
        ||
| 59 | pmbaty | 883 | if (_wcsicmp (cc.theme, languages[language_id].name) == 0)  | 
        
| 1 | pmbaty | 884 | naturallanguagechannel_index = chatterchannel_count; // if this channel is the natural language one, remember it  | 
        
| 885 | field_start = field_stop + 1; // and continue reading the string  | 
        ||
| 886 | REACH_NEXT_FIELD_ELSE (field_start, continue);  | 
        ||
| 887 |       } | 
        ||
| 888 | |||
| 889 |       // determine the channel color according to channel ID | 
        ||
| 890 | srand (1000 + cc.id);  | 
        ||
| 891 | cc.color = RGBA_TO_RGBACOLOR (rand () % 256, rand () % 256, rand () % 256, 0xff);  | 
        ||
| 892 | |||
| 893 |       // now read the channel members | 
        ||
| 894 | cc.members = NULL;  | 
        ||
| 895 | cc.member_count = 0;  | 
        ||
| 896 | while (*field_start != 0)  | 
        ||
| 897 |       { | 
        ||
| 898 | cc.members = (chatterchannelmember_t *) SAFE_realloc (cc.members, cc.member_count, cc.member_count + 1, sizeof (chatterchannelmember_t), false);  | 
        ||
| 899 | if (*field_start == '{')  | 
        ||
| 900 |          { | 
        ||
| 901 | ReadNickname (cc.members[cc.member_count].nickname, WCHAR_SIZEOF (cc.members[cc.member_count].nickname), &field_start[1]);  | 
        ||
| 902 | cc.members[cc.member_count].is_silenced = true; // this player plays in silence  | 
        ||
| 903 |          } | 
        ||
| 904 |          else | 
        ||
| 905 |          { | 
        ||
| 906 | ReadNickname (cc.members[cc.member_count].nickname, WCHAR_SIZEOF (cc.members[cc.member_count].nickname), field_start);  | 
        ||
| 907 | cc.members[cc.member_count].is_silenced = false; // this player allows us to talk to him  | 
        ||
| 908 |          } | 
        ||
| 909 | if (wcscmp (cc.members[cc.member_count].nickname, local_player->name) == 0)  | 
        ||
| 910 | cc.is_open = true; // if we are on this channel, mark this channel as open  | 
        ||
| 911 | cc.member_count++; // reallocate, read player nickname and increase chatter channel members array size  | 
        ||
| 912 | REACH_NEXT_FIELD_ELSE (field_start, continue); // and advance one player more  | 
        ||
| 913 |       } | 
        ||
| 914 | |||
| 915 |       // all parsing was OK, reallocate chatter channels list to have one channel more | 
        ||
| 916 | chatterchannels = (chatterchannel_t *) SAFE_realloc (chatterchannels, chatterchannel_count, chatterchannel_count + 1, sizeof (chatterchannel_t), true);  | 
        ||
| 917 | memcpy (&chatterchannels[chatterchannel_count], &cc, sizeof (chatterchannel_t)); // now save data  | 
        ||
| 918 | chatterchannel_count++; // we know now one sought game more  | 
        ||
| 919 |    } | 
        ||
| 920 | |||
| 921 |    // free the big buffer space we used | 
        ||
| 922 | SAFE_free ((void **) &big_buffer);  | 
        ||
| 923 | |||
| 924 |    // now that the chatter channels are read, find again the one that was previously selected | 
        ||
| 925 | |||
| 926 |    // if no chatter channel is selected yet, and we have a natural language channel exists and this channel is not open yet... | 
        ||
| 927 | if ((previouslyselected_channelid == -1) && (naturallanguagechannel_index != -1) && !chatterchannels[naturallanguagechannel_index].is_open)  | 
        ||
| 928 | Player_SendBuffer_Add (player, 1000, L"+channel %d\n", chatterchannels[naturallanguagechannel_index].id); // open this channel  | 
        ||
| 929 | |||
| 930 |    // cycle through all the chatter channels we know... | 
        ||
| 931 | for (chatterchannel_index = 0; chatterchannel_index < chatterchannel_count; chatterchannel_index++)  | 
        ||
| 932 | if ((previouslyselected_channelid != -1) && (chatterchannels[chatterchannel_index].id == previouslyselected_channelid))  | 
        ||
| 933 | break; // break as soon as we find it  | 
        ||
| 934 | |||
| 935 |    // have we found none ? | 
        ||
| 936 | if (chatterchannel_index == chatterchannel_count)  | 
        ||
| 937 |    { | 
        ||
| 938 |       // cycle through all the chatter channels we know... | 
        ||
| 939 | for (chatterchannel_index = 0; chatterchannel_index < chatterchannel_count; chatterchannel_index++)  | 
        ||
| 940 | if (chatterchannels[chatterchannel_index].is_open && (wcsistr (chatterchannels[chatterchannel_index].theme, L"chat") != NULL))  | 
        ||
| 941 | break; // break on the first open general chatter channel we find  | 
        ||
| 942 | |||
| 943 |       // have we found none ? | 
        ||
| 944 | if (chatterchannel_index == chatterchannel_count)  | 
        ||
| 945 | chatterchannel_index = 0; // ultimate fallback, select the first channel  | 
        ||
| 946 |    } | 
        ||
| 947 | |||
| 948 | selected_chatterchannel = &chatterchannels[chatterchannel_index]; // in the end, select the channel that was previously selected  | 
        ||
| 949 | |||
| 950 | chatterchannels_updated = true; // remember chatter channels list is to be updated  | 
        ||
| 951 | return; // finished evaluating this channel list  | 
        ||
| 952 | } | 
        ||
| 953 | |||
| 954 | |||
| 955 | void EvaluateServerReply_SoughtList (player_t *player)  | 
        ||
| 956 | { | 
        ||
| 957 |    // this function parses a network reply and evaluates it, deciding what to do | 
        ||
| 958 | |||
| 959 | wchar_t line_buffer[256];  | 
        ||
| 960 |    soughtgame_t sg; | 
        ||
| 961 | wchar_t *field_start;  | 
        ||
| 962 | wchar_t *string_pointer;  | 
        ||
| 963 | |||
| 964 |    // is the sought games footer bit NOT present ? | 
        ||
| 965 | if (!IS_FIELD_PRESENT (field_start, L" ads displayed.") && !IS_FIELD_PRESENT (field_start, L" ad displayed."))  | 
        ||
| 966 | return; // if so, this reply can't be a sought games list so just return  | 
        ||
| 967 | |||
| 968 | SAFE_free ((void **) &soughtgames); // free the sought games list we know  | 
        ||
| 969 | soughtgame_count = 0; // reset the sought games count  | 
        ||
| 970 | |||
| 971 |    // now read line per line | 
        ||
| 972 | string_pointer = player->recvbuffer; // start at the first character  | 
        ||
| 973 | while ((string_pointer = wcsgets (line_buffer, sizeof (line_buffer), string_pointer)) != NULL)  | 
        ||
| 974 |    { | 
        ||
| 975 | if (line_buffer[0] == L'\n')  | 
        ||
| 976 | continue; // discard empty lines  | 
        ||
| 977 | else if ((wcsstr (line_buffer, L" ads displayed.") != NULL) || (wcsstr (line_buffer, L" ad displayed.") != NULL))  | 
        ||
| 978 | break; // if it's the end of the list, stop reading  | 
        ||
| 979 | |||
| 980 |       // now parse the sought games data | 
        ||
| 981 | field_start = line_buffer;  | 
        ||
| 982 | memset (&sg, 0, sizeof (sg));  | 
        ||
| 983 | |||
| 984 | while (*field_start && iswspace (*field_start))  | 
        ||
| 985 | field_start++; // skip leading spaces  | 
        ||
| 986 | if (*field_start == 0)  | 
        ||
| 987 | continue; // discard bogus lines  | 
        ||
| 988 | |||
| 989 | sg.id = _wtoi (field_start); // read sought game id  | 
        ||
| 990 | REACH_NEXT_FIELD_ELSE (field_start, continue);  | 
        ||
| 991 | if ((*field_start != L'-') && (*field_start != L'+'))  | 
        ||
| 992 | sg.rating = _wtoi (field_start); // read player rating  | 
        ||
| 993 | REACH_NEXT_FIELD_ELSE (field_start, continue);  | 
        ||
| 994 | ReadNickname (sg.nickname, WCHAR_SIZEOF (sg.nickname), field_start); // read nickname  | 
        ||
| 995 | REACH_NEXT_FIELD_ELSE (field_start, continue);  | 
        ||
| 996 | sg.initial_time = (float) _wtof (field_start); // read initial time  | 
        ||
| 997 | REACH_NEXT_FIELD_ELSE (field_start, continue);  | 
        ||
| 998 | sg.increment = (float) _wtof (field_start); // read Fischer increment  | 
        ||
| 999 | REACH_NEXT_FIELD_ELSE (field_start, continue);  | 
        ||
| 1000 | sg.rating_type = (*field_start == L'r' ? GAMERATINGTYPE_SUPPORTEDRATED : GAMERATINGTYPE_SUPPORTEDUNRATED); // read whether it is rated or not  | 
        ||
| 1001 | REACH_NEXT_FIELD_ELSE (field_start, continue);  | 
        ||
| 1002 | ReadGamename (sg.game_type, WCHAR_SIZEOF (sg.game_type), field_start); // read game type  | 
        ||
| 1003 | sg.game_type[0] = towupper (sg.game_type[0]); // capitalize first character  | 
        ||
| 1004 | REACH_NEXT_FIELD_ELSE (field_start, continue);  | 
        ||
| 1005 | sg.color = COLOR_UNSPECIFIED; // set unspecified color until told otherwise  | 
        ||
| 1006 | if (*field_start == L'[')  | 
        ||
| 1007 |       { | 
        ||
| 1008 | if (field_start[1] == L'b') sg.color = COLOR_BLACK; // read specified color  | 
        ||
| 1009 | else if (field_start[1] == L'w') sg.color = COLOR_WHITE;  | 
        ||
| 1010 | REACH_NEXT_FIELD_ELSE (field_start, continue);  | 
        ||
| 1011 |       } | 
        ||
| 1012 | if (swscanf_s (field_start, L"%d-%d", &sg.lowest_accepted, &sg.highest_accepted) != 2)  | 
        ||
| 1013 | continue; // read minimal and maximal accepted ELO, and discard bogus lines  | 
        ||
| 1014 | |||
| 1015 |       // read whether the game will start automatically and whether the player's filter formula will be checked | 
        ||
| 1016 | if (wcsstr (field_start, L" mf") != NULL)  | 
        ||
| 1017 |       { | 
        ||
| 1018 | sg.manual_start = true;  | 
        ||
| 1019 | sg.formula_checked = true;  | 
        ||
| 1020 |       } | 
        ||
| 1021 | else if (wcsstr (field_start, L" m") != NULL)  | 
        ||
| 1022 | sg.manual_start = true;  | 
        ||
| 1023 | else if (wcsstr (field_start, L" f") != NULL)  | 
        ||
| 1024 | sg.formula_checked = true;  | 
        ||
| 1025 | |||
| 1026 |       // is this variant unsupported ? | 
        ||
| 1027 | if ((wcscmp (sg.game_type, L"Untimed") != 0) && (wcscmp (sg.game_type, L"Standard") != 0)  | 
        ||
| 1028 | && (wcscmp (sg.game_type, L"Blitz") != 0) && (wcscmp (sg.game_type, L"Lightning") != 0))  | 
        ||
| 1029 |       { | 
        ||
| 1030 | sg.rating = 0;  | 
        ||
| 1031 | sg.initial_time = 0.0f;  | 
        ||
| 1032 | sg.increment = 0.0f;  | 
        ||
| 1033 | sg.rating_type = GAMERATINGTYPE_UNSUPPORTED; // if so, clear some values to clean up the display  | 
        ||
| 1034 | sg.color = COLOR_UNSPECIFIED;  | 
        ||
| 1035 | sg.lowest_accepted = 0;  | 
        ||
| 1036 | sg.highest_accepted = 0;  | 
        ||
| 1037 | sg.manual_start = true;  | 
        ||
| 1038 | sg.formula_checked = false;  | 
        ||
| 1039 |       } | 
        ||
| 1040 | |||
| 1041 |       // all parsing was OK, reallocate sought games list to have one sought game more | 
        ||
| 1042 | soughtgames = (soughtgame_t *) SAFE_realloc (soughtgames, soughtgame_count, soughtgame_count + 1, sizeof (soughtgame_t), true);  | 
        ||
| 1043 | memcpy (&soughtgames[soughtgame_count], &sg, sizeof (soughtgame_t)); // now save data  | 
        ||
| 1044 | soughtgame_count++; // we know now one sought game more  | 
        ||
| 1045 |    } | 
        ||
| 1046 | |||
| 1047 | soughtgames_updated = true; // remember sought games display is to be updated  | 
        ||
| 1048 | lastsought_time = current_time; // remember when we were last updated  | 
        ||
| 1049 | return; // finished evaluating this sought games list  | 
        ||
| 1050 | } | 
        ||
| 1051 | |||
| 1052 | |||
| 1053 | void EvaluateServerReply_PlayersList (player_t *player)  | 
        ||
| 1054 | { | 
        ||
| 1055 |    // this function parses a network reply and evaluates it, deciding what to do | 
        ||
| 1056 | |||
| 1057 | wchar_t line_buffer[256];  | 
        ||
| 1058 |    onlineplayer_t olp; | 
        ||
| 1059 | wchar_t *field_stop;  | 
        ||
| 1060 | wchar_t *string_pointer;  | 
        ||
| 1061 | int buffer_length;  | 
        ||
| 1062 | int char_index;  | 
        ||
| 1063 | |||
| 1064 |    // is the players list footer bit NOT present ? | 
        ||
| 1065 | if (!IS_FIELD_PRESENT (field_stop, L"(*) indicates system administrator."))  | 
        ||
| 1066 | return; // if so, this reply can't be a players list so just return  | 
        ||
| 1067 | |||
| 1068 | SAFE_free ((void **) &onlineplayers); // free the online players list we know  | 
        ||
| 1069 | onlineplayer_count = 0; // reset the players count  | 
        ||
| 1070 | |||
| 1071 |    // format the player list well. We slightly modify recvbuffer here. | 
        ||
| 1072 |    // for each character in string... | 
        ||
| 1073 | buffer_length = wcslen (player->recvbuffer);  | 
        ||
| 1074 | for (char_index = 0; char_index < buffer_length - 1; char_index++)  | 
        ||
| 1075 |    { | 
        ||
| 1076 |       // is it a separator (two spaces) ? | 
        ||
| 1077 | if ((player->recvbuffer[char_index] == L' ') && (player->recvbuffer[char_index + 1] == L' '))  | 
        ||
| 1078 |       { | 
        ||
| 1079 | while (iswspace (player->recvbuffer[char_index]))  | 
        ||
| 1080 |          { | 
        ||
| 1081 | player->recvbuffer[char_index] = L'\n'; // replace all spaces by newlines  | 
        ||
| 1082 | char_index++; // skip all spaces and reach the next non-space character  | 
        ||
| 1083 |          } | 
        ||
| 1084 |       } | 
        ||
| 1085 |    } | 
        ||
| 1086 | |||
| 1087 |    // now read line per line | 
        ||
| 1088 | string_pointer = player->recvbuffer; // start at the first character  | 
        ||
| 1089 | while ((string_pointer = wcsgets (line_buffer, sizeof (line_buffer), string_pointer)) != NULL)  | 
        ||
| 1090 |    { | 
        ||
| 1091 | if (line_buffer[0] == L'\n')  | 
        ||
| 1092 | continue; // discard empty lines  | 
        ||
| 1093 | else if (wcsstr (line_buffer, L"(*) indicates system administrator.") != NULL)  | 
        ||
| 1094 | break; // if it's the end of the list, stop parsing  | 
        ||
| 1095 | |||
| 1096 |       // now parse the player data | 
        ||
| 1097 | |||
| 1098 |       // parse the handle status | 
        ||
| 1099 | if (wcschr (line_buffer, L'^') != NULL) olp.handlestatus = HANDLESTATUS_INGAME; // player is in game  | 
        ||
| 1100 | else if (wcschr (line_buffer, L'~') != NULL) olp.handlestatus = HANDLESTATUS_INSIMULATION; // player is in simulation  | 
        ||
| 1101 | else if (wcschr (line_buffer, L'&') != NULL) olp.handlestatus = HANDLESTATUS_INTOURNAMENT; // player is in tournament  | 
        ||
| 1102 | else if (wcschr (line_buffer, L'#') != NULL) olp.handlestatus = HANDLESTATUS_EXAMININGAGAME; // player is examining a game  | 
        ||
| 1103 | else if (wcschr (line_buffer, L':') != NULL) olp.handlestatus = HANDLESTATUS_NOTOPENFORAMATCH; // player is not open for a match  | 
        ||
| 1104 | else if (wcschr (line_buffer, L'.') != NULL) olp.handlestatus = HANDLESTATUS_INACTIVEORBUSY; // player is inactive or busy  | 
        ||
| 1105 | else olp.handlestatus = HANDLESTATUS_AVAILABLE; // player is available  | 
        ||
| 1106 | |||
| 1107 |       // parse the handle codes | 
        ||
| 1108 | olp.handlecodes = 0;  | 
        ||
| 1109 | if (wcsstr (line_buffer, L"(*)") != NULL) olp.handlecodes |= HANDLECODE_ADMINISTRATOR; // player is administrator  | 
        ||
| 1110 | if (wcsstr (line_buffer, L"(B)") != NULL) olp.handlecodes |= HANDLECODE_BLINDFOLD; // player is blindfold  | 
        ||
| 1111 | if (wcsstr (line_buffer, L"(C)") != NULL) olp.handlecodes |= HANDLECODE_COMPUTER; // player is a computer  | 
        ||
| 1112 | if (wcsstr (line_buffer, L"(T)") != NULL) olp.handlecodes |= HANDLECODE_TEAM; // player is several persons  | 
        ||
| 1113 | if (wcsstr (line_buffer, L"(U)") != NULL) olp.handlecodes |= HANDLECODE_UNREGISTERED; // player is unregistered  | 
        ||
| 1114 | if (wcsstr (line_buffer, L"(CA)") != NULL) olp.handlecodes |= HANDLECODE_CHESSADVISOR; // player is a chess advisor  | 
        ||
| 1115 | if (wcsstr (line_buffer, L"(SR)") != NULL) olp.handlecodes |= HANDLECODE_SERVICEREPRESENTATIVE; // player is a service representative  | 
        ||
| 1116 | if (wcsstr (line_buffer, L"(TD)") != NULL) olp.handlecodes |= HANDLECODE_TOURNAMENTDIRECTOR; // player is a tournament director  | 
        ||
| 1117 | if (wcsstr (line_buffer, L"(TM)") != NULL) olp.handlecodes |= HANDLECODE_MAMERMANAGER; // player is a mamer manager  | 
        ||
| 1118 | if (wcsstr (line_buffer, L"(FM)") != NULL) olp.handlecodes |= HANDLECODE_FIDEMASTER; // player is a FIDE master  | 
        ||
| 1119 | if (wcsstr (line_buffer, L"(IM)") != NULL) olp.handlecodes |= HANDLECODE_FIDEINTERNATIONALMASTER; // player is a FIDE international master  | 
        ||
| 1120 | if (wcsstr (line_buffer, L"(GM)") != NULL) olp.handlecodes |= HANDLECODE_FIDEGREATMASTER; // player is a FIDE grand master  | 
        ||
| 1121 | if (wcsstr (line_buffer, L"(WIM)") != NULL) olp.handlecodes |= HANDLECODE_FIDEWOMENSINTERNATIONALMASTER; // player is a FIDE woman international master  | 
        ||
| 1122 | if (wcsstr (line_buffer, L"(WGM)") != NULL) olp.handlecodes |= HANDLECODE_FIDEWOMENSGREATMASTER; // player is a FIDE woman great master  | 
        ||
| 1123 | |||
| 1124 |       // get to the first non-numeric character | 
        ||
| 1125 | buffer_length = wcslen (line_buffer);  | 
        ||
| 1126 | for (char_index = 0; char_index < buffer_length; char_index++)  | 
        ||
| 1127 | if (!iswdigit (line_buffer[char_index]) && (line_buffer[char_index] != L'+') && (line_buffer[char_index] != L'-'))  | 
        ||
| 1128 | break; // break as soon as we find it  | 
        ||
| 1129 | |||
| 1130 | if (char_index >= buffer_length - 1)  | 
        ||
| 1131 | continue; // consistency check: this player is bogus  | 
        ||
| 1132 | |||
| 1133 |       // is it a E ? else is it a P ? | 
        ||
| 1134 | if (line_buffer[char_index] == L'E') olp.ratingtype = OPPONENTRATINGTYPE_ESTIMATED; // this player's rating is estimated  | 
        ||
| 1135 | else if (line_buffer[char_index] == L'P') olp.ratingtype = OPPONENTRATINGTYPE_PROVISIONAL; // this player's rating is provisional  | 
        ||
| 1136 | else olp.ratingtype = OPPONENTRATINGTYPE_DEFAULT; // this player's rating is normal  | 
        ||
| 1137 | |||
| 1138 | wcscpy_s (olp.nickname, WCHAR_SIZEOF (olp.nickname), &line_buffer[char_index + 1]); // copy nickname  | 
        ||
| 1139 | if ((field_stop = wcschr (olp.nickname, L'(')) != NULL)  | 
        ||
| 1140 | *field_stop = 0; // separate nickname from its handle flags  | 
        ||
| 1141 | |||
| 1142 |       // read opponent rating | 
        ||
| 1143 | olp.rating = _wtoi (line_buffer); // ++++ (unregistered) and ---- (no rating) will be translated as 0  | 
        ||
| 1144 | |||
| 1145 |       // all parsing was OK, reallocate online players list to have one player more | 
        ||
| 1146 | onlineplayers = (onlineplayer_t *) SAFE_realloc (onlineplayers, onlineplayer_count, onlineplayer_count + 1, sizeof (onlineplayer_t), true);  | 
        ||
| 1147 | memcpy (&onlineplayers[onlineplayer_count], &olp, sizeof (onlineplayer_t)); // now save data  | 
        ||
| 1148 | onlineplayer_count++; // we know now one player more  | 
        ||
| 1149 |    } | 
        ||
| 1150 | |||
| 1151 | onlineplayers_updated = true; // remember online player list is to be updated  | 
        ||
| 1152 | lastonlineplayers_time = current_time; // remember when we were last updated  | 
        ||
| 1153 | return; // finished evaluating this players list  | 
        ||
| 1154 | } | 
        ||
| 1155 | |||
| 1156 | |||
| 1157 | void EvaluateServerReply_GameStarting (player_t *player)  | 
        ||
| 1158 | { | 
        ||
| 1159 |    // this function parses a network reply and evaluates it, deciding what to do | 
        ||
| 1160 | |||
| 1161 | wchar_t white_name[32];  | 
        ||
| 1162 | wchar_t black_name[32];  | 
        ||
| 1163 | wchar_t *field_start;  | 
        ||
| 1164 | player_t *local_player;  | 
        ||
| 1165 | |||
| 1166 |    // is the game starting notification header bit NOT present ? | 
        ||
| 1167 | if (!IS_FIELD_PRESENT_AT_BEGINNING_OF_LINE (field_start, L"Creating: "))  | 
        ||
| 1168 | return; // if so, this reply can't be a game starting notification so just return  | 
        ||
| 1169 | |||
| 1170 |    // read the interesting parameters, namely white player's name (so that we know which side to display) | 
        ||
| 1171 | if (swscanf_s (field_start, L"Creating: %s %*s %s %*s %*s %*s %*d %*d", white_name, WCHAR_SIZEOF (white_name), black_name, WCHAR_SIZEOF (black_name)) != 2)  | 
        ||
| 1172 | Debug_Log (L"===WARNING: unable to parse game starting notification message!===\n%s\n======\n", field_start);  | 
        ||
| 1173 | |||
| 1174 | local_player = Player_FindByType (PLAYER_HUMAN); // get a pointer to the local player  | 
        ||
| 1175 | |||
| 1176 |    // is local player NOT the white color AND should be, OR is local player white color AND should NOT be ? | 
        ||
| 1177 | if (((local_player->color != COLOR_WHITE) && (wcscmp (white_name, local_player->name) == 0))  | 
        ||
| 1178 | || ((local_player->color == COLOR_WHITE) && (wcscmp (white_name, local_player->name) != 0)))  | 
        ||
| 1179 |    { | 
        ||
| 1180 | Debug_Log (L"===Game starting and local player is not white and should be (or is white and should not be), swapping sides===\n");  | 
        ||
| 1181 | the_board.want_playerswap = true; // swap sides  | 
        ||
| 1182 |    } | 
        ||
| 1183 | |||
| 1184 |    // display the game starting message notification | 
        ||
| 140 | pmbaty | 1185 | Scene_UpdateText (&the_scene.gui.central_text, RGBA_TO_RGBACOLOR (255, 255, 255, 191), 5.0f, true, L"\n\n\n\n\n%s\n%s (%s) %s %s (%s)", LOCALIZE (L"NewGame_Title"), white_name, LOCALIZE (L"Games_White"), LOCALIZE (L"Versus"), black_name, LOCALIZE (L"Games_Black"));  | 
        
| 1 | pmbaty | 1186 | |
| 1187 |    // close any possible singleton window | 
        ||
| 1188 | if (IsWindow (hChatterChannelsWnd))  | 
        ||
| 1189 | DestroyWindow (hChatterChannelsWnd);  | 
        ||
| 1190 | if (IsWindow (hGamesWnd))  | 
        ||
| 1191 | DestroyWindow (hGamesWnd); // PGN games window (improbable)  | 
        ||
| 1192 | if (IsWindow (hMOTDWnd))  | 
        ||
| 1193 | DestroyWindow (hMOTDWnd);  | 
        ||
| 1194 | if (IsWindow (hOpponentsWnd))  | 
        ||
| 1195 | DestroyWindow (hOpponentsWnd);  | 
        ||
| 1196 | if (IsWindow (hSoughtWnd))  | 
        ||
| 1197 | DestroyWindow (hSoughtWnd);  | 
        ||
| 1198 | |||
| 1199 | return; // finished evaluating the game starting notification  | 
        ||
| 1200 | } | 
        ||
| 1201 | |||
| 1202 | |||
| 1203 | void EvaluateServerReply_GameState (player_t *player)  | 
        ||
| 1204 | { | 
        ||
| 1205 |    // this function parses a network reply and evaluates it, deciding what to do | 
        ||
| 1206 | |||
| 1207 |    // Format of style12 computer-parseable lines (ICC/FICS): | 
        ||
| 1208 |    //  | 
        ||
| 1209 |    // <12> rnbqkb-r pppppppp -----n-- -------- ----P--- -------- PPPPKPPP RNBQ-BNR B -1 0 0 1 1 0 7 Newton Einstein 1 2 12 39 39 119 122 2 K/e1-e2 (0:06) Ke2 0 | 
        ||
| 1210 |    //  | 
        ||
| 1211 |    // This string always begins on a new line, and there are always exactly 31 non-empty fields separated by blanks. The fields are: | 
        ||
| 1212 |    //  | 
        ||
| 1213 |    // * the string "<12>" to identify this line. | 
        ||
| 1214 |    // * eight fields representing the board position. First one is White's 8th rank (also Black's 1st rank), then White's 7th rank (also Black's 2nd), etc. | 
        ||
| 1215 |    // * color whose turn it is to move ("B" or "W") | 
        ||
| 1216 |    // * -1 if the previous move was NOT a double pawn push, otherwise the chess board file  (numbered 0--7 for a--h) in which the double push was made | 
        ||
| 1217 |    // * can White still castle short? (0=no, 1=yes) | 
        ||
| 1218 |    // * can White still castle long? | 
        ||
| 1219 |    // * can Black still castle short? | 
        ||
| 1220 |    // * can Black still castle long? | 
        ||
| 1221 |    // * the number of moves made since the last irreversible move (0 if last move was irreversible. If value is >= 100, game can be declared a draw due to the 50 move rule.) | 
        ||
| 1222 |    // * The game number | 
        ||
| 1223 |    // * White's name | 
        ||
| 1224 |    // * Black's name | 
        ||
| 1225 |    // * my relation to this game: | 
        ||
| 1226 |    //     -3 isolated position, such as for "ref 3" or the "sposition" command | 
        ||
| 1227 |    //     -2 I am observing game being examined | 
        ||
| 1228 |    //      2 I am the examiner of this game | 
        ||
| 1229 |    //     -1 I am playing, it is my opponent's move | 
        ||
| 1230 |    //      1 I am playing and it is my move | 
        ||
| 1231 |    //      0 I am observing a game being played | 
        ||
| 1232 |    // * initial time (in seconds) of the match | 
        ||
| 1233 |    // * increment In seconds) of the match | 
        ||
| 1234 |    // * White material strength | 
        ||
| 1235 |    // * Black material strength | 
        ||
| 1236 |    // * White's remaining time | 
        ||
| 1237 |    // * Black's remaining time | 
        ||
| 1238 |    // * the number of the move about to be made (standard chess numbering -- White's and Black's first moves are both 1, etc.) | 
        ||
| 1239 |    // * verbose coordinate notation for the previous move ("none" if there were none) [note this used to be broken for examined games] | 
        ||
| 1240 |    // * time taken to make previous move "(min:sec)". | 
        ||
| 1241 |    // * pretty notation for the previous move ("none" if there is none) | 
        ||
| 1242 |    // * flip field for board orientation: 1 = Black at bottom, 0 = White at bottom. | 
        ||
| 1243 |    //  | 
        ||
| 1244 |    // In the future, new fields may be added to the end of the data string, so programs should parse from left to right. | 
        ||
| 1245 |    //  | 
        ||
| 1246 |    // Special information for bughouse games | 
        ||
| 1247 |    // -------------------------------------- | 
        ||
| 1248 |    // When showing positions from bughouse games, a second line showing piece holding is given, with "<b1>" at the beginning, for example: | 
        ||
| 1249 |    //   <b1> game 6 white [PNBBB] black [PNB] | 
        ||
| 1250 |    // Also, when pieces are "passed" during bughouse, a short data string -- not the entire board position -- is sent.  For example: | 
        ||
| 1251 |    //   <b1> game 52 white [NB] black [N] <- BN | 
        ||
| 1252 |    // The final two letters indicate the piece that was passed; in the above example, a knight (N) was passed to Black. | 
        ||
| 1253 |    // A prompt may preceed the <b1> header. | 
        ||
| 1254 | |||
| 1255 | wchar_t positions[65];  | 
        ||
| 1256 | wchar_t move_color_as_string[2];  | 
        ||
| 1257 | int move_color;  | 
        ||
| 1258 | int player_color;  | 
        ||
| 1259 | int pawnrush_column;  | 
        ||
| 1260 | int can_white_castle_short;  | 
        ||
| 1261 | int can_white_castle_long;  | 
        ||
| 1262 | int can_black_castle_short;  | 
        ||
| 1263 | int can_black_castle_long;  | 
        ||
| 1264 | int number_of_moves_since_last_irreversible_move;  | 
        ||
| 1265 | int game_number;  | 
        ||
| 1266 | wchar_t white_name[32];  | 
        ||
| 1267 | wchar_t black_name[32];  | 
        ||
| 1268 | int my_status;  | 
        ||
| 1269 | int white_remaining_time_in_seconds;  | 
        ||
| 1270 | int black_remaining_time_in_seconds;  | 
        ||
| 1271 | int turn_number_1_based;  | 
        ||
| 1272 | int move_index;  | 
        ||
| 1273 | wchar_t pretty_movestring[8];  | 
        ||
| 1274 |    boardmove_t move; | 
        ||
| 1275 | wchar_t *field_start;  | 
        ||
| 1276 | int recognized_fields;  | 
        ||
| 1277 | boardmove_t *last_move;  | 
        ||
| 1278 | |||
| 1279 |    // is it NOT a style12 line reply ? | 
        ||
| 1280 | if (!IS_FIELD_PRESENT_AT_BEGINNING_OF_LINE (field_start, L"<12> "))  | 
        ||
| 1281 | return; // if so, this reply can't be a style12 notification so just return  | 
        ||
| 1282 | |||
| 1283 | recognized_fields = swscanf_s (field_start, L"<12> " // style12 header  | 
        ||
| 1284 | L"%s %s %s %s %s %s %s %s " // "rnbqkbnr pppppppp -------- -------- -------- -------- PPPPPPPP RNBQKBNR"  | 
        ||
| 1285 | L"%s %d %d %d %d %d %d %d " // "W -1 1 1 1 1 0 2"  | 
        ||
| 1286 | L"%s %s %d %*d %*d %*d %*d " // "guestpmtelnet GuestJXTJ -1 2 12 39 39"  | 
        ||
| 1287 | L"%d %d %d %*s %*s %s %*d", // "120 120 1 none (0:00) none 1 0 0"  | 
        ||
| 1288 | &positions[0 * 8], 9, &positions[1 * 8], 9, &positions[2 * 8], 9, &positions[3 * 8], 9, &positions[4 * 8], 9, &positions[5 * 8], 9, &positions[6 * 8], 9, &positions[7 * 8], 9,  | 
        ||
| 1289 | move_color_as_string, WCHAR_SIZEOF (move_color_as_string), &pawnrush_column, &can_white_castle_short, &can_white_castle_long, &can_black_castle_short, &can_black_castle_long, &number_of_moves_since_last_irreversible_move, &game_number,  | 
        ||
| 1290 | white_name, WCHAR_SIZEOF (white_name), black_name, WCHAR_SIZEOF (black_name), &my_status,  | 
        ||
| 1291 | &white_remaining_time_in_seconds, &black_remaining_time_in_seconds, &turn_number_1_based, pretty_movestring, WCHAR_SIZEOF (pretty_movestring));  | 
        ||
| 1292 | if (recognized_fields != 23)  | 
        ||
| 1293 | return; // unparseable style12 reply  | 
        ||
| 1294 | |||
| 1295 |    // remember we are in game and which game it is | 
        ||
| 1296 | player->is_in_game = true;  | 
        ||
| 1297 | player->game_number = game_number;  | 
        ||
| 1298 | |||
| 1299 |    // convert some data into easier formats first. The color which just moved is the opposite of the color to move now | 
        ||
| 1300 | move_color = (towupper (move_color_as_string[0]) == L'W' ? COLOR_BLACK : COLOR_WHITE);  | 
        ||
| 1301 | move_index = (2 * turn_number_1_based) - 2 + (move_color == COLOR_WHITE ? 1 : 0);  | 
        ||
| 1302 | |||
| 1303 |    // save the unquestionable parameters (direct it to the opposite player if the board is going to be swapped) | 
        ||
| 1304 | player_color = the_board.want_playerswap ? COLOR_WHITE : COLOR_BLACK;  | 
        ||
| 1305 | ReadNickname (the_board.players[player_color].name, WCHAR_SIZEOF (the_board.players[player_color].name), black_name);  | 
        ||
| 1306 | the_board.players[player_color].remaining_seconds = black_remaining_time_in_seconds;  | 
        ||
| 1307 | |||
| 1308 | player_color = the_board.want_playerswap ? COLOR_BLACK : COLOR_WHITE;  | 
        ||
| 1309 | ReadNickname (the_board.players[player_color].name, WCHAR_SIZEOF (the_board.players[player_color].name), white_name);  | 
        ||
| 1310 | the_board.players[player_color].remaining_seconds = white_remaining_time_in_seconds;  | 
        ||
| 1311 | |||
| 1312 |    // is it an initial board position ? | 
        ||
| 1313 | if (wcscmp (pretty_movestring, L"none") == 0)  | 
        ||
| 1314 |    { | 
        ||
| 1315 | Debug_Log (L"===Setting up board according to chess server's specifications and beginning new game===\n");  | 
        ||
| 1316 | |||
| 1317 |       // set up the board according to what the chess server tells us | 
        ||
| 1318 | memset (&move, 0, sizeof (move));  | 
        ||
| 1319 | Move_SetupFromStyle12 (&move, positions, move_color, pawnrush_column,  | 
        ||
| 1320 | (can_white_castle_short != 0), (can_white_castle_long != 0), (can_black_castle_short != 0), (can_black_castle_long != 0), pretty_movestring);  | 
        ||
| 1321 | Board_Reset (&the_board, move.fen_string);  | 
        ||
| 1322 |    } | 
        ||
| 1323 | |||
| 1324 |    // else play the move and swap sides | 
        ||
| 1325 |    else | 
        ||
| 1326 |    { | 
        ||
| 1327 | Debug_Log (L"===Received board status update from chess server===\n");  | 
        ||
| 1328 | |||
| 1329 |       // are we appending a new move ? | 
        ||
| 1330 | if (((move_color == player->color) && (move_index == the_board.move_count)) // either a new move by our opponent  | 
        ||
| 1331 | || ((move_color != player->color) && (move_index == the_board.move_count - 1))) // or a confirmation of a move already played by the local player  | 
        ||
| 1332 |       { | 
        ||
| 1333 | Debug_Log (L"===Appending new move %d===\n", move_index);  | 
        ||
| 1334 | |||
| 1335 |          // get a pointer to the previous move | 
        ||
| 1336 | last_move = &the_board.moves[the_board.move_count - 1];  | 
        ||
| 1337 | |||
| 1338 |          // is the server reporting the remote player's move ? | 
        ||
| 1339 | if (move_color == player->color)  | 
        ||
| 1340 |          { | 
        ||
| 1341 |             // evaluate the move string | 
        ||
| 1342 | wcscpy_s (move.pgntext, WCHAR_SIZEOF (move.pgntext), pretty_movestring);  | 
        ||
| 1343 | if (!Move_SetupFromSAN (last_move, &move, player->color))  | 
        ||
| 1344 | Debug_Log (L"===WARNING: unable to interpret server's table state while evaluating its notification of remote player move!===\n%s\n======\n", pretty_movestring);  | 
        ||
| 1345 | |||
| 1346 |             // play the remote opponent's move | 
        ||
| 1347 | Board_AppendMove (&the_board, move.source[0], move.source[1], move.target[0], move.target[1], move.promotion_type, NULL);  | 
        ||
| 1348 | |||
| 1349 | the_board.has_playerchanged = true; // remember players changed  | 
        ||
| 1350 | animation_endtime = current_time + ANIMATION_DURATION; // play move animation now  | 
        ||
| 1351 |          } | 
        ||
| 1352 | |||
| 1353 |          // else the server is acknowledging the local player's move | 
        ||
| 1354 |          else | 
        ||
| 1355 |          { | 
        ||
| 1356 |             // evaluate the move string | 
        ||
| 1357 | wcscpy_s (last_move->pgntext, WCHAR_SIZEOF (last_move->pgntext), pretty_movestring);  | 
        ||
| 1358 | if (!Move_SetupFromSAN (&the_board.moves[the_board.move_count - 2], last_move, last_move->color))  | 
        ||
| 1359 | Debug_Log (L"===WARNING: unable to interpret server's table state while evaluating its reply after local player move!===\n%s\n======\n", pretty_movestring);  | 
        ||
| 1360 |          } | 
        ||
| 1361 | |||
| 1362 |          // in case a new move was appended, update the last move pointer | 
        ||
| 1363 | last_move = &the_board.moves[the_board.move_count - 1];  | 
        ||
| 1364 |       } | 
        ||
| 1365 | |||
| 1366 |       // else the server must be time-warping the game backwards | 
        ||
| 1367 |       else | 
        ||
| 1368 |       { | 
        ||
| 1369 | Debug_Log (L"===Backing up to move %d===\n", move_index);  | 
        ||
| 1370 | |||
| 1371 | last_move = &the_board.moves[move_index]; // get a pointer to the move we're backing up to  | 
        ||
| 1372 | the_board.move_count = move_index + 1; // update the board's move count  | 
        ||
| 1373 | if (the_board.viewed_move > the_board.move_count - 1)  | 
        ||
| 1374 | the_board.viewed_move = the_board.move_count - 1; // and the board's viewed move as well  | 
        ||
| 1375 | |||
| 1376 |          // send a notification to the player's chat window | 
        ||
| 161 | pmbaty | 1377 | Interlocutor_Notify (Interlocutor_FindOrCreate (player->name), LOCALIZE (L"Chat_TakebackAccepted"), NULL);  | 
        
| 1 | pmbaty | 1378 |       } | 
        
| 1379 | |||
| 1380 |       // make the server set it up correctly | 
        ||
| 1381 |       Move_SetupFromStyle12 (last_move, positions, move_color, pawnrush_column, | 
        ||
| 1382 | (can_white_castle_short != 0), (can_white_castle_long != 0), (can_black_castle_short != 0), (can_black_castle_long != 0), pretty_movestring);  | 
        ||
| 1383 |    } | 
        ||
| 1384 | |||
| 136 | pmbaty | 1385 | the_board.game_state = STATE_PLAYING; // remember that a game is currently playing (this may be overwritten later by EvaluateServerReply_GameResults)  | 
        
| 1 | pmbaty | 1386 | the_board.reevaluate = true; // and reeevaluate the board  | 
        
| 1387 | return; // finished evaluating this style12 notification line  | 
        ||
| 1388 | } | 
        ||
| 1389 | |||
| 1390 | |||
| 1391 | void EvaluateServerReply_GameResults (player_t *player)  | 
        ||
| 1392 | { | 
        ||
| 1393 |    // this function parses a network reply and evaluates it, deciding what to do | 
        ||
| 1394 | |||
| 1395 | wchar_t *field_start;  | 
        ||
| 1396 | int game_number;  | 
        ||
| 1397 | |||
| 1398 |    // is the game results notification header bit NOT present ? | 
        ||
| 1399 | if (!IS_FIELD_PRESENT_AT_BEGINNING_OF_LINE (field_start, L"{Game "))  | 
        ||
| 1400 | return; // if so, this reply can't be a game results notification so just return  | 
        ||
| 1401 | |||
| 1402 |    // are we NOT in game yet OR is it a game creation message ? | 
        ||
| 1403 | if (!player->is_in_game || (wcsstr (field_start, L") Creating") != NULL))  | 
        ||
| 1404 | return; // if so, this message is not a game results but a game creation message  | 
        ||
| 1405 | |||
| 1406 |    // verify it's for the game we're playing | 
        ||
| 1407 | if (swscanf_s (field_start, L"{Game %d ", &game_number) != 1)  | 
        ||
| 1408 |    { | 
        ||
| 1409 | Debug_Log (L"===WARNING: unable to parse game results notification message!===\n%s\n======\n", field_start);  | 
        ||
| 1410 | return; // on error, drop a warning in the log file and return  | 
        ||
| 1411 |    } | 
        ||
| 1412 | if (game_number != player->game_number)  | 
        ||
| 1413 |    { | 
        ||
| 1414 | Debug_Log (L"===WARNING: received game results notification message with wrong game number! Ignoring.===\n");  | 
        ||
| 1415 | return; // on error, drop a warning in the log file and return  | 
        ||
| 1416 |    } | 
        ||
| 1417 | |||
| 1418 |    //////////////////////////// | 
        ||
| 1419 |    // interpret the game result | 
        ||
| 1420 | |||
| 1421 |    // do the white win ? | 
        ||
| 1422 | if (wcsstr (field_start, L"} 1-0") != NULL)  | 
        ||
| 1423 |    { | 
        ||
| 1424 |       // is it a checkmate ? | 
        ||
| 1425 | if (wcsstr (field_start, L"checkmate") != NULL)  | 
        ||
| 1426 |       { | 
        ||
| 1427 | Debug_Log (L"===Server tells us that black is checkmate: white wins!===\n");  | 
        ||
| 1428 | the_board.game_state = STATE_WHITEWIN_CHECKMATE; // remember game state  | 
        ||
| 1429 |       } | 
        ||
| 1430 | |||
| 1431 |       // else it must be a resign, a forfeit or an adjudication | 
        ||
| 1432 |       else | 
        ||
| 1433 |       { | 
        ||
| 1434 | Debug_Log (L"===Server tells us that black [resigns|forfeits|loses adjudication]: white wins!===\n");  | 
        ||
| 1435 | the_board.game_state = STATE_WHITEWIN_RESIGNORFORFEIT; // remember game state  | 
        ||
| 1436 |       } | 
        ||
| 1437 | |||
| 116 | pmbaty | 1438 |       // if white player is human, play the victory sound, else, play defeat sound at the center of the board | 
        
| 1439 | Audio_PlaySound (the_board.players[COLOR_WHITE].type == PLAYER_HUMAN ? SOUNDTYPE_VICTORY : SOUNDTYPE_DEFEAT, 0.0f, 0.0f, 0.04f);  | 
        ||
| 1 | pmbaty | 1440 |    } | 
        
| 1441 | |||
| 1442 |    // else do the black win ? | 
        ||
| 1443 | else if (wcsstr (field_start, L"} 0-1") != NULL)  | 
        ||
| 1444 |    { | 
        ||
| 1445 |       // is it a checkmate ? | 
        ||
| 1446 | if (wcsstr (field_start, L" checkmate") != NULL)  | 
        ||
| 1447 |       { | 
        ||
| 1448 | Debug_Log (L"===Server tells us that white is checkmate: black wins!===\n");  | 
        ||
| 1449 | the_board.game_state = STATE_BLACKWIN_CHECKMATE; // remember game state  | 
        ||
| 1450 |       } | 
        ||
| 1451 | |||
| 1452 |       // else it must be a resign, a forfeit or an adjudication | 
        ||
| 1453 |       else | 
        ||
| 1454 |       { | 
        ||
| 1455 | Debug_Log (L"===Server tells us that white [resigns|forfeits|loses adjudication]: black wins!===\n");  | 
        ||
| 1456 | the_board.game_state = STATE_BLACKWIN_RESIGNORFORFEIT; // remember game state  | 
        ||
| 1457 |       } | 
        ||
| 1458 | |||
| 116 | pmbaty | 1459 |       // if black player is human, play the victory sound, else, play defeat sound at the center of the board | 
        
| 1460 | Audio_PlaySound (the_board.players[COLOR_BLACK].type == PLAYER_HUMAN ? SOUNDTYPE_VICTORY : SOUNDTYPE_DEFEAT, 0.0f, 0.0f, 0.04f);  | 
        ||
| 1 | pmbaty | 1461 |    } | 
        
| 1462 | |||
| 1463 |    // else is it a draw ? | 
        ||
| 1464 | else if (wcsstr (field_start, L"} 1/2-1/2") != NULL)  | 
        ||
| 1465 |    { | 
        ||
| 1466 |       // is it a stalemate ? | 
        ||
| 1467 | if (wcsstr (field_start, L" stalemate") != NULL)  | 
        ||
| 1468 |       { | 
        ||
| 1469 | Debug_Log (L"===Server tells us it's a stalemate: game is a draw===\n");  | 
        ||
| 1470 | the_board.game_state = STATE_DRAW_STALEMATE; // remember game state  | 
        ||
| 1471 |       } | 
        ||
| 1472 | |||
| 1473 |       // else is it a mutual agreement ? | 
        ||
| 1474 | else if (wcsstr (field_start, L" mutual agreement") != NULL)  | 
        ||
| 1475 |       { | 
        ||
| 1476 | Debug_Log (L"===Server tells us it's a draw by mutual agreement: game is a draw===\n");  | 
        ||
| 1477 | the_board.game_state = STATE_DRAW_AGREEMENT; // remember game state  | 
        ||
| 1478 |       } | 
        ||
| 1479 | |||
| 1480 |       // else it's another reason | 
        ||
| 1481 |       else | 
        ||
| 1482 |       { | 
        ||
| 1483 | Debug_Log (L"===Server tells us it's a draw for another reason: game is a draw===\n");  | 
        ||
| 1484 | the_board.game_state = STATE_DRAW_OTHER; // remember game state  | 
        ||
| 1485 |       } | 
        ||
| 1486 | |||
| 116 | pmbaty | 1487 |       // play a defeat sound at the center of the board | 
        
| 1488 | Audio_PlaySound (SOUNDTYPE_DEFEAT, 0.0f, 0.0f, 0.04f);  | 
        ||
| 1 | pmbaty | 1489 |    } | 
        
| 1490 | |||
| 1491 |    // else is it an adjournment ? | 
        ||
| 1492 | else if (wcsstr (field_start, L"} *") != NULL)  | 
        ||
| 1493 |    { | 
        ||
| 1494 | Debug_Log (L"===Server tells the game is adjourned: game adjourned===\n");  | 
        ||
| 1495 | the_board.game_state = STATE_ADJOURNED; // remember game state  | 
        ||
| 1496 |    } | 
        ||
| 1497 | |||
| 1498 |    // else we can't interpret the game state | 
        ||
| 1499 |    else | 
        ||
| 1500 |    { | 
        ||
| 1501 | Debug_Log (L"===WARNING: unable to interpret game results notification message!===\n%s\n======\n", field_start);  | 
        ||
| 1502 | return; // on error, drop a warning in the log file and return  | 
        ||
| 1503 |    } | 
        ||
| 1504 | |||
| 1505 |    // remember player is no longer in game | 
        ||
| 1506 | player->is_in_game = false;  | 
        ||
| 1507 | player->game_number = 0;  | 
        ||
| 1508 | |||
| 1509 |    // reevaluate the board and display the endgame dialog box | 
        ||
| 1510 | the_board.reevaluate = true;  | 
        ||
| 1511 | DialogBox_EndGame ();  | 
        ||
| 1512 | |||
| 1513 | return; // finished evaluating the game results notification  | 
        ||
| 1514 | } | 
        ||
| 1515 | |||
| 1516 | |||
| 1517 | static void ReadNickname (wchar_t *nickname, size_t nickname_size, wchar_t *from_string)  | 
        ||
| 1518 | { | 
        ||
| 1519 |    // helper function to read a nickname and strip it from its eventual flags | 
        ||
| 1520 | |||
| 1521 | unsigned int char_index;  | 
        ||
| 1522 | |||
| 1523 |    // as long as we don't read a forbidden character... | 
        ||
| 1524 | for (char_index = 0; char_index < nickname_size; char_index++)  | 
        ||
| 1525 | if (iswalpha (from_string[char_index]))  | 
        ||
| 1526 | nickname[char_index] = from_string[char_index]; // copy nickname one character after the other  | 
        ||
| 1527 |       else | 
        ||
| 1528 | break; // else stop copying immediately  | 
        ||
| 1529 | |||
| 1530 | if (char_index < nickname_size)  | 
        ||
| 1531 | nickname[char_index] = 0; // finish the string ourselves  | 
        ||
| 1532 |    else | 
        ||
| 1533 | nickname[nickname_size - 1] = 0; // truncate it if neeeded  | 
        ||
| 1534 | |||
| 1535 | return; // finished  | 
        ||
| 1536 | } | 
        ||
| 1537 | |||
| 1538 | |||
| 1539 | static void ReadGamename (wchar_t *gamename, size_t gamename_size, wchar_t *from_string)  | 
        ||
| 1540 | { | 
        ||
| 1541 |    // helper function to read a game name | 
        ||
| 1542 | |||
| 1543 | unsigned int char_index;  | 
        ||
| 1544 | |||
| 1545 |    // as long as we don't read a forbidden character... | 
        ||
| 1546 | for (char_index = 0; char_index < gamename_size; char_index++)  | 
        ||
| 1547 | if (iswgraph (from_string[char_index]))  | 
        ||
| 1548 | gamename[char_index] = from_string[char_index]; // copy game name one character after the other  | 
        ||
| 1549 |       else | 
        ||
| 1550 | break; // else stop copying immediately  | 
        ||
| 1551 | |||
| 1552 | if (char_index < gamename_size)  | 
        ||
| 1553 | gamename[char_index] = 0; // finish the string ourselves  | 
        ||
| 1554 |    else | 
        ||
| 1555 | gamename[gamename_size - 1] = 0; // truncate it if neeeded  | 
        ||
| 1556 | |||
| 1557 | return; // finished  | 
        ||
| 1558 | } | 
        ||
| 1559 | |||
| 1560 | |||
| 1561 | static void ReadSpannedLine (wchar_t *outstring, size_t outstring_size, wchar_t *multiline_string)  | 
        ||
| 1562 | { | 
        ||
| 1563 |    // this function linearizes a multiline string and takes out any special formatting character of it | 
        ||
| 1564 | |||
| 1565 | int length;  | 
        ||
| 1566 | int read_index;  | 
        ||
| 1567 | unsigned int write_index;  | 
        ||
| 1568 | |||
| 1569 | length = wcslen (multiline_string); // get text length first  | 
        ||
| 1570 | |||
| 1571 |    // for each character in string... | 
        ||
| 1572 | write_index = 0;  | 
        ||
| 1573 | for (read_index = 0; read_index < length; read_index++)  | 
        ||
| 1574 |    { | 
        ||
| 1575 | if (wcsncmp (&multiline_string[read_index], L"\\ ", 4) == 0)  | 
        ||
| 1576 | read_index += 4; // if it's a new line indentation, skip it  | 
        ||
| 1577 | |||
| 1578 |       // are we NOT reading a newline followed by a backslash AND is it still room in the output string ? | 
        ||
| 1579 | if (!((read_index < length - 1) && (multiline_string[read_index] == L'\n') && (multiline_string[read_index + 1] == L'\\'))  | 
        ||
| 1580 | && (write_index < outstring_size - 1))  | 
        ||
| 1581 |       { | 
        ||
| 1582 | if (multiline_string[read_index] == L'\n')  | 
        ||
| 1583 | break; // if it's a newline (without backslash following), stop reading  | 
        ||
| 1584 | |||
| 1585 | outstring[write_index] = multiline_string[read_index]; // else copy this character to output string  | 
        ||
| 1586 | write_index++; // and advance in string  | 
        ||
| 1587 |       } | 
        ||
| 1588 |    } | 
        ||
| 1589 | |||
| 1590 | outstring[write_index] = 0; // finish string  | 
        ||
| 1591 | return; // and return  | 
        ||
| 1592 | } |