bishop

Overwolf App Tutorial

Recommended Posts

Hey guys so I'm going to be posting up a 5 part tutorial on making apps for Overwolf! If you want any help with specific APIs let me know and I'll include them into this tutorial. A little background on me: I was a web developer for around 9 years and now I'm working with a tonne of different technologies. I'm also a part time instructor at Lighthouse Labs where we teach you how to be a badass developer in 8 weeks (if you live in Vancouver or Toronto). 

 

Alright let's make an app called Blastin Fools!

 

k5bNZnd.png

 

 

So first things first, Overwolf is in beta and things are constantly changing. It's very possible that the information here may be out of date in a few months or a few weeks. That being said the Overwolf API is really simple, while still being really powerful and having a tonne of cool stuff! It's a really exciting time for developers as more and more features are being added, and more games are being supported. Anyway let's do this!

 

Prerequisites:

  • You're down with me calling Overwolf OV for the rest of this
  • You've got the dev version of OV 
  • You got the demo app somwhere
  • You've skimmed through the "ODK introduction" and therefore know how to load an app into the OV software
  • You're using SublimeText, Notepad++, or an equally decent text editor

Project so far (github link)

 

Setup

 

Let's start with getting rid of the sub window from the demo. Open up the manifest and get rid of this:

"SubWindow":{
     "file":"Files/subwindow.html",
     "transparent":true,
     "resizable":false,
     "size":{"width":400, "height":300},
     "in_game_only":true
    }

Also in your index.html file we can get rid of these two bits:

function openSubWindow(){
    alert("the subwindow will only be visible inside a game");
    overwolf.windows.obtainDeclaredWindow("SubWindow", function(result){
     if (result.status == "success"){
      overwolf.windows.restore(result.window.id, function(result){
        console.log(result);
      });
     }
    });
   };
   <button id="open-subwindow" onclick="openSubWindow();">Open Sub-Window</button>

Remember any changes to the manifest require the app to be unloaded and reloaded in OV. Any other changes you can just close the app and reopen it.

 

PRO TIP: Sublime has a JSON Lint plugin that will validate your manifest JSON for errors, otherwise you should be using something like http://jsonlint.com/

 

Now that we have that out of the way the first thing we want to do is clean up the code. It's always a good idea to seperate your layout (HTML), from business logic (Javascript), and presentation (CSS). It's easier to read smaller files and keeps things more modular. Cut out everything between the <script> tags in index.html and paste it into a new file called script.js. Save this in the Files folder and then include it back in the html right before the body close tag:

<script type="text/javascript" src="script.js"></script>
</body>
</html>

You can try loading the app at this point to make sure everything works.

 

Project so far

 

PRO TIP: Save a copy of this project so far somewhere and use it as a base for your apps until you come up with your own base project.

 

Planning

 

We're gonna make an app called BlastinFools! It's going to be pretty basic (and kind of lame actually) but it's the first thing that I could come up with. Basically it's going to let us set a hot key that gets fired automatically when we press a button (say you're playing a game where there's a weapon cooldown and you want to auto-fire) and then keep track of how many kills you've made with it.

 

Before coding anything let's use user stories to plan this out:

 

1. As a user I want to see my username somewhere so I feel all warm and fuzzy
2. As a user I want to bind a hotkey and set a time frequency that it fires
3. As a user I want to keep track of how many kills I got in a match and in total
4. As a user I want to see a screenshot of my last kill
5. As a user I want to minimize and close the app

 

User stories are a great way to plan out a project and also keep yourself focused on one task at a time. So let's get to work!

 

Let the coding begin

 

So taking all the requiements from our user stories I've put together a quick sketch of what I want the UI to look like. You always want to design your UI around your content, not your content around your UI, so it's a good to have a rough idea of what you're going to be displaying.

 

R6Rhfa4.png

 

1. As a user I want to see my username somewhere so I feel all warm and fuzzy

 

Alright so looking at the documentation section for overwolf.profile there's a method called getCurrentUser(callback) which seems like it should return the current user. At this point if you don't know what a callback is in javascript you might want to read up about it, to explain it very briefly you're passing a function with code that will be executed to another function which will then execute the code when it's ready.

 

