Connecting the virtual and physical world: how to build a Minecraft companion app with Azure

For some time, some coworkers and I have wondered if it would be possible to get Minecraft talking to Azure. The recent news about Microsoft and Minecraft gave us some extra incentive to try out out… so we decided to build a companion mobile app using Azure Mobile Services. The app sends me a push notification if someone enters my house in Minecraft and also lets me check what items I have in my in-game inventory. 

image

The basic design is as follows:

  1. Certain objects in Minecraft are connected to computers in the game. This includes things like a pressure plate and a chest.
  2. The computers send HTTP requests to a mobile service, passing in some simple payloads. This is implemented via some simple programs running on the computers.
  3. The mobile service can store data in a table or send a push notification
  4. The mobile app is connected to the service to pull table data and receive notifications.

Setting up the mobile service

First, we need to set up endpoints in the cloud for Minecraft to send information to. This is needed because the app can’t talk directly to the game.

  1. Create a Node.js-based mobile service in the Azure Management portal.
  2. On the API tab, create a new custom API called notifyallusers, using the code available here. This API simply takes any message passed in the body of the HTTP POST and sends it as a push notification to all subscribed devices.
  3. Create another API called updateinventory, using the code from here. This API takes any key=value pairs in the body of the HTTP POST and tries to insert them into an Inventory table.
  4. We need to create the Inventory table, so head over to the DATA tab and create it. You can leave the permissions as is.
  5. Head over to the PUSH tab and complete sections 1 and 2 of this tutorial to get your service set up to send push notifications. Don’t worry about the server or client code, just do the configuration steps in the portal.

Now your mobile service is now all set up to receive requests. Let’s now go into Minecraft and configure it to send requests. 

Minecraft setup

Before we even launch the game, we need to make sure we have the right mod installed, since programmable computers are not part of the game by default. In this example, we used ComputerCraft, which, lets you write simple Lua scripts and run them in the computer.

Once you have the mod running, you need to do the following:

  1. To monitor the contents of a chest, put a Trapped Chest directly adjacent to a Computer (in my case it was right on top).
  2. To set up the push notification when someone enters a house, place a Pressure Plate right behind the door. Then put a Computer somewhere nearby. Connect the two by putting down Redstone to connect the two. 
  3. We now need to load the right programs into the two computers. The computer attached to the pressure plate will use the script AlertIntruder and the other one will use the script PostInventory. Be sure to change the URLs in the scripts to point to your own mobile service.
  4. Typing the script in is quite cumbersome, so it’s easier to use Pastebin as an intermediary. Create a paste with the code and get the ID of the paste.
  5. In the computer, type pastebin get ID scriptName.
  6. Now that the script is downloaded locally, you can run it easily by typing it in (with the above example, just type scriptName). If you need to stop the program, hold down Ctrl + T.
  7. Make sure both scripts are downloaded and running on both computers.

When done it will look something like this:

image

Run the app

Now that the mobile service is set up and Minecraft is ready to send requests, the last part is to get our app set up and connected to the mobile service. 

  1. Get the source for the app from here and open it in Android Studio.
  2. Download the right libraries from here and drop them in the lib folder. You’ll need to grab both mobileservices-2.0.1-beta.jar as well as notifications-1.0.1.jar, the other dependencies should get brought down by Gradle when you build your project in Android Studio.
  3. The last step is to configure the connection information for your mobile service. Copy the file Secrets.java.example and rename it to Secrets.java. You will then need to copy your mobile service URI and app key from the Azure portal and paste them into that file. You will also need your project ID from the Google APIs Console.

You should now be good to go! Stepping on the pressure plate should cause a push notification to fire off on the phone. Opening and closing the chest should trigger an update to be sent to the service. Add or remove items and then use the Refresh button in the app to see the content update.

This quick example is just the start of what you can do with the awesome capabilities provided by Azure Mobile Service. Excited to see what other cool Minecraft hacks you’ll put together using these tools!

Can almost see our place at 4:00 and Kandahar.

Can almost see our place at 4:00 and Kandahar.

(Source: skybox.com)

Tags: Burning Man

Like it was yesterday.

Droidcon NYC 2014 keynote slides and code

Thanks to all who joined me for the Droidcon NYC 2014 keynote on Sunday morning. I’ll share the code and video recording as soon as it’s available, for now here are the slides.

