|
1 /* |
|
2 * Hedgewars-iOS, a Hedgewars port for iOS devices |
|
3 * Copyright (c) 2009-2011 Vittorio Giovara <vittorio.giovara@gmail.com> |
|
4 * |
|
5 * This program is free software; you can redistribute it and/or modify |
|
6 * it under the terms of the GNU General Public License as published by |
|
7 * the Free Software Foundation; version 2 of the License |
|
8 * |
|
9 * This program is distributed in the hope that it will be useful, |
|
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 * GNU General Public License for more details. |
|
13 * |
|
14 * You should have received a copy of the GNU General Public License |
|
15 * along with this program; if not, write to the Free Software |
|
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
|
17 * |
|
18 * File created on 10/01/2010. |
|
19 */ |
|
20 |
|
21 |
|
22 #import "EngineProtocolNetwork.h" |
|
23 #import "PascalImports.h" |
|
24 #import "CommodityFunctions.h" |
|
25 #import "OverlayViewController.h" |
|
26 |
|
27 #define BUFFER_SIZE 255 // like in original frontend |
|
28 |
|
29 @implementation EngineProtocolNetwork |
|
30 @synthesize statsArray, savePath, gameConfig, ipcPort, csd; |
|
31 |
|
32 -(id) init { |
|
33 if (self = [super init]) { |
|
34 self.savePath = nil; |
|
35 self.statsArray = nil; |
|
36 self.gameConfig = nil; |
|
37 self.ipcPort = 0; |
|
38 } |
|
39 return self; |
|
40 } |
|
41 |
|
42 -(void) dealloc { |
|
43 [statsArray release]; |
|
44 [savePath release]; |
|
45 [gameConfig release]; |
|
46 [super dealloc]; |
|
47 } |
|
48 |
|
49 -(void) spawnThreadOnPort:(NSInteger) port { |
|
50 self.ipcPort = port; |
|
51 |
|
52 [NSThread detachNewThreadSelector:@selector(engineProtocol) |
|
53 toTarget:self |
|
54 withObject:nil]; |
|
55 } |
|
56 |
|
57 #pragma mark - |
|
58 #pragma mark Provider functions |
|
59 // unpacks team data from the selected team.plist to a sequence of engine commands |
|
60 -(void) provideTeamData:(NSString *)teamName forHogs:(NSInteger) numberOfPlayingHogs withHealth:(NSInteger) initialHealth ofColor:(NSNumber *)teamColor { |
|
61 /* |
|
62 addteam <32charsMD5hash> <color> <team name> |
|
63 addhh <level> <health> <hedgehog name> |
|
64 <level> is 0 for human, 1-5 for bots (5 is the most stupid) |
|
65 */ |
|
66 |
|
67 NSString *teamFile = [[NSString alloc] initWithFormat:@"%@/%@", TEAMS_DIRECTORY(), teamName]; |
|
68 NSDictionary *teamData = [[NSDictionary alloc] initWithContentsOfFile:teamFile]; |
|
69 [teamFile release]; |
|
70 |
|
71 NSString *teamHashColorAndName = [[NSString alloc] initWithFormat:@"eaddteam %@ %@ %@", |
|
72 [teamData objectForKey:@"hash"], [teamColor stringValue], [teamName stringByDeletingPathExtension]]; |
|
73 [self sendToEngine: teamHashColorAndName]; |
|
74 [teamHashColorAndName release]; |
|
75 |
|
76 NSString *grave = [[NSString alloc] initWithFormat:@"egrave %@", [teamData objectForKey:@"grave"]]; |
|
77 [self sendToEngine: grave]; |
|
78 [grave release]; |
|
79 |
|
80 NSString *fort = [[NSString alloc] initWithFormat:@"efort %@", [teamData objectForKey:@"fort"]]; |
|
81 [self sendToEngine: fort]; |
|
82 [fort release]; |
|
83 |
|
84 NSString *voicepack = [[NSString alloc] initWithFormat:@"evoicepack %@", [teamData objectForKey:@"voicepack"]]; |
|
85 [self sendToEngine: voicepack]; |
|
86 [voicepack release]; |
|
87 |
|
88 NSString *flag = [[NSString alloc] initWithFormat:@"eflag %@", [teamData objectForKey:@"flag"]]; |
|
89 [self sendToEngine: flag]; |
|
90 [flag release]; |
|
91 |
|
92 NSArray *hogs = [teamData objectForKey:@"hedgehogs"]; |
|
93 for (int i = 0; i < numberOfPlayingHogs; i++) { |
|
94 NSDictionary *hog = [hogs objectAtIndex:i]; |
|
95 |
|
96 NSString *hogLevelHealthAndName = [[NSString alloc] initWithFormat:@"eaddhh %@ %d %@", |
|
97 [hog objectForKey:@"level"], initialHealth, [hog objectForKey:@"hogname"]]; |
|
98 [self sendToEngine: hogLevelHealthAndName]; |
|
99 [hogLevelHealthAndName release]; |
|
100 |
|
101 NSString *hogHat = [[NSString alloc] initWithFormat:@"ehat %@", [hog objectForKey:@"hat"]]; |
|
102 [self sendToEngine: hogHat]; |
|
103 [hogHat release]; |
|
104 } |
|
105 |
|
106 [teamData release]; |
|
107 } |
|
108 |
|
109 // unpacks ammostore data from the selected ammo.plist to a sequence of engine commands |
|
110 -(void) provideAmmoData:(NSString *)ammostoreName forPlayingTeams:(NSInteger) numberOfTeams { |
|
111 NSString *weaponPath = [[NSString alloc] initWithFormat:@"%@/%@",WEAPONS_DIRECTORY(),ammostoreName]; |
|
112 NSDictionary *ammoData = [[NSDictionary alloc] initWithContentsOfFile:weaponPath]; |
|
113 [weaponPath release]; |
|
114 |
|
115 // if we're loading an older version of ammos fill the engine message with 0s |
|
116 int diff = HW_getNumberOfWeapons() - [[ammoData objectForKey:@"ammostore_initialqt"] length]; |
|
117 NSString *update = @""; |
|
118 while ([update length] < diff) |
|
119 update = [update stringByAppendingString:@"0"]; |
|
120 |
|
121 NSString *ammloadt = [[NSString alloc] initWithFormat:@"eammloadt %@%@", [ammoData objectForKey:@"ammostore_initialqt"], update]; |
|
122 [self sendToEngine: ammloadt]; |
|
123 [ammloadt release]; |
|
124 |
|
125 NSString *ammprob = [[NSString alloc] initWithFormat:@"eammprob %@%@", [ammoData objectForKey:@"ammostore_probability"], update]; |
|
126 [self sendToEngine: ammprob]; |
|
127 [ammprob release]; |
|
128 |
|
129 NSString *ammdelay = [[NSString alloc] initWithFormat:@"eammdelay %@%@", [ammoData objectForKey:@"ammostore_delay"], update]; |
|
130 [self sendToEngine: ammdelay]; |
|
131 [ammdelay release]; |
|
132 |
|
133 NSString *ammreinf = [[NSString alloc] initWithFormat:@"eammreinf %@%@", [ammoData objectForKey:@"ammostore_crate"], update]; |
|
134 [self sendToEngine: ammreinf]; |
|
135 [ammreinf release]; |
|
136 |
|
137 // send this for each team so it applies the same ammostore to all teams |
|
138 NSString *ammstore = [[NSString alloc] initWithString:@"eammstore"]; |
|
139 for (int i = 0; i < numberOfTeams; i++) |
|
140 [self sendToEngine: ammstore]; |
|
141 [ammstore release]; |
|
142 |
|
143 [ammoData release]; |
|
144 } |
|
145 |
|
146 // unpacks scheme data from the selected scheme.plist to a sequence of engine commands |
|
147 -(NSInteger) provideScheme:(NSString *)schemeName { |
|
148 NSString *schemePath = [[NSString alloc] initWithFormat:@"%@/%@",SCHEMES_DIRECTORY(),schemeName]; |
|
149 NSDictionary *schemeDictionary = [[NSDictionary alloc] initWithContentsOfFile:schemePath]; |
|
150 [schemePath release]; |
|
151 NSArray *basicArray = [schemeDictionary objectForKey:@"basic"]; |
|
152 NSArray *gamemodArray = [schemeDictionary objectForKey:@"gamemod"]; |
|
153 int result = 0; |
|
154 int mask = 0x00000004; |
|
155 |
|
156 // pack the gameflags in a single var and send it |
|
157 for (NSNumber *value in gamemodArray) { |
|
158 if ([value boolValue] == YES) |
|
159 result |= mask; |
|
160 mask <<= 1; |
|
161 } |
|
162 NSString *flags = [[NSString alloc] initWithFormat:@"e$gmflags %d",result]; |
|
163 [self sendToEngine:flags]; |
|
164 [flags release]; |
|
165 |
|
166 /* basic game flags */ |
|
167 NSString *path = [[NSString alloc] initWithFormat:@"%@/basicFlags_en.plist",IFRONTEND_DIRECTORY()]; |
|
168 NSArray *mods = [[NSArray alloc] initWithContentsOfFile:path]; |
|
169 [path release]; |
|
170 |
|
171 result = [[basicArray objectAtIndex:0] intValue]; |
|
172 |
|
173 for (int i = 1; i < [basicArray count]; i++) { |
|
174 NSDictionary *dict = [mods objectAtIndex:i]; |
|
175 NSString *command = [dict objectForKey:@"command"]; |
|
176 NSInteger value = [[basicArray objectAtIndex:i] intValue]; |
|
177 if ([[dict objectForKey:@"checkOverMax"] boolValue] && value >= [[dict objectForKey:@"max"] intValue]) |
|
178 value = 9999; |
|
179 if ([[dict objectForKey:@"times1000"] boolValue]) |
|
180 value = value * 1000; |
|
181 NSString *strToSend = [[NSString alloc] initWithFormat:@"%@ %d",command,value]; |
|
182 [self sendToEngine:strToSend]; |
|
183 [strToSend release]; |
|
184 } |
|
185 [mods release]; |
|
186 |
|
187 [schemeDictionary release]; |
|
188 return result; |
|
189 } |
|
190 |
|
191 #pragma mark - |
|
192 #pragma mark Network relevant code |
|
193 -(void) dumpRawData:(const char *)buffer ofSize:(uint8_t) length { |
|
194 // is it performant to reopen the stream every time? |
|
195 NSOutputStream *os = [[NSOutputStream alloc] initToFileAtPath:self.savePath append:YES]; |
|
196 [os open]; |
|
197 [os write:&length maxLength:1]; |
|
198 [os write:(const uint8_t *)buffer maxLength:length]; |
|
199 [os close]; |
|
200 [os release]; |
|
201 } |
|
202 |
|
203 // wrapper that computes the length of the message and then sends the command string, saving the command on a file |
|
204 -(int) sendToEngine:(NSString *)string { |
|
205 uint8_t length = [string length]; |
|
206 |
|
207 [self dumpRawData:[string UTF8String] ofSize:length]; |
|
208 SDLNet_TCP_Send(csd, &length, 1); |
|
209 return SDLNet_TCP_Send(csd, [string UTF8String], length); |
|
210 } |
|
211 |
|
212 // wrapper that computes the length of the message and then sends the command string, skipping file writing |
|
213 -(int) sendToEngineNoSave:(NSString *)string { |
|
214 uint8_t length = [string length]; |
|
215 |
|
216 SDLNet_TCP_Send(csd, &length, 1); |
|
217 return SDLNet_TCP_Send(csd, [string UTF8String], length); |
|
218 } |
|
219 |
|
220 // this is launched as thread and handles all IPC with engine |
|
221 -(void) engineProtocol { |
|
222 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
|
223 TCPsocket sd; |
|
224 IPaddress ip; |
|
225 int eProto; |
|
226 BOOL clientQuit; |
|
227 char const buffer[BUFFER_SIZE]; |
|
228 uint8_t msgSize; |
|
229 |
|
230 clientQuit = NO; |
|
231 csd = NULL; |
|
232 |
|
233 if (SDLNet_Init() < 0) { |
|
234 DLog(@"SDLNet_Init: %s", SDLNet_GetError()); |
|
235 clientQuit = YES; |
|
236 } |
|
237 |
|
238 // Resolving the host using NULL make network interface to listen |
|
239 if (SDLNet_ResolveHost(&ip, NULL, ipcPort) < 0 && !clientQuit) { |
|
240 DLog(@"SDLNet_ResolveHost: %s\n", SDLNet_GetError()); |
|
241 clientQuit = YES; |
|
242 } |
|
243 |
|
244 // Open a connection with the IP provided (listen on the host's port) |
|
245 if (!(sd = SDLNet_TCP_Open(&ip)) && !clientQuit) { |
|
246 DLog(@"SDLNet_TCP_Open: %s %\n", SDLNet_GetError(), ipcPort); |
|
247 clientQuit = YES; |
|
248 } |
|
249 |
|
250 DLog(@"Waiting for a client on port %d", ipcPort); |
|
251 while (csd == NULL) |
|
252 csd = SDLNet_TCP_Accept(sd); |
|
253 SDLNet_TCP_Close(sd); |
|
254 |
|
255 while (!clientQuit) { |
|
256 msgSize = 0; |
|
257 memset((void *)buffer, '\0', BUFFER_SIZE); |
|
258 if (SDLNet_TCP_Recv(csd, &msgSize, sizeof(uint8_t)) <= 0) |
|
259 break; |
|
260 if (SDLNet_TCP_Recv(csd, (void *)buffer, msgSize) <= 0) |
|
261 break; |
|
262 |
|
263 switch (buffer[0]) { |
|
264 case 'C': |
|
265 DLog(@"Sending game config...\n%@", self.gameConfig); |
|
266 |
|
267 /*if (isNetGame == YES) |
|
268 [self sendToEngineNoSave:@"TN"]; |
|
269 else*/ |
|
270 [self sendToEngineNoSave:@"TL"]; |
|
271 NSString *saveHeader = @"TS"; |
|
272 [self dumpRawData:[saveHeader UTF8String] ofSize:[saveHeader length]]; |
|
273 |
|
274 // seed info |
|
275 [self sendToEngine:[self.gameConfig objectForKey:@"seed_command"]]; |
|
276 |
|
277 // dimension of the map |
|
278 [self sendToEngine:[self.gameConfig objectForKey:@"templatefilter_command"]]; |
|
279 [self sendToEngine:[self.gameConfig objectForKey:@"mapgen_command"]]; |
|
280 [self sendToEngine:[self.gameConfig objectForKey:@"mazesize_command"]]; |
|
281 |
|
282 // static land (if set) |
|
283 NSString *staticMap = [self.gameConfig objectForKey:@"staticmap_command"]; |
|
284 if ([staticMap length] != 0) |
|
285 [self sendToEngine:staticMap]; |
|
286 |
|
287 // lua script (if set) |
|
288 NSString *script = [self.gameConfig objectForKey:@"mission_command"]; |
|
289 if ([script length] != 0) |
|
290 [self sendToEngine:script]; |
|
291 |
|
292 // theme info |
|
293 [self sendToEngine:[self.gameConfig objectForKey:@"theme_command"]]; |
|
294 |
|
295 // scheme (returns initial health) |
|
296 NSInteger health = [self provideScheme:[self.gameConfig objectForKey:@"scheme"]]; |
|
297 |
|
298 // send an ammostore for each team |
|
299 NSArray *teamsConfig = [self.gameConfig objectForKey:@"teams_list"]; |
|
300 [self provideAmmoData:[self.gameConfig objectForKey:@"weapon"] forPlayingTeams:[teamsConfig count]]; |
|
301 |
|
302 // finally add hogs |
|
303 for (NSDictionary *teamData in teamsConfig) { |
|
304 [self provideTeamData:[teamData objectForKey:@"team"] |
|
305 forHogs:[[teamData objectForKey:@"number"] intValue] |
|
306 withHealth:health |
|
307 ofColor:[teamData objectForKey:@"color"]]; |
|
308 } |
|
309 break; |
|
310 case '?': |
|
311 DLog(@"Ping? Pong!"); |
|
312 [self sendToEngine:@"!"]; |
|
313 break; |
|
314 case 'E': |
|
315 DLog(@"ERROR - last console line: [%s]", &buffer[1]); |
|
316 clientQuit = YES; |
|
317 break; |
|
318 case 'e': |
|
319 [self dumpRawData:buffer ofSize:msgSize]; |
|
320 |
|
321 sscanf((char *)buffer, "%*s %d", &eProto); |
|
322 int netProto; |
|
323 char *versionStr; |
|
324 |
|
325 HW_versionInfo(&netProto, &versionStr); |
|
326 if (netProto == eProto) { |
|
327 DLog(@"Setting protocol version %d (%s)", eProto, versionStr); |
|
328 } else { |
|
329 DLog(@"ERROR - wrong protocol number: %d (expecting %d)", netProto, eProto); |
|
330 clientQuit = YES; |
|
331 } |
|
332 break; |
|
333 case 'i': |
|
334 if (self.statsArray == nil) { |
|
335 self.statsArray = [[NSMutableArray alloc] initWithCapacity:10 - 2]; |
|
336 NSMutableArray *ranking = [[NSMutableArray alloc] initWithCapacity:4]; |
|
337 [self.statsArray insertObject:ranking atIndex:0]; |
|
338 [ranking release]; |
|
339 } |
|
340 NSString *tempStr = [NSString stringWithUTF8String:&buffer[2]]; |
|
341 NSArray *info = [tempStr componentsSeparatedByString:@" "]; |
|
342 NSString *arg = [info objectAtIndex:0]; |
|
343 int index = [arg length] + 3; |
|
344 switch (buffer[1]) { |
|
345 case 'r': // winning team |
|
346 [self.statsArray insertObject:[NSString stringWithUTF8String:&buffer[2]] atIndex:1]; |
|
347 break; |
|
348 case 'D': // best shot |
|
349 [self.statsArray addObject:[NSString stringWithFormat:@"The best shot award won by %s (with %@ points)", &buffer[index], arg]]; |
|
350 break; |
|
351 case 'k': // best hedgehog |
|
352 [self.statsArray addObject:[NSString stringWithFormat:@"The best killer is %s with %@ kills in a turn", &buffer[index], arg]]; |
|
353 break; |
|
354 case 'K': // number of hogs killed |
|
355 [self.statsArray addObject:[NSString stringWithFormat:@"%@ hedgehog(s) were killed during this round", arg]]; |
|
356 break; |
|
357 case 'H': // team health/graph |
|
358 break; |
|
359 case 'T': // local team stats |
|
360 // still WIP in statsPage.cpp |
|
361 break; |
|
362 case 'P': // teams ranking |
|
363 [[self.statsArray objectAtIndex:0] addObject:tempStr]; |
|
364 break; |
|
365 case 's': // self damage |
|
366 [self.statsArray addObject:[NSString stringWithFormat:@"%s thought it's good to shoot his own hedgehogs with %@ points", &buffer[index], arg]]; |
|
367 break; |
|
368 case 'S': // friendly fire |
|
369 [self.statsArray addObject:[NSString stringWithFormat:@"%s killed %@ of his own hedgehogs", &buffer[index], arg]]; |
|
370 break; |
|
371 case 'B': // turn skipped |
|
372 [self.statsArray addObject:[NSString stringWithFormat:@"%s was scared and skipped turn %@ times", &buffer[index], arg]]; |
|
373 break; |
|
374 default: |
|
375 DLog(@"Unhandled stat message, see statsPage.cpp"); |
|
376 break; |
|
377 } |
|
378 break; |
|
379 case 'q': |
|
380 // game ended, can remove the savefile and the trailing overlay (when dualhead) |
|
381 [[NSFileManager defaultManager] removeItemAtPath:self.savePath error:nil]; |
|
382 if (IS_DUALHEAD()) |
|
383 [[NSNotificationCenter defaultCenter] postNotificationName:@"remove overlay" object:nil]; |
|
384 break; |
|
385 case 'Q': |
|
386 // game exited but not completed, nothing to do (just don't save the message) |
|
387 break; |
|
388 default: |
|
389 [self dumpRawData:buffer ofSize:msgSize]; |
|
390 break; |
|
391 } |
|
392 } |
|
393 DLog(@"Engine exited, ending thread"); |
|
394 |
|
395 // Close the client socket |
|
396 SDLNet_TCP_Close(csd); |
|
397 SDLNet_Quit(); |
|
398 |
|
399 [pool release]; |
|
400 // Invoking this method should be avoided as it does not give your thread a chance |
|
401 // to clean up any resources it allocated during its execution. |
|
402 //[NSThread exit]; |
|
403 } |
|
404 |
|
405 @end |