project_files/frontlib/ipc/gameconn.c
changeset 10017 de822cd3df3a
parent 7580 c92596feac0d
equal deleted inserted replaced
10015:4feced261c68 10017:de822cd3df3a
    26 #include <stdbool.h>
    26 #include <stdbool.h>
    27 #include <stdlib.h>
    27 #include <stdlib.h>
    28 #include <string.h>
    28 #include <string.h>
    29 
    29 
    30 typedef enum {
    30 typedef enum {
    31 	AWAIT_CONNECTION,
    31     AWAIT_CONNECTION,
    32 	CONNECTED,
    32     CONNECTED,
    33 	FINISHED
    33     FINISHED
    34 } gameconn_state;
    34 } gameconn_state;
    35 
    35 
    36 struct _flib_gameconn {
    36 struct _flib_gameconn {
    37 	flib_ipcbase *ipcBase;
    37     flib_ipcbase *ipcBase;
    38 	flib_vector *configBuffer;
    38     flib_vector *configBuffer;
    39 	flib_vector *demoBuffer;
    39     flib_vector *demoBuffer;
    40 	char *playerName;
    40     char *playerName;
    41 
    41 
    42 	gameconn_state state;
    42     gameconn_state state;
    43 	bool netgame;
    43     bool netgame;
    44 	int disconnectReason;
    44     int disconnectReason;
    45 
    45 
    46 	void (*onConnectCb)(void* context);
    46     void (*onConnectCb)(void* context);
    47 	void *onConnectCtx;
    47     void *onConnectCtx;
    48 
    48 
    49 	void (*onDisconnectCb)(void* context, int reason);
    49     void (*onDisconnectCb)(void* context, int reason);
    50 	void *onDisconnectCtx;
    50     void *onDisconnectCtx;
    51 
    51 
    52 	void (*onErrorMessageCb)(void* context, const char *msg);
    52     void (*onErrorMessageCb)(void* context, const char *msg);
    53 	void *onErrorMessageCtx;
    53     void *onErrorMessageCtx;
    54 
    54 
    55 	void (*onChatCb)(void* context, const char *msg, bool teamchat);
    55     void (*onChatCb)(void* context, const char *msg, bool teamchat);
    56 	void *onChatCtx;
    56     void *onChatCtx;
    57 
    57 
    58 	void (*onGameRecordedCb)(void *context, const uint8_t *record, size_t size, bool isSavegame);
    58     void (*onGameRecordedCb)(void *context, const uint8_t *record, size_t size, bool isSavegame);
    59 	void *onGameRecordedCtx;
    59     void *onGameRecordedCtx;
    60 
    60 
    61 	void (*onEngineMessageCb)(void *context, const uint8_t *em, size_t size);
    61     void (*onEngineMessageCb)(void *context, const uint8_t *em, size_t size);
    62 	void *onEngineMessageCtx;
    62     void *onEngineMessageCtx;
    63 
    63 
    64 	bool running;
    64     bool running;
    65 	bool destroyRequested;
    65     bool destroyRequested;
    66 };
    66 };
    67 
    67 
    68 static void defaultCallback_onErrorMessage(void* context, const char *msg) {
    68 static void defaultCallback_onErrorMessage(void* context, const char *msg) {
    69 	flib_log_w("Error from engine (no callback set): %s", msg);
    69     flib_log_w("Error from engine (no callback set): %s", msg);
    70 }
    70 }
    71 
    71 
    72 static void clearCallbacks(flib_gameconn *conn) {
    72 static void clearCallbacks(flib_gameconn *conn) {
    73 	flib_gameconn_onConnect(conn, NULL, NULL);
    73     flib_gameconn_onConnect(conn, NULL, NULL);
    74 	flib_gameconn_onDisconnect(conn, NULL, NULL);
    74     flib_gameconn_onDisconnect(conn, NULL, NULL);
    75 	flib_gameconn_onErrorMessage(conn, NULL, NULL);
    75     flib_gameconn_onErrorMessage(conn, NULL, NULL);
    76 	flib_gameconn_onChat(conn, NULL, NULL);
    76     flib_gameconn_onChat(conn, NULL, NULL);
    77 	flib_gameconn_onGameRecorded(conn, NULL, NULL);
    77     flib_gameconn_onGameRecorded(conn, NULL, NULL);
    78 	flib_gameconn_onEngineMessage(conn, NULL, NULL);
    78     flib_gameconn_onEngineMessage(conn, NULL, NULL);
    79 }
    79 }
    80 
    80 
    81 static flib_gameconn *flib_gameconn_create_partial(bool record, const char *playerName, bool netGame) {
    81 static flib_gameconn *flib_gameconn_create_partial(bool record, const char *playerName, bool netGame) {
    82 	flib_gameconn *result = NULL;
    82     flib_gameconn *result = NULL;
    83 	flib_gameconn *tempConn = flib_calloc(1, sizeof(flib_gameconn));
    83     flib_gameconn *tempConn = flib_calloc(1, sizeof(flib_gameconn));
    84 	if(tempConn) {
    84     if(tempConn) {
    85 		tempConn->ipcBase = flib_ipcbase_create();
    85         tempConn->ipcBase = flib_ipcbase_create();
    86 		tempConn->configBuffer = flib_vector_create();
    86         tempConn->configBuffer = flib_vector_create();
    87 		tempConn->playerName = flib_strdupnull(playerName);
    87         tempConn->playerName = flib_strdupnull(playerName);
    88 		if(tempConn->ipcBase && tempConn->configBuffer && tempConn->playerName) {
    88         if(tempConn->ipcBase && tempConn->configBuffer && tempConn->playerName) {
    89 			if(record) {
    89             if(record) {
    90 				tempConn->demoBuffer = flib_vector_create();
    90                 tempConn->demoBuffer = flib_vector_create();
    91 			}
    91             }
    92 			tempConn->state = AWAIT_CONNECTION;
    92             tempConn->state = AWAIT_CONNECTION;
    93 			tempConn->netgame = netGame;
    93             tempConn->netgame = netGame;
    94 			tempConn->disconnectReason = GAME_END_ERROR;
    94             tempConn->disconnectReason = GAME_END_ERROR;
    95 			clearCallbacks(tempConn);
    95             clearCallbacks(tempConn);
    96 			result = tempConn;
    96             result = tempConn;
    97 			tempConn = NULL;
    97             tempConn = NULL;
    98 		}
    98         }
    99 	}
    99     }
   100 	flib_gameconn_destroy(tempConn);
   100     flib_gameconn_destroy(tempConn);
   101 	return result;
   101     return result;
   102 }
   102 }
   103 
   103 
   104 flib_gameconn *flib_gameconn_create(const char *playerName, const flib_gamesetup *setup, bool netgame) {
   104 flib_gameconn *flib_gameconn_create(const char *playerName, const flib_gamesetup *setup, bool netgame) {
   105 	if(log_badargs_if2(playerName==NULL, setup==NULL)) {
   105     if(log_badargs_if2(playerName==NULL, setup==NULL)) {
   106 		return NULL;
   106         return NULL;
   107 	}
   107     }
   108 	flib_gameconn *result = NULL;
   108     flib_gameconn *result = NULL;
   109 	flib_gameconn *tempConn = flib_gameconn_create_partial(true, playerName, netgame);
   109     flib_gameconn *tempConn = flib_gameconn_create_partial(true, playerName, netgame);
   110 	if(tempConn) {
   110     if(tempConn) {
   111 		if(flib_ipc_append_fullconfig(tempConn->configBuffer, setup, netgame)) {
   111         if(flib_ipc_append_fullconfig(tempConn->configBuffer, setup, netgame)) {
   112 			flib_log_e("Error generating full game configuration for the engine.");
   112             flib_log_e("Error generating full game configuration for the engine.");
   113 		} else {
   113         } else {
   114 			result = tempConn;
   114             result = tempConn;
   115 			tempConn = NULL;
   115             tempConn = NULL;
   116 		}
   116         }
   117 	}
   117     }
   118 	flib_gameconn_destroy(tempConn);
   118     flib_gameconn_destroy(tempConn);
   119 	return result;
   119     return result;
   120 }
   120 }
   121 
   121 
   122 flib_gameconn *flib_gameconn_create_playdemo(const uint8_t *demoFileContent, size_t size) {
   122 flib_gameconn *flib_gameconn_create_playdemo(const uint8_t *demoFileContent, size_t size) {
   123 	if(log_badargs_if(demoFileContent==NULL && size>0)) {
   123     if(log_badargs_if(demoFileContent==NULL && size>0)) {
   124 		return NULL;
   124         return NULL;
   125 	}
   125     }
   126 	flib_gameconn *result = NULL;
   126     flib_gameconn *result = NULL;
   127 	flib_gameconn *tempConn = flib_gameconn_create_partial(false, "Player", false);
   127     flib_gameconn *tempConn = flib_gameconn_create_partial(false, "Player", false);
   128 	if(tempConn) {
   128     if(tempConn) {
   129 		if(!flib_vector_append(tempConn->configBuffer, demoFileContent, size)) {
   129         if(!flib_vector_append(tempConn->configBuffer, demoFileContent, size)) {
   130 			result = tempConn;
   130             result = tempConn;
   131 			tempConn = NULL;
   131             tempConn = NULL;
   132 		}
   132         }
   133 	}
   133     }
   134 	flib_gameconn_destroy(tempConn);
   134     flib_gameconn_destroy(tempConn);
   135 	return result;
   135     return result;
   136 }
   136 }
   137 
   137 
   138 flib_gameconn *flib_gameconn_create_loadgame(const char *playerName, const uint8_t *saveFileContent, size_t size) {
   138 flib_gameconn *flib_gameconn_create_loadgame(const char *playerName, const uint8_t *saveFileContent, size_t size) {
   139 	if(log_badargs_if(saveFileContent==NULL && size>0)) {
   139     if(log_badargs_if(saveFileContent==NULL && size>0)) {
   140 		return NULL;
   140         return NULL;
   141 	}
   141     }
   142 	flib_gameconn *result = NULL;
   142     flib_gameconn *result = NULL;
   143 	flib_gameconn *tempConn = flib_gameconn_create_partial(true, playerName, false);
   143     flib_gameconn *tempConn = flib_gameconn_create_partial(true, playerName, false);
   144 	if(tempConn) {
   144     if(tempConn) {
   145 		if(!flib_vector_append(tempConn->configBuffer, saveFileContent, size)) {
   145         if(!flib_vector_append(tempConn->configBuffer, saveFileContent, size)) {
   146 			result = tempConn;
   146             result = tempConn;
   147 			tempConn = NULL;
   147             tempConn = NULL;
   148 		}
   148         }
   149 	}
   149     }
   150 	flib_gameconn_destroy(tempConn);
   150     flib_gameconn_destroy(tempConn);
   151 	return result;
   151     return result;
   152 }
   152 }
   153 
   153 
   154 flib_gameconn *flib_gameconn_create_campaign(const char *playerName, const char *seed, const char *script) {
   154 flib_gameconn *flib_gameconn_create_campaign(const char *playerName, const char *seed, const char *script) {
   155 	if(log_badargs_if3(playerName==NULL, seed==NULL, script==NULL)) {
   155     if(log_badargs_if3(playerName==NULL, seed==NULL, script==NULL)) {
   156 		return NULL;
   156         return NULL;
   157 	}
   157     }
   158 	flib_gameconn *result = NULL;
   158     flib_gameconn *result = NULL;
   159 	flib_gameconn *tempConn = flib_gameconn_create_partial(true, playerName, false);
   159     flib_gameconn *tempConn = flib_gameconn_create_partial(true, playerName, false);
   160 	if(tempConn) {
   160     if(tempConn) {
   161 		if(!flib_ipc_append_message(tempConn->configBuffer, "TL")
   161         if(!flib_ipc_append_message(tempConn->configBuffer, "TL")
   162 				&& !flib_ipc_append_seed(tempConn->configBuffer, seed)
   162                 && !flib_ipc_append_seed(tempConn->configBuffer, seed)
   163 				&& !flib_ipc_append_script(tempConn->configBuffer, script)
   163                 && !flib_ipc_append_script(tempConn->configBuffer, script)
   164 				&& !flib_ipc_append_message(tempConn->configBuffer, "!")) {
   164                 && !flib_ipc_append_message(tempConn->configBuffer, "!")) {
   165 			result = tempConn;
   165             result = tempConn;
   166 			tempConn = NULL;
   166             tempConn = NULL;
   167 		}
   167         }
   168 	}
   168     }
   169 	flib_gameconn_destroy(tempConn);
   169     flib_gameconn_destroy(tempConn);
   170 	return result;
   170     return result;
   171 }
   171 }
   172 
   172 
   173 void flib_gameconn_destroy(flib_gameconn *conn) {
   173 void flib_gameconn_destroy(flib_gameconn *conn) {
   174 	if(conn) {
   174     if(conn) {
   175 		if(conn->running) {
   175         if(conn->running) {
   176 			/*
   176             /*
   177 			 * The function was called from a callback, so the tick function is still running
   177              * The function was called from a callback, so the tick function is still running
   178 			 * and we delay the actual destruction. We ensure no further callbacks will be
   178              * and we delay the actual destruction. We ensure no further callbacks will be
   179 			 * sent to prevent surprises.
   179              * sent to prevent surprises.
   180 			 */
   180              */
   181 			clearCallbacks(conn);
   181             clearCallbacks(conn);
   182 			conn->destroyRequested = true;
   182             conn->destroyRequested = true;
   183 		} else {
   183         } else {
   184 			flib_ipcbase_destroy(conn->ipcBase);
   184             flib_ipcbase_destroy(conn->ipcBase);
   185 			flib_vector_destroy(conn->configBuffer);
   185             flib_vector_destroy(conn->configBuffer);
   186 			flib_vector_destroy(conn->demoBuffer);
   186             flib_vector_destroy(conn->demoBuffer);
   187 			free(conn->playerName);
   187             free(conn->playerName);
   188 			free(conn);
   188             free(conn);
   189 		}
   189         }
   190 	}
   190     }
   191 }
   191 }
   192 
   192 
   193 int flib_gameconn_getport(flib_gameconn *conn) {
   193 int flib_gameconn_getport(flib_gameconn *conn) {
   194 	if(log_badargs_if(conn==NULL)) {
   194     if(log_badargs_if(conn==NULL)) {
   195 		return 0;
   195         return 0;
   196 	}
   196     }
   197 	return flib_ipcbase_port(conn->ipcBase);
   197     return flib_ipcbase_port(conn->ipcBase);
   198 }
   198 }
   199 
   199 
   200 static void demo_append(flib_gameconn *conn, const void *data, size_t len) {
   200 static void demo_append(flib_gameconn *conn, const void *data, size_t len) {
   201 	if(conn->demoBuffer) {
   201     if(conn->demoBuffer) {
   202 		if(flib_vector_append(conn->demoBuffer, data, len)) {
   202         if(flib_vector_append(conn->demoBuffer, data, len)) {
   203 			flib_log_e("Error recording demo: Out of memory.");
   203             flib_log_e("Error recording demo: Out of memory.");
   204 			flib_vector_destroy(conn->demoBuffer);
   204             flib_vector_destroy(conn->demoBuffer);
   205 			conn->demoBuffer = NULL;
   205             conn->demoBuffer = NULL;
   206 		}
   206         }
   207 	}
   207     }
   208 }
   208 }
   209 
   209 
   210 static int format_chatmessage(uint8_t buffer[257], const char *playerName, const char *message) {
   210 static int format_chatmessage(uint8_t buffer[257], const char *playerName, const char *message) {
   211 	size_t msglen = strlen(message);
   211     size_t msglen = strlen(message);
   212 
   212 
   213 	// If the message starts with /me, it will be displayed differently.
   213     // If the message starts with /me, it will be displayed differently.
   214 	bool meMessage = msglen >= 4 && !memcmp(message, "/me ", 4);
   214     bool meMessage = msglen >= 4 && !memcmp(message, "/me ", 4);
   215 	const char *template = meMessage ? "s\x02* %s %s  " : "s\x01%s: %s  ";
   215     const char *template = meMessage ? "s\x02* %s %s  " : "s\x01%s: %s  ";
   216 	int size = snprintf((char*)buffer+1, 256, template, playerName, meMessage ? message+4 : message);
   216     int size = snprintf((char*)buffer+1, 256, template, playerName, meMessage ? message+4 : message);
   217 	if(log_e_if(size<=0, "printf error")) {
   217     if(log_e_if(size<=0, "printf error")) {
   218 		return -1;
   218         return -1;
   219 	} else {
   219     } else {
   220 		buffer[0] = size>255 ? 255 : size;
   220         buffer[0] = size>255 ? 255 : size;
   221 		return 0;
   221         return 0;
   222 	}
   222     }
   223 }
   223 }
   224 
   224 
   225 static void demo_append_chatmessage(flib_gameconn *conn, const char *message) {
   225 static void demo_append_chatmessage(flib_gameconn *conn, const char *message) {
   226 	// Chat messages are reformatted to make them look as if they were received, not sent.
   226     // Chat messages are reformatted to make them look as if they were received, not sent.
   227 	uint8_t converted[257];
   227     uint8_t converted[257];
   228 	if(!format_chatmessage(converted, conn->playerName, message)) {
   228     if(!format_chatmessage(converted, conn->playerName, message)) {
   229 		demo_append(conn, converted, converted[0]+1);
   229         demo_append(conn, converted, converted[0]+1);
   230 	}
   230     }
   231 }
   231 }
   232 
   232 
   233 static void demo_replace_gamemode(flib_buffer buf, char gamemode) {
   233 static void demo_replace_gamemode(flib_buffer buf, char gamemode) {
   234 	size_t msgStart = 0;
   234     size_t msgStart = 0;
   235 	uint8_t *data = (uint8_t*)buf.data;
   235     uint8_t *data = (uint8_t*)buf.data;
   236 	while(msgStart+2 < buf.size) {
   236     while(msgStart+2 < buf.size) {
   237 		if(!memcmp(data+msgStart, "\x02T", 2)) {
   237         if(!memcmp(data+msgStart, "\x02T", 2)) {
   238 			data[msgStart+2] = gamemode;
   238             data[msgStart+2] = gamemode;
   239 		}
   239         }
   240 		msgStart += (uint8_t)data[msgStart]+1;
   240         msgStart += (uint8_t)data[msgStart]+1;
   241 	}
   241     }
   242 }
   242 }
   243 
   243 
   244 int flib_gameconn_send_enginemsg(flib_gameconn *conn, const uint8_t *data, size_t len) {
   244 int flib_gameconn_send_enginemsg(flib_gameconn *conn, const uint8_t *data, size_t len) {
   245 	if(log_badargs_if2(conn==NULL, data==NULL && len>0)) {
   245     if(log_badargs_if2(conn==NULL, data==NULL && len>0)) {
   246 		return -1;
   246         return -1;
   247 	}
   247     }
   248 	int result = flib_ipcbase_send_raw(conn->ipcBase, data, len);
   248     int result = flib_ipcbase_send_raw(conn->ipcBase, data, len);
   249 	if(!result) {
   249     if(!result) {
   250 		demo_append(conn, data, len);
   250         demo_append(conn, data, len);
   251 	}
   251     }
   252 	return result;
   252     return result;
   253 }
   253 }
   254 
   254 
   255 int flib_gameconn_send_textmsg(flib_gameconn *conn, int msgtype, const char *msg) {
   255 int flib_gameconn_send_textmsg(flib_gameconn *conn, int msgtype, const char *msg) {
   256 	if(log_badargs_if2(conn==NULL, msg==NULL)) {
   256     if(log_badargs_if2(conn==NULL, msg==NULL)) {
   257 		return -1;
   257         return -1;
   258 	}
   258     }
   259 	int result = -1;
   259     int result = -1;
   260 	uint8_t converted[257];
   260     uint8_t converted[257];
   261 	int size = snprintf((char*)converted+1, 256, "s%c%s", (char)msgtype, msg);
   261     int size = snprintf((char*)converted+1, 256, "s%c%s", (char)msgtype, msg);
   262 	if(size>0) {
   262     if(size>0) {
   263 		converted[0] = size>255 ? 255 : size;
   263         converted[0] = size>255 ? 255 : size;
   264 		if(!flib_ipcbase_send_raw(conn->ipcBase, converted, converted[0]+1)) {
   264         if(!flib_ipcbase_send_raw(conn->ipcBase, converted, converted[0]+1)) {
   265 			demo_append(conn, converted, converted[0]+1);
   265             demo_append(conn, converted, converted[0]+1);
   266 			result = 0;
   266             result = 0;
   267 		}
   267         }
   268 	}
   268     }
   269 	return result;
   269     return result;
   270 }
   270 }
   271 
   271 
   272 int flib_gameconn_send_chatmsg(flib_gameconn *conn, const char *playername, const char *msg) {
   272 int flib_gameconn_send_chatmsg(flib_gameconn *conn, const char *playername, const char *msg) {
   273 	if(log_badargs_if3(conn==NULL, playername==NULL, msg==NULL)) {
   273     if(log_badargs_if3(conn==NULL, playername==NULL, msg==NULL)) {
   274 		return -1;
   274         return -1;
   275 	}
   275     }
   276 	uint8_t converted[257];
   276     uint8_t converted[257];
   277 	if(!format_chatmessage(converted, playername, msg)
   277     if(!format_chatmessage(converted, playername, msg)
   278 			&& !flib_ipcbase_send_raw(conn->ipcBase, converted, converted[0]+1)) {
   278             && !flib_ipcbase_send_raw(conn->ipcBase, converted, converted[0]+1)) {
   279 		demo_append(conn, converted, converted[0]+1);
   279         demo_append(conn, converted, converted[0]+1);
   280 		return 0;
   280         return 0;
   281 	}
   281     }
   282 	return -1;
   282     return -1;
   283 }
   283 }
   284 
   284 
   285 int flib_gameconn_send_quit(flib_gameconn *conn) {
   285 int flib_gameconn_send_quit(flib_gameconn *conn) {
   286 	return flib_gameconn_send_cmd(conn, "efinish");
   286     return flib_gameconn_send_cmd(conn, "efinish");
   287 }
   287 }
   288 
   288 
   289 int flib_gameconn_send_cmd(flib_gameconn *conn, const char *cmdString) {
   289 int flib_gameconn_send_cmd(flib_gameconn *conn, const char *cmdString) {
   290 	if(log_badargs_if2(conn==NULL, cmdString==NULL)) {
   290     if(log_badargs_if2(conn==NULL, cmdString==NULL)) {
   291 		return -1;
   291         return -1;
   292 	}
   292     }
   293 	int result = -1;
   293     int result = -1;
   294 	uint8_t converted[256];
   294     uint8_t converted[256];
   295 	size_t msglen = strlen(cmdString);
   295     size_t msglen = strlen(cmdString);
   296 	if(!log_e_if(msglen>255, "Message too long: %s", cmdString)) {
   296     if(!log_e_if(msglen>255, "Message too long: %s", cmdString)) {
   297 		strcpy((char*)converted+1, cmdString);
   297         strcpy((char*)converted+1, cmdString);
   298 		converted[0] = msglen;
   298         converted[0] = msglen;
   299 		if(!flib_ipcbase_send_raw(conn->ipcBase, converted, msglen+1)) {
   299         if(!flib_ipcbase_send_raw(conn->ipcBase, converted, msglen+1)) {
   300 			demo_append(conn, converted, msglen+1);
   300             demo_append(conn, converted, msglen+1);
   301 			result = 0;
   301             result = 0;
   302 		}
   302         }
   303 	}
   303     }
   304 	return result;
   304     return result;
   305 }
   305 }
   306 
   306 
   307 /**
   307 /**
   308  * This macro generates a callback setter function. It uses the name of the callback to
   308  * This macro generates a callback setter function. It uses the name of the callback to
   309  * automatically generate the function name and the fields to set, so a consistent naming
   309  * automatically generate the function name and the fields to set, so a consistent naming
   310  * convention needs to be enforced (not that that is a bad thing). If null is passed as
   310  * convention needs to be enforced (not that that is a bad thing). If null is passed as
   311  * callback to the generated function, the defaultCb will be set instead (with conn
   311  * callback to the generated function, the defaultCb will be set instead (with conn
   312  * as the context).
   312  * as the context).
   313  */
   313  */
   314 #define GENERATE_CB_SETTER(cbName, cbParameterTypes, defaultCb) \
   314 #define GENERATE_CB_SETTER(cbName, cbParameterTypes, defaultCb) \
   315 	void flib_gameconn_##cbName(flib_gameconn *conn, void (*callback)cbParameterTypes, void *context) { \
   315     void flib_gameconn_##cbName(flib_gameconn *conn, void (*callback)cbParameterTypes, void *context) { \
   316 		if(!log_badargs_if(conn==NULL)) { \
   316         if(!log_badargs_if(conn==NULL)) { \
   317 			conn->cbName##Cb = callback ? callback : &defaultCb; \
   317             conn->cbName##Cb = callback ? callback : &defaultCb; \
   318 			conn->cbName##Ctx = callback ? context : conn; \
   318             conn->cbName##Ctx = callback ? context : conn; \
   319 		} \
   319         } \
   320 	}
   320     }
   321 
   321 
   322 /**
   322 /**
   323  * Generate a callback setter function like GENERATE_CB_SETTER, and automatically generate a
   323  * Generate a callback setter function like GENERATE_CB_SETTER, and automatically generate a
   324  * no-op callback function as well that is used as default.
   324  * no-op callback function as well that is used as default.
   325  */
   325  */
   326 #define GENERATE_CB_SETTER_AND_DEFAULT(cbName, cbParameterTypes) \
   326 #define GENERATE_CB_SETTER_AND_DEFAULT(cbName, cbParameterTypes) \
   327 	static void _noop_callback_##cbName cbParameterTypes {} \
   327     static void _noop_callback_##cbName cbParameterTypes {} \
   328 	GENERATE_CB_SETTER(cbName, cbParameterTypes, _noop_callback_##cbName)
   328     GENERATE_CB_SETTER(cbName, cbParameterTypes, _noop_callback_##cbName)
   329 
   329 
   330 GENERATE_CB_SETTER_AND_DEFAULT(onConnect, (void *context));
   330 GENERATE_CB_SETTER_AND_DEFAULT(onConnect, (void *context));
   331 GENERATE_CB_SETTER_AND_DEFAULT(onDisconnect, (void* context, int reason));
   331 GENERATE_CB_SETTER_AND_DEFAULT(onDisconnect, (void* context, int reason));
   332 GENERATE_CB_SETTER(onErrorMessage, (void* context, const char *msg), defaultCallback_onErrorMessage);
   332 GENERATE_CB_SETTER(onErrorMessage, (void* context, const char *msg), defaultCallback_onErrorMessage);
   333 GENERATE_CB_SETTER_AND_DEFAULT(onChat, (void* context, const char *msg, bool teamchat));
   333 GENERATE_CB_SETTER_AND_DEFAULT(onChat, (void* context, const char *msg, bool teamchat));
   336 
   336 
   337 #undef GENERATE_CB_SETTER_AND_DEFAULT
   337 #undef GENERATE_CB_SETTER_AND_DEFAULT
   338 #undef GENERATE_CB_SETTER
   338 #undef GENERATE_CB_SETTER
   339 
   339 
   340 static void flib_gameconn_wrappedtick(flib_gameconn *conn) {
   340 static void flib_gameconn_wrappedtick(flib_gameconn *conn) {
   341 	if(conn->state == AWAIT_CONNECTION) {
   341     if(conn->state == AWAIT_CONNECTION) {
   342 		flib_ipcbase_accept(conn->ipcBase);
   342         flib_ipcbase_accept(conn->ipcBase);
   343 		switch(flib_ipcbase_state(conn->ipcBase)) {
   343         switch(flib_ipcbase_state(conn->ipcBase)) {
   344 		case IPC_CONNECTED:
   344         case IPC_CONNECTED:
   345 			{
   345             {
   346 				flib_constbuffer configBuffer = flib_vector_as_constbuffer(conn->configBuffer);
   346                 flib_constbuffer configBuffer = flib_vector_as_constbuffer(conn->configBuffer);
   347 				if(flib_ipcbase_send_raw(conn->ipcBase, configBuffer.data, configBuffer.size)) {
   347                 if(flib_ipcbase_send_raw(conn->ipcBase, configBuffer.data, configBuffer.size)) {
   348 					conn->state = FINISHED;
   348                     conn->state = FINISHED;
   349 					conn->onDisconnectCb(conn->onDisconnectCtx, GAME_END_ERROR);
   349                     conn->onDisconnectCb(conn->onDisconnectCtx, GAME_END_ERROR);
   350 					return;
   350                     return;
   351 				} else {
   351                 } else {
   352 					demo_append(conn, configBuffer.data, configBuffer.size);
   352                     demo_append(conn, configBuffer.data, configBuffer.size);
   353 					conn->state = CONNECTED;
   353                     conn->state = CONNECTED;
   354 					conn->onConnectCb(conn->onConnectCtx);
   354                     conn->onConnectCb(conn->onConnectCtx);
   355 					if(conn->destroyRequested) {
   355                     if(conn->destroyRequested) {
   356 						return;
   356                         return;
   357 					}
   357                     }
   358 				}
   358                 }
   359 			}
   359             }
   360 			break;
   360             break;
   361 		case IPC_NOT_CONNECTED:
   361         case IPC_NOT_CONNECTED:
   362 			conn->state = FINISHED;
   362             conn->state = FINISHED;
   363 			conn->onDisconnectCb(conn->onDisconnectCtx, GAME_END_ERROR);
   363             conn->onDisconnectCb(conn->onDisconnectCtx, GAME_END_ERROR);
   364 			return;
   364             return;
   365 		default:
   365         default:
   366 			break;
   366             break;
   367 		}
   367         }
   368 	}
   368     }
   369 
   369 
   370 	if(conn->state == CONNECTED) {
   370     if(conn->state == CONNECTED) {
   371 		uint8_t msgbuffer[257];
   371         uint8_t msgbuffer[257];
   372 		int len;
   372         int len;
   373 		while(!conn->destroyRequested && (len = flib_ipcbase_recv_message(conn->ipcBase, msgbuffer))>=0) {
   373         while(!conn->destroyRequested && (len = flib_ipcbase_recv_message(conn->ipcBase, msgbuffer))>=0) {
   374 			if(len<2) {
   374             if(len<2) {
   375 				flib_log_w("Received short message from IPC (<2 bytes)");
   375                 flib_log_w("Received short message from IPC (<2 bytes)");
   376 				continue;
   376                 continue;
   377 			}
   377             }
   378 			switch(msgbuffer[1]) {
   378             switch(msgbuffer[1]) {
   379 			case '?':	// Ping
   379             case '?':   // Ping
   380 				// The pong is already part of the config message
   380                 // The pong is already part of the config message
   381 				break;
   381                 break;
   382 			case 'C':	// Config query
   382             case 'C':   // Config query
   383 				// And we already send the config message on connecting.
   383                 // And we already send the config message on connecting.
   384 				break;
   384                 break;
   385 			case 'E':	// Error message
   385             case 'E':   // Error message
   386 				if(len>=3) {
   386                 if(len>=3) {
   387 					msgbuffer[len-2] = 0;
   387                     msgbuffer[len-2] = 0;
   388 					conn->onErrorMessageCb(conn->onErrorMessageCtx, (char*)msgbuffer+2);
   388                     conn->onErrorMessageCb(conn->onErrorMessageCtx, (char*)msgbuffer+2);
   389 				}
   389                 }
   390 				break;
   390                 break;
   391 			case 'i':	// Statistics
   391             case 'i':   // Statistics
   392 				// TODO stats
   392                 // TODO stats
   393 				break;
   393                 break;
   394 			case 'Q':	// Game interrupted
   394             case 'Q':   // Game interrupted
   395 			case 'H':	// Game halted
   395             case 'H':   // Game halted
   396 			case 'q':	// game finished
   396             case 'q':   // game finished
   397 				{
   397                 {
   398 					int reason = msgbuffer[1]=='Q' ? GAME_END_INTERRUPTED : msgbuffer[1]=='H' ? GAME_END_HALTED : GAME_END_FINISHED;
   398                     int reason = msgbuffer[1]=='Q' ? GAME_END_INTERRUPTED : msgbuffer[1]=='H' ? GAME_END_HALTED : GAME_END_FINISHED;
   399 					conn->disconnectReason = reason;
   399                     conn->disconnectReason = reason;
   400 					bool savegame = (reason != GAME_END_FINISHED) && !conn->netgame;
   400                     bool savegame = (reason != GAME_END_FINISHED) && !conn->netgame;
   401 					if(conn->demoBuffer) {
   401                     if(conn->demoBuffer) {
   402 						flib_buffer demoBuffer = flib_vector_as_buffer(conn->demoBuffer);
   402                         flib_buffer demoBuffer = flib_vector_as_buffer(conn->demoBuffer);
   403 						demo_replace_gamemode(demoBuffer, savegame ? 'S' : 'D');
   403                         demo_replace_gamemode(demoBuffer, savegame ? 'S' : 'D');
   404 						conn->onGameRecordedCb(conn->onGameRecordedCtx, demoBuffer.data, demoBuffer.size, savegame);
   404                         conn->onGameRecordedCb(conn->onGameRecordedCtx, demoBuffer.data, demoBuffer.size, savegame);
   405 						if(conn->destroyRequested) {
   405                         if(conn->destroyRequested) {
   406 							return;
   406                             return;
   407 						}
   407                         }
   408 					}
   408                     }
   409 					return;
   409                     return;
   410 				}
   410                 }
   411 			case 's':	// Chat message
   411             case 's':   // Chat message
   412 				if(len>=3) {
   412                 if(len>=3) {
   413 					msgbuffer[len-2] = 0;
   413                     msgbuffer[len-2] = 0;
   414 					demo_append_chatmessage(conn, (char*)msgbuffer+2);
   414                     demo_append_chatmessage(conn, (char*)msgbuffer+2);
   415 
   415 
   416 					conn->onChatCb(conn->onChatCtx, (char*)msgbuffer+2, false);
   416                     conn->onChatCb(conn->onChatCtx, (char*)msgbuffer+2, false);
   417 				}
   417                 }
   418 				break;
   418                 break;
   419 			case 'b':	// Teamchat message
   419             case 'b':   // Teamchat message
   420 				if(len>=3) {
   420                 if(len>=3) {
   421 					msgbuffer[len-2] = 0;
   421                     msgbuffer[len-2] = 0;
   422 					conn->onChatCb(conn->onChatCtx, (char*)msgbuffer+2, true);
   422                     conn->onChatCb(conn->onChatCtx, (char*)msgbuffer+2, true);
   423 				}
   423                 }
   424 				break;
   424                 break;
   425 			default:	// Engine message
   425             default:    // Engine message
   426 				demo_append(conn, msgbuffer, len);
   426                 demo_append(conn, msgbuffer, len);
   427 
   427 
   428 				conn->onEngineMessageCb(conn->onEngineMessageCtx, msgbuffer, len);
   428                 conn->onEngineMessageCb(conn->onEngineMessageCtx, msgbuffer, len);
   429 				break;
   429                 break;
   430 			}
   430             }
   431 		}
   431         }
   432 	}
   432     }
   433 
   433 
   434 	if(flib_ipcbase_state(conn->ipcBase) == IPC_NOT_CONNECTED) {
   434     if(flib_ipcbase_state(conn->ipcBase) == IPC_NOT_CONNECTED) {
   435 		conn->state = FINISHED;
   435         conn->state = FINISHED;
   436 		conn->onDisconnectCb(conn->onDisconnectCtx, conn->disconnectReason);
   436         conn->onDisconnectCb(conn->onDisconnectCtx, conn->disconnectReason);
   437 	}
   437     }
   438 }
   438 }
   439 
   439 
   440 void flib_gameconn_tick(flib_gameconn *conn) {
   440 void flib_gameconn_tick(flib_gameconn *conn) {
   441 	if(!log_badargs_if(conn == NULL)
   441     if(!log_badargs_if(conn == NULL)
   442 			&& !log_w_if(conn->running, "Call to flib_gameconn_tick from a callback")
   442             && !log_w_if(conn->running, "Call to flib_gameconn_tick from a callback")
   443 			&& !log_w_if(conn->state == FINISHED, "We are already done.")) {
   443             && !log_w_if(conn->state == FINISHED, "We are already done.")) {
   444 		conn->running = true;
   444         conn->running = true;
   445 		flib_gameconn_wrappedtick(conn);
   445         flib_gameconn_wrappedtick(conn);
   446 		conn->running = false;
   446         conn->running = false;
   447 
   447 
   448 		if(conn->destroyRequested) {
   448         if(conn->destroyRequested) {
   449 			flib_gameconn_destroy(conn);
   449             flib_gameconn_destroy(conn);
   450 		}
   450         }
   451 	}
   451     }
   452 }
   452 }