├── .editorconfig ├── .github └── workflows │ └── main.yml ├── .gitignore ├── README.md ├── c2airconsole.c2addon ├── construct2 projects ├── pong │ ├── controller.html │ ├── controller_resources │ │ ├── js │ │ │ └── main.js │ │ └── styles │ │ │ └── mainStyle.css │ └── pong.capx └── test │ └── ac2.capx └── plugin ├── files └── AirConsole2 │ ├── PluginIcon.ico │ ├── common.js │ ├── edittime.js │ └── runtime.js └── info.xml /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = tab 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build c2addon 2 | 3 | on: push 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: ➕ Install zip 10 | uses: actions/checkout@v2 11 | - name: 📦 Packaging c2addon 12 | uses: vimtor/action-zip@v1 13 | with: 14 | files: plugin/ 15 | dest: c2airconsole.c2addon 16 | - name: ✅ Saving artifact for testing 17 | uses: actions/upload-artifact@v1 18 | with: 19 | name: c2airconsole.c2addon 20 | path: ${{ github.workspace }}/c2airconsole.c2addon 21 | - name: 🔖 Commit and push 22 | uses: EndBug/add-and-commit@v7 23 | with: 24 | add: '["${{ github.workspace }}/c2airconsole.c2addon"]' 25 | message: '🚀 Deploying plugin' 26 | push: true 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AirConsole Construct2 2 | 3 | Construct2 plugin second generation for AirConsole version 1.8.0 4 | 5 | AirConsole can be visited on www.airconsole.com and is a great way for unique game experience using your smartphones as controllers 6 | Construct 2 is a great 2D game creation tool that allows anybody, with or without coding experiences, to start developing their own games 7 | 8 | This plugin allows you to build both games and the controllers with Construct2! Don't forget to check out the external merging tool when you need to package your game before testing: [AirConsoleMerger](https://github.com/Psychokiller1888/AirConsoleMerger/releases) 9 | 10 | ## Installation 11 | 12 | [Download the plugin](c2airconsole.c2addon) and drag and drop it into Construct2 13 | 14 | More information about the plugin: 15 | 16 | [AirConsole guides](http://developers.airconsole.com/#/guides/construct2) 17 | 18 | ## Version 1.8.0.3 19 | 20 | * Added "Start AirConsole" action, required to actually let AC know we are ready to communicate. This should be used before "Game Ready" 21 | 22 | ## Version 1.8.0.2 23 | 24 | * Deprecated onMute/onUnmute as API 1.8.0 25 | 26 | ## Version 1.8.0.1 27 | 28 | * Minor fixes 29 | 30 | ## Version 1.8.0.0 31 | 32 | * Updating to AirConsole API v. 1.8.0 33 | 34 | ## Version 1.7.0.19 35 | 36 | * On device motion should be a trigger 37 | * Edit profile impossible from screen 38 | * Using isController 39 | * Setting controller orientation 40 | * Ordering 41 | 42 | ## Version 1.7.0.18 43 | 44 | * Fixing all messaging functions for controller mode 45 | 46 | ## Version 1.7.0.17 47 | 48 | * Fixed controller orientation 49 | 50 | ## Version 1.7.0.16 51 | 52 | * Added OnDeviceMotion trigger 53 | * Added GetPremium action 54 | * Added Vibrate action 55 | * Added MotionData expression 56 | 57 | ## Version 1.7.0.15 58 | 59 | * Added ability to get device ID that is currently being used (Controller function) 60 | * Added Edit Profile 61 | 62 | ## Version 1.7.0.14 63 | 64 | * Added controller mode support 65 | * Brought API mock-up from C3 to C2 plugin 66 | 67 | ## Version 1.7.0.13 68 | 69 | * Added preset message support, on an idea by Toby R 70 | 71 | ## Version 1.7.0.12 72 | 73 | * Fixed wrong usage of "deprecated", thanks Mad_Spy and Toby R for bringing this up 74 | 75 | ## Version 1.7.0.11 76 | 77 | * Added expression airconsole.AdShown() 78 | * Added expression airconsole.IsAdShowing() 79 | * Added expression airconsole.GetProfilePictureWithSize(deviceId, picturesize) 80 | * Deprecated expression airconsole.GetProfilePicture() 81 | * Added condition AdShown() 82 | * Added condition IsAdShowing() 83 | * Fixed N-Dream naming in plugin infos.... 84 | 85 | ## Version 1.7.0.10 86 | 87 | * Fixed OnCustomDeviceStateChange trigger 88 | * Fixed ConvertDeviceIdToPlayerNumber expression 89 | * Comparison fix 90 | * Cleanup 91 | 92 | ## Version 1.7.0.9 93 | 94 | * Fixed GetPersistentData and GetHighscores expressions wrong return type 95 | 96 | ## Version 1.7.0.8 97 | 98 | * Fixed onDeviceProfileChange (trigger not defined) 99 | 100 | ## Version 1.7.0.7 101 | 102 | * Fixed GetMasterControllerDeviceId 103 | 104 | ## Version 1.7.0.6 105 | 106 | * Fixed illogic expressions order and naming 107 | 108 | ## Version 1.7.0.5 109 | 110 | * Updated documentation links and website links 111 | 112 | ## Version 1.7.0.4 113 | 114 | * Fixed Message and Broadcast 115 | 116 | ## Version 1.7.0.3 117 | 118 | * Missing GetActivePlayerDeviceIds 119 | 120 | ## Version 1.7.0.2 121 | 122 | * Missing IsPluginOffline condition 123 | * Missing IsPluginOffline expression 124 | * Missing IsMultipartMessage condition 125 | 126 | ## Version 1.7.0.1 127 | 128 | * Initial plugin release 129 | 130 | ## About this Plugin 131 | 132 | This plugin was created and is maintained by Psychokiller1888 and not by the AirConsole Team. It has become so popular that the AirConsole Team has decided to host it in the official AirConsole GitHub account. 133 | -------------------------------------------------------------------------------- /c2airconsole.c2addon: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AirConsole/airconsole-construct2/f3fed7c3530ed95ec20fda9910ccc226f4b85604/c2airconsole.c2addon -------------------------------------------------------------------------------- /construct2 projects/pong/controller.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /construct2 projects/pong/controller_resources/js/main.js: -------------------------------------------------------------------------------- 1 | /* Controller JS template by Psychokiller1888 - www.sickrabbitstudios.com */ 2 | 3 | var airConsole = null; 4 | var downEvent; 5 | var upEvent; 6 | 7 | $(document).ready(function() { 8 | airConsole = new AirConsole(); 9 | 10 | downEvent = isMobile() ? 'touchstart' : 'mousedown'; 11 | upEvent = isMobile() ? 'touchend' : 'mouseup'; 12 | 13 | var sendHandshake = function() { 14 | airConsole.message(AirConsole.SCREEN, { 15 | handshake: true 16 | }); 17 | }; 18 | 19 | var sendMessage = function(string) { 20 | airConsole.message(AirConsole.SCREEN, { 21 | message: string 22 | }); 23 | } 24 | 25 | airConsole.onReady = function() { 26 | sendHandshake(); 27 | }; 28 | 29 | airConsole.onMessage = function(deviceId, data) { 30 | }; 31 | 32 | $('#up').on(downEvent, function (event) { 33 | sendMessage("up"); 34 | }); 35 | 36 | $('#up').on(upEvent, function (event) { 37 | sendMessage("stop"); 38 | }); 39 | 40 | $('#down').on(downEvent, function (event) { 41 | sendMessage("down"); 42 | }); 43 | 44 | $('#down').on(upEvent, function (event) { 45 | sendMessage("stop"); 46 | }); 47 | }); 48 | 49 | /** 50 | * Returns true if device is a mobile device 51 | * @return {Boolean} 52 | */ 53 | function isMobile() { 54 | if (navigator.userAgent.match(/Android/i) || 55 | navigator.userAgent.match(/webOS/i) || 56 | navigator.userAgent.match(/iPhone/i) || 57 | navigator.userAgent.match(/iPad/i) || 58 | navigator.userAgent.match(/iPod/i) || 59 | navigator.userAgent.match(/BlackBerry/i) || 60 | navigator.userAgent.match(/Windows Phone/i) || 61 | navigator.userAgent.match(/WPDesktop/i) || 62 | navigator.userAgent.match(/Opera Mini/i) || 63 | navigator.userAgent.match(/IEMobile/i)) { 64 | return true; 65 | } 66 | else { 67 | return false; 68 | } 69 | } -------------------------------------------------------------------------------- /construct2 projects/pong/controller_resources/styles/mainStyle.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | overflow: hidden; 4 | } 5 | 6 | body { 7 | background-color: #222; 8 | } 9 | 10 | button { 11 | margin-bottom: 1px; 12 | width: 100%; 13 | height: 48%; 14 | border: 0; 15 | box-sizing: border-box; 16 | font-size: 40px; 17 | color: #FFFFFF; 18 | background-color: #333333; 19 | outline: none; 20 | cursor: pointer; 21 | } 22 | 23 | button:hover { 24 | background-color: #444444; 25 | } -------------------------------------------------------------------------------- /construct2 projects/pong/pong.capx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AirConsole/airconsole-construct2/f3fed7c3530ed95ec20fda9910ccc226f4b85604/construct2 projects/pong/pong.capx -------------------------------------------------------------------------------- /construct2 projects/test/ac2.capx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AirConsole/airconsole-construct2/f3fed7c3530ed95ec20fda9910ccc226f4b85604/construct2 projects/test/ac2.capx -------------------------------------------------------------------------------- /plugin/files/AirConsole2/PluginIcon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AirConsole/airconsole-construct2/f3fed7c3530ed95ec20fda9910ccc226f4b85604/plugin/files/AirConsole2/PluginIcon.ico -------------------------------------------------------------------------------- /plugin/files/AirConsole2/common.js: -------------------------------------------------------------------------------- 1 | // Scripts in this file are included in both the IDE and runtime, so you only 2 | // need to write scripts common to both once. 3 | -------------------------------------------------------------------------------- /plugin/files/AirConsole2/edittime.js: -------------------------------------------------------------------------------- 1 | function GetPluginSettings() { 2 | return { 3 | 'name': 'AirConsole 2', // as appears in 'insert object' dialog, can be changed as long as "id" stays the same 4 | 'id': 'AirConsole2', // this is used to identify this plugin and is saved to the project; never change it 5 | 'version': '1.8.0.3', // 3 first digits follow AirConsole API's version. Last digit for the plugin's version 6 | 'description': 'Extend your game with local multiplayer fun', 7 | 'author': 'Psychokiller1888 for N-Dreams AG', 8 | 'help url': 'https://github.com/AirConsole/airconsole-construct2/wiki', 9 | 'dependency': 'https://www.airconsole.com/api/airconsole-1.8.0.js', 10 | 'category': 'Web', // Prefer to re-use existing categories, but you can set anything here 11 | 'type': 'object', // either "world" (appears in layout and is drawn), else "object" 12 | 'rotatable': false, // only used when "type" is "world". Enables an angle property on the object. 13 | 'flags': pf_singleglobal // uncomment lines to enable flags... 14 | // | pf_singleglobal // exists project-wide, e.g. mouse, keyboard. "type" must be "object". 15 | // | pf_texture // object has a single texture (e.g. tiled background) 16 | // | pf_position_aces // compare/set/get x, y... 17 | // | pf_size_aces // compare/set/get width, height... 18 | // | pf_angle_aces // compare/set/get angle (recommended that "rotatable" be set to true) 19 | // | pf_appearance_aces // compare/set/get visible, opacity... 20 | // | pf_tiling // adjusts image editor features to better suit tiled images (e.g. tiled background) 21 | // | pf_animations // enables the animations system. See 'Sprite' for usage 22 | // | pf_zorder_aces // move to top, bottom, layer... 23 | // | pf_nosize // prevent resizing in the editor 24 | // | pf_effects // allow WebGL shader effects to be added 25 | // | pf_predraw // set for any plugin which draws and is not a sprite (i.e. does not simply draw 26 | // a single non-tiling image the size of the object) - required for effects to work properly 27 | } 28 | } 29 | 30 | //////////////////////////////////////// 31 | // Parameter types: 32 | // AddNumberParam(label, description [, initial_string = "0"]) // a number 33 | // AddStringParam(label, description [, initial_string = "\"\""]) // a string 34 | // AddAnyTypeParam(label, description [, initial_string = "0"]) // accepts either a number or string 35 | // AddCmpParam(label, description) // combo with equal, not equal, less, etc. 36 | // AddComboParamOption(text) // (repeat before "AddComboParam" to add combo items) 37 | // AddComboParam(label, description [, initial_selection = 0]) // a dropdown list parameter 38 | // AddObjectParam(label, description) // a button to click and pick an object type 39 | // AddLayerParam(label, description) // accepts either a layer number or name (string) 40 | // AddLayoutParam(label, description) // a dropdown list with all project layouts 41 | // AddKeybParam(label, description) // a button to click and press a key (returns a VK) 42 | // AddAnimationParam(label, description) // a string intended to specify an animation name 43 | // AddAudioFileParam(label, description) // a dropdown list with all imported project audio files 44 | 45 | //////////////////////////////////////// 46 | // Conditions 47 | 48 | // Signalling 49 | AddCondition(0, cf_trigger, 'On connect', 'Signalling', 'On new connection', 'Triggered when a device connects to the game.', 'OnConnect') 50 | 51 | AddCondition(1, cf_trigger, 'On disconnect', 'Signalling', 'On disconnection', 'Triggered when a device disconnects from the game.', 'OnDisconnect') 52 | 53 | AddNumberParam('Device id', 'Device id to track', '') 54 | AddCondition(2, cf_trigger, 'On device disconnect', 'Signalling', 'On device id {0} disconnects', 'Triggered when a specific device disconnects from the game.', 'OnDeviceDisconnect') 55 | 56 | AddCondition(3, cf_trigger, 'On too many players', 'Signalling', 'On too many players', 'Triggered when a device connects but the max player limit property is reached or exceeded.', 'OnTooManyPlayers') 57 | 58 | AddCondition(4, cf_trigger, 'On premium connect', 'Signalling', 'On premium connect', 'Triggered when a device becomes premium or when a premium device connects.', 'OnPremium') 59 | 60 | AddCondition(5, cf_trigger, 'On message', 'Messaging', 'On message from any device', 'Triggered when receiving a message from any device', 'OnMessage') 61 | 62 | AddNumberParam('Device id', 'Device id from which the message should be', '') 63 | AddCondition(6, cf_trigger, 'On message from', 'Messaging', 'On message from device id {0}', 'Triggered when receiving a message from a specific device', 'OnMessageFrom') 64 | 65 | AddStringParam('Property', 'The message property to compare to', '"message"') 66 | AddStringParam('Property value', 'The message content awaited', '') 67 | AddCondition(7, cf_trigger, 'On message is', 'Messaging', 'On message property {0} is {1}', 'Triggered when receiving a message with a specific content', 'OnMessageIs') 68 | 69 | AddStringParam('Property', 'The message property to compare to', '"message"') 70 | AddStringParam('Property value', 'The message content awaited', '') 71 | AddNumberParam('Device id', 'Device id from which the message should be', '') 72 | AddCondition(8, cf_trigger, 'On message from is', 'Messaging', 'On message property {0} is {1} and from device id {2}', 'Triggered when receiving a message with a specific content from a specific device', 'OnMessageFromIs') 73 | 74 | AddStringParam('Property', 'The message property awaited') 75 | AddCondition(9, cf_trigger, 'On message has property', 'Messaging', 'On message has property {0}', 'Triggered when a message from any device has a specific property.', 'OnMessageHasProperty') 76 | 77 | AddNumberParam('Device id', 'Device id to check if logged in', '') 78 | AddCondition(10, 0, 'Is user logged in', 'Device and user', 'Is device id {0} user logged in', 'True if the device\'s user is logged in, false otherwise.', 'IsUserLoggedIn') 79 | 80 | AddCondition(11, cf_trigger, 'On ad complete', 'Ads', 'On ad complete', 'Triggered when an advertisement is finished or no advertisement was shown.', 'OnAdComplete') 81 | 82 | AddCondition(12, cf_trigger, 'On ad show', 'Ads', 'On advertisement showing', 'Triggered when an advertisement is shown.', 'OnAdShow') 83 | 84 | AddCondition(13, cf_trigger, 'On persistent data loaded', 'Persistent data', 'On persistent data loaded', 'Gets called when persistent data was loaded from RequestPersistentData.', 'OnPersistentDataLoaded') 85 | 86 | AddCondition(14, cf_trigger, 'On persistent data stored', 'Persistent data', 'On persistent data stored', 'Gets called when persistent data was stored from StorePersistentData.', 'OnPersistentDataStored') 87 | 88 | AddCondition(15, cf_trigger, 'On receiving highscores', 'Highscores', 'On receiving highscores', 'Triggered when highscores received.', 'OnHighScores') 89 | 90 | AddCondition(16, cf_trigger, 'On highscores stored', 'Highscores', 'On highscores stored', 'Triggered when highscores storing is done.', 'OnHighScoreStored') 91 | 92 | AddCondition(17, cf_trigger, 'On device profile change', 'Device and user', 'On device profile change', 'Triggered when a device updates it\'s profile pic, nickname or email.', 'OnDeviceProfileChange') 93 | 94 | AddCondition(18, cf_trigger, 'On custom device state change', 'Device and user', 'On custom device state change', 'Triggered when a device updates it\'s custom device state.', 'OnCustomDeviceStateChange') 95 | 96 | AddNumberParam('Device id', 'Device id to check if premium', '') 97 | AddCondition(19, 0, 'Is premium', 'Device and user', 'Is device id {0} premium', 'True if the device\'s user is premium, false otherwise.', 'IsPremium') 98 | 99 | AddCondition(20, 0, 'Is plugin offline', 'Plugin', 'Is plugin offline', 'True if the plugin loaded as offline, false otherwise.', 'IsPluginOffline') 100 | 101 | AddCondition(21, 0, 'Is multipart message', 'Messaging', 'Is multipart message', 'True if the last received message has more than one property set, false otherwise', 'IsMultipartMessage') 102 | 103 | AddCondition(22, 0, 'Ad shown', 'Ads', 'Ad shown', 'True if an ad was shown.', 'AdShown') 104 | 105 | AddCondition(23, 0, 'Is ad showing', 'Ads', 'Is ad showing', 'True if an ad is currently showing.', 'IsAdShowing') 106 | 107 | AddCondition(24, cf_trigger, 'On device motion', 'Controller only', 'On device motion', 'Triggered every X millisecond if the plugin property has the \'Device motion\' property set higher than 0. This only works for controllers.', 'OnDeviceMotion') 108 | 109 | AddCondition(25, cf_trigger, 'On mute', 'Deprecated', 'On mute', 'Triggered when the game should mute any sound.', 'OnMute') 110 | 111 | AddCondition(26, cf_trigger, 'On unmute', 'Deprecated', 'On unmute', 'Triggered when the game should unmute all sounds.', 'OnUnmute') 112 | 113 | AddCondition(27, cf_trigger, 'On pause', 'Signalling', 'On pause', 'Triggered when the game should be paused.', 'OnPause') 114 | 115 | AddCondition(28, cf_trigger, 'On resume', 'Signalling', 'On resume', 'Triggered when the game should be resumed.', 'OnResume') 116 | //////////////////////////////////////// 117 | // Actions 118 | 119 | // AddAction(id, // any positive integer to uniquely identify this action 120 | // flags, // (see docs) af_none, af_deprecated 121 | // list_name, // appears in event wizard list 122 | // category, // category in event wizard list 123 | // display_str, // as appears in event sheet - use {0}, {1} for parameters and also , 124 | // description, // appears in event wizard dialog when selected 125 | // script_name); // corresponding runtime function name 126 | 127 | // example 128 | //AddStringParam("Message", "Enter a string to alert."); 129 | AddAction(0, af_none, 'Game ready', 'Game', 'Set the game as ready', 'Sets the game as ready. This will trigger OnConnect for all already connected devices', 'GameReady') 130 | 131 | AddNumberParam('Device id', 'Device id to send the message to.') 132 | AddStringParam('Property name', 'Message property name. (can only be \'message\' for now)', '"message"') 133 | AddStringParam('Message', 'Message to send') 134 | AddAction(1, af_none, 'Message', 'Messaging', 'Send {1}: {2} to device id {0}.', 'Sends a message to a specific device', 'Message') 135 | 136 | AddStringParam('Property name', 'Message property name. (can only be \'message\' for now)', '"message"') 137 | AddStringParam('Message', 'Message to send') 138 | AddAction(2, af_none, 'Broadcast', 'Messaging', 'Send {0}: {1} to all connected devices.', 'Sends a message to all connected devices', 'Broadcast') 139 | 140 | AddAnyTypeParam('Property', 'Device state property to set') 141 | AddAnyTypeParam('Value', 'Device state value') 142 | AddAction(3, af_none, 'Set custom device state property', 'Messaging', 'Set custom state property {0} to {1}', 'Sets a device custom state property', 'SetCustomDeviceStateProperty') 143 | 144 | AddStringParam('Level name', 'The name of the level.') 145 | AddStringParam('Level version', 'The version of the level.', '"1.0"') 146 | AddAnyTypeParam('Uids', 'A comma separated list of users UIDs that should be included in the result. Default is all connected controllers.', '"all"') 147 | AddStringParam('Ranks', 'A comma separated list of high score rank types. High score rank types can include data from across the world, only a specific area or a users friends. Valid array entries are \'world\', \'country\', \'region\', \'city\', \'friends\'. Use comma to separate the types if you need more than one.', '"world"') 148 | AddNumberParam('Total', 'Amount of high scores to return per rank type.', '8') 149 | AddNumberParam('Top', 'Amount of top high scores to return per rank type. top is part of total.', '5') 150 | AddAction(4, af_none, 'Request HighScores', 'Highscores', 'Request highscores', 'Requests highscores. OnHighScores triggered when the highscores are returned.', 'RequestHighScores') 151 | 152 | AddStringParam('Level name', 'The name of the level the user was playing. This should be a human readable string because it appears in the high score sharing image. You can also just pass an empty string.', '') 153 | AddStringParam('Level version', 'The version of the level the user was playing. This is for your internal use.', '"1.0"') 154 | AddNumberParam('Score', 'The score the user has achieved.') 155 | AddStringParam('Uid', 'The UIDs of the users that achieved the high score. Can be a single uid or an array of uids. Default is the uid of this device. Use comma to separate multiple UIDS.') 156 | AddStringParam('Data', 'Custom high score data (e.g. can be used to implement Ghost modes or include data to verify that it is not a fake high score).', '') 157 | AddStringParam('Score string', 'A short human readable representation of the score. (e.g. \'4 points in 3s\'). Defaults to \'X points\' where x is the score converted to an integer.', '') 158 | AddAction(5, af_none, 'Store HighScores', 'Highscores', 'Store highscores', 'Stores highscores. OnHighScoreStored triggered when the request is completed.', 'StoreHighScores') 159 | 160 | AddNumberParam('Max player', 'The maximum number of controllers that should get a player number assigned.') 161 | AddAction(6, af_none, 'Set active players', 'System', 'Set active players', 'Takes all currently connected controllers and assigns them a player number. Can only be called by the screen. The assigned player numbers always start with 0 and are consecutive. You can hardcode player numbers, but not device_ids. Once the screen has called setActivePlayers you can get the device_id of the first player by calling convertPlayerNumberToDeviceId(0), the device_id of the second player by using ConvertPlayerNumberToDeviceId(1). You can also convert device_ids to player numbers by using ConvertDeviceIdToPlayerNumber(device_id). You can get all device_ids that are active players by using GetActivePlayerDeviceIds().', 'SetActivePlayers') 162 | 163 | AddAction(7, af_none, 'Show ad', 'Ads', 'Show ad on controllers and screen', 'Show ad on every connected controller and screen. onAdComplete is called when showing ad is over', 'ShowAd') 164 | 165 | AddAction(8, af_none, 'Navigate home', 'Browser', 'Navigate home', 'Request that all devices return to the AirConsole store.', 'NavigateHome') 166 | 167 | AddStringParam('Url', 'The base url of the game to navigate to (excluding screen.html or controller.html).', '""') 168 | AddAction(9, af_none, 'Navigate to', 'Browser', 'Navigate to {0}', 'Request that all devices load a game by url.', 'NavigateTo') 169 | 170 | AddStringParam('Uids', 'A comma separated list of the uids for which you would like to request the persistent data.', '""') 171 | AddAction(10, af_none, 'Request persistent data', 'Persistent data', 'Request persistent data for ids {0}', 'Requests persistent data from the servers.', 'RequestPersistentData') 172 | 173 | AddStringParam('Property', 'Persistent data property name.', '""') 174 | AddStringParam('Value', 'Persistent data property value.', '""') 175 | AddStringParam('uid', 'The uid for which the data should be stored.', '""') 176 | AddAction(11, af_none, 'Store persistent data', 'Persistent data', 'Store persistent data {0} = {1} for uid {2}', 'Stores a property-value pair persistently on the AirConsole servers. Storage is per game. Total storage can not exceed 1 MB per game and uid. Storage is public, not secure and anyone can request and tamper with it. Do not store sensitive data.', 'StorePersistentData') 177 | 178 | AddNumberParam('Device id', 'Device id to send the message to.') 179 | AddAction(12, af_none, 'Send preset message', 'Preset message', 'Send preset message to device id {0}.', 'Sends a previously set message to a specific device', 'SendPresetMessage') 180 | 181 | AddAction(13, af_none, 'Broadcast preset message', 'Preset message', 'Send preset message to all connected devices.', 'Sends a previously set message to all connected devices', 'BroadcastPresetMessage') 182 | 183 | AddStringParam('Property', 'The message property.') 184 | AddAnyTypeParam('Value', 'The property value.') 185 | AddAction(14, af_none, 'Set message property', 'Preset message', 'Set {0} property to {1}', 'Set message property', 'SetPresetMessage') 186 | 187 | AddAction(15, af_none, 'Clear preset message', 'Preset message', 'Clear the preset message.', 'Clear the preset message', 'ClearPresetMessage') 188 | 189 | AddAction(16, af_none, 'Edit profile', 'Profile', '(Controller only) Edit the player profile', 'Edit profile', 'EditProfile') 190 | 191 | AddAction(17, af_none, 'Get premium', 'Profile', '(Controller only) Offers the user to become a premium member. Can only be called from controllers. If you call getPremium in development mode, the device becomes premium immediately', 'Get Premium', 'GetPremium') 192 | 193 | AddNumberParam('Time', 'Duration, in milliseconds, of the vibration.') 194 | AddAction(18, af_none, 'Vibrate', 'Controller only', '(Controller only) Vibrate the controller for {0} millisecond', 'Vibrate', 'Vibrate') 195 | 196 | AddComboParamOption('Landscape') 197 | AddComboParamOption('Portrait') 198 | AddComboParam('Orientation', 'Desired controller orientation.', 0) 199 | AddAction(19, af_none, 'Set orientation', 'Controller only', 'Set orientation to {0}', 'Set orientation', 'SetOrientation') 200 | 201 | AddAction(20, af_none, 'Start AirConsole', 'System', 'Start AirConsole', 'Instanciates AirConsole. Must be your very first action, as without it, no AirConsole!', 'StartAirConsole') 202 | //////////////////////////////////////// 203 | // Expressions 204 | 205 | // AddExpression(id, // any positive integer to uniquely identify this expression 206 | // flags, // (see docs) ef_none, ef_deprecated, ef_return_number, ef_return_string, 207 | // // ef_return_any, ef_variadic_parameters (one return flag must be specified) 208 | // list_name, // currently ignored, but set as if appeared in event wizard 209 | // category, // category in expressions panel 210 | // exp_name, // the expression name after the dot, e.g. "foo" for "myobject.foo" - also the runtime function name 211 | // description); // description in expressions panel 212 | 213 | // example 214 | AddExpression(0, ef_return_number, 'Ids', 'Ids', 'DeviceId', 'Returns the device id that last triggered an AirConsole condition') 215 | 216 | AddExpression(1, ef_return_string, 'Data', 'Data', 'Message', 'Last message received.') 217 | 218 | AddStringParam('Property name', 'Property name', '') 219 | AddExpression(2, ef_return_string, 'Data', 'Data', 'MessageAtProperty', 'Last message received with a specific property name') 220 | 221 | AddExpression(3, ef_return_number, 'Data', 'Data', 'IsMultipartMessage', 'Returns 1 if the last message has more than 1 property, else 0') 222 | 223 | AddStringParam('Property name', 'Property name', '') 224 | AddExpression(4, ef_return_number, 'Data', 'Data', 'MessageHasProperty', 'Returns 1 if the last message has the specified property set, else 0') 225 | 226 | AddExpression(5, ef_return_string, 'Data', 'Data', 'MessageAsJSON', 'Returns a JSON string representation of the last message received') 227 | 228 | AddNumberParam('Device id', 'Device id') 229 | AddExpression(6, ef_return_string | ef_deprecated, 'Profile', 'Profile', 'GetProfilePicture', 'Returns the profile picture url of the specified device id') 230 | 231 | AddNumberParam('Device id', 'Device id') 232 | AddExpression(7, ef_return_string, 'Profile', 'Profile', 'GetNickname', 'Returns the nickname of the specified device id') 233 | 234 | AddNumberParam('Device id', 'Device id') 235 | AddExpression(8, ef_return_string, 'Profile', 'Profile', 'GetUID', 'Returns UID of the specified device id') 236 | 237 | AddExpression(9, ef_return_number, 'Data', 'Data', 'GetMessagePropertiesCount', 'Returns how many properties the last received message contains') 238 | 239 | AddExpression(10, ef_return_number, 'Ids', 'Ids', 'GetMasterControllerDeviceId', 'Returns the device id of the master controller') 240 | 241 | AddNumberParam('Player number', 'Player number') 242 | AddExpression(11, ef_return_number, 'Ids', 'Ids', 'ConvertPlayerNumberToDeviceId', 'Converts the specified player number into its attributed device id') 243 | 244 | AddNumberParam('Device id', 'Device id') 245 | AddExpression(12, ef_return_number, 'Ids', 'Ids', 'ConvertDeviceIdToPlayerNumber', 'Converts the specified device id into its attributed player number') 246 | 247 | AddExpression(13, ef_return_string, 'Ids', 'Ids', 'GetControllerDeviceIds', 'Returns a JSON converted C2Array of all the device ids that have loaded your game') 248 | 249 | AddNumberParam('Device id', 'Device id') 250 | AddExpression(14, ef_return_number, 'Profile', 'Profile', 'IsPremium', 'Returns 1 if the specified device id is premium, else 0') 251 | 252 | AddExpression(15, ef_return_string, 'Persistent data', 'Persistent data', 'GetPersistentData', 'Returns a JSON converted C2Dictionary of the last loaded persistent data') 253 | 254 | AddExpression(16, ef_return_string, 'Highscores', 'Highscores', 'GetHighscores', 'Returns a JSON converted C2Dictionary of the last loaded highscores') 255 | 256 | AddExpression(17, ef_return_number, 'Plugin', 'Plugin', 'IsPluginOffline', 'Returns 1 if the plugin loaded as offline, else 0') 257 | 258 | AddExpression(18, ef_return_string, 'Ids', 'Ids', 'GetActivePlayerDeviceIds', 'Returns an array of device_ids of the active players previously set by the screen by calling setActivePlayers. The first device_id in the array is the first player, the second device_id in the array is the second player etc.') 259 | 260 | AddNumberParam('Device id', 'Device id') 261 | AddNumberParam('Picture size', 'Picture size', '64') 262 | AddExpression(19, ef_return_string, 'Profile', 'Profile', 'GetProfilePictureWithSize', 'Returns the profile picture url of the specified device id') 263 | 264 | AddExpression(20, ef_return_number, 'Ads', 'Ads', 'AdShown', 'Returns 1 if ads were shown, else 0') 265 | 266 | AddExpression(21, ef_return_number, 'Ads', 'Ads', 'IsAddShowing', 'Returns 1 if ads are currently showing, else 0.') 267 | 268 | AddExpression(22, ef_return_number, 'Profile', 'Profile', 'GetThisDeviceId', 'Returns the current device id from which this function is called.') 269 | 270 | AddExpression(23, ef_return_number, 'Controller only', 'Controller only', 'MotionData', 'Returns a JSON converted C2Dictionary containing the device motion data. This works for controllers only, and the plugin should have it\'s \'Device motion\' property set higher than 0') 271 | 272 | AddNumberParam('Device id', 'Device id') 273 | AddExpression(24, ef_return_string, 'Translations', 'Translations', 'GetLanguage', 'Returns the current IETF language tag of a device e.g. "en" or "en-US"') 274 | 275 | AddStringParam('String id', 'The id of the translation string.', '') 276 | AddStringParam('Replacements', 'A dictionary of values that should be used for replacement in the translated string. E.g. if a translated string is "Hi %name%" and values is {"name": "Tom"} then this will be replaced to "Hi Tom".') 277 | AddExpression(25, ef_return_string, 'Translations', 'Translations', 'GetTranslation', 'Gets a translation for the users current language See http://developers.airconsole.com/#!/guides/translations') 278 | //////////////////////////////////////// 279 | ACESDone() 280 | 281 | //////////////////////////////////////// 282 | // Array of property grid properties for this plugin 283 | // new cr.Property(ept_integer, name, initial_value, description) // an integer value 284 | // new cr.Property(ept_float, name, initial_value, description) // a float value 285 | // new cr.Property(ept_text, name, initial_value, description) // a string 286 | // new cr.Property(ept_color, name, initial_value, description) // a color dropdown 287 | // new cr.Property(ept_font, name, "Arial,-16", description) // a font with the given face name and size 288 | // new cr.Property(ept_combo, name, "Item 1", description, "Item 1|Item 2|Item 3") // a dropdown list (initial_value is string of initially selected item) 289 | // new cr.Property(ept_link, name, link_text, description, "firstonly") // has no associated value; simply calls "OnPropertyChanged" on click 290 | 291 | var property_list = [ 292 | new cr.Property(ept_integer, 'Max players', 4, 'Define the maximum amount of players'), 293 | new cr.Property(ept_combo, 'Type', 'Screen', 'Is this project intended to be a controller?', 'Screen|Controller'), 294 | new cr.Property(ept_combo, 'Use translations', 'false', 'Use AirConsole server translations service', 'false|true'), 295 | new cr.Property(ept_section, 'Controller only', 'These properties only take effect if \'Is controller\' is checked'), 296 | new cr.Property(ept_combo, 'Orientation', 'Landscape', 'CONTROLLER ONLY - Sets this controller in either PORTRAIT or LANDSCAPE mode', 'Landscape|Portrait'), 297 | new cr.Property(ept_combo, 'Synchronize time', 'false', 'CONTROLLER ONLY - Enable time synchronization with server. This is needed for \'getServerTime()\'', 'false|true'), 298 | new cr.Property(ept_integer, 'Device motion', 0, 'CONTROLLER ONLY - If set > 0, onDeviceMotion gets called every \'Device motion\' milliseconds with the data from the accelerometer and gyroscope') 299 | ] 300 | 301 | // Called by IDE when a new object type is to be created 302 | function CreateIDEObjectType() { 303 | return new IDEObjectType() 304 | } 305 | 306 | // Class representing an object type in the IDE 307 | function IDEObjectType() { 308 | assert2(this instanceof arguments.callee, 'Constructor called as a function') 309 | } 310 | 311 | // Called by IDE when a new object instance of this type is to be created 312 | IDEObjectType.prototype.CreateInstance = function (instance) { 313 | return new IDEInstance(instance) 314 | } 315 | 316 | // Class representing an individual instance of an object in the IDE 317 | function IDEInstance(instance, type) { 318 | assert2(this instanceof arguments.callee, 'Constructor called as a function') 319 | 320 | // Save the constructor parameters 321 | this.instance = instance 322 | this.type = type 323 | 324 | // Set the default property values from the property table 325 | this.properties = {} 326 | 327 | for (var i = 0; i < property_list.length; i++) 328 | this.properties[property_list[i].name] = property_list[i].initial_value 329 | 330 | // Plugin-specific variables 331 | // this.myValue = 0... 332 | } 333 | 334 | // Called when inserted via Insert Object Dialog for the first time 335 | IDEInstance.prototype.OnInserted = function () { 336 | } 337 | 338 | // Called when double clicked in layout 339 | IDEInstance.prototype.OnDoubleClicked = function () { 340 | } 341 | 342 | // Called after a property has been changed in the properties bar 343 | IDEInstance.prototype.OnPropertyChanged = function (property_name) { 344 | } 345 | 346 | // For rendered objects to load fonts or textures 347 | IDEInstance.prototype.OnRendererInit = function (renderer) { 348 | } 349 | 350 | // Called to draw self in the editor if a layout object 351 | IDEInstance.prototype.Draw = function (renderer) { 352 | } 353 | 354 | // For rendered objects to release fonts or textures 355 | IDEInstance.prototype.OnRendererReleased = function (renderer) { 356 | } 357 | -------------------------------------------------------------------------------- /plugin/files/AirConsole2/runtime.js: -------------------------------------------------------------------------------- 1 | // ECMAScript 5 strict mode 2 | 'use strict' 3 | 4 | assert2(cr, 'cr namespace not created') 5 | assert2(cr.plugins_, 'cr.plugins_ not created') 6 | 7 | ///////////////////////////////////// 8 | // Plugin class 9 | cr.plugins_.AirConsole2 = function (runtime) { 10 | this.runtime = runtime 11 | } 12 | 13 | function AirConsoleOffline() { 14 | console.warn('You are currently offline or AirConsole could not be loaded. Plugin fallback to AirConsole mock-up.') 15 | AirConsoleOffline.prototype.getNickname = function () { 16 | console.log('AirConsole mock-up: Getting nickname') 17 | return 'John Doe' 18 | } 19 | AirConsoleOffline.prototype.getProfilePicture = function () { 20 | console.log('AirConsole mock-up: Getting profile picture') 21 | return 'undefined when offline' 22 | } 23 | AirConsoleOffline.prototype.getUID = function () { 24 | console.log('AirConsole mock-up: Getting UID') 25 | return -9999 26 | } 27 | AirConsoleOffline.prototype.isPremium = function () { 28 | console.log('AirConsole mock-up: Checking if premium') 29 | return false 30 | } 31 | AirConsoleOffline.prototype.getControllerDeviceIds = function () { 32 | console.log('AirConsole mock-up: Getting controller device ids') 33 | return [] 34 | } 35 | AirConsoleOffline.prototype.getCustomDeviceState = function () { 36 | console.log('AirConsole mock-up: Getting custom device state') 37 | return null 38 | } 39 | AirConsoleOffline.prototype.isUserLoggedIn = function () { 40 | console.log('AirConsole mock-up: Checking if user is logged in') 41 | return false 42 | } 43 | AirConsoleOffline.prototype.message = function () { 44 | console.log('AirConsole mock-up: Sending a message') 45 | } 46 | AirConsoleOffline.prototype.broadcast = function () { 47 | console.log('AirConsole mock-up: Broadcasting a message') 48 | } 49 | AirConsoleOffline.prototype.requestHighScores = function () { 50 | console.log('AirConsole mock-up: Requesting highscores') 51 | } 52 | AirConsoleOffline.prototype.storeHighScore = function () { 53 | console.log('AirConsole mock-up: Storing highscores') 54 | } 55 | AirConsoleOffline.prototype.setActivePlayers = function () { 56 | console.log('AirConsole mock-up: Setting active players') 57 | } 58 | AirConsoleOffline.prototype.showAd = function () { 59 | console.log('AirConsole mock-up: Showing ad') 60 | } 61 | AirConsoleOffline.prototype.navigateHome = function () { 62 | console.log('AirConsole mock-up: Navigating home') 63 | } 64 | AirConsoleOffline.prototype.navigateTo = function () { 65 | console.log('AirConsole mock-up: Navigating to given url') 66 | } 67 | AirConsoleOffline.prototype.requestPersistentData = function () { 68 | console.log('AirConsole mock-up: Requesting persistent data') 69 | } 70 | AirConsoleOffline.prototype.storePersistentData = function () { 71 | console.log('AirConsole mock-up: Storing persistent data') 72 | } 73 | AirConsoleOffline.prototype.getMasterControllerDeviceId = function () { 74 | console.log('AirConsole mock-up: Getting master controller device id') 75 | return -9999 76 | } 77 | AirConsoleOffline.prototype.getActivePlayerDeviceIds = function () { 78 | console.log('AirConsole mock-up: Getting active player device ids') 79 | return [] 80 | } 81 | AirConsoleOffline.prototype.convertPlayerNumberToDeviceId = function () { 82 | console.log('AirConsole mock-up: Converting player number to device id') 83 | return -1 84 | } 85 | AirConsoleOffline.prototype.convertDeviceIdToPlayerNumber = function () { 86 | console.log('AirConsole mock-up: Converting device id to player number') 87 | return -1 88 | } 89 | AirConsoleOffline.prototype.getLanguage = function () { 90 | console.log('AirConsole mock-up: Sending a message') 91 | return 'en-US' 92 | } 93 | } 94 | 95 | (function () { 96 | var pluginProto = cr.plugins_.AirConsole2.prototype 97 | 98 | ///////////////////////////////////// 99 | // Object type class 100 | pluginProto.Type = function (plugin) { 101 | this.plugin = plugin 102 | this.runtime = plugin.runtime 103 | } 104 | 105 | var typeProto = pluginProto.Type.prototype 106 | 107 | // called on startup for each object type 108 | typeProto.onCreate = function () { 109 | } 110 | 111 | ///////////////////////////////////// 112 | // Instance class 113 | pluginProto.Instance = function (type) { 114 | this.type = type 115 | this.runtime = type.runtime 116 | this.maxPlayers 117 | this.useTranslation = false 118 | this.gameReady = false 119 | this.airConsoleStarted = false 120 | this.isController 121 | this.deviceId 122 | this.message 123 | this.adCompleted = 0 124 | this.adShowing = 0 125 | this.persistentData = null 126 | this.highscores = null 127 | this.emailAddress = null 128 | this.customData = null 129 | this.presetMessage = {} 130 | this.motionData = {} 131 | } 132 | 133 | var self = null 134 | var instanceProto = pluginProto.Instance.prototype 135 | 136 | // called whenever an instance is created 137 | instanceProto.onCreate = function () { 138 | self = this 139 | } 140 | 141 | // only called if a layout object - draw to a canvas 2D context 142 | instanceProto.draw = function (ctx) { 143 | } 144 | 145 | // only called if a layout object in WebGL mode - draw to the WebGL context 146 | // 'glw' is not a WebGL context, it's a wrapper - you can find its methods in GLWrap.js in the install 147 | // directory or just copy what other plugins do. 148 | instanceProto.drawGL = function (glw) { 149 | } 150 | 151 | ////////////////////////////////////// 152 | // Conditions 153 | function Cnds() { 154 | } 155 | 156 | Cnds.prototype.OnConnect = function () { 157 | return true 158 | } 159 | 160 | Cnds.prototype.OnDisconnect = function () { 161 | return true 162 | } 163 | 164 | Cnds.prototype.OnDeviceDisconnect = function (deviceId) { 165 | return this.deviceId === deviceId 166 | } 167 | 168 | Cnds.prototype.OnTooManyPlayers = function () { 169 | return true 170 | } 171 | 172 | Cnds.prototype.OnPremium = function () { 173 | return true 174 | } 175 | 176 | Cnds.prototype.OnMessage = function () { 177 | return true 178 | } 179 | 180 | Cnds.prototype.OnMessageFrom = function (deviceId) { 181 | return this.deviceId === deviceId 182 | } 183 | 184 | Cnds.prototype.OnMessageIs = function (property, value) { 185 | if (typeof this.message === 'string') { 186 | return this.message === value 187 | } else { 188 | return (this.message.hasOwnProperty(property) && this.message[property] == value) 189 | } 190 | } 191 | 192 | Cnds.prototype.OnMessageFromIs = function (property, value, deviceId) { 193 | if (typeof this.message === 'string') { 194 | return (this.message === value && this.deviceId === deviceId) 195 | } else { 196 | return (this.message.hasOwnProperty(property) && this.message[property] == value && this.deviceId === deviceId) 197 | } 198 | } 199 | 200 | Cnds.prototype.OnMessageHasProperty = function (property) { 201 | return (this.message.hasOwnProperty(property)) 202 | } 203 | 204 | Cnds.prototype.IsUserLoggedIn = function (deviceId) { 205 | return this.airConsole.isUserLoggedIn(deviceId) 206 | } 207 | 208 | Cnds.prototype.OnAdComplete = function () { 209 | return true 210 | } 211 | 212 | Cnds.prototype.OnAdShow = function () { 213 | return true 214 | } 215 | 216 | Cnds.prototype.OnPersistentDataLoaded = function () { 217 | return true 218 | } 219 | 220 | Cnds.prototype.OnPersistentDataStored = function () { 221 | return true 222 | } 223 | 224 | Cnds.prototype.OnHighScores = function () { 225 | return true 226 | } 227 | 228 | Cnds.prototype.OnHighScoreStored = function () { 229 | return true 230 | } 231 | 232 | Cnds.prototype.OnEmailAddress = function () { 233 | return true 234 | } 235 | 236 | Cnds.prototype.OnDeviceProfileChange = function () { 237 | return true 238 | } 239 | 240 | Cnds.prototype.OnCustomDeviceStateChange = function () { 241 | return true 242 | } 243 | 244 | Cnds.prototype.IsPremium = function (deviceId) { 245 | return this.airConsole.isPremium(deviceId) 246 | } 247 | 248 | Cnds.prototype.IsPluginOffline = function () { 249 | return this.runningOffline 250 | } 251 | 252 | Cnds.prototype.IsMultipartMessage = function () { 253 | return (this.message !== null && typeof this.message === 'object' && Object.keys(this.message).length > 1) 254 | } 255 | 256 | Cnds.prototype.AdShown = function () { 257 | return this.adCompleted === 1 258 | } 259 | 260 | Cnds.prototype.IsAdShowing = function () { 261 | return this.adShowing === 1 262 | } 263 | 264 | Cnds.prototype.OnDeviceMotion = function () { 265 | return true 266 | } 267 | 268 | Cnds.prototype.OnMute = function () { 269 | return true 270 | } 271 | 272 | Cnds.prototype.OnUnmute = function () { 273 | return true 274 | } 275 | 276 | Cnds.prototype.OnPause = function () { 277 | return true 278 | } 279 | 280 | Cnds.prototype.OnResume = function () { 281 | return true 282 | } 283 | 284 | pluginProto.cnds = new Cnds() 285 | 286 | ////////////////////////////////////// 287 | // Actions 288 | function Acts() { 289 | } 290 | 291 | Acts.prototype.StartAirConsole = function() { 292 | if (this.airConsoleStarted) { 293 | return 294 | } 295 | this.airConsoleStarted = true 296 | if (typeof AirConsole !== 'undefined') { 297 | this.runningOffline = false 298 | if (self.properties[1] === 1) { 299 | this.gameReady = true 300 | var config = { 301 | orientation: AirConsole.ORIENTATION_LANDSCAPE, 302 | synchronize_time: false, 303 | setup_document: true, 304 | device_motion: false 305 | } 306 | if (self.properties[2] === 0) { 307 | config.translation = true 308 | } 309 | if (self.properties[3] === 1) { 310 | config.orientation = AirConsole.ORIENTATION_PORTRAIT 311 | } 312 | if (self.properties[4] === 0) { 313 | config.synchronize_time = true 314 | } 315 | if (self.properties[5] > 0) { 316 | config.device_motion = self.properties[4] 317 | } 318 | 319 | this.airConsole = new AirConsole(config) 320 | } else { 321 | this.airConsole = new AirConsole() 322 | } 323 | } else { 324 | this.runningOffline = true 325 | this.airConsole = new AirConsoleOffline() 326 | } 327 | 328 | this.maxPlayers = self.properties[0] 329 | this.isController = (self.properties[1] === 1) 330 | this.useTranslation = (self.properties[2] === 1) 331 | 332 | if (this.isController) { 333 | this.airConsole.onReady = function () { 334 | self.airConsole.message(AirConsole.SCREEN, { 335 | handshake: true 336 | }) 337 | } 338 | } 339 | 340 | this.airConsole.onConnect = function (deviceId) { 341 | if (self.gameReady) { 342 | self.deviceId = deviceId 343 | if (self.airConsole.getControllerDeviceIds().length > self.maxPlayers) { 344 | self.runtime.trigger(pluginProto.cnds.OnTooManyPlayers, self) 345 | } else { 346 | self.runtime.trigger(pluginProto.cnds.OnConnect, self) 347 | } 348 | } 349 | } 350 | 351 | this.airConsole.onDisconnect = function (deviceId) { 352 | if (self.gameReady) { 353 | self.deviceId = deviceId 354 | self.runtime.trigger(pluginProto.cnds.OnDisconnect, self) 355 | self.runtime.trigger(pluginProto.cnds.OnDeviceDisconnect, self) 356 | } 357 | } 358 | 359 | this.airConsole.onMessage = function (deviceId, data) { 360 | if (self.gameReady && data) { 361 | self.deviceId = deviceId 362 | self.message = data 363 | self.runtime.trigger(pluginProto.cnds.OnMessage, self) 364 | self.runtime.trigger(pluginProto.cnds.OnMessageFrom, self) 365 | self.runtime.trigger(pluginProto.cnds.OnMessageIs, self) 366 | self.runtime.trigger(pluginProto.cnds.OnMessageFromIs, self) 367 | self.runtime.trigger(pluginProto.cnds.OnMessageHasProperty, self) 368 | } 369 | } 370 | 371 | this.airConsole.onDeviceStateChange = function (deviceId, data) { 372 | } 373 | 374 | this.airConsole.onCustomDeviceStateChange = function (deviceId, customData) { 375 | self.deviceId = deviceId 376 | self.customData = customData 377 | self.runtime.trigger(pluginProto.cnds.OnCustomDeviceStateChange, self) 378 | } 379 | 380 | this.airConsole.onHighScores = function (highscores) { 381 | if (highscores) { 382 | self.highscores = highscores 383 | self.runtime.trigger(pluginProto.cnds.OnHighScores, self) 384 | } 385 | } 386 | 387 | this.airConsole.onHighScoreStored = function (highscores) { 388 | if (highscores) { 389 | self.highscores = highscores 390 | self.runtime.trigger(pluginProto.cnds.OnHighScoreStored, self) 391 | } 392 | } 393 | 394 | this.airConsole.onAdComplete = function (adWasShown) { 395 | self.adCompleted = (adWasShown) ? 1 : 0 396 | self.adShowing = 0 397 | self.runtime.trigger(pluginProto.cnds.OnAdComplete, self) 398 | } 399 | 400 | this.airConsole.onAdShow = function () { 401 | self.adShowing = 1 402 | self.runtime.trigger(pluginProto.cnds.OnAdShow, self) 403 | } 404 | 405 | this.airConsole.onPremium = function (deviceId) { 406 | if (self.gameReady) { 407 | self.deviceId = deviceId 408 | self.runtime.trigger(pluginProto.cnds.OnPremium, self) 409 | } 410 | } 411 | 412 | this.airConsole.onPersistentDataLoaded = function (data) { 413 | if (data) { 414 | self.persistentData = data 415 | self.runtime.trigger(pluginProto.cnds.OnPersistentDataLoaded, self) 416 | } 417 | } 418 | 419 | this.airConsole.onPersistentDataStored = function (uid) { 420 | self.runtime.trigger(pluginProto.cnds.OnPersistentDataStored, self) 421 | } 422 | 423 | this.airConsole.onDeviceProfileChange = function (deviceId) { 424 | self.deviceId = deviceId 425 | self.runtime.trigger(pluginProto.cnds.OnDeviceProfileChange, self) 426 | } 427 | 428 | this.airConsole.onDeviceMotion = function (data) { 429 | self.motionData = data 430 | self.runtime.trigger(pluginProto.cnds.OnDeviceMotion, self) 431 | } 432 | 433 | this.airConsole.onMute = function (mute) { 434 | console.warn('Using deprecated action "onMute/onUnmute"') 435 | } 436 | 437 | this.airConsole.onPause = function () { 438 | self.runtime.trigger(pluginProto.cnds.OnPause, self) 439 | } 440 | 441 | this.airConsole.onResume = function () { 442 | self.runtime.trigger(pluginProto.cnds.OnResume, self) 443 | } 444 | } 445 | 446 | Acts.prototype.GameReady = function () { 447 | this.gameReady = true 448 | var deviceIds = this.airConsole.getControllerDeviceIds() 449 | for (var i = 0; i < deviceIds.length; i++) { 450 | this.airConsole.onConnect(deviceIds[i]) 451 | } 452 | } 453 | 454 | Acts.prototype.Message = function (deviceId, property, value) { 455 | if (property !== 'message') { 456 | console.warn('Property other than "message" isn\'t currently supported') 457 | } 458 | 459 | var obj = parseJSON(value) 460 | if (obj !== false) { 461 | value = obj 462 | } 463 | 464 | this.airConsole.message(deviceId, value) 465 | } 466 | 467 | Acts.prototype.Broadcast = function (property, message) { 468 | this.airConsole.broadcast(message) 469 | } 470 | 471 | Acts.prototype.SetCustomDeviceStateProperty = function (property, value) { 472 | this.airConsole.setCustomDeviceState(property, value) 473 | } 474 | 475 | Acts.prototype.RequestHighScores = function (level_name, level_version, uids, ranks, total, top) { 476 | this.highscores = null 477 | var uidsArray 478 | if (uids === 'all') { 479 | uidsArray = '' 480 | } else if (uids.indexOf(',') > -1) { 481 | uidsArray = uids.split(',') 482 | } else { 483 | uidsArray = [uids] 484 | } 485 | var ranksArray = (ranks === 'world') ? [ranks] : ranks.split(',') 486 | this.airConsole.requestHighScores(level_name, level_version, uidsArray, ranksArray, total, top) 487 | } 488 | 489 | Acts.prototype.StoreHighScores = function (level_name, level_version, score, uid, data, score_string) { 490 | var uidArray = uid.split(',') 491 | this.airConsole.storeHighScore(level_name, level_version, score, uidArray, data, score_string) 492 | } 493 | 494 | Acts.prototype.SetActivePlayers = function (max_players) { 495 | this.airConsole.setActivePlayers(max_players) 496 | } 497 | 498 | Acts.prototype.ShowAd = function () { 499 | this.airConsole.showAd() 500 | } 501 | 502 | Acts.prototype.NavigateHome = function () { 503 | this.airConsole.navigateHome() 504 | } 505 | 506 | Acts.prototype.NavigateTo = function (url) { 507 | this.airConsole.navigateTo(url) 508 | } 509 | 510 | Acts.prototype.RequestPersistentData = function (uids) { 511 | this.persistentData = null 512 | var uidsArray = (uids.indexOf(',') > -1) ? uids.split(',') : [uids] 513 | this.airConsole.requestPersistentData(uidsArray) 514 | } 515 | 516 | Acts.prototype.StorePersistentData = function (key, value, uid) { 517 | this.airConsole.storePersistentData(key, value, uid) 518 | } 519 | 520 | Acts.prototype.SendPresetMessage = function (deviceId) { 521 | if (this.runningOffline) return 522 | 523 | this.airConsole.message(deviceId, this.presetMessage) 524 | this.presetMessage = {} 525 | } 526 | 527 | Acts.prototype.BroadcastPresetMessage = function () { 528 | this.airConsole.broadcast(this.presetMessage) 529 | this.presetMessage = {} 530 | } 531 | 532 | Acts.prototype.SetPresetMessage = function (key, value) { 533 | this.presetMessage[key] = value 534 | } 535 | 536 | Acts.prototype.ClearPresetMessage = function () { 537 | this.presetMessage = {} 538 | } 539 | 540 | Acts.prototype.EditProfile = function () { 541 | if (this.isController) { 542 | this.airConsole.editProfile() 543 | } else { 544 | console.warn('You can\' use "Edit profile" on screen') 545 | } 546 | } 547 | 548 | Acts.prototype.SetOrientation = function (orientation) { 549 | if (this.isController) { 550 | this.airConsole.setOrientation((orientation === 1) ? AirConsole.ORIENTATION_PORTRAIT : AirConsole.ORIENTATION_LANDSCAPE) 551 | } 552 | } 553 | 554 | Acts.prototype.GetPremium = function () { 555 | this.airConsole.getPremium() 556 | } 557 | 558 | Acts.prototype.Vibrate = function (time) { 559 | if (this.properties[1] === 1 && time > 0) { 560 | this.airConsole.vibrate(time) 561 | } 562 | } 563 | 564 | pluginProto.acts = new Acts() 565 | 566 | ////////////////////////////////////// 567 | // Expressions 568 | function Exps() { 569 | } 570 | 571 | // ret.set_int(1337); // return our value 572 | // ret.set_float(0.5); // for returning floats 573 | // ret.set_string("Hello"); // for ef_return_string 574 | // ret.set_any("woo"); // for ef_return_any, accepts either a number or string 575 | 576 | Exps.prototype.DeviceId = function (ret) { 577 | ret.set_int(this.deviceId) 578 | } 579 | 580 | Exps.prototype.Message = function (ret) { 581 | if (typeof this.message === 'object') { 582 | if (Object.keys(this.message).length === 1) { 583 | ret.set_string(this.message[Object.keys(this.message)[0]]) 584 | } else { 585 | ret.set_string(JSON.stringify(this.message)) 586 | } 587 | } else { 588 | ret.set_string(this.message) 589 | } 590 | } 591 | 592 | Exps.prototype.MessageAtProperty = function (ret, property) { 593 | if (typeof this.message === 'object' && this.message.hasOwnProperty(property)) { 594 | ret.set_string(this.message[property]) 595 | } else { 596 | console.warn('MessageAtProperty - Tried to access a non existing property') 597 | } 598 | } 599 | 600 | Exps.prototype.IsMultipartMessage = function (ret) { 601 | if (this.message !== null && typeof this.message === 'object' && Object.keys(this.message).length > 1) { 602 | ret.set_int(1) 603 | } else { 604 | ret.set_int(0) 605 | } 606 | } 607 | 608 | Exps.prototype.MessageHasProperty = function (ret, property) { 609 | if (this.message !== null && typeof this.message === 'object' && this.message.hasOwnProperty(property)) { 610 | ret.set_int(1) 611 | } else { 612 | ret.set_int(0) 613 | } 614 | } 615 | 616 | Exps.prototype.MessageAsJSON = function (ret) { 617 | var c2Dictionary = {} 618 | c2Dictionary['c2dictionary'] = true 619 | c2Dictionary['data'] = getProperties(this.message) 620 | ret.set_string(JSON.stringify(c2Dictionary)) 621 | } 622 | 623 | Exps.prototype.GetProfilePicture = function (ret, deviceId) { 624 | var pic = this.airConsole.getProfilePicture(deviceId) || 'https://www.gravatar.com/avatar/00000000000000000000000000000000?f=y' 625 | ret.set_string(pic) 626 | } 627 | 628 | Exps.prototype.GetProfilePictureWithSize = function (ret, deviceId, pictureSize) { 629 | var pic = this.airConsole.getProfilePicture(deviceId, pictureSize) || 'https://www.gravatar.com/avatar/00000000000000000000000000000000?f=y' 630 | ret.set_string(pic) 631 | } 632 | 633 | Exps.prototype.GetNickname = function (ret, deviceId) { 634 | var nickname = this.airConsole.getNickname(deviceId) || 'Nickname not found' 635 | ret.set_string(nickname) 636 | } 637 | 638 | Exps.prototype.GetUID = function (ret, deviceId) { 639 | var uid = this.airConsole.getUID(deviceId) || 'Unknown UID' 640 | ret.set_string(uid) 641 | } 642 | 643 | Exps.prototype.GetMessagePropertiesCount = function (ret) { 644 | if (this.message !== null && typeof this.message === 'object') { 645 | ret.set_int(Object.keys(this.message).length) 646 | } else { 647 | ret.set_int(0) 648 | } 649 | } 650 | 651 | Exps.prototype.GetMasterControllerDeviceId = function (ret) { 652 | var id = this.airConsole.getMasterControllerDeviceId() 653 | ret.set_int((typeof id !== 'number' || isNaN(id)) ? -1 : id) 654 | } 655 | 656 | Exps.prototype.ConvertPlayerNumberToDeviceId = function (ret, playerNumber) { 657 | var id = this.airConsole.convertPlayerNumberToDeviceId(playerNumber) 658 | ret.set_int((typeof id !== 'number') ? -1 : id) 659 | } 660 | 661 | Exps.prototype.ConvertDeviceIdToPlayerNumber = function (ret, deviceId) { 662 | var playerNumber = this.airConsole.convertDeviceIdToPlayerNumber(deviceId) 663 | ret.set_int((typeof playerNumber !== 'number') ? -1 : playerNumber) 664 | } 665 | 666 | Exps.prototype.IsPremium = function (ret, deviceId) { 667 | ret.set_int((this.airConsole.isPremium(deviceId) !== false) ? 1 : 0) 668 | } 669 | 670 | Exps.prototype.GetControllerDeviceIds = function (ret) { 671 | var arr = this.airConsole.getControllerDeviceIds() 672 | 673 | var c2array = {} 674 | c2array['c2array'] = true 675 | c2array['size'] = [arr.length, 1, 1] 676 | var data = [] 677 | for (var i in arr) { 678 | data.push([[arr[i]]]) 679 | } 680 | c2array['data'] = data 681 | 682 | ret.set_string(JSON.stringify(c2array)) 683 | } 684 | 685 | Exps.prototype.GetPersistentData = function (ret) { 686 | if (this.persistentData !== null) { 687 | var c2Dictionary = {} 688 | c2Dictionary['c2dictionary'] = true 689 | c2Dictionary['data'] = getProperties(this.persistentData) 690 | ret.set_string(JSON.stringify(c2Dictionary)) 691 | } else { 692 | console.warn('Persistent data requested but they weren\'t loaded. Did you forget to use RequestPersistentData?') 693 | ret.set_string('') 694 | } 695 | } 696 | 697 | Exps.prototype.GetHighscores = function (ret) { 698 | if (this.highscores !== null) { 699 | var c2Dictionary = {} 700 | c2Dictionary['c2dictionary'] = true 701 | c2Dictionary['data'] = getProperties(this.highscores) 702 | ret.set_string(JSON.stringify(c2Dictionary)) 703 | } else { 704 | console.warn('Highscores data requested but they weren\'t loaded. Did you forget to use RequestHighscores?') 705 | ret.set_string('') 706 | } 707 | } 708 | 709 | Exps.prototype.IsPluginOffline = function (ret) { 710 | if (this.runningOffline) { 711 | ret.set_int(1) 712 | } else { 713 | ret.set_int(0) 714 | } 715 | } 716 | 717 | Exps.prototype.GetActivePlayerDeviceIds = function (ret) { 718 | var arr = this.airConsole.getActivePlayerDeviceIds() 719 | 720 | var c2array = {} 721 | c2array['c2array'] = true 722 | c2array['size'] = [arr.length, 1, 1] 723 | var data = [] 724 | for (var i in arr) { 725 | data.push([[arr[i]]]) 726 | } 727 | c2array['data'] = data 728 | 729 | ret.set_string(JSON.stringify(c2array)) 730 | } 731 | 732 | Exps.prototype.IsAddShowing = function (ret) { 733 | ret.set_int(this.adShowing) 734 | } 735 | 736 | Exps.prototype.AdShown = function (ret) { 737 | ret.set_int(this.adCompleted) 738 | } 739 | 740 | Exps.prototype.GetThisDeviceId = function (ret) { 741 | if (this.isController) { 742 | ret.set_int(this.airConsole.getDeviceId()) 743 | } else { 744 | ret.set_int(0) 745 | } 746 | } 747 | 748 | Exps.prototype.MotionData = function (ret) { 749 | if (this.motionData !== null) { 750 | var c2Dictionary = {} 751 | c2Dictionary['c2dictionary'] = true 752 | c2Dictionary['data'] = getProperties(this.motionData) 753 | ret.set_string(JSON.stringify(c2Dictionary)) 754 | } else { 755 | ret.set_string('') 756 | } 757 | } 758 | 759 | Exps.prototype.GetLanguage = function (ret, deviceId) { 760 | if (!this.useTranslation) { 761 | console.log('Translation support not enabled. Please turn it on in your Construct 2 project settings.') 762 | ret.set_string('') 763 | } else { 764 | ret.set_string(this.airConsole.getLanguage(deviceId) || 'en-US') 765 | } 766 | } 767 | 768 | Exps.prototype.GetTranslation = function (ret, id, values) { 769 | if (!this.useTranslation) { 770 | console.warn('Translation support not enabled. Please turn it on in your Construct 2 project settings.') 771 | ret.set_string('') 772 | return 773 | } 774 | 775 | if (!id) { 776 | console.warn('Cannot fetch translation without a string id') 777 | ret.set_string('') 778 | return 779 | } 780 | 781 | if (values) { 782 | try { 783 | values = JSON.parse(values) 784 | } catch (e) { 785 | console.warn('Couldn\'t parse passed values for AirConsole GetTranslation.') 786 | values = {} 787 | } 788 | if (values.hasOwnProperty('foo')) { 789 | console.warn('Removed "foo" from translation values') 790 | delete values['foo'] 791 | } 792 | } else { 793 | values = {} 794 | } 795 | ret.set_string(this.airConsole.getTranslation(id, values) || '') 796 | } 797 | 798 | pluginProto.exps = new Exps() 799 | 800 | function getProperties(object) { 801 | if (object === null) return 802 | var data = {} 803 | $.each(object, function (property, value) { 804 | if (typeof value === 'object') { 805 | var c2Dictionary = {} 806 | c2Dictionary['c2dictionary'] = true 807 | c2Dictionary['data'] = getProperties(value) 808 | data[property] = JSON.stringify(c2Dictionary) 809 | } else { 810 | if (typeof value === 'boolean') { 811 | value = (!value) ? 0 : 1 812 | } 813 | data[property] = value 814 | } 815 | }) 816 | return data 817 | } 818 | 819 | /** 820 | * Check if a given string is JSON or not 821 | * @param string 822 | * @return false or the parsed object 823 | */ 824 | function parseJSON(string) { 825 | try { 826 | var obj = JSON.parse(string) 827 | } catch (e) { 828 | return false 829 | } 830 | return obj 831 | } 832 | 833 | }()) 834 | -------------------------------------------------------------------------------- /plugin/info.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | plugin 4 | AirConsole2 5 | 1.8.0.4 6 | Psychokiller1888 for N-Dream AG 7 | https://www.airconsole.com/ 8 | https://github.com/AirConsole/airconsole-construct2/wiki 9 | Extend your game with local multiplayer fun 10 | 11 | --------------------------------------------------------------------------------