In this case we can't just use something like the code below because overwolf needs to get the data first.

var playerData = overwolf.profile.getCurrentUser();

Instead we need to do something like:

overwolf.profile.getCurrentUser(function(value){
  var playerData = value; //Once the player data is ready we can access it from here
});

Before coding anything let's punch this in to the console and try it out. Launch the app in OV and press ctrl+shift+i to bring up the dev tools and press the console button.

 

C43zw4K.png

 

We should modify the code a bit so it prints the result to the console:

overwolf.profile.getCurrentUser(function(value){
  var playerData = value;
  console.log(playerData);
});

And let's see what happens when we copy/paste it to the console:

 

PlUmZlk.png

 

Sweet we can see the result object in the console! Now let's print it in the app. Let's modify the existing "Hello, world!" code in index.html:

<h1>Hello, world!</h1>

We should add an ID and some default value:

<h1 id="usernameContainer">Hello, unknown user!</h1>

We want to update this label with the username so let's open up script.js and add our code to the bottom:

overwolf.profile.getCurrentUser(function(value){
  var playerData = value;
  document.getElementById('usernameContainer').innerHTML = playerData.username;
});

Now before we run the app it says in the docs that accessing overwolf.profile requires the "Profile" permission. This is a good point to give our app a custom name too so we don't make things confusing. Let's update the manifest, all we need to do is change the name, description, and add a new "Permissions" key with the profile permission:

{
"manifest_version":1,
"type":"WebApp",
"meta":{
  "name":"Blastin Fools",
  "version":"1.0.0",
  "minimum-overwolf-version":"0.77.10",
  "author":"Developer Name",
  "icon":"IconMouseOver.png",
  "icon_gray":"IconMouseNormal.png",
  "description":"Blastin Fools"
},
"permissions": ["Profile"],
"data": {
  "start_window":"MainWindow",
  "windows":{
   "MainWindow":{
     "file":"Files/index.html",
     "transparent":true,
     "resizable":true,
     "size":{"width":400, "height":300},
     "min_size":{"width":200, "height":200},
     "max_size":{"width":600, "height":500}
    }
  }
}
}

If your app is loaded into OV unload it and load it again, once you launch it your name should now be displayed!

 

Gca1OBo.png

 

User story #1 is done! Tommorow we'll look into some other Overwolf APIs and finish up user story #2.

 

Project so far

 

BONUS POINTS!

  •  If our app was running and a new user logged in the old username would still be displayed? What do the docs tell you about how to catch this?
  •  How would you implement a function to update the username?
Edited by bishop

Share this post


Link to post
Share on other sites

Thumbs up :)

 

I'd lobby for a mention of location.reload as alternative for reopening the app - it's a real time saver.

Everyone's a critic ;)

 

please explain! this concept is foreign to me.

Share this post


Link to post
Share on other sites

Link

 

It's simple really, you enter location.reload() in the dev console - page reloads.

Good enough for changes in html/js/css.

 

When one has the console open anyways, that's simply faster than reopening the app.

 

Sweet! Hmm... I'd opt for a button visible in debug mode then to avoid having to open the console. Thanks for the tip that's a huge time saver!

Share this post


Link to post
Share on other sites

Link

 

It's simple really, you enter location.reload() in the dev console - page reloads.

Good enough for changes in html/js/css.

 

When one has the console open anyways, that's simply faster than reopening the app.

 

F5 with focus on the console does the same thing - even faster (unless you set up a debug-panel as mentioned by op)

Share this post


Link to post
Share on other sites
I considered making my own thread, but instead I'll just hijack this one so the info is all in the same place (sorry bishop :smile: ).
 
I wanted to share a few of the methods I used to in my app. Most of the things I will cover are not super complicated, but they are little things that helped me offer more customization and functionality in my app. I'm admittedly a Javascript noob so if you see something that you have a better solution for feel free to share it.
 
Storing Window ID's
This is pretty basic, but you'll see me using the IDs throughout my post so I figured I might as well cover it.
function getWinID(name, ID){
  //Start the process of the window named, retrieve and save its ID, and then end the process if it is not the main window.
  overwolf.windows.obtainDeclaredWindow(name, 
    function(result){
      if (result.status == "success"){
	localStorage.setItem(ID, result.window.id);
	if(name != "MainWindow")
	  overwolf.windows.close(result.window.id);
      }
    }
  );
};
	