A walktrhough of the Minecraft demo and how to build it is now available here.

EF development-time initializer for your .NET-based mobile service

Update: look at that… based on your feedback we’ve baked this right into the product (starting with version 1.0.342 of our NuGet package). Check out this tutorial for instructions on how to use the new initializers, and please disregard the code below. What’s in the box works better!

A few folks have encountered an issue while developing a .NET-based mobile service using our VS template or portal quickstart. If you publish your mobile service to the cloud and then make some changes to the model and re-publish, the EF initializer will fail complaining that it doesn’t have sufficient permissions. The error might look like so in the Logs tab of the portal:

Exception=System.InvalidOperationException: This operation requires a connection to the 'master' database. Unable to create a connection to the 'master' database because the original database connection has been opened and credentials have been removed from the connection string. Supply an unopened connection. ---> System.Data.SqlClient.SqlException: Login failed for user 'zXCBHhDWhTLogin'.

This is the case because by default we use the DropCreateDatabaseIfModelChanges initializer, but the user under which your mobile service accesses your Azure SQL database doesn’t have the permission to drop the database.

To work around that we have developed an alternative initializer that does largely the same thing, but works using the permission set we already have. Please exercise caution: this initializer will delete your data. It is intended to be used during development while you are experimenting with your model and database schema.

Here is the example: instead of dropping the whole database, it just drops the tables used by your mobile service:

public class DropCreateSchemaTablesIfModelChanges<TContext> : CreateDatabaseIfNotExists<TContext> where TContext : DbContext
{
    private string schemaName;

    public DropCreateSchemaTablesIfModelChanges(string schemaName)
    {
        this.schemaName = schemaName;
    }

    public override void InitializeDatabase(TContext context)
    {
        if (context == null)
        {
            throw new ArgumentException("Context is null");
        }

        if (String.IsNullOrEmpty(schemaName))
        {
            throw new ArgumentException("Please provide a non-empty schema name");
        }

        if (context.Database.Exists())
        {
            try
            {
                if (context.Database.CompatibleWithModel(true))
                {
                    return;
                }
            }
            catch
            {

            }
            finally
            {
                var command = @"
                    DECLARE @tableName NVARCHAR(500)
                    DECLARE cur cursor
                        FOR
                            SELECT '[' + TABLE_SCHEMA + '].[' + TABLE_NAME + ']'
                            FROM INFORMATION_SCHEMA.TABLES
                            WHERE TABLE_SCHEMA = @ms_schema
                    OPEN cur
                    FETCH NEXT FROM cur INTO @tableName
                        WHILE @@FETCH_STATUS = 0
                        BEGIN
                            EXEC('DROP TABLE ' + @tableName);
                            FETCH NEXT FROM cur INTO @tableName;
                        END
                    CLOSE cur
                    DEALLOCATE cur";

                var cs = context.Database.Connection.ConnectionString;

                using (var objConn = new SqlConnection(cs))
                {
                    objConn.Open();
                    var objCmd = new SqlCommand(command, objConn);
                    objCmd.Parameters.Add("@ms_schema", SqlDbType.NVarChar, 64);
                    objCmd.Parameters["@ms_schema"].Value = schemaName;
                    objCmd.ExecuteNonQuery();
                }
            }


        }

        // Make sure all the tables are there
        base.InitializeDatabase(context);

        this.Seed(context);
        context.SaveChanges();
    }

    protected virtual void Seed(TContext context)
    {
    }
}

To use this initializer, create a class that inherits from it so you can specify a Seed method with your sample data:

public class myAwesomeServiceInitializer : DropCreateSchemaTablesIfModelChanges<myAwesomeServiceContext>
{
    public myAwesomeServiceInitializer(string schemaName) : base(schemaName)
    {

    }

    protected override void Seed(myAwesomeServiceContext context)
    {
        List<TodoItem> todoItems = new List<TodoItem>
        {
            new TodoItem { Id = "1", Text = "First item", Complete = false },
            new TodoItem { Id = "2", Text = "Second item", Complete = false },
        };

        foreach (TodoItem todoItem in todoItems)
        {
            context.Set<TodoItem>().Add(todoItem);
        }

        base.Seed(context);
    }
}

Then, put the following in your WebApiConfig.Register method:

Database.SetInitializer(new myAwesomeServiceInitializer(ServiceSettingsDictionary.GetSchemaName()));

Hope this helps!