Introduction
I think my first workshop project is finally complete, so I’m ready to share!
Some of you may remember the game type “Oddball” from Halo 2, which involved holding a skull for a certain amount of cumulative time during a match to win. I’ve recreated this style of game in Overwatch using the FFA Deathmatch gametype as a base to build upon. Play testing has been ludicrous fun when I could get enough people to fill the lobby, so I look forward to seeing what the rest of the community thinks of this one!
How to Play
The basic ideas are simple, but the meta-game is actually quite complicated. Here’s the basics:
- Touch the ball to pick it up
- Kill other players to make them drop the ball
- Score a point by holding the ball for 3 seconds
- Win by scoring 50 points, or by having the most points when time runs out
As you play, you’ll find that getting in fights away from the objective will slow you down, but that it is actually beneficial to deal damage to everyone near the ball in case they pick it up before you do. The result is a chaotic brawl that moves around the map in a path dictated by whoever holds the ball, giving ample opportunity for game experiences similar to standard FFA alongside objective-based gameplay with more interesting dynamics. Attacking anyone who isn’t the ball carrier is a gamble but has a compelling payoff, because you need cooperation to corner the ball carrier but you also need to be the last man standing after the ball drops to gain control over it.
Game Balance Issues and Solutions
As you might imagine, there are a few issues with balance in this game type but most are dealt with handily when the lobby is full. It’s very difficult for one player to hold the ball against 11 other people trying to kill them, no matter what hero they are playing! There are two exceptions, and I’ve made special scripts for each of them that I’d like to go over here.
Particularly on Chateau Guillard, Lucio and Pharah are both able to use Wall Riding or flight abilities to loiter in areas of the map that are inaccessible to other heroes. Each of these heroes required a unique but similar solution in order to reign them in without sacrificing their abilities or game play.
The solution I chose was simpler for Lucio, of the two. A script disables Lucio’s jump button (and by extension his wall riding ability) after 10 seconds of not touching the ground while holding the ball. This allows Lucio just enough time to wall ride around the outside of Chateau Guillard’s longest stretch of outside wall, but if he wastes any time loitering with the ball outside the accessible area of the map, he will fall.
Like Lucio, Pharah’s limitation as a ball-carrier is based on a 10 second timer. However, unlike Lucio, Pharah is expected to be able to loiter in the air almost indefinitely during normal game play. Pharah’s timer does reset when she touches the ground, but it also resets when Pharah moves in to line of sight of the nearest walkable terrain. This allows Pharah to play far out over the water and fly indefinitely even with the ball, but ensures that she always presents a reachable target for everyone else. Players shouldn’t notice her limiting script unless they’re actually trying to take the ball somewhere they shouldn’t. As with Lucio, expiry of the timer results in Pharah falling out of the sky. Both timers are displayed in the corner of the player’s screen.
Wrecking Ball is a third possible consideration for scripted limits and I did develop a script to disable Wrecking Ball’s ability to roll along the ground (but nothing else) while holding the ball, but given how hard Mei counters him with 3 or more people in the lobby I decided not to include that feature in this release. There are other examples of questionable hero mechanics, but I think any I haven’t addressed can be countered without requiring everyone else in the lobby to switch to a Lucio or Pharah. Sombra just needs someone camping the translocator. Tracer can only recall backward into the pack chasing her. For every remaining potential balance problem, there is a hero-selection-based solution or an exploitable weakness.
I humbly request for any modifications made to this game mode to keep this ideal in mind: all heroes should be playable and as close to original functionality as possible. I like the idea of people jumping into Oddball and using the same skills they’ve developed everywhere else in Overwatch. That said, I think there’s also a lot of fun that could be had by forcing all players on to the same hero at set intervals. Especially Reinhardt.
Interesting Notes and Challenges
While the scripts for specific heroes took a considerable amount of time to get right, the most time-consuming part was trying to figure out why everything that depended on the Nearest Walkable Position function has a tendency to randomly break. I don’t know what the proper solution is (probably something the Overwatch devs have to handle), but I did find a workaround. When Nearest Walkable Position doesn’t give a valid position (for whatever reason), the result will be not equal to true. Using that discovery, I store the last known good Nearest Walkable Position everywhere that I use it as a backup source for that data.
Also included is a method for tracking the average position of all living heroes, which is stored in the global variable O and used to determine where the ball will spawn both at the start of the game and any other time that no ball is in play (such as ball carriers disconnecting or becoming spectators). This allows the game mode to adapt to any map with dynamic ball placement. To accomplish the task, I had to learn and leverage some of the execution control statements like Skip alongside prior knowledge of how to code for multi-threaded applications using semaphores. While I don’t know if the workshop feature actually uses more than one thread to run its scripts, I was unable to ensure a known order of execution using the Ongoing - Each Player event I chose to work with. Approaching the problem as if it were a multi-threaded function led to success, but I’d be interested to hear of alternative solutions to do the same thing using Ongoing - Global events and, perhaps, some kind of array functionality I haven’t discovered yet.
I realize that the previous two paragraphs are probably not everyone’s cup of tea, but I hope that they help people who are trying to learn the Workshop while looking at my code. (Is it really code when you’re just clicking on it?) So without further ado, the part most of you are probably here for follows…
Workshop Import Code
Y2G72
I look forward to your feedback!