//Get ID's of each window. Window name works 90% of the time but I still use the IDs just in case
getWinID("MainWindow",'MainID');
getWinID("otherWindows", 'otherWindowsID');
 
GameInfo change handler
overwolf.games.onGameInfoUpdated.addListener( function(data){
  var closeOnEnd = <User's settings>;
  var minimizeOnTab = <User's settings>;
  var restoreOnTab = <User's settings>;

  if(closeOnEnd == true){
    if(data.runningChanged == true){
      overwolf.games.getRunningGameInfo( function(test){
        if(test == null)
	  overwolf.windows.close(localStorage.getItem("MainID")); //game closed so close the app
      });
    }
  }
      
  if(data.focusChanged === true){
    if(data.gameInfo.isInFocus  === true){
      if(restoreOnTab === true){
        //game is in focus so restore all
        restoreAllWindows();
      }
    }else{
      if(minimizeOnTab === true){
        //game is out of focus so minimize all
        minimizeAllWindows();
      }
    }
  }
});
 
Detecting app auto-launches
The first step is to set up the launch settings in the manifest.
{
  "data": {
    "launch_events": [{
      "event": "GameLaunch",
        "event_data": {
          game_ids": [7784]
        },
        "start_minimized": true
      }],
    ...
   ...
  ...
  }
}

After you have set the app to launch minimized, you can use a function like this. I run it shortly after launching:

function initLaunch(){
  var launch = {}; //Will store all the information gathered about the state of the game and app upon launching

  overwolf.windows.getCurrentWindow(function(data){
    launch.autoLaunch = !data.window.isVisible; 
      /*Because we set the autolaunch to minimize automatically we can assume that
        not being visible indicates an autolaunch (as long as you didn't make the 
        main window visible prior to running this function).*/ 
    checkRunningGame();
  });

  function checkRunningGame(){
    overwolf.games.getRunningGameInfo(function(data){
      if(data !== null){ //this could be shortened of course, but I like my truey falsey
        launch.focused = data.isInFocus;
        launch.playing = data.isRunning;
      }else{
        launch.playing = false;
        launch.focused = false;
      }
    
      // If user doesn't want app to autolaunch, and it autolaunched, close app.
      if(JSON.parse(localStorage.getItem("userWantsToAutolaunch")) == false 
       && launch.autoLaunch === true)
        overwolf.windows.close(localStorage.getItem('MainID'));
    
      if(launch.autoLaunch === true)
        console.log("auto-launch");

      else if(launch.autoLaunch === false && launch.playing === false)
        console.log("app launched before the game");

      else if(launch.playing === true)
        console.log("app launched after game")

      overwolf.windows.restore(localStorage.getItem('MainID'));
    });
  }
};
The formatting on this is getting to be a pain and I'm scared to lose my progress so I'm going to submit the post now. I'll be making another post that covers the solutions I came up with regarding recordings.
Edited by NathanSchwartz

Share this post


Link to post
Share on other sites

This post will be purely Recording stuff so I'm making a separate post. I will cover a few pitfalls, making capture recordings that include future duration in quick succession without recharge, and dealing with skipped recordings.

 

Dealing with turnOn() errors

The first pitfall I noticed was that only 1 app can record at a time. I used this, but don't forget to make turnOff() reverse the indicator.

function turnOn(callback){
  overwolf.media.replays.turnOn({}, function(result) {
    if(result.status== "success"){
      localStorage.setItem('recordingOn', true);
    }else if(result.error == "Already turned on." && JSON.parse(localStorage.getItem('recordingOn')) === false)
	//Either another recording app is running or you failed to turn off the process before closing last time.
  });
};

Making start-stop recordings get along with captures

This could easily be a problem that is specific to my app, but since I can't remember the exact details I won't speculate. I found that when I used captures before start/stop recordings I was getting an error that wouldn't let the recording start. To solve this I ended up detecting whether or not captures had been used previously and if they were I called turnOff() followed by turnOn().

 

 

Catching skipped recordings

