From 8ca1de163fe1887675583fb83bb3479b00047814 Mon Sep 17 00:00:00 2001 From: JoshuaKaden Date: Tue, 4 Aug 2015 11:29:19 -0400 Subject: [PATCH] A step towards mudball remediation. --- Partisans.xcodeproj/project.pbxproj | 26 + Partisans/Classes/AppDelegate.m | 7 +- Partisans/Classes/GameDirector.m | 18 +- Partisans/Classes/HostFinder.m | 7 +- .../Libraries/My Library/JSKCommandMessage.h | 5 +- .../My Library/JSKCommandMessageProtocol.h | 14 + .../Libraries/My Library/JSKCommandParcel.h | 5 +- Partisans/Classes/MissionViewController.m | 5 +- Partisans/Classes/NetHandler.h | 28 + Partisans/Classes/NetHandler.m | 141 +++ Partisans/Classes/NetHostHandler.h | 16 + Partisans/Classes/NetHostHandler.m | 372 ++++++ Partisans/Classes/NetHostManager.h | 14 + Partisans/Classes/NetPlayerHandler.h | 16 + Partisans/Classes/NetPlayerHandler.m | 295 +++++ Partisans/Classes/NetworkManager.h | 28 + Partisans/Classes/NetworkManager.m | 219 ++++ Partisans/Classes/PlayerViewController.m | 3 +- Partisans/Classes/RoundMenuItems.m | 3 +- Partisans/Classes/SetupGameMenuItems.m | 17 +- Partisans/Classes/SystemMessage.h | 25 +- Partisans/Classes/SystemMessage.m | 1087 +---------------- Partisans/Classes/VoteViewController.m | 5 +- 23 files changed, 1234 insertions(+), 1122 deletions(-) create mode 100644 Partisans/Classes/Libraries/My Library/JSKCommandMessageProtocol.h create mode 100644 Partisans/Classes/NetHandler.h create mode 100644 Partisans/Classes/NetHandler.m create mode 100644 Partisans/Classes/NetHostHandler.h create mode 100644 Partisans/Classes/NetHostHandler.m create mode 100644 Partisans/Classes/NetHostManager.h create mode 100644 Partisans/Classes/NetPlayerHandler.h create mode 100644 Partisans/Classes/NetPlayerHandler.m create mode 100644 Partisans/Classes/NetworkManager.h create mode 100644 Partisans/Classes/NetworkManager.m diff --git a/Partisans.xcodeproj/project.pbxproj b/Partisans.xcodeproj/project.pbxproj index e411ee7..79f0cdf 100644 --- a/Partisans.xcodeproj/project.pbxproj +++ b/Partisans.xcodeproj/project.pbxproj @@ -7,7 +7,11 @@ objects = { /* Begin PBXBuildFile section */ + 77195CFE1B70E9CF00E11518 /* NetPlayerHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 77195CFD1B70E9CF00E11518 /* NetPlayerHandler.m */; }; 7721B8751B6FA4B1004D3BE0 /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 7721B8741B6FA4B1004D3BE0 /* Reachability.m */; }; + 7721B8781B6FA748004D3BE0 /* NetworkManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7721B8771B6FA748004D3BE0 /* NetworkManager.m */; }; + 7795A2941B70211000382382 /* NetHostHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 7795A2931B70211000382382 /* NetHostHandler.m */; }; + 7795A29A1B70D48400382382 /* NetHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 7795A2991B70D48400382382 /* NetHandler.m */; }; 9300C7D8175BFCA00047AA15 /* GameTesterMenuItems.m in Sources */ = {isa = PBXBuildFile; fileRef = 9300C7D7175BFCA00047AA15 /* GameTesterMenuItems.m */; }; 9303BE5117467CAE0065ADD2 /* JoinGameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9303BE5017467CAE0065ADD2 /* JoinGameViewController.m */; }; 9303BE551746836A0065ADD2 /* GameJoiner.m in Sources */ = {isa = PBXBuildFile; fileRef = 9303BE541746836A0065ADD2 /* GameJoiner.m */; }; @@ -132,9 +136,18 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 77195CFC1B70E9CF00E11518 /* NetPlayerHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NetPlayerHandler.h; path = Classes/NetPlayerHandler.h; sourceTree = ""; }; + 77195CFD1B70E9CF00E11518 /* NetPlayerHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NetPlayerHandler.m; path = Classes/NetPlayerHandler.m; sourceTree = ""; }; 7721B8731B6FA4B1004D3BE0 /* Reachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Reachability.h; sourceTree = ""; }; 7721B8741B6FA4B1004D3BE0 /* Reachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Reachability.m; sourceTree = ""; }; + 7721B8761B6FA748004D3BE0 /* NetworkManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NetworkManager.h; path = Classes/NetworkManager.h; sourceTree = ""; }; + 7721B8771B6FA748004D3BE0 /* NetworkManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NetworkManager.m; path = Classes/NetworkManager.m; sourceTree = ""; }; 775407A31B6EE8B9002AE2DA /* Partisans-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Partisans-Bridging-Header.h"; sourceTree = ""; }; + 7795A2921B70211000382382 /* NetHostHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NetHostHandler.h; path = Classes/NetHostHandler.h; sourceTree = ""; }; + 7795A2931B70211000382382 /* NetHostHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NetHostHandler.m; path = Classes/NetHostHandler.m; sourceTree = ""; }; + 7795A2971B70583400382382 /* JSKCommandMessageProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = JSKCommandMessageProtocol.h; path = "Classes/Libraries/My Library/JSKCommandMessageProtocol.h"; sourceTree = ""; }; + 7795A2981B70D48400382382 /* NetHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NetHandler.h; path = Classes/NetHandler.h; sourceTree = ""; }; + 7795A2991B70D48400382382 /* NetHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NetHandler.m; path = Classes/NetHandler.m; sourceTree = ""; }; 9300C7D6175BFCA00047AA15 /* GameTesterMenuItems.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GameTesterMenuItems.h; path = Classes/GameTesterMenuItems.h; sourceTree = ""; }; 9300C7D7175BFCA00047AA15 /* GameTesterMenuItems.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GameTesterMenuItems.m; path = Classes/GameTesterMenuItems.m; sourceTree = ""; }; 9303BE4F17467CAD0065ADD2 /* JoinGameViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JoinGameViewController.h; path = Classes/JoinGameViewController.h; sourceTree = ""; }; @@ -388,10 +401,18 @@ children = ( 9307B3D2177F8A41003CEC6F /* HostFinder.h */, 9307B3D3177F8A41003CEC6F /* HostFinder.m */, + 7795A2981B70D48400382382 /* NetHandler.h */, + 7795A2991B70D48400382382 /* NetHandler.m */, 936CDFBC1761276900C6A4EE /* NetHost.h */, 936CDFBD1761276900C6A4EE /* NetHost.m */, + 7795A2921B70211000382382 /* NetHostHandler.h */, + 7795A2931B70211000382382 /* NetHostHandler.m */, 936CDFC0176202E500C6A4EE /* NetPlayer.h */, 936CDFC1176202E500C6A4EE /* NetPlayer.m */, + 77195CFC1B70E9CF00E11518 /* NetPlayerHandler.h */, + 77195CFD1B70E9CF00E11518 /* NetPlayerHandler.m */, + 7721B8761B6FA748004D3BE0 /* NetworkManager.h */, + 7721B8771B6FA748004D3BE0 /* NetworkManager.m */, ); name = Network; sourceTree = ""; @@ -550,6 +571,7 @@ children = ( 936510C41741597500F2AD06 /* JSKCommandMessage.h */, 936510C51741597500F2AD06 /* JSKCommandMessage.m */, + 7795A2971B70583400382382 /* JSKCommandMessageProtocol.h */, 9365111D17415D0A00F2AD06 /* JSKCommandParcel.h */, 9365111E17415D0A00F2AD06 /* JSKCommandParcel.m */, 936510C61741597500F2AD06 /* JSKDataMiner.h */, @@ -932,6 +954,7 @@ 936510D51741597500F2AD06 /* JSKViewStack.m in Sources */, 936510DC1741598800F2AD06 /* NSManagedObjectContext+FetchAdditions.m in Sources */, 936510E3174159C900F2AD06 /* RootViewController.m in Sources */, + 7795A2941B70211000382382 /* NetHostHandler.m in Sources */, 936510E4174159C900F2AD06 /* SystemMessage.m in Sources */, 936510EB17415A1000F2AD06 /* MainMenuItems.m in Sources */, 936510F317415A4E00F2AD06 /* ColorPickerViewController.m in Sources */, @@ -946,6 +969,7 @@ 93C10B9D1744028300B47095 /* GameEnvoy.m in Sources */, 93C10BBA17440C4500B47095 /* PlayGameMenuItems.m in Sources */, 93C10BC8174439B300B47095 /* SetupGameMenuItems.m in Sources */, + 77195CFE1B70E9CF00E11518 /* NetPlayerHandler.m in Sources */, 93FECAB1174521A40046529A /* LabelProgressView.m in Sources */, 93FECAB3174521A40046529A /* ProgressCell.m in Sources */, 9303BE5117467CAE0065ADD2 /* JoinGameViewController.m in Sources */, @@ -964,7 +988,9 @@ 936CDFC2176202E500C6A4EE /* NetPlayer.m in Sources */, 93BF3B531771CABF00B20A71 /* GameDirector.m in Sources */, 93BF3B571771CEA500B20A71 /* MissionEnvoy.m in Sources */, + 7795A29A1B70D48400382382 /* NetHandler.m in Sources */, 9398A9C6177224EE00E5D31C /* Image.m in Sources */, + 7721B8781B6FA748004D3BE0 /* NetworkManager.m in Sources */, 9398A9CA177224EE00E5D31C /* Player.m in Sources */, 9398A9D2177224EF00E5D31C /* Scorecard.m in Sources */, 939697E717732A110013B125 /* RoundEnvoy.m in Sources */, diff --git a/Partisans/Classes/AppDelegate.m b/Partisans/Classes/AppDelegate.m index 23e694c..11d2f2b 100644 --- a/Partisans/Classes/AppDelegate.m +++ b/Partisans/Classes/AppDelegate.m @@ -22,6 +22,7 @@ #import "JSKDataMiner.h" #import "RootViewController.h" +#import "NetworkManager.h" #import "SystemMessage.h" @interface AppDelegate () @@ -49,19 +50,19 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( - (void)applicationWillResignActive:(UIApplication *)application { - [SystemMessage putPlayerOffline]; + [NetworkManager putPlayerOffline]; } - (void)applicationDidEnterBackground:(UIApplication *)application { - [SystemMessage putPlayerOffline]; + [NetworkManager putPlayerOffline]; [JSKDataMiner save]; } - (void)applicationWillTerminate:(UIApplication *)application { [JSKDataMiner save]; - [SystemMessage putPlayerOffline]; + [NetworkManager putPlayerOffline]; } @end diff --git a/Partisans/Classes/GameDirector.m b/Partisans/Classes/GameDirector.m index 668eae4..586e86b 100644 --- a/Partisans/Classes/GameDirector.m +++ b/Partisans/Classes/GameDirector.m @@ -25,6 +25,7 @@ #import "GamePrecis.h" #import "JSKCommandParcel.h" #import "MissionEnvoy.h" +#import "NetworkManager.h" #import "PlayerEnvoy.h" #import "RoundEnvoy.h" #import "SystemMessage.h" @@ -81,7 +82,7 @@ - (void)startGame [self saveGame]; JSKCommandParcel *parcel = [[JSKCommandParcel alloc] initWithType:JSKCommandParcelTypeUpdate to:nil from:[SystemMessage playerEnvoy].peerID object:gameEnvoy]; - [SystemMessage sendParcelToPlayers:parcel]; + [NetworkManager sendParcelToPlayers:parcel]; } - (void)startMission @@ -172,13 +173,6 @@ - (void)saveGame { GameEnvoy *gameEnvoy = self.gameEnvoy; [gameEnvoy commitAndSave]; - -// NSDate *oldDate = [NSDate distantPast]; -// [self sendGameUpdateTo:nil modifiedDate:oldDate shouldSendAllData:NO]; - -// JSKCommandParcel *parcel = [[JSKCommandParcel alloc] initWithType:JSKCommandParcelTypeUpdate to:nil from:[SystemMessage playerEnvoy].peerID object:gameEnvoy]; -// [SystemMessage sendParcelToPlayers:parcel]; -// [parcel release]; } @@ -234,11 +228,11 @@ - (void)sendGameUpdateTo:(NSString *)peerID modifiedDate:(NSDate *)modifiedDate JSKCommandParcel *parcel = [[JSKCommandParcel alloc] initWithType:JSKCommandParcelTypeUpdate to:peerID from:hostID object:gameEnvoy]; if (peerID) { - [SystemMessage sendCommandParcel:parcel shouldAwaitResponse:NO]; + [NetworkManager sendCommandParcel:parcel shouldAwaitResponse:NO]; } else { - [SystemMessage sendParcelToPlayers:parcel]; + [NetworkManager sendParcelToPlayers:parcel]; } return; } @@ -278,11 +272,11 @@ - (void)sendGameUpdateTo:(NSString *)peerID modifiedDate:(NSDate *)modifiedDate JSKCommandParcel *parcel = [[JSKCommandParcel alloc] initWithType:JSKCommandParcelTypeUpdate to:peerID from:hostID object:objectArray]; if (peerID) { - [SystemMessage sendCommandParcel:parcel shouldAwaitResponse:NO]; + [NetworkManager sendCommandParcel:parcel shouldAwaitResponse:NO]; } else { - [SystemMessage sendParcelToPlayers:parcel]; + [NetworkManager sendParcelToPlayers:parcel]; } } diff --git a/Partisans/Classes/HostFinder.m b/Partisans/Classes/HostFinder.m index 7f115f4..bd41d8e 100644 --- a/Partisans/Classes/HostFinder.m +++ b/Partisans/Classes/HostFinder.m @@ -21,6 +21,7 @@ #import "HostFinder.h" #import "GameEnvoy.h" +#import "NetworkManager.h" #import "PlayerEnvoy.h" #import "SystemMessage.h" @@ -53,7 +54,7 @@ - (void)dealloc - (BOOL)isConnected { - self.isConnected = [SystemMessage isPlayerOnline]; + self.isConnected = [NetworkManager isPlayerOnline]; return m_isConnected; } @@ -65,14 +66,14 @@ - (NSString *)hostPeerID - (void)connect { - if ([SystemMessage isPlayerOnline]) + if ([NetworkManager isPlayerOnline]) { return; } [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(hostReadyToCommunicate:) name:kPartisansNotificationHostReadyToCommunicate object:nil]; - [SystemMessage putPlayerOnline]; + [NetworkManager putPlayerOnline]; if (!self.timer) { diff --git a/Partisans/Classes/Libraries/My Library/JSKCommandMessage.h b/Partisans/Classes/Libraries/My Library/JSKCommandMessage.h index 5b09827..33ddc71 100644 --- a/Partisans/Classes/Libraries/My Library/JSKCommandMessage.h +++ b/Partisans/Classes/Libraries/My Library/JSKCommandMessage.h @@ -19,6 +19,7 @@ // #import +#import "JSKCommandMessageProtocol.h" typedef enum @@ -47,12 +48,12 @@ typedef enum // My intent here is a small, generic object that can provide punctuation for other // objects when sending them to and fro between devices. -@interface JSKCommandMessage : NSObject +@interface JSKCommandMessage : NSObject @property (nonatomic, assign) JSKCommandMessageType commandMessageType; @property (nonatomic, strong) NSString *to; @property (nonatomic, strong) NSString *from; -@property (weak, readonly, nonatomic) NSString *commandMessageTypeName; +@property (nonatomic, readonly) NSString *commandMessageTypeName; @property (nonatomic, strong) NSString *responseKey; - (id)initWithType:(JSKCommandMessageType)commandMessageType to:(NSString *)to from:(NSString *)from; diff --git a/Partisans/Classes/Libraries/My Library/JSKCommandMessageProtocol.h b/Partisans/Classes/Libraries/My Library/JSKCommandMessageProtocol.h new file mode 100644 index 0000000..9ef61b6 --- /dev/null +++ b/Partisans/Classes/Libraries/My Library/JSKCommandMessageProtocol.h @@ -0,0 +1,14 @@ +// +// JSKCommandMessageProtocol.h +// Partisans +// +// Created by Joshua Kaden on 8/3/15. +// Copyright © 2015 Chadford Software. All rights reserved. +// + +#import + +@protocol JSKCommandMessageProtocol +@property (nonatomic, strong) NSString *from; +@property (nonatomic, strong) NSString *responseKey; +@end diff --git a/Partisans/Classes/Libraries/My Library/JSKCommandParcel.h b/Partisans/Classes/Libraries/My Library/JSKCommandParcel.h index 5f028ed..ccb15f7 100644 --- a/Partisans/Classes/Libraries/My Library/JSKCommandParcel.h +++ b/Partisans/Classes/Libraries/My Library/JSKCommandParcel.h @@ -19,6 +19,7 @@ // #import +#import "JSKCommandMessageProtocol.h" typedef enum { @@ -31,11 +32,11 @@ typedef enum } JSKCommandParcelType; -@interface JSKCommandParcel : NSObject +@interface JSKCommandParcel : NSObject @property (nonatomic, assign) JSKCommandParcelType commandParcelType; @property (nonatomic, strong) NSString *responseKey; -@property (weak, readonly, nonatomic) NSString *commandParcelTypeName; +@property (nonatomic, readonly) NSString *commandParcelTypeName; @property (nonatomic, strong) NSString *to; @property (nonatomic, strong) NSString *from; @property (nonatomic, strong) NSObject *object; diff --git a/Partisans/Classes/MissionViewController.m b/Partisans/Classes/MissionViewController.m index ec38a5b..4807f27 100644 --- a/Partisans/Classes/MissionViewController.m +++ b/Partisans/Classes/MissionViewController.m @@ -23,6 +23,7 @@ #import "GameEnvoy.h" #import "HostFinder.h" #import "MissionEnvoy.h" +#import "NetworkManager.h" #import "PlayerEnvoy.h" #import "SystemMessage.h" @@ -200,7 +201,7 @@ - (void)connectAndPerformMission:(BOOL)shouldSucceed [self.spinner startAnimating]; - if ([SystemMessage isPlayerOnline]) + if ([NetworkManager isPlayerOnline]) { GameEnvoy *gameEnvoy = [SystemMessage gameEnvoy]; self.hostPeerID = gameEnvoy.host.peerID; @@ -239,7 +240,7 @@ - (void)performMission:(BOOL)shouldSucceed { message.commandMessageType = JSKCommandMessageTypeFail; } - [SystemMessage sendCommandMessage:message shouldAwaitResponse:YES]; + [NetworkManager sendCommandMessage:message shouldAwaitResponse:YES]; } diff --git a/Partisans/Classes/NetHandler.h b/Partisans/Classes/NetHandler.h new file mode 100644 index 0000000..9dea79d --- /dev/null +++ b/Partisans/Classes/NetHandler.h @@ -0,0 +1,28 @@ +// +// NetHandler.h +// Partisans +// +// Created by Joshua Kaden on 8/4/15. +// Copyright © 2015 Chadford Software. All rights reserved. +// + +#import + +@class JSKCommandParcel; +@class GameEnvoy; +@class PlayerEnvoy; + +@interface NetHandler : NSObject + +@property (nonatomic, readonly) GameEnvoy *gameEnvoy; +@property (nonatomic, readonly) NSString *myPeerID; +@property (nonatomic, readonly) PlayerEnvoy *playerEnvoy; + +- (void)sendDigestTo:(NSString *)toPeerID; + +/** This will check the local data and ask for new data as needed. */ +- (void)processDigest:(NSDictionary *)digest; + +- (void)handlePlayerUpdate:(JSKCommandParcel *)parcel; + +@end diff --git a/Partisans/Classes/NetHandler.m b/Partisans/Classes/NetHandler.m new file mode 100644 index 0000000..9277c5d --- /dev/null +++ b/Partisans/Classes/NetHandler.m @@ -0,0 +1,141 @@ +// +// NetHandler.m +// Partisans +// +// Created by Joshua Kaden on 8/4/15. +// Copyright © 2015 Chadford Software. All rights reserved. +// + +#import "NetHandler.h" + +#import "GameEnvoy.h" +#import "JSKCommandMessage.h" +#import "JSKCommandParcel.h" +#import "NetworkManager.h" +#import "PlayerEnvoy.h" +#import "SystemMessage.h" +#import "UpdatePlayerOperation.h" + +@interface NetHandler () +@end + +@implementation NetHandler + +- (GameEnvoy *)gameEnvoy +{ + return [SystemMessage gameEnvoy]; +} + +- (NSString *)myPeerID +{ + return [SystemMessage playerEnvoy].peerID; +} + +- (PlayerEnvoy *)playerEnvoy +{ + return [SystemMessage sharedInstance].playerEnvoy; +} + +- (void)sendDigestTo:(NSString *)toPeerID +{ + NSDictionary *digest = [self buildDigestFor:toPeerID]; + JSKCommandParcel *parcel = [[JSKCommandParcel alloc] initWithType:JSKCommandParcelTypeDigest + to:toPeerID + from:self.myPeerID + object:digest + responseKey:nil]; + [NetworkManager sendCommandParcel:parcel shouldAwaitResponse:NO]; +} + +- (NSDictionary *)buildDigestFor:(NSString *)forPeerID +{ + NSArray *gamePlayers = [[SystemMessage gameEnvoy] players]; + NSMutableDictionary *digest = [[NSMutableDictionary alloc] initWithCapacity:gamePlayers.count]; + for (PlayerEnvoy *player in gamePlayers) { + if (![player.peerID isEqualToString:forPeerID]) { + [digest setValue:player.modifiedDate forKey:player.peerID]; + } + } + NSDictionary *returnValue = [NSDictionary dictionaryWithDictionary:digest]; + return returnValue; +} + +- (void)processDigest:(NSDictionary *)digest +{ + // Crash prevention when hosting after joining. + if (digest.count == 0) { + return; + } + + BOOL wasNewDataRequested = NO; + // The digest is a dictionary of Player.modifiedDate values, keyed on peerID. + PlayerEnvoy *playerEnvoy = [SystemMessage playerEnvoy]; + for (NSString *otherID in digest.allKeys) { + BOOL shouldAskForData = NO; + NSDate *otherDate = [digest valueForKey:otherID]; + if (otherDate) { + PlayerEnvoy *otherEnvoy = [PlayerEnvoy envoyFromPeerID:otherID]; + if (otherEnvoy) { + if ([SystemMessage secondsBetweenDates:otherEnvoy.modifiedDate toDate:otherDate] > 0 || [otherEnvoy.modifiedDate isEqualToDate:[NSDate distantPast]]) { + shouldAskForData = YES; + } + } else { + shouldAskForData = YES; + } + } + + if (shouldAskForData) { + // If we are a Host, then the dictionary will contain one row, and we'll send the message to that Player. + // If we are a Player, the "to" field will not necessarily be the Host's address. But we'll be sending the message to the Host. + // Note the use of the "to" field here, to indicate that we're interested in that player's info, not (necessarily) the recipient's. + JSKCommandMessage *message = [[JSKCommandMessage alloc] initWithType:JSKCommandMessageTypeGetInfo to:otherID from:playerEnvoy.peerID]; + [NetworkManager sendCommandMessage:message shouldAwaitResponse:YES]; + wasNewDataRequested = YES; + } + } + + if (!wasNewDataRequested) { + if ([SystemMessage isHost]) { + NSString *otherID = [digest.allKeys objectAtIndex:0]; + [self sendDigestTo:otherID]; + } else { + if ([SystemMessage sharedInstance].isLookingForGame) { + [NetworkManager askToJoinGame]; + } else { + if (self.gameEnvoy) { + [SystemMessage requestGameUpdate]; + } + } + } + } +} + +- (void)handlePlayerUpdate:(JSKCommandParcel *)parcel +{ + NSObject *object = parcel.object; + if ([object isKindOfClass:[PlayerEnvoy class]]) + { + PlayerEnvoy *other = (PlayerEnvoy *)object; + other.isNative = NO; + other.isDefault = NO; + + // BOOL isHost = [SystemMessage isHost]; + + UpdatePlayerOperation *op = [[UpdatePlayerOperation alloc] initWithEnvoy:other]; + [op setCompletionBlock:^(void) + { + // if (isHost) + // { + // [self broadcastPlayerData:other.peerID]; + // } + // Update the UI. + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:kJSKNotificationPeerUpdated object:other.peerID]; + }); + }]; + NSOperationQueue *queue = [SystemMessage mainQueue]; + [queue addOperation:op]; + } +} + +@end diff --git a/Partisans/Classes/NetHostHandler.h b/Partisans/Classes/NetHostHandler.h new file mode 100644 index 0000000..828ba99 --- /dev/null +++ b/Partisans/Classes/NetHostHandler.h @@ -0,0 +1,16 @@ +// +// NetHostHandler.h +// Partisans +// +// Created by Joshua Kaden on 8/3/15. +// Copyright © 2015 Chadford Software. All rights reserved. +// + +#import + +#import "NetHandler.h" +#import "NetHost.h" + +@interface NetHostHandler : NetHandler + +@end diff --git a/Partisans/Classes/NetHostHandler.m b/Partisans/Classes/NetHostHandler.m new file mode 100644 index 0000000..d6bf7bb --- /dev/null +++ b/Partisans/Classes/NetHostHandler.m @@ -0,0 +1,372 @@ +// +// NetHostHandler.m +// Partisans +// +// Created by Joshua Kaden on 8/3/15. +// Copyright © 2015 Chadford Software. All rights reserved. +// + +#import "NetHostHandler.h" + +#import "AddGamePlayerOperation.h" +#import "CoordinatorVote.h" +#import "GameDirector.h" +#import "GameEnvoy.h" +#import "JSKCommandMessageProtocol.h" +#import "JSKCommandParcel.h" +#import "MissionEnvoy.h" +#import "NetworkManager.h" +#import "PlayerEnvoy.h" +#import "RemoveGamePlayerOperation.h" +#import "RoundEnvoy.h" +#import "SystemMessage.h" +#import "UpdateGameOperation.h" +#import "UpdatePlayerOperation.h" +#import "VoteEnvoy.h" + +@interface NetHostHandler () +@end + +@implementation NetHostHandler + +- (JSKCommandMessage *)buildAcknowledgementFromMessage:(id)message +{ + JSKCommandMessage *acknowledgement = [[JSKCommandMessage alloc] initWithType:JSKCommandMessageTypeAcknowledge to:message.from from:self.myPeerID]; + acknowledgement.responseKey = message.responseKey; + return acknowledgement; +} + +- (void)handlePlayerResponse:(JSKCommandParcel *)commandParcel inResponseTo:(JSKCommandMessage *)inResponseTo +{ + // This could be a response to a GetInfo message. + if (inResponseTo.commandMessageType == JSKCommandMessageTypeGetInfo) { + // In this case we expect a PlayerEnvoy. + PlayerEnvoy *otherEnvoy = (PlayerEnvoy *)commandParcel.object; + otherEnvoy.isNative = NO; + otherEnvoy.isDefault = NO; + + UpdatePlayerOperation *op = [[UpdatePlayerOperation alloc] initWithEnvoy:otherEnvoy]; + [op setCompletionBlock:^{ + dispatch_async(dispatch_get_main_queue(), ^{ + [self sendDigestTo:commandParcel.from]; + [[NSNotificationCenter defaultCenter] postNotificationName:kJSKNotificationPeerUpdated object:otherEnvoy.peerID]; + }); + }]; + NSOperationQueue *queue = [SystemMessage mainQueue]; + [queue addOperation:op]; + } else { + // no op + } +} + +// Send the player's data to everyone but the player (who already knows it), and the host (who is me). +- (void)broadcastPlayerData:(NSString *)peerID +{ + PlayerEnvoy *envoy = [PlayerEnvoy envoyFromPeerID:peerID]; + NSArray *gamePlayers = [[SystemMessage gameEnvoy] players]; + for (PlayerEnvoy *player in gamePlayers) { + if (![player.peerID isEqualToString:peerID] && ![player.peerID isEqualToString:self.myPeerID]) { + JSKCommandParcel *parcel = [[JSKCommandParcel alloc] initWithType:JSKCommandParcelTypeUpdate to:player.peerID from:self.myPeerID object:envoy]; + [NetworkManager sendCommandParcel:parcel shouldAwaitResponse:NO]; + } + } +} + +- (void)handleJoinGameMessage:(JSKCommandMessage *)message +{ + PlayerEnvoy *other = [PlayerEnvoy envoyFromPeerID:message.from]; + if (!other || !other.playerName || other.playerName.length == 0) { + debugLog(@"invalid join game message") + return; + } + [self addPlayerToGame:other responseKey:message.responseKey]; +} + +- (void)addPlayerToGame:(PlayerEnvoy *)playerEnvoy responseKey:(NSString *)responseKey +{ + BOOL proceed = NO; + if ([SystemMessage isHost]) { + // We are hosting a game. + if (self.gameEnvoy.players.count < kPartisansMaxPlayers && !self.gameEnvoy.startDate) { + // The game has room and hasn't yet started. + // So, let the other player join, and send the game object back! + proceed = YES; + } + } + if (proceed) { + // Make sure this player isn't already in the game. + if ([self.gameEnvoy isPlayerInGame:playerEnvoy]) { + [self sendGameUpdateTo:playerEnvoy.peerID modifiedDate:nil shouldSendAllData:YES]; + proceed = NO; + } + } + + if (!proceed) { + return; + } + + NSString *peerID = playerEnvoy.peerID; + NSString *hostID = self.myPeerID; + + // Add this player to the game. + AddGamePlayerOperation *op = [[AddGamePlayerOperation alloc] initWithPlayerEnvoy:playerEnvoy gameEnvoy:self.gameEnvoy]; + [op setCompletionBlock:^(void) { + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:kPartisansNotificationGameChanged object:nil]; + }); + + JSKCommandParcel *parcel = [[JSKCommandParcel alloc] initWithType:JSKCommandParcelTypeResponse to:peerID from:hostID object:self.gameEnvoy responseKey:responseKey]; + [NetworkManager sendCommandParcel:parcel shouldAwaitResponse:NO]; + }]; + NSOperationQueue *queue = [SystemMessage mainQueue]; + [queue addOperation:op]; +} + +- (void)sendGameUpdateTo:(NSString *)peerID modifiedDate:(NSDate *)modifiedDate shouldSendAllData:(BOOL)shouldSendAllData +{ + [[SystemMessage gameDirector] sendGameUpdateTo:peerID modifiedDate:modifiedDate shouldSendAllData:shouldSendAllData]; +} + + +- (void)handleCoordinatorVote:(JSKCommandParcel *)parcel +{ + CoordinatorVote *coordinatorVote = (CoordinatorVote *)parcel.object; + VoteEnvoy *voteEnvoy = coordinatorVote.voteEnvoy; + voteEnvoy.isCast = YES; + NSArray *candidateIDs = coordinatorVote.candidateIDs; + + JSKCommandMessage *acknowledgement = [self buildAcknowledgementFromMessage:parcel]; + + RoundEnvoy *roundEnvoy = [self.gameEnvoy currentRound]; + for (NSString *candidateID in candidateIDs) { + PlayerEnvoy *candidate = [PlayerEnvoy envoyFromIntramuralID:candidateID]; + [roundEnvoy addCandidate:candidate]; + } + [roundEnvoy addVote:voteEnvoy]; + UpdateGameOperation *op = [[UpdateGameOperation alloc] initWithEnvoy:self.gameEnvoy]; + [op setCompletionBlock:^{ + dispatch_async(dispatch_get_main_queue(), ^{ + [NetworkManager sendCommandMessage:acknowledgement shouldAwaitResponse:NO]; + }); + }]; + NSOperationQueue *queue = [SystemMessage mainQueue]; + [queue addOperation:op]; +} + +- (void)handleVote:(JSKCommandParcel *)parcel +{ + VoteEnvoy *voteEnvoy = (VoteEnvoy *)parcel.object; + voteEnvoy.isCast = YES; + + RoundEnvoy *roundEnvoy = [self.gameEnvoy currentRound]; + [roundEnvoy addVote:voteEnvoy]; + + JSKCommandMessage *acknowledgement = [self buildAcknowledgementFromMessage:parcel]; + + UpdateGameOperation *op = [[UpdateGameOperation alloc] initWithEnvoy:self.gameEnvoy]; + [op setCompletionBlock:^{ + dispatch_async(dispatch_get_main_queue(), ^{ + [SystemMessage reloadGame:self.gameEnvoy]; + [NetworkManager sendCommandMessage:acknowledgement shouldAwaitResponse:NO]; + [[NSNotificationCenter defaultCenter] postNotificationName:kPartisansNotificationGameChanged object:nil]; + }); + }]; + NSOperationQueue *queue = [SystemMessage mainQueue]; + [queue addOperation:op]; +} + +- (void)handlePerformMissionMessage:(JSKCommandMessage *)message +{ + MissionEnvoy *missionEnvoy = [self.gameEnvoy currentMission]; + PlayerEnvoy *from = [PlayerEnvoy envoyFromPeerID:message.from]; + if (message.commandMessageType == JSKCommandMessageTypeSucceed) { + [missionEnvoy applyContributeur:from]; + } else if (message.commandMessageType == JSKCommandMessageTypeFail) { + [missionEnvoy applySaboteur:from]; + } else { + // Unexpected message type. + debugLog(@"Unexpected message type"); + return; + } + + JSKCommandMessage *acknowledgement = [self buildAcknowledgementFromMessage:message]; + + UpdateGameOperation *op = [[UpdateGameOperation alloc] initWithEnvoy:self.gameEnvoy]; + [op setCompletionBlock:^{ + dispatch_async(dispatch_get_main_queue(), ^{ + [SystemMessage reloadGame:self.gameEnvoy]; + [NetworkManager sendCommandMessage:acknowledgement shouldAwaitResponse:NO]; + [[NSNotificationCenter defaultCenter] postNotificationName:kPartisansNotificationGameChanged object:nil]; + }); + }]; + NSOperationQueue *queue = [SystemMessage mainQueue]; + [queue addOperation:op]; +} + + +- (void)handleLeaveGameMessage:(JSKCommandMessage *)message +{ + PlayerEnvoy *other = [PlayerEnvoy envoyFromPeerID:message.from]; + if (!other) { + return; + } + // Make sure this player is in the game. + if (![self.gameEnvoy isPlayerInGame:other]) { + return; + } + + // Remove this player from the game. + RemoveGamePlayerOperation *op = [[RemoveGamePlayerOperation alloc] initWithEnvoy:other]; + [op setCompletionBlock:^(void) { + JSKCommandParcel *gameParcel = [[JSKCommandParcel alloc] initWithType:JSKCommandParcelTypeUpdate to:nil from:self.myPeerID object:self.gameEnvoy]; + [NetworkManager sendParcelToPlayers:gameParcel]; + dispatch_async(dispatch_get_main_queue(), ^(void) { + [[NSNotificationCenter defaultCenter] postNotificationName:kPartisansNotificationGameChanged object:nil]; + }); + }]; + NSOperationQueue *queue = [SystemMessage mainQueue]; + [queue addOperation:op]; +} + +#pragma mark - NetHost delegate + +- (NSString *)netHostPeerID:(NetHost *)netHost +{ + return [SystemMessage playerEnvoy].peerID; +} + +- (void)netHost:(NetHost *)netHost receivedCommandParcel:(JSKCommandParcel *)commandParcel +{ + switch (commandParcel.commandParcelType) { + case JSKCommandParcelTypeDigest: + // A player will not send this. + break; + + case JSKCommandParcelTypeModifiedDate: { + NSString *otherID = commandParcel.from; + NSDictionary *dictionary = (NSDictionary *)commandParcel.object; + NSString *entity = [dictionary valueForKey:@"entity"]; + NSDate *otherDate = [dictionary valueForKey:@"modifiedDate"]; + BOOL shouldSendAllData = [[dictionary valueForKey:@"shouldSendAllData"] boolValue]; + if ([entity isEqualToString:@"Player"]) { + [self processDigest:[NSDictionary dictionaryWithObject:otherDate forKey:otherID]]; + } else if ([entity isEqualToString:@"Game"]) { + [self sendGameUpdateTo:otherID modifiedDate:otherDate shouldSendAllData:shouldSendAllData]; + } + break; + } + + case JSKCommandParcelTypeResponse: + break; + + case JSKCommandParcelTypeUpdate: + if ([commandParcel.object isKindOfClass:[PlayerEnvoy class]]) { + [self handlePlayerUpdate:commandParcel]; + } + if ([commandParcel.object isKindOfClass:[CoordinatorVote class]]) { + [self handleCoordinatorVote:commandParcel]; + } + if ([commandParcel.object isKindOfClass:[VoteEnvoy class]]) { + [self handleVote:commandParcel]; + } + break; + + case JSKCommandParcelTypeUnknown: + break; + + case JSKCommandParcelType_maxValue: + break; + } +} + + +- (void)netHost:(NetHost *)netHost receivedCommandParcel:(JSKCommandParcel *)commandParcel respondingTo:(NSObject *)inResponseTo +{ + if ([inResponseTo isKindOfClass:[JSKCommandMessage class]]) { + JSKCommandMessage *msg = (JSKCommandMessage *)inResponseTo; + [self handlePlayerResponse:commandParcel inResponseTo:msg]; + } + // else if ([inResponseTo isKindOfClass:[JSKCommandParcel class]]) + // { + // JSKCommandParcel *parcel = (JSKCommandParcel *)inResponseTo; + // [self handleResponse:commandParcel inResponseToParcel:parcel]; + // } +} + + +- (void)netHost:(NetHost *)netHost receivedCommandMessage:(JSKCommandMessage *)commandMessage +{ + NSString *peerID = commandMessage.from; + PlayerEnvoy *other = [PlayerEnvoy envoyFromPeerID:peerID]; + if (!other) { + // Unidentified or unmatched player. + debugLog(@"Message from unknown peer %@", commandMessage); + return; + } + + if (commandMessage.commandMessageType == JSKCommandMessageTypeJoinGame) { + [self handleJoinGameMessage:commandMessage]; + return; + } + + if (commandMessage.commandMessageType == JSKCommandMessageTypeGetDigest) { + [self sendDigestTo:peerID]; + return; + } + + // The "to" field tells us which player the sender is interested in. + PlayerEnvoy *playerEnvoy = [PlayerEnvoy envoyFromPeerID:commandMessage.to]; + // PlayerEnvoy *playerEnvoy = self.playerEnvoy; + JSKCommandMessageType messageType = commandMessage.commandMessageType; + // JSKCommandParcelType parcelType = JSKCommandParcelTypeUnknown; + NSObject *responseObject = nil; + + switch (messageType) { + case JSKCommandMessageTypeGetInfo: + // parcelType = JSKCommandParcelTypeResponse; + responseObject = playerEnvoy; + break; + case JSKCommandMessageTypeGetModifiedDate: + // parcelType = JSKCommandParcelTypeResponse; + responseObject = playerEnvoy.modifiedDate; + break; + case JSKCommandMessageTypeJoinGame: + // This was caught above. + // [self handleJoinGameMessage:commandMessage]; + break; + case JSKCommandMessageTypeIdentification: { + // If not caught above then we already know about this player. + // Let's get their data. + JSKCommandMessage *msg = [[JSKCommandMessage alloc] initWithType:JSKCommandMessageTypeGetInfo to:commandMessage.from from:self.playerEnvoy.peerID]; + [NetworkManager sendCommandMessage:msg shouldAwaitResponse:YES]; + break; + } + case JSKCommandMessageTypeLeaveGame: + [self handleLeaveGameMessage:commandMessage]; + break; + + case JSKCommandMessageTypeSucceed: + case JSKCommandMessageTypeFail: + [self handlePerformMissionMessage:commandMessage]; + break; + + default: + break; + } + + if (responseObject) { + JSKCommandParcel *response = [[JSKCommandParcel alloc] initWithType:JSKCommandParcelTypeResponse + to:peerID + from:playerEnvoy.peerID + object:responseObject + responseKey:commandMessage.responseKey]; + [netHost sendCommandParcel:response]; + } +} + +- (void)netHost:(NetHost *)netHost terminated:(NSString *)reason +{ + debugLog(@"NetHost terminated: %@", reason); +} + +@end diff --git a/Partisans/Classes/NetHostManager.h b/Partisans/Classes/NetHostManager.h new file mode 100644 index 0000000..7a05607 --- /dev/null +++ b/Partisans/Classes/NetHostManager.h @@ -0,0 +1,14 @@ +// +// NetHostHandler.h +// Partisans +// +// Created by Joshua Kaden on 8/3/15. +// Copyright © 2015 Chadford Software. All rights reserved. +// + +#import +#import "NetHandler.h" + +@interface NetHostHandler : NetHandler + +@end diff --git a/Partisans/Classes/NetPlayerHandler.h b/Partisans/Classes/NetPlayerHandler.h new file mode 100644 index 0000000..24b4c7a --- /dev/null +++ b/Partisans/Classes/NetPlayerHandler.h @@ -0,0 +1,16 @@ +// +// NetPlayerHandler.h +// Partisans +// +// Created by Joshua Kaden on 8/4/15. +// Copyright © 2015 Chadford Software. All rights reserved. +// + +#import + +#import "NetHandler.h" +#import "NetPlayer.h" + +@interface NetPlayerHandler : NetHandler + +@end diff --git a/Partisans/Classes/NetPlayerHandler.m b/Partisans/Classes/NetPlayerHandler.m new file mode 100644 index 0000000..7930626 --- /dev/null +++ b/Partisans/Classes/NetPlayerHandler.m @@ -0,0 +1,295 @@ +// +// NetPlayerHandler.m +// Partisans +// +// Created by Joshua Kaden on 8/4/15. +// Copyright © 2015 Chadford Software. All rights reserved. +// + +#import "NetPlayerHandler.h" + +#import "CreateGameOperation.h" +#import "GameEnvoy.h" +#import "GamePrecis.h" +#import "JSKDataMiner.h" +#import "NetworkManager.h" +#import "NSManagedObjectContext+FetchAdditions.h" +#import "PlayerEnvoy.h" +#import "UpdateEnvoysOperation.h" +#import "UpdateGameOperation.h" +#import "UpdatePlayerOperation.h" +#import "SystemMessage.h" + +@interface NetPlayerHandler () +@property (nonatomic, strong) NSDictionary *playerDigest; +@end + +@implementation NetPlayerHandler + +- (BOOL)isDigestCurrent +{ + // Loop through the player digest and see if our saved Player data is up-to-date. + BOOL returnValue = YES; + NSDictionary *digest = self.playerDigest; + for (NSString *otherID in digest.allKeys) + { + NSDate *otherDate = [digest valueForKey:otherID]; + PlayerEnvoy *otherEnvoy = [PlayerEnvoy envoyFromPeerID:otherID]; + if (!otherEnvoy) + { + returnValue = NO; + break; + } + if ([SystemMessage secondsBetweenDates:otherEnvoy.modifiedDate toDate:otherDate] != 0) + { + returnValue = NO; + break; + } + } + return returnValue; +} + +- (void)handleJoinGameResponse:(JSKCommandParcel *)response +{ + if (!response.object) + { + return; + } + if (![response.object isKindOfClass:[GameEnvoy class]]) + { + return; + } + + [SystemMessage sharedInstance].isLookingForGame = NO; + + // Save the game locally. + GameEnvoy *envoy = (GameEnvoy *)response.object; + CreateGameOperation *op = [[CreateGameOperation alloc] initWithEnvoy:envoy]; + [op setCompletionBlock:^(void) { + dispatch_async(dispatch_get_main_queue(), ^{ + // Once the save is done, update our own gameEnvoy property. + NSManagedObjectContext *context = [JSKDataMiner mainObjectContext]; + NSArray *games = [context fetchObjectArrayForEntityName:@"Game" withPredicateFormat:@"intramuralID == %@", envoy.intramuralID]; + if (games.count > 0) + { + GameEnvoy *updatedEnvoy = [GameEnvoy envoyFromManagedObject:[games objectAtIndex:0]]; + [[SystemMessage sharedInstance] setGameEnvoy:updatedEnvoy]; + + // Also, post a notification that we joined the game. + [[NSNotificationCenter defaultCenter] postNotificationName:kPartisansNotificationJoinedGame object:updatedEnvoy]; + } + }); + }]; + NSOperationQueue *queue = [SystemMessage mainQueue]; + [queue addOperation:op]; +} + + +- (void)handleHostAcknowledgement:(JSKCommandMessage *)message +{ + dispatch_async(dispatch_get_main_queue(), ^(void) + { + [[NSNotificationCenter defaultCenter] postNotificationName:kPartisansNotificationHostAcknowledgement object:message.responseKey]; + }); +} + +#pragma mark - NetPlayer delegate + +- (NSString *)netPlayerPeerID:(NetPlayer *)netPlayer +{ + return self.playerEnvoy.peerID; +} + +- (NSDate *)netPlayerModifiedDate:(NetPlayer *)netPlayer +{ + return self.playerEnvoy.modifiedDate; +} + +- (void)netPlayer:(NetPlayer *)netPlayer receivedCommandMessage:(JSKCommandMessage *)commandMessage +{ + PlayerEnvoy *playerEnvoy = self.playerEnvoy; + JSKCommandMessageType messageType = commandMessage.commandMessageType; + NSObject *responseObject = nil; + BOOL shouldAwaitResponse = NO; + + switch (messageType) + { + case JSKCommandMessageTypeAcknowledge: + [self handleHostAcknowledgement:commandMessage]; + break; + case JSKCommandMessageTypeGetInfo: + responseObject = playerEnvoy; + break; + case JSKCommandMessageTypeGetModifiedDate: + responseObject = playerEnvoy.modifiedDate; + break; + // case JSKCommandMessageTypeIdentification: + // // We'll get this back from a host after sending them our ID. + // // Send the host our player data. + // // In return, the host will send us their data. + // responseObject = playerEnvoy; + // shouldAwaitResponse = YES; + // break; + default: + debugLog(@"Unexpected message type from host: %@", commandMessage); + break; + } + + if (responseObject) + { + JSKCommandParcel *response = [[JSKCommandParcel alloc] initWithType:JSKCommandParcelTypeResponse + to:commandMessage.from + from:playerEnvoy.peerID + object:responseObject + responseKey:commandMessage.responseKey]; + [netPlayer sendCommandParcel:response shouldAwaitResponse:shouldAwaitResponse]; + } +} + +- (void)netPlayer:(NetPlayer *)netPlayer receivedCommandParcel:(JSKCommandParcel *)commandParcel +{ + NSObject *object = commandParcel.object; + if (commandParcel.commandParcelType == JSKCommandParcelTypeDigest) + { + // This means that the object is a dictionary of Player.modifiedDate values, keyed on peerID. + NSDictionary *digest = (NSDictionary *)object; + self.playerDigest = digest; + [self processDigest:digest]; + return; + } + + // Save the player data locally. + if ([object isKindOfClass:[PlayerEnvoy class]]) + { + PlayerEnvoy *other = (PlayerEnvoy *)object; + other.isNative = NO; + other.isDefault = NO; + [SystemMessage clearImageCache]; + + UpdatePlayerOperation *op = [[UpdatePlayerOperation alloc] initWithEnvoy:other]; + [op setCompletionBlock:^(void){ + dispatch_async(dispatch_get_main_queue(), ^{ + if ([SystemMessage sharedInstance].isLookingForGame) + { + if ([self isDigestCurrent]) + { + [NetworkManager askToJoinGameHostedBy:other.peerID]; + } + } + [[NSNotificationCenter defaultCenter] postNotificationName:kJSKNotificationPeerUpdated object:other.peerID]; + }); + }]; + NSOperationQueue *queue = [SystemMessage mainQueue]; + [queue addOperation:op]; + return; + } + + // Save the Game data locally. + if ([object isKindOfClass:[GameEnvoy class]]) + { + // Let's make sure the copy we're getting is newer than the one we have currently. + GameEnvoy *currentGame = self.gameEnvoy; + if (!currentGame) + { + return; + } + GameEnvoy *updatedGame = (GameEnvoy *)commandParcel.object; + + if (![currentGame.intramuralID isEqualToString:updatedGame.intramuralID]) + { + return; + } + + if (currentGame.modifiedDate && updatedGame.modifiedDate) + { + NSInteger delta = [SystemMessage secondsBetweenDates:currentGame.modifiedDate toDate:updatedGame.modifiedDate]; + if (delta <= 0) + { + return; + } + } + + updatedGame.hasScoreBeenShown = YES; + + UpdateGameOperation *op = [[UpdateGameOperation alloc] initWithEnvoy:updatedGame]; + [op setCompletionBlock:^(void){ + dispatch_async(dispatch_get_main_queue(), ^(void) + { + updatedGame.hasScoreBeenShown = YES; + [[SystemMessage sharedInstance] setGameEnvoy:updatedGame]; + [[NSNotificationCenter defaultCenter] postNotificationName:kPartisansNotificationGameChanged object:nil]; + }); + }]; + NSOperationQueue *queue = [SystemMessage mainQueue]; + [queue addOperation:op]; + return; + } + + + // Save the envoys locally. + if ([object isKindOfClass:[NSArray class]]) + { + GameEnvoy *currentGame = self.gameEnvoy; + if (!currentGame) + { + return; + } + NSArray *envoys = (NSArray *)commandParcel.object; + + GamePrecis *precis = (GamePrecis *)[envoys objectAtIndex:0]; + if (!precis) + { + return; + } + if (currentGame.modifiedDate && precis.modifiedDate) + { + NSInteger delta = [SystemMessage secondsBetweenDates:currentGame.modifiedDate toDate:precis.modifiedDate]; + if (delta <= 0) + { + return; + } + } + + UpdateEnvoysOperation *op = [[UpdateEnvoysOperation alloc] initWithEnvoys:envoys]; + [op setCompletionBlock:^(void) + { + dispatch_async(dispatch_get_main_queue(), ^(void) + { + [SystemMessage reloadGame:currentGame]; + [[NSNotificationCenter defaultCenter] postNotificationName:kPartisansNotificationGameChanged object:nil]; + }); + }]; + NSOperationQueue *queue = [SystemMessage mainQueue]; + [queue addOperation:op]; + return; + } +} + + +- (void)netPlayer:(NetPlayer *)netPlayer receivedCommandParcel:(JSKCommandParcel *)commandParcel respondingTo:(NSObject *)inResponseTo +{ + if ([inResponseTo isKindOfClass:[JSKCommandMessage class]]) { + JSKCommandMessage *message = (JSKCommandMessage *)inResponseTo; + if (message.commandMessageType == JSKCommandMessageTypeJoinGame) { + [self handleJoinGameResponse:commandParcel]; + return; + } + } + [self netPlayer:netPlayer receivedCommandParcel:commandParcel]; +} + +- (void)netPlayer:(NetPlayer *)netPlayer terminated:(NSString *)reason +{ + [netPlayer stop]; + // debugLog(@"NetPlayer terminated: %@", reason); +} + +- (void)netPlayerDidResolveAddress:(NetPlayer *)netPlayer +{ + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:kPartisansNotificationConnectedToHost object:nil]; + }); + [NetworkManager sendToHost:JSKCommandMessageTypeIdentification shouldAwaitResponse:YES]; +} + +@end diff --git a/Partisans/Classes/NetworkManager.h b/Partisans/Classes/NetworkManager.h new file mode 100644 index 0000000..a725035 --- /dev/null +++ b/Partisans/Classes/NetworkManager.h @@ -0,0 +1,28 @@ +// +// NetworkManager.h +// Partisans +// +// Created by Joshua Kaden on 8/3/15. +// Copyright © 2015 Chadford Software. All rights reserved. +// + +#import +#import "JSKCommandMessage.h" + +@class JSKCommandParcel; + +@interface NetworkManager : NSObject + ++ (BOOL)isPlayerOnline; ++ (void)putPlayerOnline; ++ (void)putPlayerOffline; ++ (void)sendCommandMessage:(JSKCommandMessage *)commandMessage shouldAwaitResponse:(BOOL)shouldAwaitResponse; ++ (void)sendCommandParcel:(JSKCommandParcel *)parcel shouldAwaitResponse:(BOOL)shouldAwaitResponse; ++ (void)sendToHost:(JSKCommandMessageType)commandMessageType shouldAwaitResponse:(BOOL)shouldAwaitResponse; ++ (void)sendParcelToPlayers:(JSKCommandParcel *)parcel; ++ (void)setupNetPlayerWithService:(NSNetService *)service; + ++ (void)askToJoinGame; ++ (void)askToJoinGameHostedBy:(NSString *)hostID; + +@end diff --git a/Partisans/Classes/NetworkManager.m b/Partisans/Classes/NetworkManager.m new file mode 100644 index 0000000..aa798e4 --- /dev/null +++ b/Partisans/Classes/NetworkManager.m @@ -0,0 +1,219 @@ +// +// NetworkManager.m +// Partisans +// +// Created by Joshua Kaden on 8/3/15. +// Copyright © 2015 Chadford Software. All rights reserved. +// + +#import "NetworkManager.h" + +#import "GameEnvoy.h" +#import "NetHost.h" +#import "NetHostHandler.h" +#import "NetPlayer.h" +#import "NetPlayerHandler.h" +#import "PlayerEnvoy.h" +#import "SystemMessage.h" + +@interface NetworkManager () +@property (nonatomic, strong) NetHost *netHost; +@property (nonatomic, strong) NetHostHandler *netHostHandler; +@property (nonatomic, strong) NetPlayer *netPlayer; +@property (nonatomic, strong) NetPlayerHandler *netPlayerHandler; +@property (nonatomic, readonly) PlayerEnvoy *playerEnvoy; +@end + +@implementation NetworkManager + ++ (NetworkManager *)sharedInstance +{ + static dispatch_once_t pred; + static NetworkManager *sharedInstance = nil; + dispatch_once(&pred, ^{ sharedInstance = [[self alloc] init]; }); + return sharedInstance; +} + +- (void)dealloc +{ + [_netHost setDelegate:nil]; + [_netPlayer setDelegate:nil]; +} + ++ (BOOL)isPlayerOnline +{ + NetworkManager *sharedInstance = [self sharedInstance]; + if ([SystemMessage isHost]) { + NetHost *netHost = sharedInstance.netHost; + if (!netHost) { + NetHost *netHost = [[NetHost alloc] init]; + netHost.serviceName = [SystemMessage serviceName]; + sharedInstance.netHostHandler = [NetHostHandler new]; + [netHost setDelegate:sharedInstance.netHostHandler]; + [sharedInstance setNetHost:netHost]; + } + return netHost.hasStarted; + } else { + NetPlayer *netPlayer = sharedInstance.netPlayer; + if (!netPlayer) { + NetPlayer *netPlayer = [[NetPlayer alloc] init]; + sharedInstance.netPlayerHandler = [NetPlayerHandler new]; + [netPlayer setDelegate:sharedInstance.netPlayerHandler]; + [sharedInstance setNetPlayer:netPlayer]; + } + return netPlayer.hasStarted; + } +} + ++ (void)putPlayerOnline +{ + if ([self isPlayerOnline]) { + return; + } + + NetworkManager *sharedInstance = [self sharedInstance]; + if ([SystemMessage isHost]) { + if (!sharedInstance.netHost.hasStarted) { + [sharedInstance.netHost start]; + } + } else { + if (!sharedInstance.netPlayer.hasStarted) { + if (![sharedInstance.netPlayer start]) { + [SystemMessage browseServers]; + } + } + } +} + ++ (void)putPlayerOffline +{ + NetworkManager *sharedInstance = [self sharedInstance]; + if ([SystemMessage isHost]) { + if (sharedInstance.netHost.hasStarted) { + [sharedInstance.netHost stop]; + } + sharedInstance.netHost.delegate = nil; + sharedInstance.netHost = nil; + } else { + if (sharedInstance.netPlayer.hasStarted) { + [sharedInstance.netPlayer stop]; + } + sharedInstance.netPlayer.delegate = nil; + sharedInstance.netPlayer = nil; + } +} + ++ (void)sendCommandMessage:(JSKCommandMessage *)commandMessage shouldAwaitResponse:(BOOL)shouldAwaitResponse +{ + if (![self isPlayerOnline]) { + return; + } + + if ([SystemMessage isHost]) { + [[self sharedInstance].netHost sendCommandMessage:commandMessage shouldAwaitResponse:shouldAwaitResponse]; + } else { + [[self sharedInstance].netPlayer sendCommandMessage:commandMessage shouldAwaitResponse:shouldAwaitResponse]; + } +} + ++ (void)sendCommandParcel:(JSKCommandParcel *)parcel shouldAwaitResponse:(BOOL)shouldAwaitResponse +{ + if (![self isPlayerOnline]) { + return; + } + + if ([SystemMessage isHost]) { + [[self sharedInstance].netHost sendCommandParcel:parcel shouldAwaitResponse:shouldAwaitResponse]; + } else { + [[self sharedInstance].netPlayer sendCommandParcel:parcel shouldAwaitResponse:shouldAwaitResponse]; + } +} + ++ (void)sendToHost:(JSKCommandMessageType)commandMessageType shouldAwaitResponse:(BOOL)shouldAwaitResponse +{ + if (![self isPlayerOnline]) { + return; + } + + PlayerEnvoy *host = [SystemMessage gameEnvoy].host; + if (!host) { + return; + } + + NSString *hostID = host.peerID; + JSKCommandMessage *msg = [[JSKCommandMessage alloc] initWithType:commandMessageType to:hostID from:[SystemMessage playerEnvoy].peerID]; + NetPlayer *netPlayer = [self sharedInstance].netPlayer; + [netPlayer sendCommandMessage:msg shouldAwaitResponse:shouldAwaitResponse]; +} + ++ (void)sendParcelToPlayers:(JSKCommandParcel *)parcel +{ + if (![self isPlayerOnline]) { + return; + } + + if (!parcel.from) { + [parcel setFrom:[SystemMessage playerEnvoy].peerID]; + } + NetHost *netHost = [self sharedInstance].netHost; + GameEnvoy *gameEnvoy = [SystemMessage gameEnvoy]; + for (PlayerEnvoy *playerEnvoy in gameEnvoy.players) { + // Don't send to ourselves. + if (![playerEnvoy.peerID isEqualToString:[SystemMessage playerEnvoy].peerID]) { + [parcel setTo:playerEnvoy.peerID]; + [netHost sendCommandParcel:parcel]; + } + } +} + ++ (void)setupNetPlayerWithService:(NSNetService *)service +{ + [[self sharedInstance] setupNetPlayerWithService:service]; +} + +- (void)setupNetPlayerWithService:(NSNetService *)service +{ + if (self.netPlayer) { + [self.netPlayer stop]; + [self.netPlayer setDelegate:nil]; + self.netPlayer = nil; + } + NetPlayer *netPlayer = [[NetPlayer alloc] initWithNetService:service]; + self.netPlayerHandler = [NetPlayerHandler new]; + [netPlayer setDelegate:self.netPlayerHandler]; + [self setNetPlayer:netPlayer]; + [self.netPlayer start]; +} + ++ (void)askToJoinGame +{ + [self askToJoinGameHostedBy:nil]; +} + ++ (void)askToJoinGameHostedBy:(NSString *)hostID +{ + if (!hostID) { + hostID = [self sharedInstance].netPlayer.hostPeerID; + } + [[self sharedInstance] askToJoinGameDelayedHostedBy:hostID]; +} + +- (void)askToJoinGameDelayedHostedBy:(NSString *)hostID +{ + [self performSelector:@selector(askToJoinGame:) withObject:hostID afterDelay:2.0]; +} + +- (void)askToJoinGame:(NSString *)toPeerID +{ + if (![SystemMessage sharedInstance].isLookingForGame) { + return; + } + if ([SystemMessage isHost]) { + return; + } + + JSKCommandMessage *msg = [[JSKCommandMessage alloc] initWithType:JSKCommandMessageTypeJoinGame to:toPeerID from:[SystemMessage playerEnvoy].peerID]; + [NetworkManager sendCommandMessage:msg shouldAwaitResponse:YES]; +} + +@end diff --git a/Partisans/Classes/PlayerViewController.m b/Partisans/Classes/PlayerViewController.m index 758e5ce..2e8b527 100644 --- a/Partisans/Classes/PlayerViewController.m +++ b/Partisans/Classes/PlayerViewController.m @@ -25,6 +25,7 @@ #import "ImageEnvoy.h" #import "JSKCommandParcel.h" #import "JSKMenuViewController.h" +#import "NetworkManager.h" #import "PlayerEnvoy.h" #import "PlayerPicklistItems.h" #import "SlickEditView.h" @@ -556,7 +557,7 @@ - (IBAction)saveButtonPressed:(id)sender to:nil from:self.playerEnvoy.peerID object:self.playerEnvoy]; - [SystemMessage sendCommandParcel:parcel shouldAwaitResponse:NO]; + [NetworkManager sendCommandParcel:parcel shouldAwaitResponse:NO]; } } diff --git a/Partisans/Classes/RoundMenuItems.m b/Partisans/Classes/RoundMenuItems.m index e26af79..03e2937 100644 --- a/Partisans/Classes/RoundMenuItems.m +++ b/Partisans/Classes/RoundMenuItems.m @@ -27,6 +27,7 @@ #import "GameEnvoy.h" #import "ImageEnvoy.h" #import "MissionEnvoy.h" +#import "NetworkManager.h" #import "PlayerEnvoy.h" #import "RoundEnvoy.h" #import "SystemMessage.h" @@ -219,7 +220,7 @@ - (void)pollingTimerFired:(id)sender - (void)menuViewControllerDidLoad:(JSKMenuViewController *)menuViewController { - [SystemMessage putPlayerOnline]; + [NetworkManager putPlayerOnline]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(gameChanged:) name:kPartisansNotificationGameChanged object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerUpdated:) name:kJSKNotificationPeerUpdated object:nil]; diff --git a/Partisans/Classes/SetupGameMenuItems.m b/Partisans/Classes/SetupGameMenuItems.m index 82440f9..38a33ad 100644 --- a/Partisans/Classes/SetupGameMenuItems.m +++ b/Partisans/Classes/SetupGameMenuItems.m @@ -28,6 +28,7 @@ #import "GamePlayerEnvoy.h" #import "ImageEnvoy.h" #import "MissionEnvoy.h" +#import "NetworkManager.h" #import "PlayerEnvoy.h" #import "RoundMenuItems.h" #import "ProgressCell.h" @@ -221,7 +222,7 @@ - (void)handleStopHostingAlertView:(UIAlertView *)alertView clickedButtonAtIndex // [SystemMessage broadcastCommandMessage:JSKCommandMessageTypeLeaveGame]; - [SystemMessage putPlayerOffline]; + [NetworkManager putPlayerOffline]; GameEnvoy *gameEnvoy = [SystemMessage gameEnvoy]; [gameEnvoy deleteGame]; [[SystemMessage sharedInstance] setGameEnvoy:nil]; @@ -258,9 +259,9 @@ - (void)handleLeaveGameAlertView:(UIAlertView *)alertView clickedButtonAtIndex:( - (void)leaveGame { - [SystemMessage sendToHost:JSKCommandMessageTypeLeaveGame shouldAwaitResponse:YES]; + [NetworkManager sendToHost:JSKCommandMessageTypeLeaveGame shouldAwaitResponse:YES]; - [SystemMessage putPlayerOffline]; + [NetworkManager putPlayerOffline]; GameEnvoy *gameEnvoy = [SystemMessage gameEnvoy]; [gameEnvoy deleteGame]; [[SystemMessage sharedInstance] setGameEnvoy:nil]; @@ -291,8 +292,8 @@ - (void)menuViewControllerDidLoad:(JSKMenuViewController *)menuViewController [[SystemMessage sharedInstance] setGameEnvoy:newEnvoy]; } } - [SystemMessage putPlayerOffline]; - [SystemMessage putPlayerOnline]; + [NetworkManager putPlayerOffline]; + [NetworkManager putPlayerOnline]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(gameChanged:) name:kPartisansNotificationGameChanged object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerUpdated:) name:kJSKNotificationPeerUpdated object:nil]; @@ -309,16 +310,16 @@ - (void)menuViewControllerDidLoad:(JSKMenuViewController *)menuViewController - (void)menuViewControllerInvokedRefresh:(JSKMenuViewController *)menuViewController { self.players = nil; - if ([SystemMessage isPlayerOnline]) + if ([NetworkManager isPlayerOnline]) { if (![self isPlayerHost]) { - [SystemMessage sendToHost:JSKCommandMessageTypeGetDigest shouldAwaitResponse:NO]; + [NetworkManager sendToHost:JSKCommandMessageTypeGetDigest shouldAwaitResponse:NO]; } } else { - [SystemMessage putPlayerOnline]; + [NetworkManager putPlayerOnline]; } } diff --git a/Partisans/Classes/SystemMessage.h b/Partisans/Classes/SystemMessage.h index 8770f4d..8788887 100644 --- a/Partisans/Classes/SystemMessage.h +++ b/Partisans/Classes/SystemMessage.h @@ -54,6 +54,14 @@ extern NSString * const kPartisansNotificationHostReadyToCommunicate; + (GameEnvoy *)gameEnvoy; + (GameDirector *)gameDirector; ++ (BOOL)isHost; ++ (void)browseServers; ++ (void)stopBrowsingServers; ++ (void)requestGameUpdate; ++ (void)leaveGame; ++ (void)reloadGame:(GameEnvoy *)gameEnvoy; ++ (NSString *)serviceName; + + (UIImage *)imageWithImage:(UIImage*)sourceImage scaledToSizeWithSameAspectRatio:(CGSize)targetSize; + (NSInteger)secondsBetweenDates:(NSDate *)fromDate toDate:(NSDate *)toDate; + (BOOL)isSameDay:(NSDate *)firstDate as:(NSDate *)secondDate; @@ -71,21 +79,4 @@ extern NSString * const kPartisansNotificationHostReadyToCommunicate; + (void)cachePlayer:(PlayerEnvoy *)playerEnvoy key:(NSString *)key; + (void)clearPlayerCache; -+ (BOOL)isPlayerOnline; -+ (void)putPlayerOnline; -+ (void)putPlayerOffline; -+ (NSString *)serviceName; -+ (void)broadcastCommandMessage:(JSKCommandMessageType)commandMessageType; -+ (void)sendCommandMessage:(JSKCommandMessage *)commandMessage; -+ (void)sendCommandMessage:(JSKCommandMessage *)commandMessage shouldAwaitResponse:(BOOL)shouldAwaitResponse; -+ (void)sendCommandParcel:(JSKCommandParcel *)parcel shouldAwaitResponse:(BOOL)shouldAwaitResponse; -+ (void)sendToHost:(JSKCommandMessageType)commandMessageType shouldAwaitResponse:(BOOL)shouldAwaitResponse; -+ (void)sendParcelToPlayers:(JSKCommandParcel *)parcel; -+ (BOOL)isHost; -+ (void)askToJoinGame; -+ (void)browseServers; -+ (void)stopBrowsingServers; -+ (void)requestGameUpdate; -+ (void)leaveGame; - @end diff --git a/Partisans/Classes/SystemMessage.m b/Partisans/Classes/SystemMessage.m index 4e34574..0181b37 100644 --- a/Partisans/Classes/SystemMessage.m +++ b/Partisans/Classes/SystemMessage.m @@ -33,6 +33,7 @@ #import "MissionEnvoy.h" #import "NetHost.h" #import "NetPlayer.h" +#import "NetworkManager.h" #import "NSManagedObjectContext+FetchAdditions.h" #import "PlayerEnvoy.h" #import "RemoveGamePlayerOperation.h" @@ -62,71 +63,27 @@ NSString * const kPartisansNetServiceName = @"ThoroughlyRandomServiceNameForPartisans"; -@interface SystemMessage () +@interface SystemMessage () -@property (nonatomic, strong) NetHost *netHost; -@property (nonatomic, strong) NetPlayer *netPlayer; @property (nonatomic, strong) NSMutableArray *stash; @property (nonatomic, strong) NSMutableArray *peerIDs; -@property (nonatomic, strong) NSDictionary *playerDigest; @property (nonatomic, strong) NSString *hostPeerID; @property (nonatomic, strong) ServerBrowser *serverBrowser; @property (nonatomic, strong) GameDirector *gameDirector; @property (nonatomic, strong) NSCache *imageCache; @property (nonatomic, strong) NSCache *playerCache; -- (void)handlePlayerResponse:(JSKCommandParcel *)commandParcel inResponseTo:(JSKCommandMessage *)inResponseTo; -- (void)handleJoinGameMessage:(JSKCommandMessage *)message; -- (void)handleJoinGameResponse:(JSKCommandParcel *)response; -- (void)handleLeaveGameMessage:(JSKCommandMessage *)message; -- (void)handlePlayerUpdate:(JSKCommandParcel *)parcel; -- (void)handleCoordinatorVote:(JSKCommandParcel *)parcel; -- (void)handleVote:(JSKCommandParcel *)parcel; -- (void)handlePerformMissionMessage:(JSKCommandMessage *)message; -- (void)handleHostAcknowledgement:(JSKCommandMessage *)message; -- (void)addPlayerToGame:(PlayerEnvoy *)playerEnvoy responseKey:(NSString *)responseKey; -- (void)askToJoinGame:(NSString *)toPeerID; -- (BOOL)isDigestCurrent; -- (void)processDigest:(NSDictionary *)digest; -- (void)sendDigestTo:(NSString *)toPeerID; -- (NSDictionary *)buildDigestFor:(NSString *)forPeerID; -- (void)broadcastPlayerData:(NSString *)peerID; -- (void)sendGameUpdateTo:(NSString *)peerID modifiedDate:(NSDate *)modifiedDate shouldSendAllData:(BOOL)shouldSendAllData; -- (void)reloadGame:(GameEnvoy *)gameEnvoy; - @end @implementation SystemMessage -@synthesize playerEnvoy = m_playerEnvoy; -@synthesize netHost = m_netHost; -@synthesize netPlayer = m_netPlayer; -@synthesize gameEnvoy = m_gameEnvoy; -@synthesize stash = m_stash; -@synthesize peerIDs = m_peerIDs; -@synthesize isLookingForGame = m_isLookingForGame; -@synthesize playerDigest = m_playerDigest; -@synthesize hostPeerID = m_hostPeerID; -@synthesize serverBrowser = m_serverBrowser; -@synthesize gameDirector = m_gameDirector; -@synthesize imageCache = m_imageCache; -@synthesize playerCache = m_playerCache; -@synthesize hasSplashBeenShown = m_hasSplashBeenShown; -@synthesize gameCode = m_gameCode; - - - (void)dealloc { - [m_netHost setDelegate:nil]; - [m_netPlayer setDelegate:nil]; - [m_serverBrowser setDelegate:nil]; - - + [_serverBrowser setDelegate:nil]; } - - (void)reloadGame:(GameEnvoy *)gameEnvoy { if (!gameEnvoy) @@ -231,824 +188,15 @@ + (void)clearPlayerCache #pragma mark - Host -- (void)handlePlayerResponse:(JSKCommandParcel *)commandParcel inResponseTo:(JSKCommandMessage *)inResponseTo -{ - // This could be a response to a GetInfo message. - if (inResponseTo.commandMessageType == JSKCommandMessageTypeGetInfo) - { - // In this case we expect a PlayerEnvoy. - PlayerEnvoy *otherEnvoy = (PlayerEnvoy *)commandParcel.object; - otherEnvoy.isNative = NO; - otherEnvoy.isDefault = NO; - - UpdatePlayerOperation *op = [[UpdatePlayerOperation alloc] initWithEnvoy:otherEnvoy]; - [op setCompletionBlock:^(void){ - dispatch_async(dispatch_get_main_queue(), ^ - { - [self sendDigestTo:commandParcel.from]; -// [self broadcastPlayerData:commandParcel.from]; - // Update the UI. - [[NSNotificationCenter defaultCenter] postNotificationName:kJSKNotificationPeerUpdated object:otherEnvoy.peerID]; - }); - }]; - NSOperationQueue *queue = [SystemMessage mainQueue]; - [queue addOperation:op]; - return; - } -} - -- (void)sendDigestTo:(NSString *)toPeerID -{ - NSDictionary *digest = [self buildDigestFor:toPeerID]; - JSKCommandParcel *parcel = [[JSKCommandParcel alloc] initWithType:JSKCommandParcelTypeDigest - to:toPeerID - from:self.playerEnvoy.peerID - object:digest - responseKey:nil]; - [self.netHost sendCommandParcel:parcel]; -} - -- (NSDictionary *)buildDigestFor:(NSString *)forPeerID -{ - NSArray *gamePlayers = [self.gameEnvoy players]; - NSMutableDictionary *digest = [[NSMutableDictionary alloc] initWithCapacity:gamePlayers.count]; - for (PlayerEnvoy *player in gamePlayers) - { - if (![player.peerID isEqualToString:forPeerID]) - { - [digest setValue:player.modifiedDate forKey:player.peerID]; - } - } - NSDictionary *returnValue = [NSDictionary dictionaryWithDictionary:digest]; - return returnValue; -} - -// Send the player's data to everyone but the player (who already knows it), and the host (who is me). -- (void)broadcastPlayerData:(NSString *)peerID -{ - PlayerEnvoy *envoy = [PlayerEnvoy envoyFromPeerID:peerID]; - NSArray *gamePlayers = [self.gameEnvoy players]; - for (PlayerEnvoy *player in gamePlayers) - { - if (![player.peerID isEqualToString:peerID] && ![player.peerID isEqualToString:self.playerEnvoy.peerID]) - { - JSKCommandParcel *parcel = [[JSKCommandParcel alloc] initWithType:JSKCommandParcelTypeUpdate to:player.peerID from:self.playerEnvoy.peerID object:envoy]; - [self.netHost sendCommandParcel:parcel]; - } - } -} - -- (void)handleJoinGameMessage:(JSKCommandMessage *)message -{ - PlayerEnvoy *other = [PlayerEnvoy envoyFromPeerID:message.from]; - if (!other || !other.playerName || other.playerName.length == 0) - { - if (!self.stash) - { - NSMutableArray *stash = [[NSMutableArray alloc] initWithCapacity:kPartisansMaxPlayers - 1]; - self.stash = stash; - } - [self.stash addObject:message]; - return; - } - [self addPlayerToGame:other responseKey:message.responseKey]; -} - - -- (void)addPlayerToGame:(PlayerEnvoy *)playerEnvoy responseKey:(NSString *)responseKey -{ - BOOL proceed = NO; - GameEnvoy *gameEnvoy = [SystemMessage gameEnvoy]; - if ([SystemMessage isHost]) - { - // We are hosting a game. - if (gameEnvoy.players.count < kPartisansMaxPlayers && !gameEnvoy.startDate) - { - // The game has room and hasn't yet started. - // So, let the other player join, and send the game object back! - proceed = YES; - } - } - if (proceed) - { - // Make sure this player isn't already in the game. - if ([gameEnvoy isPlayerInGame:playerEnvoy]) - { - [self sendGameUpdateTo:playerEnvoy.peerID modifiedDate:nil shouldSendAllData:YES]; - proceed = NO; - } - } - - if (!proceed) - { - return; - } - - - NSString *peerID = playerEnvoy.peerID; - NSString *hostID = self.playerEnvoy.peerID; - - - // Add this player to the game. - AddGamePlayerOperation *op = [[AddGamePlayerOperation alloc] initWithPlayerEnvoy:playerEnvoy gameEnvoy:gameEnvoy]; -// [gameEnvoy addPlayer:playerEnvoy]; -// UpdateGameOperation *op = [[UpdateGameOperation alloc] initWithEnvoy:gameEnvoy]; - [op setCompletionBlock:^(void) - { - - dispatch_async(dispatch_get_main_queue(), ^(void) - { - [[NSNotificationCenter defaultCenter] postNotificationName:kPartisansNotificationGameChanged object:nil]; - }); - - - JSKCommandParcel *parcel = [[JSKCommandParcel alloc] initWithType:JSKCommandParcelTypeResponse to:peerID from:hostID object:gameEnvoy responseKey:responseKey]; - [self.netHost sendCommandParcel:parcel]; - - -// // Then, once we've saved, send the game envoy to all players. All players need to know! -// JSKCommandParcel *parcel = [[JSKCommandParcel alloc] initWithType:JSKCommandParcelTypeResponse -// to:nil -// from:self.playerEnvoy.peerID -// object:gameEnvoy -// responseKey:responseKey]; -// [self.netHost broadcastCommandParcel:parcel]; -// [parcel release]; - }]; - NSOperationQueue *queue = [SystemMessage mainQueue]; - [queue addOperation:op]; -} - - -- (void)sendGameUpdateTo:(NSString *)peerID modifiedDate:(NSDate *)modifiedDate shouldSendAllData:(BOOL)shouldSendAllData -{ - [[SystemMessage gameDirector] sendGameUpdateTo:peerID modifiedDate:modifiedDate shouldSendAllData:shouldSendAllData]; -} - - -- (void)handleCoordinatorVote:(JSKCommandParcel *)parcel -{ - CoordinatorVote *coordinatorVote = (CoordinatorVote *)parcel.object; - VoteEnvoy *voteEnvoy = coordinatorVote.voteEnvoy; - voteEnvoy.isCast = YES; - NSArray *candidateIDs = coordinatorVote.candidateIDs; - RoundEnvoy *roundEnvoy = [self.gameEnvoy currentRound]; - for (NSString *candidateID in candidateIDs) - { - PlayerEnvoy *candidate = [PlayerEnvoy envoyFromIntramuralID:candidateID]; - [roundEnvoy addCandidate:candidate]; - } - [roundEnvoy addVote:voteEnvoy]; - UpdateGameOperation *op = [[UpdateGameOperation alloc] initWithEnvoy:self.gameEnvoy]; - [op setCompletionBlock:^(void) { - dispatch_async(dispatch_get_main_queue(), ^(void) - { -// [self reloadGame:self.gameEnvoy]; - JSKCommandMessage *message = [[JSKCommandMessage alloc] initWithType:JSKCommandMessageTypeAcknowledge to:parcel.from from:self.playerEnvoy.peerID]; - message.responseKey = parcel.responseKey; - [self.netHost sendCommandMessage:message]; -// [[NSNotificationCenter defaultCenter] postNotificationName:kPartisansNotificationGameChanged object:nil]; - }); - }]; - NSOperationQueue *queue = [SystemMessage mainQueue]; - [queue addOperation:op]; -} - - -- (void)handleVote:(JSKCommandParcel *)parcel -{ - VoteEnvoy *voteEnvoy = (VoteEnvoy *)parcel.object; - voteEnvoy.isCast = YES; - RoundEnvoy *roundEnvoy = [self.gameEnvoy currentRound]; - [roundEnvoy addVote:voteEnvoy]; - UpdateGameOperation *op = [[UpdateGameOperation alloc] initWithEnvoy:self.gameEnvoy]; - [op setCompletionBlock:^(void) { - dispatch_async(dispatch_get_main_queue(), ^(void) - { - [self reloadGame:self.gameEnvoy]; - JSKCommandMessage *message = [[JSKCommandMessage alloc] initWithType:JSKCommandMessageTypeAcknowledge to:parcel.from from:self.playerEnvoy.peerID]; - message.responseKey = parcel.responseKey; - [self.netHost sendCommandMessage:message]; - [[NSNotificationCenter defaultCenter] postNotificationName:kPartisansNotificationGameChanged object:nil]; - }); - }]; - NSOperationQueue *queue = [SystemMessage mainQueue]; - [queue addOperation:op]; -} - - -- (void)handlePerformMissionMessage:(JSKCommandMessage *)message -{ - MissionEnvoy *missionEnvoy = [self.gameEnvoy currentMission]; - PlayerEnvoy *from = [PlayerEnvoy envoyFromPeerID:message.from]; - if (message.commandMessageType == JSKCommandMessageTypeSucceed) - { - [missionEnvoy applyContributeur:from]; - } - else if (message.commandMessageType == JSKCommandMessageTypeFail) - { - [missionEnvoy applySaboteur:from]; - } - else - { - // Unexpected message type. - debugLog(@"Unexpected message type"); - return; - } - UpdateGameOperation *op = [[UpdateGameOperation alloc] initWithEnvoy:self.gameEnvoy]; - [op setCompletionBlock:^(void) { - dispatch_async(dispatch_get_main_queue(), ^(void) - { - [self reloadGame:self.gameEnvoy]; - JSKCommandMessage *response = [[JSKCommandMessage alloc] initWithType:JSKCommandMessageTypeAcknowledge to:message.from from:self.playerEnvoy.peerID]; - response.responseKey = message.responseKey; - [self.netHost sendCommandMessage:response]; - [[NSNotificationCenter defaultCenter] postNotificationName:kPartisansNotificationGameChanged object:nil]; - }); - }]; - NSOperationQueue *queue = [SystemMessage mainQueue]; - [queue addOperation:op]; -} - - -- (void)handleLeaveGameMessage:(JSKCommandMessage *)message -{ - PlayerEnvoy *other = [PlayerEnvoy envoyFromPeerID:message.from]; - if (!other) - { - return; - } - // Make sure this player is in the game. - GameEnvoy *gameEnvoy = [SystemMessage gameEnvoy]; - if (![gameEnvoy isPlayerInGame:other]) - { - return; - } - - // Remove this player from the game. - RemoveGamePlayerOperation *op = [[RemoveGamePlayerOperation alloc] initWithEnvoy:other]; - [op setCompletionBlock:^(void) { - JSKCommandParcel *gameParcel = [[JSKCommandParcel alloc] initWithType:JSKCommandParcelTypeUpdate to:nil from:self.playerEnvoy.peerID object:gameEnvoy]; - [SystemMessage sendParcelToPlayers:gameParcel]; - dispatch_async(dispatch_get_main_queue(), ^(void) { - [[NSNotificationCenter defaultCenter] postNotificationName:kPartisansNotificationGameChanged object:nil]; - }); - }]; - NSOperationQueue *queue = [SystemMessage mainQueue]; - [queue addOperation:op]; -} #pragma mark - Player -- (BOOL)isDigestCurrent -{ - // Loop through the player digest and see if our saved Player data is up-to-date. - BOOL returnValue = YES; - NSDictionary *digest = self.playerDigest; - for (NSString *otherID in digest.allKeys) - { - NSDate *otherDate = [digest valueForKey:otherID]; - PlayerEnvoy *otherEnvoy = [PlayerEnvoy envoyFromPeerID:otherID]; - if (!otherEnvoy) - { - returnValue = NO; - break; - } - if ([SystemMessage secondsBetweenDates:otherEnvoy.modifiedDate toDate:otherDate] != 0) - { - returnValue = NO; - break; - } - } - return returnValue; -} - -- (void)askToJoinGameDelayed:(NSString *)toPeerID -{ - [self performSelector:@selector(askToJoinGame:) withObject:toPeerID afterDelay:2.0]; -} - -- (void)askToJoinGame:(NSString *)toPeerID -{ - if (!self.isLookingForGame) - { - return; - } - if ([SystemMessage isHost]) - { - return; - } - - JSKCommandMessage *msg = [[JSKCommandMessage alloc] initWithType:JSKCommandMessageTypeJoinGame to:toPeerID from:[SystemMessage playerEnvoy].peerID]; - [SystemMessage sendCommandMessage:msg shouldAwaitResponse:YES]; -} - -- (void)handleJoinGameResponse:(JSKCommandParcel *)response -{ - if (!response.object) - { - return; - } - if (![response.object isKindOfClass:[GameEnvoy class]]) - { - return; - } - - self.isLookingForGame = NO; - - // Save the game locally. - GameEnvoy *envoy = (GameEnvoy *)response.object; - CreateGameOperation *op = [[CreateGameOperation alloc] initWithEnvoy:envoy]; - [op setCompletionBlock:^(void) { - dispatch_async(dispatch_get_main_queue(), ^{ - // Once the save is done, update our own gameEnvoy property. - NSManagedObjectContext *context = [JSKDataMiner mainObjectContext]; - NSArray *games = [context fetchObjectArrayForEntityName:@"Game" withPredicateFormat:@"intramuralID == %@", envoy.intramuralID]; - if (games.count > 0) - { - GameEnvoy *updatedEnvoy = [GameEnvoy envoyFromManagedObject:[games objectAtIndex:0]]; - [self setGameEnvoy:updatedEnvoy]; - - // Also, post a notification that we joined the game. - [[NSNotificationCenter defaultCenter] postNotificationName:kPartisansNotificationJoinedGame object:updatedEnvoy]; - } - }); - }]; - NSOperationQueue *queue = [SystemMessage mainQueue]; - [queue addOperation:op]; -} - - -- (void)handleHostAcknowledgement:(JSKCommandMessage *)message -{ - dispatch_async(dispatch_get_main_queue(), ^(void) - { - [[NSNotificationCenter defaultCenter] postNotificationName:kPartisansNotificationHostAcknowledgement object:message.responseKey]; - }); -} #pragma mark - Player / Host -// The will check the local data and ask for new data as needed. -- (void)processDigest:(NSDictionary *)digest -{ - // Crash prevention when hosting after joining. - if (digest.count == 0) - { - return; - } - - BOOL wasNewDataRequested = NO; - // The digest is a dictionary of Player.modifiedDate values, keyed on peerID. - PlayerEnvoy *playerEnvoy = [SystemMessage playerEnvoy]; - for (NSString *otherID in digest.allKeys) - { - BOOL shouldAskForData = NO; - NSDate *otherDate = [digest valueForKey:otherID]; - if (otherDate) - { - PlayerEnvoy *otherEnvoy = [PlayerEnvoy envoyFromPeerID:otherID]; - if (otherEnvoy) - { - if ([SystemMessage secondsBetweenDates:otherEnvoy.modifiedDate toDate:otherDate] > 0 || [otherEnvoy.modifiedDate isEqualToDate:[NSDate distantPast]]) - { - shouldAskForData = YES; - } - } - else - { - shouldAskForData = YES; - } - } - if (shouldAskForData) - { - // If we are a Host, then the dictionary will contain one row, and we'll send the message to that Player. - // If we are a Player, the "to" field will not necessarily be the Host's address. But we'll be sending the message to the Host. - // Note the use of the "to" field here, to indicate that we're interested in that player's info, not (necessarily) the recipient's. - JSKCommandMessage *message = [[JSKCommandMessage alloc] initWithType:JSKCommandMessageTypeGetInfo to:otherID from:playerEnvoy.peerID]; - [SystemMessage sendCommandMessage:message shouldAwaitResponse:YES]; - wasNewDataRequested = YES; - } - } - if (!wasNewDataRequested) - { - if ([SystemMessage isHost]) - { - NSString *otherID = [digest.allKeys objectAtIndex:0]; - [self sendDigestTo:otherID]; - } - else - { - if (self.isLookingForGame) - { -// if ([self isDigestCurrent]) -// { - [self askToJoinGameDelayed:self.netPlayer.hostPeerID]; -// } - } - else - { - if (self.gameEnvoy) - { - [SystemMessage requestGameUpdate]; - } - } - } - } -} - -- (void)handlePlayerUpdate:(JSKCommandParcel *)parcel -{ - NSObject *object = parcel.object; - if ([object isKindOfClass:[PlayerEnvoy class]]) - { - PlayerEnvoy *other = (PlayerEnvoy *)object; - other.isNative = NO; - other.isDefault = NO; - -// BOOL isHost = [SystemMessage isHost]; - - UpdatePlayerOperation *op = [[UpdatePlayerOperation alloc] initWithEnvoy:other]; - [op setCompletionBlock:^(void) - { -// if (isHost) -// { -// [self broadcastPlayerData:other.peerID]; -// } - // Update the UI. - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:kJSKNotificationPeerUpdated object:other.peerID]; - }); - }]; - NSOperationQueue *queue = [SystemMessage mainQueue]; - [queue addOperation:op]; - } -} - - - -#pragma mark - NetHost delegate - -- (NSString *)netHostPeerID:(NetHost *)netHost -{ - return self.playerEnvoy.peerID; -} - -- (void)netHost:(NetHost *)netHost receivedCommandParcel:(JSKCommandParcel *)commandParcel -{ - switch (commandParcel.commandParcelType) - { - case JSKCommandParcelTypeDigest: - // A player will not send this. - break; - - case JSKCommandParcelTypeModifiedDate: - { - NSString *otherID = commandParcel.from; - NSDictionary *dictionary = (NSDictionary *)commandParcel.object; - NSString *entity = [dictionary valueForKey:@"entity"]; - NSDate *otherDate = [dictionary valueForKey:@"modifiedDate"]; - BOOL shouldSendAllData = [[dictionary valueForKey:@"shouldSendAllData"] boolValue]; - if ([entity isEqualToString:@"Player"]) - { - [self processDigest:[NSDictionary dictionaryWithObject:otherDate forKey:otherID]]; - } - else if ([entity isEqualToString:@"Game"]) - { - [self sendGameUpdateTo:otherID modifiedDate:otherDate shouldSendAllData:shouldSendAllData]; - } - break; - } - - case JSKCommandParcelTypeResponse: - break; - - case JSKCommandParcelTypeUpdate: - if ([commandParcel.object isKindOfClass:[PlayerEnvoy class]]) - { - [self handlePlayerUpdate:commandParcel]; - } - if ([commandParcel.object isKindOfClass:[CoordinatorVote class]]) - { - [self handleCoordinatorVote:commandParcel]; - } - if ([commandParcel.object isKindOfClass:[VoteEnvoy class]]) - { - [self handleVote:commandParcel]; - } - break; - - case JSKCommandParcelTypeUnknown: - break; - - case JSKCommandParcelType_maxValue: - break; - } -} - - -- (void)netHost:(NetHost *)netHost receivedCommandParcel:(JSKCommandParcel *)commandParcel respondingTo:(NSObject *)inResponseTo -{ - if ([inResponseTo isKindOfClass:[JSKCommandMessage class]]) - { - JSKCommandMessage *msg = (JSKCommandMessage *)inResponseTo; - [self handlePlayerResponse:commandParcel inResponseTo:msg]; - } -// else if ([inResponseTo isKindOfClass:[JSKCommandParcel class]]) -// { -// JSKCommandParcel *parcel = (JSKCommandParcel *)inResponseTo; -// [self handleResponse:commandParcel inResponseToParcel:parcel]; -// } -} - - -- (void)netHost:(NetHost *)netHost receivedCommandMessage:(JSKCommandMessage *)commandMessage -{ - NSString *peerID = commandMessage.from; - PlayerEnvoy *other = [PlayerEnvoy envoyFromPeerID:peerID]; - if (!other) - { - // Unidentified or unmatched player. - debugLog(@"Message from unknown peer %@", commandMessage); - return; - } - - if (commandMessage.commandMessageType == JSKCommandMessageTypeJoinGame) - { - [self handleJoinGameMessage:commandMessage]; - return; - } - - if (commandMessage.commandMessageType == JSKCommandMessageTypeGetDigest) - { - [self sendDigestTo:peerID]; - return; - } - - // The "to" field tells us which player the sender is interested in. - PlayerEnvoy *playerEnvoy = [PlayerEnvoy envoyFromPeerID:commandMessage.to]; -// PlayerEnvoy *playerEnvoy = self.playerEnvoy; - JSKCommandMessageType messageType = commandMessage.commandMessageType; -// JSKCommandParcelType parcelType = JSKCommandParcelTypeUnknown; - NSObject *responseObject = nil; - - switch (messageType) - { - case JSKCommandMessageTypeGetInfo: -// parcelType = JSKCommandParcelTypeResponse; - responseObject = playerEnvoy; - break; - case JSKCommandMessageTypeGetModifiedDate: -// parcelType = JSKCommandParcelTypeResponse; - responseObject = playerEnvoy.modifiedDate; - break; - case JSKCommandMessageTypeJoinGame: - // This was caught above. -// [self handleJoinGameMessage:commandMessage]; - break; - case JSKCommandMessageTypeIdentification: - { - // If not caught above then we already know about this player. - // Let's get their data. - JSKCommandMessage *msg = [[JSKCommandMessage alloc] initWithType:JSKCommandMessageTypeGetInfo to:commandMessage.from from:self.playerEnvoy.peerID]; - [self.netHost sendCommandMessage:msg shouldAwaitResponse:YES]; - break; - } - case JSKCommandMessageTypeLeaveGame: - [self handleLeaveGameMessage:commandMessage]; - break; - - case JSKCommandMessageTypeSucceed: - case JSKCommandMessageTypeFail: - [self handlePerformMissionMessage:commandMessage]; - break; - - default: - break; - } - - if (responseObject) - { - JSKCommandParcel *response = [[JSKCommandParcel alloc] initWithType:JSKCommandParcelTypeResponse - to:peerID - from:playerEnvoy.peerID - object:responseObject - responseKey:commandMessage.responseKey]; - [netHost sendCommandParcel:response]; - } -} - -- (void)netHost:(NetHost *)netHost terminated:(NSString *)reason -{ - debugLog(@"NetHost terminated: %@", reason); -} - - - -#pragma mark - NetPlayer delegate - -- (NSString *)netPlayerPeerID:(NetPlayer *)netPlayer -{ - return self.playerEnvoy.peerID; -} - -- (NSDate *)netPlayerModifiedDate:(NetPlayer *)netPlayer -{ - return self.playerEnvoy.modifiedDate; -} - -- (void)netPlayer:(NetPlayer *)netPlayer receivedCommandMessage:(JSKCommandMessage *)commandMessage -{ - PlayerEnvoy *playerEnvoy = self.playerEnvoy; - JSKCommandMessageType messageType = commandMessage.commandMessageType; - NSObject *responseObject = nil; - BOOL shouldAwaitResponse = NO; - - switch (messageType) - { - case JSKCommandMessageTypeAcknowledge: - [self handleHostAcknowledgement:commandMessage]; - break; - case JSKCommandMessageTypeGetInfo: - responseObject = playerEnvoy; - break; - case JSKCommandMessageTypeGetModifiedDate: - responseObject = playerEnvoy.modifiedDate; - break; -// case JSKCommandMessageTypeIdentification: -// // We'll get this back from a host after sending them our ID. -// // Send the host our player data. -// // In return, the host will send us their data. -// responseObject = playerEnvoy; -// shouldAwaitResponse = YES; -// break; - default: - debugLog(@"Unexpected message type from host: %@", commandMessage); - break; - } - - if (responseObject) - { - JSKCommandParcel *response = [[JSKCommandParcel alloc] initWithType:JSKCommandParcelTypeResponse - to:commandMessage.from - from:playerEnvoy.peerID - object:responseObject - responseKey:commandMessage.responseKey]; - [netPlayer sendCommandParcel:response shouldAwaitResponse:shouldAwaitResponse]; - } -} - -- (void)netPlayer:(NetPlayer *)netPlayer receivedCommandParcel:(JSKCommandParcel *)commandParcel -{ - NSObject *object = commandParcel.object; - if (commandParcel.commandParcelType == JSKCommandParcelTypeDigest) - { - // This means that the object is a dictionary of Player.modifiedDate values, keyed on peerID. - NSDictionary *digest = (NSDictionary *)object; - self.playerDigest = digest; - [self processDigest:digest]; - return; - } - - // Save the player data locally. - if ([object isKindOfClass:[PlayerEnvoy class]]) - { - PlayerEnvoy *other = (PlayerEnvoy *)object; - other.isNative = NO; - other.isDefault = NO; - [self.imageCache removeAllObjects]; - - UpdatePlayerOperation *op = [[UpdatePlayerOperation alloc] initWithEnvoy:other]; - [op setCompletionBlock:^(void){ - dispatch_async(dispatch_get_main_queue(), ^{ - if (self.isLookingForGame) - { - if ([self isDigestCurrent]) - { - [self askToJoinGameDelayed:other.peerID]; - } - } - [[NSNotificationCenter defaultCenter] postNotificationName:kJSKNotificationPeerUpdated object:other.peerID]; - }); - }]; - NSOperationQueue *queue = [SystemMessage mainQueue]; - [queue addOperation:op]; - return; - } - - // Save the Game data locally. - if ([object isKindOfClass:[GameEnvoy class]]) - { - // Let's make sure the copy we're getting is newer than the one we have currently. - GameEnvoy *currentGame = self.gameEnvoy; - if (!currentGame) - { - return; - } - GameEnvoy *updatedGame = (GameEnvoy *)commandParcel.object; - - if (![currentGame.intramuralID isEqualToString:updatedGame.intramuralID]) - { - return; - } - - if (currentGame.modifiedDate && updatedGame.modifiedDate) - { - NSInteger delta = [SystemMessage secondsBetweenDates:currentGame.modifiedDate toDate:updatedGame.modifiedDate]; - if (delta <= 0) - { - return; - } - } - - updatedGame.hasScoreBeenShown = YES; - - UpdateGameOperation *op = [[UpdateGameOperation alloc] initWithEnvoy:updatedGame]; - [op setCompletionBlock:^(void){ - dispatch_async(dispatch_get_main_queue(), ^(void) - { - updatedGame.hasScoreBeenShown = YES; - [[SystemMessage sharedInstance] setGameEnvoy:updatedGame]; - [[NSNotificationCenter defaultCenter] postNotificationName:kPartisansNotificationGameChanged object:nil]; - }); - }]; - NSOperationQueue *queue = [SystemMessage mainQueue]; - [queue addOperation:op]; - return; - } - - - // Save the envoys locally. - if ([object isKindOfClass:[NSArray class]]) - { - GameEnvoy *currentGame = self.gameEnvoy; - if (!currentGame) - { - return; - } - NSArray *envoys = (NSArray *)commandParcel.object; - - GamePrecis *precis = (GamePrecis *)[envoys objectAtIndex:0]; - if (!precis) - { - return; - } - if (currentGame.modifiedDate && precis.modifiedDate) - { - NSInteger delta = [SystemMessage secondsBetweenDates:currentGame.modifiedDate toDate:precis.modifiedDate]; - if (delta <= 0) - { - return; - } - } - - UpdateEnvoysOperation *op = [[UpdateEnvoysOperation alloc] initWithEnvoys:envoys]; - [op setCompletionBlock:^(void) - { - dispatch_async(dispatch_get_main_queue(), ^(void) - { - [[SystemMessage sharedInstance] reloadGame:currentGame]; - [[NSNotificationCenter defaultCenter] postNotificationName:kPartisansNotificationGameChanged object:nil]; - }); - }]; - NSOperationQueue *queue = [SystemMessage mainQueue]; - [queue addOperation:op]; - return; - } -} - - -- (void)netPlayer:(NetPlayer *)netPlayer receivedCommandParcel:(JSKCommandParcel *)commandParcel respondingTo:(NSObject *)inResponseTo -{ - if ([inResponseTo isKindOfClass:[JSKCommandMessage class]]) - { - JSKCommandMessage *message = (JSKCommandMessage *)inResponseTo; - if (message.commandMessageType == JSKCommandMessageTypeJoinGame) - { - [self handleJoinGameResponse:commandParcel]; - return; - } - } - [self netPlayer:netPlayer receivedCommandParcel:commandParcel]; -} - -- (void)netPlayer:(NetPlayer *)netPlayer terminated:(NSString *)reason -{ - [netPlayer stop]; -// debugLog(@"NetPlayer terminated: %@", reason); -} - -- (void)netPlayerDidResolveAddress:(NetPlayer *)netPlayer -{ - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:kPartisansNotificationConnectedToHost object:nil]; - }); - [SystemMessage sendToHost:JSKCommandMessageTypeIdentification shouldAwaitResponse:YES]; -} - #pragma mark - ServerBrowser delegate @@ -1056,27 +204,14 @@ - (void)updateServerList { NSString *serviceName = [SystemMessage serviceName]; NSArray *servers = self.serverBrowser.servers; -// debugLog(@"ServerBrowser did find: %@", servers); - for (NSNetService *service in servers) - { - if ([service.name isEqualToString:serviceName]) - { - if (self.netPlayer) - { - [self.netPlayer stop]; - [self.netPlayer setDelegate:nil]; - self.netPlayer = nil; - } - NetPlayer *netPlayer = [[NetPlayer alloc] initWithNetService:service]; - [netPlayer setDelegate:self]; - [self setNetPlayer:netPlayer]; - [self.netPlayer start]; + for (NSNetService *service in servers) { + if ([service.name isEqualToString:serviceName]) { + [NetworkManager setupNetPlayerWithService:service]; break; } } } - #pragma mark - Class methods + (void)leaveGame @@ -1097,8 +232,7 @@ + (UIView *)rootView + (void)browseServers { SystemMessage *sharedInstance = [self sharedInstance]; - if (sharedInstance.serverBrowser) - { + if (sharedInstance.serverBrowser) { [self stopBrowsingServers]; } ServerBrowser *serverBrowser = [[ServerBrowser alloc] init]; @@ -1118,10 +252,9 @@ + (void)stopBrowsingServers + (void)requestGameUpdate { - if (![self isPlayerOnline]) - { + if (![NetworkManager isPlayerOnline]) { // This begins a chain reaction that will end up with us requesting a game update (in processDigest:). - [self putPlayerOnline]; + [NetworkManager putPlayerOnline]; return; } SystemMessage *sharedInstance = [self sharedInstance]; @@ -1131,110 +264,24 @@ + (void)requestGameUpdate to:sharedInstance.hostPeerID from:sharedInstance.playerEnvoy.peerID object:dictionary]; - [sharedInstance.netPlayer sendCommandParcel:parcel]; -} - -+ (void)askToJoinGame -{ - [self sendToHost:JSKCommandMessageTypeJoinGame shouldAwaitResponse:YES]; -// [self broadcastCommandMessage:JSKCommandMessageTypeJoinGame]; + [NetworkManager sendCommandParcel:parcel shouldAwaitResponse:NO]; } + (BOOL)isHost { GameEnvoy *gameEnvoy = [self gameEnvoy]; - if (!gameEnvoy) - { + if (!gameEnvoy) { return NO; } PlayerEnvoy *playerEnvoy = [self playerEnvoy]; PlayerEnvoy *host = [gameEnvoy host]; - if ([playerEnvoy.intramuralID isEqualToString:host.intramuralID]) - { + if ([playerEnvoy.intramuralID isEqualToString:host.intramuralID]) { return YES; - } - else - { + } else { return NO; } } -+ (BOOL)isPlayerOnline -{ - SystemMessage *sharedInstance = [self sharedInstance]; - if ([self isHost]) - { - NetHost *netHost = sharedInstance.netHost; - if (!netHost) - { - NetHost *netHost = [[NetHost alloc] init]; - netHost.serviceName = [self serviceName]; - [netHost setDelegate:sharedInstance]; - [sharedInstance setNetHost:netHost]; - } - return netHost.hasStarted; - } - else - { - NetPlayer *netPlayer = sharedInstance.netPlayer; - if (!netPlayer) - { - NetPlayer *netPlayer = [[NetPlayer alloc] init]; - [netPlayer setDelegate:sharedInstance]; - [sharedInstance setNetPlayer:netPlayer]; - } - return netPlayer.hasStarted; - } -} - -+ (void)putPlayerOffline -{ - SystemMessage *sharedInstance = [self sharedInstance]; - if ([self isHost]) - { - if (sharedInstance.netHost.hasStarted) - { - [sharedInstance.netHost stop]; - } - sharedInstance.netHost.delegate = nil; - sharedInstance.netHost = nil; - } - else - { - if (sharedInstance.netPlayer.hasStarted) - { - [sharedInstance.netPlayer stop]; - } - sharedInstance.netPlayer.delegate = nil; - sharedInstance.netPlayer = nil; - } -} - -+ (void)putPlayerOnline -{ - if ([self isPlayerOnline]) - { - return; - } - SystemMessage *sharedInstance = [self sharedInstance]; - if ([self isHost]) - { - if (!sharedInstance.netHost.hasStarted) - { - [sharedInstance.netHost start]; - } - } - else - { - if (!sharedInstance.netPlayer.hasStarted) - { - if (![sharedInstance.netPlayer start]) - { - [self browseServers]; - } - } - } -} + (NSString *)serviceName @@ -1250,109 +297,6 @@ + (NSString *)serviceName } -+ (void)broadcastCommandMessage:(JSKCommandMessageType)commandMessageType -{ - if (![self isPlayerOnline]) - { - return; - } - SystemMessage *sharedInstance = [self sharedInstance]; - NetHost *netHost = sharedInstance.netHost; - - [netHost broadcastCommandMessageType:commandMessageType]; -} - - -+ (void)sendCommandMessage:(JSKCommandMessage *)commandMessage -{ - if (![self isPlayerOnline]) - { - return; - } - if ([self isHost]) - { - [[self sharedInstance].netHost sendCommandMessage:commandMessage]; - } - else - { - [[self sharedInstance].netPlayer sendCommandMessage:commandMessage]; - } -} - - -+ (void)sendCommandMessage:(JSKCommandMessage *)commandMessage shouldAwaitResponse:(BOOL)shouldAwaitResponse -{ - if (![self isPlayerOnline]) - { - return; - } - if ([self isHost]) - { - [[self sharedInstance].netHost sendCommandMessage:commandMessage shouldAwaitResponse:shouldAwaitResponse]; - } - else - { - [[self sharedInstance].netPlayer sendCommandMessage:commandMessage shouldAwaitResponse:shouldAwaitResponse]; - } -} - -+ (void)sendCommandParcel:(JSKCommandParcel *)parcel shouldAwaitResponse:(BOOL)shouldAwaitResponse -{ - if (![self isPlayerOnline]) - { - return; - } - if ([self isHost]) - { - [[self sharedInstance].netHost sendCommandParcel:parcel shouldAwaitResponse:shouldAwaitResponse]; - } - else - { - [[self sharedInstance].netPlayer sendCommandParcel:parcel shouldAwaitResponse:shouldAwaitResponse]; - } -} - -+ (void)sendToHost:(JSKCommandMessageType)commandMessageType shouldAwaitResponse:(BOOL)shouldAwaitResponse -{ - if (![self isPlayerOnline]) - { - return; - } - NSString *hostID = nil; - PlayerEnvoy *host = [self sharedInstance].gameEnvoy.host; - if (host) - { - hostID = host.peerID; - } - JSKCommandMessage *msg = [[JSKCommandMessage alloc] initWithType:commandMessageType to:hostID from:[self playerEnvoy].peerID]; - NetPlayer *netPlayer = [self sharedInstance].netPlayer; - [netPlayer sendCommandMessage:msg shouldAwaitResponse:shouldAwaitResponse]; -} - -+ (void)sendParcelToPlayers:(JSKCommandParcel *)parcel -{ - if (![self isPlayerOnline]) - { - return; - } - if (!parcel.from) - { - [parcel setFrom:[self playerEnvoy].peerID]; - } - NetHost *netHost = [self sharedInstance].netHost; - GameEnvoy *gameEnvoy = [self gameEnvoy]; - for (PlayerEnvoy *playerEnvoy in gameEnvoy.players) - { - // Don't send to ourselves. - if (![playerEnvoy.peerID isEqualToString:[self playerEnvoy].peerID]) - { - [parcel setTo:playerEnvoy.peerID]; - [netHost sendCommandParcel:parcel]; - } - } -} - - + (SystemMessage *)sharedInstance { SystemMessage *singleton = (SystemMessage *)[super sharedInstance]; @@ -1536,4 +480,9 @@ + (UIImage*)imageWithImage:(UIImage*)sourceImage scaledToSizeWithSameAspectRatio return newImage; } ++ (void)reloadGame:(GameEnvoy *)gameEnvoy +{ + [[self sharedInstance] reloadGame:gameEnvoy]; +} + @end diff --git a/Partisans/Classes/VoteViewController.m b/Partisans/Classes/VoteViewController.m index 8e1c899..f70ebf3 100644 --- a/Partisans/Classes/VoteViewController.m +++ b/Partisans/Classes/VoteViewController.m @@ -24,6 +24,7 @@ #import "GameEnvoy.h" #import "HostFinder.h" #import "JSKCommandParcel.h" +#import "NetworkManager.h" #import "PlayerEnvoy.h" #import "RoundEnvoy.h" #import "SystemMessage.h" @@ -240,7 +241,7 @@ - (void)connectAndVote:(BOOL)vote [self.spinner startAnimating]; - if ([SystemMessage isPlayerOnline]) + if ([NetworkManager isPlayerOnline]) { GameEnvoy *gameEnvoy = [SystemMessage gameEnvoy]; self.hostPeerID = gameEnvoy.host.peerID; @@ -307,7 +308,7 @@ - (void)vote:(BOOL)vote [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(hostAcknowledgement:) name:kPartisansNotificationHostAcknowledgement object:nil]; JSKCommandParcel *parcel = [[JSKCommandParcel alloc] initWithType:JSKCommandParcelTypeUpdate to:hostPeerID from:playerEnvoy.peerID object:parcelObject responseKey:self.responseKey]; - [SystemMessage sendCommandParcel:parcel shouldAwaitResponse:YES]; + [NetworkManager sendCommandParcel:parcel shouldAwaitResponse:YES]; }