A lot of DM programmers don't like using libraries and I don't blame them - BYOND has a lot of libraries that are poorly written and a lot of well-written ones that perform useless tasks. This post is meant to show how handy the right library can be.
In Tiny Heroes I had already implemented one boss fight and it didn't take a whole lot of code, but it wasn't generic. I'd have to write just as much code again to add another boss fight. Eventually this would become too difficult to manage. I wanted to find a way to easily handle:
1. Starting boss fights. This includes tracking the status of the fight so that it can't start a second time once it's already in progress (but if you die, it resets, and can be started again). If the fight has been completed successfully it won't be started again.
2. Victory and defeat conditions. The game needs to be able to determine when the fight is over, and it if ended favorably.
I wanted to create some type of object that would handle this. My first idea was some type of global variable that's an instance of a datum, one instance per boss fight. The problem is that boss fights are not just collections of vars, they are connected to maps. The boss fight object needs to know these two things:
1. What starts the fight. Usually, it'll be "when a player steps on this tile, the fight starts."
2. What turfs are part of the boss's room. There can be multiple players participating in the fight, so to check when the players have been defeated it'll need to check if there are no living players in the boss's room.
The problem is that these areas will overlap because the place you step to start the fight is inside of the boss's room. This means that we can't use /area objects to define these regions (because each turf can only be assigned to a single area). Luckily, there's a library to make this possible!
The Region library gives us a new kind of object called /region. It's a type of atom so we can place it on the map. We create two regions, one for the boss's room and one for the trigger. When you run the game, the /region objects figure out what turfs also contain /region objects of the same type. When you enter a turf (and the turf's Entered() proc is called), the Region library figures out if you're entering a new region (it can figure this out because it knows what regions* each turf is in). This way we can use it to detect when you've entered a turf that triggers the boss fight.
* This is the key. "regions" there is plural because if you place multiple /region objects on the same turf, that turf will be inside multiple regions. You can't do this with areas.
All /region objects of a type are deleted except for one and the remaining one is removed from the map. The library does this automatically so that the region objects don't interfere with your game. If you did "for(var/obj/o in view())", you wouldn't want to iterate over a bunch of region objects. This has an added benefit because this is exactly what we want to happen for boss fights - we want a single, global instance of the boss fight object to store information about the fight (is it in progress? have you already won the fight? etc.)
In Tiny Heroes, I defined two types of regions:
For each fight you'd define a new child type of each of those two types. The /boss_fight region stores all of the information about the fight and is responsible for the logic. The /boss_start object is just responsible for detecting when the fight should start. When a player enters a /boss_start type of region, the corresponding /boss_fight object is found and told to start (these objects correspond by having the same name, ex: /boss_fight/boss_01 and /boss_start/boss_01).
Those objects have a basic default implementation that handles starting the fight (and making sure it's only started once), checking for victory/defeat conditions, and resetting the fight when you lose. With this generic stuff handled for all fights, this is all it takes to make a custom fight:
// ..() will return zero if the fight has already been started
// close the doorway
// center each player's camera
// spawn the boss
spawn(20 * world.tick_lag)
boss = new /mob/enemy/boss_01(locate(56, 21, 7))
// reset is called when all of the players died.
// ..() will return 1 if we should reset the fight
// open the doorway
// delete the boss
// The defeat and victory procs are named from the player's
// perspective - victory() is called when the player wins and
// defeat() is called when all players in the battle have died.
world << "defeat!"
world << "victory!"
// This doesn't need any code, the default behavior of it's
// Entered proc handles everything as long as it has the same
// name as the /boss_fight one (dungeon_01_boss_01)
The /boss_fight/dungeon_01_boss_01 object just has to define the new parts of the behavior. Most of the behavior is handled by the default implementation of each proc so we create a custom boss fight with very little code.
BYOND has lots of libraries that cover lots of topics. When you need to do something, most of the libraries will be useless to you because they're on a different topic. This leads to some interesting points:
1. There are lots of libraries you'll never use because you don't know that they exist. There are so many resources that you can't keep track of them all. If you don't use a resource often you won't remember it exists. There are so many junk resources on the hub that you can't simply look through the whole hub to find resources that would be potentially useful. Even if you could, you wouldn't remember most of them. What would help is...
2. People blogging about libraries they use. You'll never hear about the obscure library that you'd never need to use or the ones that are terribly written - people won't blog about those. If you read a blog post like the one you're currently reading, the Region library is more likely to stick in your head than if you stumbled across the library on your own. The blog posts give you a reason to use the library so you can say "yes, that sounds useful" instead of coming across the library with a clear mind and having to figure out "would this ever be useful?"
3. If you're going to make a resource (library, demo, etc.), take some time to find a topic that'll be useful. In three and a half years only 59 people have downloaded pif_ComplexNumbers. People seem to get a kick out of making things like this, but these creations aren't very useful to BYOND game developers.
As a comparison, Lummox JR has 18 hub entries on the developer page. These resources have a total of 24,075 downloads (an average of ~1337 downloads per resource) and they average ~12 combined downloads per day. Popisfizzy has 11 hub entries on the developer page. These resources have a total of 786 downloads (~71 DLs per resource) and have averaged a combined 0.79 downloads per day.
If you're going to make a resource, make something useful. It doesn't have to be monumental. My region library is very simple, 94 lines of code, much of which is whitespace and comments, but it's useful and has gotten over 200 downloads.
4. These points are surprisingly similar to the problems with BYOND game development - people want to make a game (or library) but don't have a good idea for one, so they end up making something terrible that nobody will ever enjoy (or use). The hub gets full of crappy games (or libraries) so if there's an old, undiscovered gem you'd never find it. People should just focus on making games (or libraries) that are fun (or useful). By blogging about how we develop games (or use libraries) we can identify better ways to use libraries (or develop games). When you start identifying dualities, I think that means it's time for bed.
I didn't have time to make a video (I never found my mic anyway), but I did take some new screenshots. I added backgrounds. Hopefully these will give the environments a little more detail and character:
Compare the last one to this screenshot that was taken before backgrounds were added.
The drop shadows look weird on the background because they backgrounds are supposed to appear distant. I'm not sure what I'll do about this, but overall I think it's an improvement. If nothing else, the backgrounds scrolling at a different speed than the rest of the image will make things more interesting.