I used a recursive solution to correct failed captures. It only will recurse for 2 types of errors (both of which have been confirmed to be fixed by this method) and the number of recursions is limited. The theory behind this is that it takes a small amount of time to process each capture, so if you get unlucky and try to capture during this period you wouldn't save the recording. In the most demanding testing I've put it through, this method achieved over 98% success even though there were many simultaneous capture requests.

function capture(before, after, recursed, recurseCount){
  overwolf.media.replays.capture(before, after, 
    function(result){},
    function(result){
      if(result.status== "success"){
        localStorage.setItem("url", result.url);
	overwolf.media.replays.finishCapture(result.url);
      }else if(result.error == "Replay is already capturing."){
      	if(!recursed){
	  console.log("wasn't recursed-already capturing");
	  setTimeout(function(){
	    capture(before, after, true, 0);
	  },300);
	}else{
	  console.log("recursed-already capturing", recurseCount);
	  if(recurseCount < 5){
            setTimeout(function(){
	      capture(before, after, true, recurseCount+1);
            },300);
	  }
	}
      }else if(result.error == "Replay not capturing."){
        if(!recursed){
	  console.log("wasn't recursed - not capturing");
	  setTimeout(function(){
	    capture(before, after, true, 0);
	  },300);
	}else{
	  console.log("recursed - not capturing", recurseCount);
	  if(recurseCount < 5){
	    setTimeout(function(){
	      capture(before, after, true, recurseCount+1);
            },300);
	  }
	}
      }
    }
  );
};

Rapid capture recordings w/ future duration in quick succession

Normally you can only have 1 recording taken at a time. If the recordings only capture past duration they can be taken in quick succession. However, if you are recording a bit into the future as well you will have to wait until the recording ends before taking any others.

localStorage.setItem("recordingLayers", JSON.parse(localStorage.getItem("recordingLayers"))+1);
setTimeout(function(){
  rec.capture(before + after, 1); //this references the capture function above
  localStorage.setItem("recordingLayers", JSON.parse(localStorage.getItem("recordingLayers"))-1);
}, after);

//I use 1 instead of -1 because at one point negative parameters were bugged and I never changed it back.

Keeping track of pending recordings

You probably already figured it out from the code above, but I use recording "layers" to track pending recordings. When a capture request is recieved the layer counter is incremented and when the setTimeout callback is executed it is decremented. Thus, if the counter is ever >0, you have a pending recording. This makes it super easy to check if there are pending recordings and allows you to warn users when they attempt to close the app or use turnOff().

 

 

I hope some of this was useful!

Share this post


Link to post
Share on other sites

 

Detecting app auto-launches
The first step is to set up the launch settings in the manifest.
{
  "data": {
    "launch_events": [{
      "event": "GameLaunch",
        "event_data": {
          game_ids": [7784]
        },
        "start_minimized": true
      }],
    ...
   ...
  ...
  }
}

After you have set the app to launch minimized, you can use a function like this. I run it shortly after launching:

function initLaunch(){
  var launch = {}; 
  overwolf.windows.getCurrentWindow(function(data){
    launch.autoLaunch = !data.window.isVisible; 
      /*Because we set the autolaunch to minimize automatically we can assume that
        not being visible indicates an autolaunch (as long as you didn't make the 
        main window visible prior to running this function).*/ 
    checkRunningGame();
[
  });

 

As an addition:

If you want to check if the App is launched through the gamelaunch event you can do the following to check it propably more reliable (e.g. if your main window doesn't start minimized):

// Overwolf actually sets a query-parameter if the window got opened through the gamelaunchevent
var autolaunched = location.search.indexOf('source=gamelaunchevent') >= 0;

This has to be done within the context (window) of your main-window obviously, but you then can set a flag globally for your application if needed.

Edited by Colorfulstan

Share this post


Link to post
Share on other sites

As an addition:

If you want to check if the App is launched through the gamelaunch event you can do the following to check it propably more reliable (e.g. if your main window doesn't start minimized):

// Overwolf actually sets a query-parameter if the window got opened through the gamelaunchevent
var autolaunched = location.search.indexOf('source=gamelaunchevent') >= 0;

This has to be done within the context (window) of your main-window obviously, but you then can set a flag globally for your application if needed.

 

Never would have thought of that, thanks!

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now