The iOS integration example is based on the open-source multiplayer game Wizard War.
In order to build and run this project, you’ll need a system running OS X.
The following sections are the detailed notes for the implementation of various Katana API events. The Katana iOS library was integrated into the AnalyticsService class under \WizardWar\Service.
Katana API events can be called like so:
[AnalyticsService.shared.kAPI logSomeEvent:foo withParameter:bar];
For the timestamp parameter required by all events, we use the following constructor to create a new NSDate object initialized to the current UTC time:
[NSDate date]
For the account id and character id parameters required by most events, we use the uniquely generated "id" and user-defined "name" properties of the "User" object that may be already provided as a local variable or accessed via the UserService:
User *currentUser = [UserService.shared currentUser];
Since the game is not a complete virtual world with areas or locations we always use an empty string for the area name as well as "0" for area id, X, Y, and Z location parameters. The game also lacks different instances or shards, so "0" is also used for shard id in all locations. The string "iOS" is used for the platform parameter.
There is no straightforward login process in this game for single or multiplayer. Therefore, it will be considered a Login event when the attribute "currentUser" from the UserService is assigned a User object. Also, a Login event is recorded when the application is brought back to the foreground after being minimized.
Parameters
Parameter | Value | Note |
---|---|---|
ACCOUNT ID | The unique user id attribute of the User object | |
CHARACTER ID | The user defined name attribute of the User object | |
SHARD ID | 0 | Zero since there's no notion of shards |
PLATFORM | "iOS" | Denotes the application platform |
AREA ID | 0 | Zero since there's no notion of areas |
AREA NAME | "" | An empty string is used since there's no notion of areas |
self.currentUser = user; // Login (Event #1) // For the purpose of this demo, we consider it to be a Login event when the currentuser attribute is then assigned. [AnalyticsService.shared.kApi logLogin:[NSDate date] theAccountId:user.userId theCharacterId:user.name theshardld:@ thePlatforn:@"ioS" theAreaId:@ theAreaName:@”"];
There is no straightforward logout process in this game for single or multiplayer. Therefore, it will be considered a Logout event when the application is terminated or moved to the background. Also by default, the iOS API wrapper uses the Logout event as a heartbeat to ping the Katana server on a regular interval.
Parameters
Parameter | Value | Note |
---|---|---|
ACCOUNT ID | The unique user id attribute of the User object | |
CHARACTER ID | The user defined name attribute of the User object |
//Logout Event #2 //For the purpose of this demo, we consider it to be a ‘ when the app terminates. User aruscr = [UserService.shared currentllserl; [AnalyticsService.shared.kApi logLogout:[NSDate date] theAccountId:user.userId theCharacterId:user.name];
The game only has a free version and there is no notion of subscriptions or upgrading to a paid subscription. However, initially a player is considered a "guest user" until the user connects their Facebook account with the game. Therefore, it will be considered a Subscription Change event when the user is upgraded from a "guest user" after they connect their Facebook account.
Parameters
Parameter | Value | Note |
---|---|---|
SUBSCRIPTION STATUS | "Active" | Since the account is still active, only upgraded |
SUBSCRIPTION TYPE | "Premium" | Since it's not paid, but different than free |
SUBSCRIPTION VALUE | 0 | Zero since the game is free |
SUBSCRIPTION EXPIRATION TIMESTAMP | There is no expiration, but for testing a date one year in the future is used |
// Subscription Change (Event #3) // For the purpose of this demo, we consider connecting with Facebook a Subscription Change event. [AnalyticsService.shared.kApi putSubChange:[NSDate date] theAccountId:user.userId theSubStatus:@"Active" theSubType:@"Premium" theSubValue:0 theExpireTs:[[NSDate date] dateByAddingTimeInterval:3.15569e7]]; // Expires in 1 year
There is no process to create an account in this game. However, the first time the application is launched, a user is created with a unique identifier for their device and a random name is chosen. This unique identifier remains the same until the game is uninstalled. Therefore, it will be considered a Create Account event when the application is first launched and a new user with a unique identifier is created.
Parameters
Parameter | Value | Note |
---|---|---|
ACCOUNT TYPE | "Free" | The game is Free |
LANGUAGE | "en-US" | Ninja Metrics US English language code |
COUNTRY | "US" | Ninja Metrics United States country code |
GENDER | "N" | Since you cannot denote a gender in game settings |
DOB | The game doesn't include DOB in game settings, so a dummy past date is used | |
CURRENCY BALACE | 0 | Zero since there is no notion of currency |
if (!user) { user = [ObjectStore.shared insertNewObjectForEntityForName:self.entityName]; user.userId = [self generateUserId]; user.isMain = YES; user.name = [self randomWizardName]; // [NSString stringWithFormat:@"Guest%@", [IdService randomId:4]]; user.isGuestAccount = YES; user.wizardLevel = 1; // user.questLevel = 10; user.color = [UIColor blackColor]; NSLog(@"UserService.currentUser: GENERATED %@", user.userId); // Check if this is the first run. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; if (![defaults objectForKey:@"firstRun"]) { // Create Account (Event #9) // For the purpose of this demo, we consider it to be a Create Account event when a new user is created on the first run. [AnalyticsService.shared.kApi loadAccountCreate:[NSDate date] theAccountId:user.userId theAccountType:@"Free" theAccLang:@"en-US" theAccCountry:@"US" theAccGender:@"N" theAccDateOfBirth:[NSDate dateWithTimeIntervalSinceReferenceDate:0] theAccCurrencyBal:0 thePlatform:@"iOS"]; // Add a key to defaults to indicate the first run. [defaults setObject:[NSDate date] forKey:@"firstRun"]; [[NSUserDefaults standardUserDefaults] synchronize]; }
The game does not have items in the classic sense, but an appropriate substitution is the spells that wizards cast against each other. Therefore, it will be considered an Initialize Item event when a player creates a Spell by moving their fingers in a particular pattern about the on screen Pentagram.
Parameters
Parameter | Value | Note |
---|---|---|
ITEM ID | The randomly generated Spell id | |
ITEM NAME | The name of the Spell | |
ITEM TYPE | "Weapons" | Since are used against other Wizards |
ITEM SEGMENT | "UGC" | Since the player creates the Spell |
if (spellToCast) { self.castSpell = spellToCast; self.lastSuccessfulSpell = self.castSpell; //User *user = [UserService.shared currentUser]; // Initialize Item (Event #15) // For the purpose of the demo, we consider the creation of a valid spell an Initialize Item event. [AnalyticsService.shared.kApi loadItem:[NSDate date] theItemId: spellToCast.itemId theItemName:spellToCast.name theItemType:@"Weapons" theItemSegment:@"INGAME" createAccountId:@"" createCharacterId:@""];
Since spells will be the representation of items in this game, it will be considered an Item Used event when a player casts a spell against another player.
Parameters
Parameter | Value | Note |
---|---|---|
ITEM ID | The randomly generated Spell id | |
ITEM NAME | The name of the Spell | |
ITEM Count | 0 | Zero since a Spell can be cast unlimited times |
ITEM SEGMENT | "UGC" | Since the player creates the Spell |
-(BOOL)castSpell:(Spell *)spell { [self.ai opponent:self.currentWizard didCastSpell:spell atTick:self.timer.nextTick]; BOOL success = [self player:self.currentWizard castSpell:spell currentTick:self.timer.nextTick]; if (success) { [self.mainPlayerSpellHistory addObject:spell.type]; // Item Used (Event #16) // For the purpose of the demo, we consider casting a spell an Item Used event. User *user = [UserService.shared currentUser]; [AnalyticsService.shared.kApi logItem:[NSDate date] theAccountId:user.userId theCharacterId:user.name theCharacterLvl:user.wizardLevel theShardId:0 theItemId:spell.itemId theItemName:spell.name theItemCount:0 theAreaId:0 theAreaName:@"" theX:0 theY:0 theZ:0];
In the game, a match begins when a player starts a quest or a multiplayer duel. It will be considered a Level Begin event when a new match, either a quest or duel, begins.
Parameters
Parameter | Value | Note |
---|---|---|
LEVEL ID | The current Quest level or 0 for duels | |
CHARACTER LEVEL | The player's Wizard level |
// Match starts.... NOW [self.match connect]; // Level Begin (Event #27) // For the purpose of this demo, we consider starting a match a Level Begin event. User *currentUser = [UserService.shared currentUser]; long level = self.questLevel != nil ? self.questLevel.level : 0; // For quests use quest level, otherwise no level for challenges [AnalyticsService.shared.kApi levelBegin:[NSDate date] theAccountId:currentUser.userId theCharacterId:currentUser.name theCharacterLevel:currentUser.wizardLevel theShardId:0 theLevelId:level theX:0 theY:0 theZ:0 theAreaName:@""];
Similar to a Level Begin event, it will be considered a Level End event when a match, either a quest or duel, ends.
Parameters
Parameter | Value | Note |
---|---|---|
LEVEL ID | The current Quest level or 0 for duels | |
CHARACTER LEVEL | The player's Wizard level |
// Level End (Event #28) // For the purpose of this demo, we consider finishing a match a Level End event. long level = self.questLevel != nil ? self.questLevel.level : 0; // For quests use quest level, otherwise no level for challenges [AnalyticsService.shared.kApi levelEnd:[NSDate date] theAccountId:currentUser.userId theCharacterId:currentUser.name theCharacterLevel:currentUser.wizardLevel theShardId:0 theLevelId:level theX:0 theY:0 theZ:0 theAreaName:@""];
In the game, a player can play quest levels that are duels against AI based wizards. It will be considered a Non-Player Character when an enemy AI wizard is spawned at the beginning of a quest. This initializes the NPC in the dimension table for the other NPC related events.
Parameters
Parameter | Value | Note |
---|---|---|
NPC EVENT NAME | "NPC Spawned" | Since this is the NPC initialization event |
NPC EVENT TYPE | "Combat" | Since players battle the NPC in a Quest |
NPC ID | The Quest level is used since each NPC is unique to a Quest level | |
NPC NAME | The AI based Wizard name | |
NPC TYPE | "Enemy" | Since the player always fights the NPC in a Quest |
MIN LEVEL | The Wizard level required to play this Quest | |
MAX LEVEL | There is no max level, so we use an arbitrarily high number that is unlikely to be achieved. Due to the current iOS API library implementation, values not required by the API or that use default values are not taken into account. Therefore where applicable values do not exist we must use placeholder values that might not make perfect sense in the current context. | |
TOUGHNESS | The Quest level | |
TOUGHNESS ENUM | The Quest level name |
// NPC Event (#31) // For the purpose of this demo, we consider it an NPC event when an AI Wizard is created. There is no max level, so we use an arbitrarily high number that would most likely never be achieved. [AnalyticsService.shared.kApi logNpcInteraction:[NSDate date] theAccountId:user.userId theCharacterId:user.name theShardId:0 npcEventName:@"NPC Spawned" npcEventType:@"Combat" npcId:npcId npcName:ai.wizard.name npcType:@"Enemy" theMinLevel:level.wizardLevel theMaxLevel:9999 theToughness:level.level theToughnessEnum:level.name]; Match * match = [[Match alloc] initWithMatchId:@"Quest" hostName:wizard.name currentWizard:wizard withAI:ai multiplayer:nil sync:nil];
In the game, a player can challenge an opponent to a duel. The act of challenging another player to a duel will be considered a Recruitment Send event.
Parameters
Parameter | Value | Note |
---|---|---|
SEND ACCOUNT ID | The user id of the player who initiated the duel challenge | |
SEND CHARACTER ID | The wizard name of the player who initiated the duel challenge | |
RECEIVER ACCOUNT ID | The user id of the player who was challenged to a duel | |
RECEIVER CHARACTER ID | The wizard name of the player who was challenged to a duel | |
RECRUITMENT TYPE | "Invited By Another User" | Since one player challenges the other |
if (isRemote) { [self notifyOpponent:challenge]; } // Recruitment Send (Event #33) // For the purpose of this demo, we will consider a Recruitment Send event when a user challenges an opponent. [AnalyticsService.shared.kApi recruitmentSend:[NSDate date] sendAccId:user.userId sendCharId:user.name recvAccId:opponent.userId recvCharId:opponent.name theRecruitmentType:@"Invited By Another User"];
In the game, a player can respond to a duel challenge sent by another player. The act of the player responding to a duel challenge will be considered a Recruitment Send event.
Parameters
Parameter | Value | Note |
---|---|---|
SEND ACCOUNT ID | The user id of the player who initiated the duel challenge | |
SEND CHARACTER ID | The wizard name of the player who initiated the duel challenge | |
RECEIVER ACCOUNT ID | The user id of the player who was challenged to a duel | |
RECEIVER CHARACTER ID | The wizard name of the player who was challenged to a duel | |
RECRUITMENT TYPE | "Invited By Another User" | Since one player challenges the other |
RECRUITMENT OUTCOME | "Accepted" or "Declined" | Depending the receiver's response |
- (void)acceptChallenge:(Challenge*)challenge { [AnalyticsService event:@"challenge-accept"]; [self setChallenge:challenge status:ChallengeStatusAccepted]; // Recruitment Receive (Event #34) // For the purpose of this demo, we will consider a Recruitment Receive event when a user accepts a challenge. [AnalyticsService.shared.kApi recruitmentReceive:[NSDate date] sendAccId:challenge.main.userId sendCharId:challenge.main.name recvAccId:challenge.opponent.userId recvCharId:challenge.opponent.name theRecruitmentType:@"Invited By Another User" theRecruitmentOutcome:@"Accepted"]; }Sample
- (void)declineChallenge:(Challenge*)challenge { [self setChallenge:challenge status:ChallengeStatusDeclined]; // Recruitment Receive (Event #34) // For the purpose of this demo, we will consider a Recruitment Receive event when a user declines a challenge. [AnalyticsService.shared.kApi recruitmentReceive:[NSDate date] sendAccId:challenge.main.userId sendCharId:challenge.main.name recvAccId:challenge.opponent.userId recvCharId:challenge.opponent.name theRecruitmentType:@"Invited By Another User" theRecruitmentOutcome:@"Declined"]; }
In the game, a quest is a match with an NPC AI based wizard. It will be considered a Begin Combat event when a quest's match status is changed to "Playing" and combat has begun.
Parameters
Parameter | Value | Note |
---|---|---|
NPC ID | The Quest level is used since each NPC is unique to a Quest level |
// Is this a Quest? if (self.aiWizard) { // Begin Combat (Event #21) // For the purpose of this demo, it is considered a Begin Combat Event when a the MatchStatus changes to Playing during a Quest. User *user = [UserService.shared currentUser]; [AnalyticsService.shared.kApi combatStart:[NSDate date] theAccountId:user.userId theCharacterId:user.name theShardId:0 theNpcId:self.aiWizard.npcId theAreaId:0 theX:0 theY:0 theZ:0 theBeginAreaName:@""];
Similar to the Begin Combat event, it will be considered an End Combat event when a quest's match status is "Finished" and combat has ceased.
Parameters
Parameter | Value | Note |
---|---|---|
NPC ID | The Quest level is used since each NPC is unique to a Quest level |
// End Combat (Event #22) // For the purpose of this demo, we consider the end of a quest an End Combat event. [AnalyticsService.shared.kApi combatEnd:[NSDate date] theAccountId:currentUser.userId theCharacterId:currentUser.name theShardId:0 theNpcId:npcId theAreaId:0 theX:0 theY:0 theZ:0 theEndAreaName:@""];
It will be considered a Kill NPC event when a player has won a quest by defeating its NPC AI based wizard.
Parameters
Parameter | Value | Note |
---|---|---|
CHARACTER LEVEL | The player's Wizard level | |
NPC ID | The Quest level is used since each NPC is unique to a Quest level |
} else if (wizard == self.aiWizard) { // Kill NPC (Event #23) // For the purpose of this demo, we consider it a Kill NPC event when the player kills the AI Wizard. [AnalyticsService.shared.kApi logKillNpc:[NSDate date] theAccountId:user.userId theCharacterId:user.name theShardId:0 theCharacterLvl:user.wizardLevel theNpcId:self.aiWizard.npcId];
It will be considered a Player Versus Player Duel event when a multiplayer match ends.
Parameters
Parameter | Value | Note |
---|---|---|
WIN FLAG: | YES or NO | Depending on the outcome of the duel |
if (self.challenge) { // PVP Duel (Event #25) // For the purpose of this demo, we consider the end of a challenge a PVP Duel event. [AnalyticsService.shared.kApi logPvpDuel:[NSDate date] theAccountId:currentUser.userId theCharacterId:currentUser.name theShardId:0 isWinner:didWin]; }
It will be considered a Player Death event when an NPC AI kills the player's wizard and the player loses the quest.
Parameters
Parameter | Value | Note |
---|---|---|
NPC ID | The Quest level is used since each NPC is unique to a Quest level | |
DEATH TYPE | "NA" | NA, since all deaths are the treated the same |
// Was the player or AI Wizard killed? if (wizard == self.currentWizard) { // Player Death (Event #26) // For the purpose of this demo, we consider it a Player Death event when the AI Wizard kills the player's Wizard. [AnalyticsService.shared.kApi logPlayerDeath:[NSDate date] theAccountId:user.userId theCharacterId:user.name theShardId:0 theKillerNpcId:self.aiWizard.npcId theDeathType:@"NA" theAreaId:0 theX:0 theY:0 theZ:0 theAreaName:@""];
In the game, a challenge could be considered a quest level where the player duels an NPC AI based wizard. It will be considered a Challenge event when a player chooses to play a quest level, leaves the quest level, or completes the quest level.
Parameters
Parameter | Value | Note |
---|---|---|
CHALLENGE ID | The Quest level name | |
CHALLENGE TYPE | "Kill An NPC or Multiple NPCs" | Since all Quests are duels with NPCs |
CHALLENGE STATUS | "Accepted", "Completed", or "Abandoned" | Depending on player action |
- (void)createMatchWithWizard:(Wizard *)wizard withLevel:(QuestLevel *)level { self.questLevel = level; [AnalyticsService event:@"match"]; [AnalyticsService event:@"match-quest"]; // Challenge (Event #32) // For the purpose of this demo, we consider accepting a quest a Challenge event. User *user = [UserService.shared currentUser]; [AnalyticsService.shared.kApi logChallenge:[NSDate date] theAccountId:user.userId theCharacterId:user.name theShardId:0 challengeId:level.name challengeType:@"Kill An NPC or Multiple NPCs" challengeStatus:@"Accepted"];Sample
if (didFinish) { // Challenge (Event #32) // For the purpose of this demo, we consider completing a quest a Challenge event. [AnalyticsService.shared.kApi logChallenge:[NSDate date] theAccountId:currentUser.userId theCharacterId:currentUser.name theShardId:0 challengeId:self.questLevel.name challengeType:@"Kill An NPC or Multiple NPCs" challengeStatus:@"Completed"]; } else { // Challenge (Event #32) // For the purpose of this demo, we consider leaving a quest a Challenge event. [AnalyticsService.shared.kApi logChallenge:[NSDate date] theAccountId:currentUser.userId theCharacterId:currentUser.name theShardId:0 challengeId:self.questLevel.name challengeType:@"Kill An NPC or Multiple NPCs" challengeStatus:@"Abandoned"]; }
It will be considered a Reward event when the player's wizard level is increased as a result of completing a quest, casting spells, or winning a duel against another player who has a greater wizard level than their own.
Parameters
Parameter | Value | Note |
---|---|---|
REWARD TYPE | 0 | Zero since only recording one type of Reward |
REWARD ID | "Wizard Level" | Since the only recorded Reward is gaining a Wizard level |
ITEM TYPE | 0 | Zero since no item is being rewarded |
REWARD COUNT | The player's new Wizard level |
if (questLevel.wizardLevel > currentUser.wizardLevel) { currentUser.wizardLevel += 1; [achievements addObject:[Achievement wizardLevel:currentUser]]; // Reward (Event #49) // For the purpose of the demo, we consider a Reward event when the user gains a Wizard level. [AnalyticsService.shared.kApi logReward:[NSDate date] theAccountId:currentUser.userId theCharacterId:currentUser.name theShardId:0 theRewardType:0 theRewardId:@"Wizard Level" theItemType:0 theRewardCount:currentUser.wizardLevel];From SpellbookService.m Line 111:
if (record.level > beforeLevel) { [achievements addObject:[Achievement spellLevel:record]]; [AnalyticsService event:[NSString stringWithFormat:@"spell-unlock-%@-%i", type, record.level]]; if (record.level > SpellbookLevelNovice) { currentUser.wizardLevel += 1; [achievements addObject:[Achievement wizardLevel:currentUser]]; // Reward (Event #49) // For the purpose of the demo, we consider a Reward event when the user gains a Wizard level. [AnalyticsService.shared.kApi logReward:[NSDate date] theAccountId:currentUser.userId theCharacterId:currentUser.name theShardId:0 theRewardType:0 theRewardId:@"Wizard Level" theItemType:0 theRewardCount:currentUser.wizardLevel]; }From UserFriendService.m Line 100:
if (friend.wizardLevel > user.wizardLevel) { user.wizardLevel += 1; [achievements addObject:[Achievement wizardLevel:user]]; // Reward (Event #49) // For the purpose of the demo, we consider a Reward event when the user gains a Wizard level. [AnalyticsService.shared.kApi logReward:[NSDate date] theAccountId:user.userId theCharacterId:user.name theShardId:0 theRewardType:0 theRewardId:@"Wizard Level" theItemType:0 theRewardCount:user.wizardLevel];
The game allows a player to send a download link to others for the game via Facebook by posting on their Facebook friends' walls. It will be considered an OGI Sender event when the player sends a message about the game to one of their Facebook friends.
Parameters
Parameter | Value | Note |
---|---|---|
SENDER ACCOUNT ID | The player's user id who shared to Facebook | |
SENDER ACCOUNT NAME | The player's wizard name who shared to Facebook | |
RECEIVER ACCOUNT ID | The Facebook id of the receiving friend | |
RECEIVER ACCOUNT NAME | The Facebook id of the receiving friend | |
OGI LOCATION | "Facebook" | |
OGI CATEGORY | "Posts on Social Media" |
[UserFriendService.shared openFeedDialogTo:friendIds complete:^{ [AnalyticsService event:@"invite-facebook-friend-complete"]; // OGI Sender (Event #7) // For the purpose of the demo, we consider when the user posts on a friends Facebook an OGI Sender event User *user = [UserService.shared currentUser]; [AnalyticsService.shared.kApi logOgiSend:[NSDate date] sendAccId:user.userId sendCharId:user.name recvAccId:[friendIds lastObject] recvCharId:[friendIds lastObject] theShardId:0 theOgiLocation:@"Facebook" theOgiCategory:@"Posts on Social Media"];
When the first duel between two players ends, they are now considered "Frenemies" and will always appear in their Lobby, even if they are logged out. It will be considered a Friend Addition Event when the first duel ends between two players and they become "Frenemies".
Parameters
Parameter | Value | Note |
---|---|---|
SENDER ACCOUNT ID | The player's user id who initiated the duel | |
SENDER ACCOUNT NAME | The player's wizard name who initiated the duel | |
RECEIVER ACCOUNT ID | The player's user id who was challenged to the duel | |
RECEIVER ACCOUNT NAME | The player's wizard name who was challenged to the duel |
// Was this the first challenge with this friend? if (friend.gamesTotal == 0) { // Friend Additon (Event #43) // For the purpose of this demo, we consider the end of the first game against a new player a Friend Addition event [AnalyticsService.shared.kApi logFriendAdd:[NSDate date] sendAccId:user.userId sendCharId:user.name recvAccId:friend.userId recvCharId:friend.name];
After a player becomes the user's "Frenemy", the user can remove them as a "Frenemy" by swiping their entry on the Lobby list and pressing the "Defrenemy" button. It will be considered a Friend Deletion event when a user removes another player as a "Frenemy".
Parameters
Parameter | Value | Note |
---|---|---|
SENDER ACCOUNT ID | The player's user id who removed the "Frenemy" | |
SENDER ACCOUNT NAME | The player's wizard name who removed the "Frenemy" | |
RECEIVER ACCOUNT ID | The player's user id who was removed as a "Frenemy" | |
RECEIVER ACCOUNT NAME | The player's wizard name who was removed as a "Frenemy" |
[UserFriendService.shared user:self.currentUser removeFrenemy:user]; // Friend Deletion (Event #44) // For the purpose of this demo, we consider removing a Frenemy a Friend Deletion event. [AnalyticsService.shared.kApi logFriendDelete:[NSDate date] sendAccId:self.currentUser.userId sendCharId:self.currentUser.name recvAccId:user.userId recvCharId:user.name];
The Katana API has an empty slot to allow for a Custom event to be recorded that is unique to the particular application. The custom event recorded here is a "Block". During a duel a player can cast a spell that prevents them from being hit by a spell cast by an opposing player or NPC.
Parameters
Parameter | Value | Note |
---|---|---|
CUSTOM ACTION | "Block" | |
CUSTOM ACTION TYPE | "Spell Interaction" | |
CUSTOM ACTION VALUE | 0 | Zero since there is only one state for a Block |
// Did the main player destory the opponents attack Spell? if (main.creator != self.currentWizard && main.status == SpellStatusDestroyed && main.isWall == NO) { // Custom Slot - Block (Event #54) // For the purpose of this demo, we are using the Custom Slot event when the player blocks the opponents attack Spell. User *user = [UserService.shared currentUser]; [AnalyticsService.shared.kApi logCustomAction:[NSDate date] theAccountId:user.userId theCharacterId:user.name theCustomAction:@"Block" theCustomActionType:@"Spell Interaction" theCustomActionValue:0];