Danmaku Fortress
An almost-TC Bullet Hell mode for TF2 (and may be ported elsewhere)
Not for the faint of heart...
Video Demonstration
Files
df_sourcemod.zip - Attached at the bottom of this post. Configs, sources, includes, and compiled plugins. To install, drag and drop this sourcemod dir onto your server's sourcemod dir. danmaku_fortress_resources.zip - Materials, models, sounds for the core and the pack-in bosses. Also contains a map that DF will play on. These MUST exist on the server side. I would recommend installing these under custom/danmaku_fortress instead of download, as many of the models are likely to be updated with better animation if this game mode finds an audience. (and update means "new models added, old models removed", since client asset overwrite is impossible) danmaku_fortress_fastdl.zip - OPTIONAL FastDL files, put these on your FastDL server.
tf2items - It's a long story...and frankly it makes my sanity hurt. Read the Known Issues for more information.
Recommended Plugins
ThirdPerson (non-cheats version) - Giving the player the option to swap between first and third person is great. If this is missing, the df_tp and df_fp commands are available, but your players won't instinctively know to look for them.
Interacts Badly With
Instant respawn (absolutely will screw up this plugin), however TF2's not-so-instant respawn should be fine, since it's actually a 4 or so second delay.
Any aggressive auto-balance plugin which also targets the living (will cause players to forfeit), TF2's autobalance is already disabled in code for this reason.
Class limit plugins could be a problem, since all combatants are forced pyro.
Any plugin that prevents OnMapStart() from executing. (i.e. a quarantine system -- not needed for this plugin, it auto-quarantines)
Freak Fortress 2 needs to be quarantined as it can interfere with the spawn/team assignment code and stop matches before they can start.
What is Danmaku Fortress?
Danmaku Fortress is a unique game mode based on Bullet Hell, with obvious heavy inspiration from Touhou. It's intended to be danmaku in a 3D arena, which really hasn't been attempted much.
All battles are 1 on 1. One player is the "hero", which is the typical danmaku protaganist. (i.e. Reimu Hakurei) The hero has a tiny hitbox that is really just a point in 3D space. The hero typically has only two abilities, standard fire and a desperation ability. The hero can have more abilities and even cooldown on said abilities, but as of v1.0.0 none of the pack-in heroes use this feature. Standard fire is "shoot forward, stuff dies". Each projectile does damage to the boss. The desperation ability is like a bomb in danmaku games. What the bomb does varies widely, but it always makes the hero invincible for a short time. The hero has a limited number of lives, and they lose if they die at 0 lives. Depending on the hero's difficulty setting, the hero will automatically use bombs when hit, or always lose a life when hit.
The other player is the "boss", antagonists like Flandre Scarlet and Koishi Komeiji. The boss has a larger hitbox than the hero, and has a life bar instead of a lives/bombs system. Some bosses even have phases, which determines what abilities can be used based on the boss' health. Boss abilities are far less direct than hero abilities, and are meant to turn the arena into an everchanging minefield that the hero must navigate. Bosses are also slower than heroes, necessary for balance reasons.
The plugin supports 5 sub-arenas, in other words 5 games with 2 players going on at once, for a maximum of 10 players at a time not sitting bored. If you plan on making a dedicated server for this mod, 16 slots could easily be enough.
Danmaku Fortress is built from the ground up, and one of the best features for server operators is that it only runs on maps which are specifically made for the mod. Maps without the prefixes df_, danmaku_, or touhou_ will cause the plugin to temporarily quarantine itself. It should not cause other plugins to malfunction -- let me know if this isn't the case! Another interesting feature is its use of reflection instead of natives and forwards, which remove load order concerns which prompt other modular mods to quarantine its subplugins. (i.e. FF2 with the .ff2 extension)
It features a lot of customization options, more than I could explain in this blurb. There are tons of cvars for aesthetics and gameplay balancing. It includes a music player which I wrote specifically for game music, and this can be customized as well. It has a modular design which allows for hero, boss, and ability creation. I built it from the ground up but took inspiration from the good features of FF2, like similar config file designs and the modular concept, while focusing on the framework early on with a set code structure in mind. The code is neat, readable, and categorized, while the "one game frame" design philosophy along with its time synchronization ensures stability.
Controls
The controls are as follows. They're not so great, but given the limitations of the engine (i.e. pitch clamped to [-89.0..89.0]) it's the least awkward I could come up with. 6dof would be great, but it's just not possible -- and I figured the control scheme would fit best with the precision of danmaku games, versus Valve's fly/noclip controls.
WASD - Move around, but it's limited to the x/y axis. Jump - Move up Secondary Fire - Move down Primary Fire - Use ability (if autofire is enabled for heroes, this key stops firing) Reload - Change ability Use - Change ability in reverse. Hidden feature since use isn't bound in TF2 by default. Special or Crouch - Precision mode, move at half speed. (note: Special defaults to middle mouse) Call for Medic - Use bomb (hero only, Hard difficulty or higher)
Main Menu and Commands Main Menu
Spoiler
Main menu accessed via /df:
Prints a help message to the user.
Swaps between never be player, prefer hero, prefer boss, and no preference.
Toggle hero autofire
Select a hero (this is stored forever until the user changes it)
Select a boss (this is stored forever until user changes it)
Change difficulty (last menu option since it's not recommended for new players) Difficulty affects your final score, along with your opponent's.
Normal difficulty is the default. Bombs save your life.
Hard difficulty is where "balance" is meant to be centered. Bombs must be activated manually. Get hit and you lose a life.
Lunatic difficulty is like Hard, but you don't get more bombs on lose life.
Player Commands
Spoiler
df_queue - View queue points
df_music - View music chooser. Danmaku Fortress has a custom coded music player made especially for game music. I'm thinking of someday making a standalone plugin with just this.
df_tp and df_fp - Thirdperson and firstperson commands. These are only usable if the Thirdperson plugin is not loaded.
df_tutorial - An unadvertised command (borderline debug) that prints all tutorial messages.
df_never, df_wanthero, df_wantboss, df_wantany, df_boss, df_hero, df_difficulty, and df_autofire are all the commands invoked by the main menu.
df_help, danmaku, and touhou are all aliases for the df command.
Admin Commands
Spoiler
df_cvars - Reloads all cvars from file.
df_botlogic - Enables rudimentary bot logic, for both puppet and standard bots.
df_addpoints (partialName) (points) - Adds (or subtracts) queue points from a user. Negative points will prevent a player from participating, which can be used as an admin punishment.
df_forcehero (partialClientName) (partialHeroName) - Forces a player to be a specific hero. Also provides temporarily override for permissions. Note that it does not force them to be a hero next round, nor does it change their hero/boss/never preference.
df_forceboss (partialClientName) (partialBossName) - Forces a player to be a specific boss. Also provides temporarily override for permissions. Note that it does not force them to be a boss next round, nor does it change their hero/boss/never preference.
df_amianadmin - Not meant to be invoked. It's used in a workaround since a couple expected methods for detecting if someone is admin failed. Will be removed once someone sets me straight. :p This is used in connection with having characters that only admins can access. ("debugboss" is one of these characters)
Debug Commands
Spoiler
These commands are compiled out of the plugin by default. You must set DEBUG_MODE_ENABLED to true for them to be usable. They can be disruptive to gameplay, and should not be used on a live server.
df_forceenemyfire - Forces the enemy to fire once. Best used for testing boss abilities with a puppet bot.
df_forceiterate - Forces the enemy to iterate ahead one ability. Best used for testing boss abilities with a puppet bot.
Cvars
Spoiler
The public version cvar is: danmaku_fortress_version
All of the others are set to FCVAR_PLUGIN.
df_tutorial_interval (default 60.0) - Interval between tutorial messages.
df_start_delay (default 10.0) - Delay before game actually begins, after two people are brought into said game.
df_new_life_invincibility_duration (default 5.0) - Duration of invincibility when hero uses a life.
df_bomb_invincibility_duration (default 3.0) - Duration of invincibility when bomb is used.
df_vertical_multiplier (default 0.35) - Vertical speed multiplier. Vertical speed is recommended slower than horizontal speed, mainly for balance reasons.
df_postgame_delay (default 10.0) - Delay before someone can enter one game after leaving another. Also the duration of win/lose sounds, before other sounds start playing.
df_max_games (default 5) - Admin-set limit for number of max games. (5 is the hard maximum, but you can set it lower)
df_queue_point_grace_time (default 25.0) - Grace period before queue points are lost if someone forfeits against you.
df_queue_points_wait (default 10) - Queue points for people waiting in line.
df_queue_points_win (default 9) - Queue points for winner of a match.
df_queue_points_lose (default - Queue points for loser of a match.
df_queue_points_stalemate (default 300) - Queue points for a stalemate. Keep in mind that unless admins go around slaying everyone, this is an incredibly rare event. You'll probably never see one occur naturally.
df_queue_points_early_forfeit_mult (default 0.0) - Early forfeit multiplier for the forfeitter. (intended as a penalty, the victim loses no points) [0.0 to 1.0]
df_queue_points_forfetted_against_mult (default 0.5) - If someone is forfeitted against after the grace period, they lose this amount of their queue points instead of all of them. [0.0 to 1.0]
df_hero_normal_max_score (default 25.0) - Max scoreboard points for the hero, if the hero is on normal difficulty.
df_hero_hard_max_score (default 50.0) - Max scoreboard points for the hero, if the hero is on hard difficulty.
df_hero_lunatic_max_score (default 100.0) - Max scoreboard points for the hero, if the hero is on lunatic difficulty.
df_boss_normal_max_score (default 100.0) - Max scoreboard points for the boss, if the hero is on normal difficulty.
df_boss_hard_max_score (default 50.0) - Max scoreboard points for the boss, if the hero is on hard difficulty.
df_boss_lunatic_max_score (default 25.0) - Max scoreboard points for the boss, if the hero is on lunatic difficulty.
df_hero_ability_terminology (default "Desperation Ability") - Aesthetic name for hero bomb ability. You might change it to Spell Card if you're running a Touhou themed server.
df_boss_ability_terminology - (default "Boss Ability") - Aesthetic name for boss ability. You might change it to Spell Card if you're running a Touhou themed server.
df_hero_projectile_resize - (default 0.7) - Projectile resize for hero. Hit area is unaffected, but making the projectiles smaller makes it easy for the hero to see.
Lesser Known Features
There is some basic support for restricting characters to donators. Since there's probably many different donator/payment management systems out there, I've included a donator interface include df_donator_interface. Simply include that and call DFP_ClientIsDonator(clientIdx) once your donator is validated, and they should have access to donator characters. This probably needs to be redone on map change.
Even version 1.0.0 of this plugin stores players' cumulative score. What will actually become of this remains to be seen.
I had some second thoughts to the use of just music.cfg, and also added support for adding songs in boss configs. debugboss.cfg has an example of how this is done.
Bosses or heroes deemed easier than others can have their points adjusted with the point_adjust argument. Flandre's config file has this commented out -- I originally had her get a point nerf, and then found it easier to play against her than Cirno when my opponent was human.
Q&A
Q: Is this plugin limited to Touhou?
A: Nope. Even though the first characters included are Touhou characters, I plan on including Epic Scout as a hero and SHADoW may include Blitzkrieg as a boss. Characters from any domain are welcome.
Q: Can I create my own characters?
A: Yep. There are 18 default abilities to work with and coders can make even more. There a bunch of pre-programmed behaviors for rockets and spawners, with more planned in the future, which makes it pretty easy to code even complex patterns. You can also choose to manage the behavior of rockets and spawners, though this is more involved.
Credits Core Credits
Code and concept by sarysa
Heavily influenced by SHADoW and his Blitzkrieg FF2 boss, plus I got the idea for this while we "dueled" flying around in noclip.
Also influenced to a small degree by Freak Fortress 2, most notably with the config file designs, Kv code, and some of my stocks.
Rocket spawning code derived from Asherkin's from his Dodgeball plugin, with contributions from voogru
Beam spawning code derived from JasonFrog's IonAttack for FF2. (at least I think it was JasonFrog's, specific parts of Phat Rages are unsigned)
Chdata for help with the Waiting For Players fix.
Obviously also heavily influenced by Touhou Project by ZUN
Asset Credits
Reimu model by Nya
Cirno model also by Nya
Flandre model by Ciel
All models so far were ported to GMod by 1337gamer15 (I grabbed them from Kenji-kun's collection)
sarysa made Reimu's crappy gohei which looks more like a staff with a pennant at the end.
sarysa also added basic animated poses to Cirno and Reimu, and fixed some of the seam and bad texture param issues. (those not directly caused by UV mapping)
Music by ZUN and various remixers of his work.
The pack in map's skybox texture is by VelvetFistIronGlove, it's called "Skylab". Seriously, without this awesome texture, people would see my map for how awful it really is.
Special Thanks
BBG Theory for allowing me use of his development server for playtesting.
SHADoW NiNE TR3S provided a ton of feedback and even a little assistance throughout development.
BBG community members (and others) for testing. Those who I can remember include: SHADoW, Deathreus, GimpYTron, Cheese, and Cirno.
Known Issues
People have reported frame drops with the beams. I don't get them, and it seems to be with people running highest or near highest settings. This one will definitely take community feedback to overcome.
Players need to have outlines enabled to be able to enjoy this mod. (or at least, to enjoy it while playing boss) Out of necessity, the hero becomes a dot with an outline of their model's body when they go into third person. Otherwise, the hero would be unable to see.
The plugin is hardcoded in English. I get that this a problem. I went out of my way to make the interface as descriptive as possible, but it's also going to make adding localization support an utter nightmare. Plus this is the type of plugin where minimum CPU usage is the top priority, and until I better understand the impact grabbing strings from a localization file will have on load, this issue is going to be put off for a long time.
Players get stuck under the map during the pre-round. Due to maps needing to have a transparent "yes projectiles no players" boundary all around, it's unlikely this will ever be fully fixable. (Dart Mann tried, but that fix only works on the first player to show up)
People with a sharp eye might notice the hero's projectiles have a larger hitbox than they should. This is by design, to make it easier for the hero to see. It's managed with a cvar.
DO NOT ADD TF2 MUSIC to the music player unless you rip it and give it a new path. Some TF2 songs and sfx are hardcoded to automatically loop on their own, which will cause songs to quickly stack.
tf2items is a requirement because I get the error below if it's not included. I have no idea why I get this error. When it happens, the line number it fails on is wrong and the plugin completely ceases to function. (no more events received either) Removing this method entirely appears to fix the problem, but hitboxes aren't read in. If anyone knows what the hell this is and why having tf2items fixes the problem, please let me know:
Spoiler
Code:
L 07/07/2015 - 23:03:21: [SM] Plugin encountered error 16: Instruction contained invalid parameter
L 07/07/2015 - 23:03:21: [SM] Displaying call stack trace for plugin "danmaku_fortress.smx":
L 07/07/2015 - 23:03:21: [SM] [0] Line 6222, danmaku_fortress.sp::ParseHull()
L 07/07/2015 - 23:03:21: [SM] [1] Line 5987, danmaku_fortress.sp::KV_ReadRectangle()
L 07/07/2015 - 23:03:21: [SM] [2] Line 3547, danmaku_fortress.sp::DG_TryStartGame()
L 07/07/2015 - 23:03:21: [SM] [3] Line 4120, danmaku_fortress.sp::DG_Tick()
L 07/07/2015 - 23:03:21: [SM] [4] Line 6175, danmaku_fortress.sp::OnGameFrame()
The following two replies will contain hero/boss descriptions and instructions for subplugin coding, creating heroes/bosses, etc.
Reimu Hakurei (A) Primary Ability: Pew Pew Lasers Description: Rapidly fires quick projectiles at the enemy, two per volley, 5 damage per shot. Every second will also fire two homing shots which do 8 damage each.
Desperation Ability: Magic Seal Description: Outwardly expanding sphere that destroys all projectiles in its path and prevents new ones from spawning. Does not damage the boss and does not destroy enemy beams.
Reimu Hakurei (B) Primary Ability: Pew Pew Lasers Description: Rapidly fires quick projectiles at the enemy, two per volley, 5 damage per shot. Every second will also fire two homing shots which do 8 damage each.
Desperation Ability: Wild Exorcism Dance Description: Inspired by Touhou 12: Fires four wide beams, destroying projectiles they cross and damaging the boss. Each beam does a maximum of 50 damage during its lifetime, for an overall maximum of 200. The downside is projectiles not crossing the beam remain and can hurt you when the ability ends.
More hero descriptions will be added as more heroes are added. I'll list community heroes and links to them if they're not just reskins.
Boss Descriptions
Spoiler
Cirno Ability #1: Tidal Wave Description: Fires 9 columns of shots around the boss for 10 seconds. The boss cannot move or use other abilities while this is active.
Ability #2: Absolute Zero Description: Waves of ice spawn from the wall furthest from the hero. (long axis) Can be tricky for the hero to avoid.
Ability #3: Hailstorm Description: Hail randomly falls from the sky for 15 seconds. This ability alone might be why third person is recommended for heroes. A warning sound is played to the hero and the boss can't move during this warning period.
Ability #4: Baka Shot Description: Fires projectiles in the direction the boss is facing. These projectiles move around chaotically, and spawn in different sizes and colors. The boss can move around while using and even use other abilities while this one is active.
Ability #5: Frozen in Time Description: Immediately freezes all friendly projectiles on screen. They will resume moving in 3 seconds. They are harmful while frozen.
Ability #6: Flash Freeze Warning Description: After 8 seconds, freezes all friendly projectiles on screen. They will resume moving in 3 seconds. They are harmful while frozen. Thanks to the delay, this version can be combined with Tidal Wave and Absolute Zero.
Flandre Scarlet
Flandre Scarlet has three phases. These phases dictate which ultimate she can use. (her ultimates are abilities 5-7)
Ability #1: Persistent Pursual Description: Fires a bunch of projectiles with "lazy homing", they briefly home in on the enemy, slowly grind to a halt, and then "home in" once again. Will easily hit a stationary target. Projectiles are protected from going out of bounds and will despawn after a set amount of time.
Ability #2: Flawed Diamond Description: Creates a formation in the center of the arena of 5/8ths of a diamond. Combine it with other abilities to make it difficult for your opponent to navigate across the map.
Ability #3: Silent Stalker Description: After a delay, projectiles start spawning closer and closer to the hero. The hero needs to make wide movements to not allow them to catch up. These projectiles (after their own delay) fly straight at the hero.
Ability #4: Lavatein Description: Creates a beam wall in the direction she's facing. After a delay, the beam wall becomes harmful and rotates around the boss. It alternates between counter-clockwise and clockwise. The wall also fires rapidly moving projectiles. (it is meant to be feared, and she gets it in all phases) Boss is immobilized while using.
Ability #5: Violent Prison Description: Phase 1 ultimate. Spawns a grid of beams which fire projectiles from random locations. Easiest to avoid from above. Here's a screenshot. Boss is immobilized while using.
Ability #6: Four of a Kind Description: Phase 2 ultimate. Creates three AI clones of herself and fires targeted radial shots at the hero. Boss can move while using, which is probably why this is arguably her most powerful ability. Here's a screenshot.
Ability #7: Q.E.D. Ripples of 495 Years Description: Phase 3 ultimate. Fires targeted radial shots from her and from random parts of the map. Can be tricky to avoid due to the randomness, which makes it hard to rate this one against Four of a Kind. Boss is immobilized while using.
More boss descriptions will be added as more bosses are added. I'll list community bosses and links to them if they're not just reskins.
Lets face it -- the map that I created, touhou_duel_a7.bsp, makes manure seem like filet mignon in comparison. It's awful, but it covers all the technical specifications that a map needs. I strongly welcome desperately need talented mapmakers to make actually good maps. The thing is, it might not be all that enjoyable to do so. It's on the list below as well, but one of the biggest concerns with this plugin is the number of entities it caches. 1750 rockets are cached and used on standby for hero and enemy standard shots and spawners, plus around 100 are forced by the engine even on a minimalist map. Then another 50 or so are eaten by player weapons and viewmodels. (though I remove primary and secondary) These are all server side entities and the maximum is 2048. Luckily the beams are "free", but this leaves virtually no server-side entities for map makers to use. If you can make an awesome looking map with only client-side entities, great...but please try to avoid more than 5 additional server side entities.
So without further adieu, first the technical specifications:
Map name must have one of the following prefixes: df_ or touhou_ or danmaku_
DF maps actually contain five sub-maps. These sub-maps allow 5 games to run concurrently. (10 active players at a time)
Each submap needs to have a 128 hammer unit wide buffer where projectiles can go but players can't. This is the "despawn zone", rockets in this space are returned to a cache instead of destroyed.
Another 100-200 hammer unit wide buffer is needed to allow projectiles to spawn outside of the player play area. This obvious with Reimu when she's to the side of a map boundary -- don't want those rockets immediately being moved to the cache. (note: touhou_duel_a7 has 128 + 192 hammer units of buffering, or 320 total)
RED and BLU team spawns must be facing out toward the long x or y axis. (depends on which axis is "long")
RED and BLU team spawns must face each other.
The RED and BLU team spawns in each subarena must have no other subarena's enemy team spawn closer to each other.
It is recommended that each subarena is at least 2000 hammer units apart. (which should also help with the above requirement)
The subarenas must not overlap anywhere on the X, Y, or Z axis. If they overlap, column danmaku will leak between arenas. touhou_duel_a6, released with v1.0.0, did not have this feature.
The playable height of subarenas must be 384 hammer units. The other two axes on touhou_duel_a7 are 1024 x 512. You can mess with this and even make the arena oval shaped if you want, but don't change things too much. Movement limiting attacks like Flandre's flawed diamond depend on the some stable values to the X and Y.
There must be an enclosure between 9500,9500,9500 and 10500,10500,10500 on your map. This space is solely for storing unused projectiles. If this space is not present, the rockets will constantly be made anew and the server will eventually crash. For more information on why I had to use this caching system: https://forums.alliedmods.net/showthread.php?t=265374
Server side entity counts on your map must be kept low. I mean extremely low, like 5 or less. A map like vsh_2fortdesk_v8 for example, with 500+ server side entities, could not run this mod.
Here are the aesthetic recommendations:
A full skybox is a must. VelvetFistIronGlove's skylab texture is a good example texture to use, as players are absolutely guaranteed to see below the horizon.
Since this game mode is based on bullet hell, which itself is based on old shooters like Gradius, it couldn't hurt to have a moving background. (i.e. the treadmill background on Chamber of Exitgency)
Players need some way of knowing where the map boundaries are. I stuck those static prop lights all over for this purpose, but surely there's something nicer that could be used. Be sure to use entities that aren't server side entities, as 40 new entities would bring this plugin near the brink.
Your finished character should be added to characters.cfg when done. The default limit for heroes is 32 and for bosses is 96. All characters, heroes and bosses, go together in this one file.
Note that with all characters (and all other config files), String parameters will automatically parse \n as a new line. All integer parameters can also be in 0x format, which is great for specifying colors. (i.e. 0xff0000 is red)
Characters have the following top level parameters:
name: The character names that players see.
type: The character type. 0 is hero, which tends to only have a primary means of fire and a bomb, but can have additional abilities if desired. 1 is boss, which has multiple different abilities with cooldowns and other specific features. 2 is a reserved type known as "survivor", which will someday be available as a cvar option for inactive players to just goof around with. (it'll be made optional, off by default, because it'll eat up a subarena but reduce boredom for waiting players)
boss_hitbox: Boss-only parameter. The dimensions of the boss' hitbox, relative to their origins. It is formated minx,miny,minz;maxx,maxy,maxz. It can be anything. TF2 models tend to be around -10,-10,0;10,10,82 (they actually have multiple mini-hitboxes), but you could easily have a tiny hitbox for a sprite or a giant hitbox for a slow moving or immobile ship.
boss_health: Boss max health. You should factor in the size of the hitbox and the boss' move speed. Fast or small bosses getting less health, large immobile ships getting tons of health. Both Cirno and Flandre got 8000 health, for the sake of scale.
boss_phases: OPTIONAL boss-only parameter, defaults to 1 if missing. Number of phases for a boss, similar to Touhou's multiple boss life bars. Entering a new phase makes different abilities available for the boss to use. You can have up to 32 phases. (internally, it's bitwise)
ability_delay: Boss only parameter. A global cooldown of sorts. After using any ability, the boss has this much of a delay before another ability is usable. Does not stack with individual ability cooldowns.
hero_lives: Hero only parameter. The number of lives the hero has. Because the invincibility duration is currently global, and Lunatic mode is a concern, this shouldn't vary too much from the two Reimus. (who have 3 lives)
hero_bombs: Hero only parameter. The number of bombs the hero has. Because the invincibility duration is currently global, and Lunatic mode is a concern, this shouldn't vary too much from the two Reimus. (who have 3 bombs)
model: Model path for this character. If invalid, it'll just be a pyro or the last model swap this player had, so you'll want to fill it in. Model must not use backslashes, as \n gets parsed as a newline.
movespeed: Default move speed for this character. Abilities may change a boss' move speed temporarily, but when abilities end the move speed returns to this. Generally, heroes should be 500 or above, and bosses should be below 500. If any boss is faster than any hero (or almost the same speed), the boss will win easily by spawning projectiles on top of the hero, and that's no good.
access_limit: Limit access to characters. There are four levels. 0 is anyone can set this character, 1 is donators and admins only can set it but anyone can get it randomly, 2 is donators and admins only can set and play it, 3 is admins only can play it. Admin-only bosses will never appear on a random pick for anyone, since it'd be annoying to admins if they could.
point_adjust: Optional param for heroes and bosses. Default is 1.0. If below 1.0, the player of this character receives reduced scoreboard points at the end of a match, while their opponent gets increased scoreboard points. If above 1.0, the player of this character receives increased scoreboard points. (their opponent's points are unchanged)
music: Boss only parameter. Specify a song or multiple songs from music.cfg for this boss to use. Multiple songs are semi-colon separated, i.e. 1;2 for Aya's two songs. Unlike FF2, all users have completely independent music players. If there are multiple songs for a boss, it's even possible for the hero and boss to get different tunes.
song#, songlength#, intro#, introlength#: Boss only parameters. Allows you to specify music for your boss and these parameters work exactly how they work in music.cfg. See the Music Specifications for more info. You must start with #1, and songs will stop being read in once a missing number is reached. People making public bosses that they tend to distribute should use these.
resdir#: Directory with resources that won't be picked up otherwise. Material directories should be specified here. Custom projectile model reskins also need to be specified here. If your boss has custom sound effects, include that directory as well. You must start with #1 and once a missing number is reached, directories are no longer read in.
Character abilities have these parameters. Limit 16 abilities. Since it's a set number of abilities, there can be breaks (i.e. a boss with ability1, ability4, ability9 will still work):
name: The name for the ability that players see. It's seen in their HUD but enemies will also see it if it's a boss ability or hero bomb being used.
desc: The description that the user sees in the HUD. Tells them what it does.
source: Name of the subplugin for this ability, without the .smx.
ability_name: Name of the ability in the above subplugin. This is pretty much the same as FF2's.
is_bomb: Unique to heroes. If an ability is a bomb, it will activate when heroes press E (Hard or Lunatic only) or when the hero is hit. (Normal only)
cooldown: Cooldown for ability. Required for boss abilities, optional for hero abilities. Cooldown starts immediately once the ability is used, regardless of any delays or durations an ability may have. Hero bombs cannot have a cooldown.
phases: Optional boss parameter. If boss_phases is 2 or greater, use this to specify what phases the ability is usable in. 0 means "all phases". Separate phases with a semicolon, i.e. 1;3
arg#: Varies by ability. There is no limit to the number of args, and they can have breaks. You can even have weirdly numbered args like arg-42.
There are limits you need to know when designing new bosses:
Each game is limited to 350 projectiles. This limit is shared by the boss and the hero.
If the projectile limit is reached, new projectiles will not spawn until the old ones are removed from play. A warning will also print to the server console.
A good rule of thumb is heroes should never use more than 30 projectiles, so the boss can get the other 320.
There's currently a limit of 32 beams that can be spawned in an unspecified amount of time. This time could be per frame or something like every 50 milliseconds.
This limit is shared by all five arenas, so don't go overboard with the beams. More for bosses than heroes, and try not to pass half this limit in your game. (reaching 32 across all games on a single frame will happen once in a blue moon, but it's possible)
The plugin for "beams" is 100 per game, including those that don't render. (i.e. Flandre's Lavatein has non-rendering beams to improve collision) It should be more than enough, given the limitations to the rendering tempent beams.
Finally, the are many varied types of patterns that boss abilities will often let you set, and their parameters: Shot Patterns
Spoiler
Code:
// Type | param1 | param2 | notes
// =====================================================================================================================================
// 0 = straight | unused | unused |
// 1 = angled | yaw per second | speed gain per second |
// 2 = wave | min/max angle per second | wavelength (in seconds) |
// 3 = homing | angle per second | unused |
// 4 = chaotic | maximum angle per second (is random -ang,ang)| unused |
// 5 = lazy homing | duration before trying to re-home | unused | can only despawn at end of life
// 6 = resizing | factor of original size to end up at | duration of resizing |
// 7 = sharp turn once | angle (yaw) to turn, does so only once | delay before turn happens |
// 8 = sharp turn repeat | angle (yaw) to turn, does so repeatedly | interval between turns |
// 9 = chaotic protected | maximum angle per second (is random -ang,ang)| unused | can only despawn at end of life
// 10 = delayed seeking | delay before the projectile starts moving | acceleration time |
// 11 = delayed chaotic | maximum angle per second | delay before rocket moves |
Spawner Move Patterns
Spoiler
Code:
// Type | param1 | param2 | notes
// ==================================================================================================================================================
// 0 = immobile | unused | unused | recommended for subplugin-controlled spawners
// 1 = chaotic | maximum angle per second (is random -ang,ang)| unused |
// 2 = vertical circle | time for single rotation | unused
Spawner Firing Patterns
Spoiler
Code:
// Spawner's projectile spawn patterns, and their parameters:
// Type | param1 | notes
// ========================================================================================================================================
// 0 = straight | unused |
// 1 = radial | number of shots per volley |
// 2 = target | unused | it's like straight, but it fires directly at the enemy
// 3 = radial target | number of shots per volley | it's like radial, but the first shot fires directly at the enemy
// 4 = homing | angle per sec
Model Specifications
Spoiler
Character Models Heroes should be around the size of a TF2 model. Heroes have the following technical specifications and models should follow this suit:
The hero's hit spot relative to the origin is at 0.0,0.0,68.0. This is eye level for a pyro, and was chosen (instead of the torso) so players could have a good experience in first person mode.
You've seen in my demonstration video that Reimu is a dot. This is actually her uber skin. Heroes (not bosses) get an outline (so the boss can see them) and are ubercharged, so the uber skin should be a dot centered around 0.0,0.0,68.0. This allows the hero to see their hit spot, but also it allows heroes to be able to see in front of them while in third person.
Because the hitbox is so high, you might want to lift up short characters so their head is at 68.0. Or if you're making a hero be a spaceship or airplane, center it around 68.0. Everyone flies anyway so it doesn't matter.
It is recommended that you attach the dot and the dot's cel shading to a bone which is attached to the pelvis. The pelvis shouldn't move much (usually just up and down to simulate floating) and it should be easy to animate the dot's bone so it's always at 68.0.
Bosses can be any size. There are no limits. The Touhou characters tend to all be around the same height because that's just how Touhou is, but you can easily have tiny bosses or bosses that occupy the entire half of an arena. (though that latter example you'll want a separate model entity to represent the boss, since a rapidly turning battleship would be weird) If you're making a hero-sized boss, one that may itself be used as a hero in the future (i.e. Aya or Cirno), you might want to include the dot uber skin for the sake of reusing the model. Bosses will never use the uber skin while they're a boss. (though nothing is preventing subplugin writers from using the uber condition for specific bosses)
It might also be a good idea to animate the models, if you know how. It's too complicated to explain here, though the examples below will have animations which were done in blender. It's also possible to include directional animations into the reference pose, and have models that actually lean while moving forward/back/left/right. A basic animation should be more than enough to make the models not seem stiff, though leaning animations would be a huge improvement. (if this plugin is successful enough, I might do so for the bosses I've worked on)
Anyway, here are the source files for Reimu, Cirno, and Flandre: df_model_sources.zip
Projectile Reskins
Projectile reskins are pretty simple. Projectiles are always going to have a hit sphere by default. Funny hitboxes like heart-shaped ones would be too computationally expensive. The default projectile model has a radius of 10, so your model should roughly have this as well. If your model is a funny shape, i.e. Koishi's heart, it probably should be 11 or 12. Players get way less frustrated if a hitbox is smaller than the model looks, than they'd get if the hitbox is larger than the model looks.
The core of your model should be pure white, so it can be recolored to any color. If your model includes basic cel shading, which is highly recommended, (since backgrounds can also be any color) the cel shading should be black.
The music player was made specifically for classic melodic video game music, which typically have an intro that plays once and a much larger section that loops infinitely. You can have up to 100 songs specified in music.cfg. If you plan on adding music of your own and these songs aren't attached to a boss, it is recommended that you add them at the end. (100, 99, 98, etc.) Pack-in bosses will likely add to this list from the lower numbers for awhile. If you're creating a boss that you plan to distribute publicly, you should put these song specifications in your boss config instead. (more about that in the Character section above)
The way the music player works is users can type /df_music to pick a specific song. If the user picks "random shuffle" or they get out of a boss battle, music will do one loop and then move on to the next song. If the user picks a song or are brought into a battle, it'll loop forever. Under the hood, songs are not stopped when it's time for the next loop and this is due to internal timers and/or threading issues. Calling StopSound() immediately followed by EmitSoundToClient() for the same sound will abort that EmitSoundToClient() call. The pack-in songs all have a roughly one second fade out for two reasons -- to smooth the transition between songs for random shuffle, and to prevent dead air when a repeating song loops.
Anyway, here are the parameters for music:
song#: Path to the sound file. This is the main part of the song that loops.
songlength#: Length of the loop in seconds.
intro#: Path to sound file for the intro. This only plays once.
introlength#: Length of the intro in seconds. It's recommended that your intro only be about 100ms or so longer than this.
Aside from the music, there are also special event sounds and gameplay sounds. These can be changed or added onto. The documentation in music.cfg is wrong, numbered specials are numbered 1-5, not 0-5. They are as follows:
win#: Numbered special. Random sound that plays for the person who wins a match.
lose#: Numbered special. Random sounds that plays for the person who loses a match.
start_5s_#, start_10s_#, and start_15s_#: Numbered special. Song that plays during the countdown for a new round. Which one plays depends entirely on your setting for cvar df_start_delay. The longest one that'll fit in the time frame will play.
rocket_sound: Sound that plays when a boss creates a projectile. It's an ambient sound that plays at the location the projectile is created.
rocket_interval: Interval in seconds between sound plays. Set this too low and your players will lag and/or go deaf.
beam_sound: Looping sound that plays when a boss creates a beam. It's played directly to the hero and the boss.
beam_interval: Interval in seconds for the beam sound loop.
hit_sound: Hit sound for when the hero hits the boss. (default is the Touhou 12 "thump") When boss hits the hero, it just plays the TF2 ding. (ding with the hero would make them go deaf)
hit_interval: Interval in seconds for the hero hit sound.
hero_death_sound: Sound that plays when the hero loses a life. Plays directly to the hero and the boss.
Subplugin Specifications
Spoiler
Subplugins are unusual in Danmaku Fortress in that their status as a subplugin is loose at best. They are considered to be a primary plugin by Sourcemod and can operate many things independently, but in practice you'll really want to restrict your subplugin to receiving calls from the core and sending method calls back to the core. However, aside from OnGameFrame(), all other "core" methods are usable.
All subplugins merely need to include df_subplugin. This include also includes anything else you'd need, including danmaku_fortress.inc and all its useful stocks.
Required (and optional) Methods
These are the methods that need to be in your subplugin. (though a couple are optional)
Code:
public DF_InitAbility(gameIdx, owner, victim, String:abilityName[MAX_ABILITY_NAME_LENGTH], String:characterName[MAX_CONFIG_NAME_LENGTH], abilityIdx)
Required. When a character in one of the 5 games is loaded and has a specific ability, this is where you need to initialize and cache all the settings for that ability in that game. By design, heroes and bosses cannot have the same ability so there's no risk of overlap. df_default_abilities caches them in an array of size DG_MAX_GAMES, and many abilities don't even need to store the owner or victim's clients because certain stocks can be used to obtain things like the hero's hit location in world space.
It would be terrible practice to not cache ability arguments as to do it every frame would increase how often the config file is loaded and parsed -- it's not computationally cheap.
This is the only required method whose absence would cause your subplugin to fail to compile. The game frame is managed so events happen in a specific order, and the standard game frame is reserved for this reason. Hero abilities always go first, because bombs have to be refreshed every frame and new projectiles need to be prevented from spawning in range of a bomb. (it won't kill the player, but it will show projectiles flicker in and out)
Anyway, this game frame is where the logic of your code should execute. For example, if you're spawning projectiles over time, these spawns should be managed here. Do not use CreateTimer(). CreateTimer will execute whenever it decides to execute and will cause many easily preventable bugs to happen.
Code:
public DF_OnAbilityUsed(gameIdx, String:abilityName[MAX_ABILITY_NAME_LENGTH], Float:curTime, Float:powerLevel, userFlags)
Required. When an ability is used, do stuff. Depending on your plugin, you may end up doing everything at once, or you may simply set some Float time values which ManagedGameFrame() manages. You will typically change the boss' move speed here, if the ability is supposed to last awhile.
Code:
public DF_GameCleanup(gameIdx)
Required. When a game ends, your variables need to be cleaned up. If you don't clean things up, effects from one game could leak into another game.
Code:
public DF_OnSpawnerDestroyed(gameIdx, spawnerEntRef)
Optional. If your code manages its own spawners, this is how you know your spawner has been destroyed. (spawner entities are not "kill"ed, they're recycled, so this is the ONLY way you'll know)
Code:
public DF_OnRocketDestroyed(gameIdx, rocketEntRef)
Optional. If your code manages its own rockets, this is how you know your rocket has been destroyed. (rocket entities are not "kill"ed, they're recycled, so this is the ONLY way you'll know)
Oh, and in case you're wondering, yes, spawners are actually a type of rocket, and use the same entity pool.
API Documentation
These methods are invoked by your subplugin to do various things with the core. I'm only going to skim through these, putting comment descriptions before each:
Code:
// this one spawns a beam from start to end.
// mostly self-explanatory, but boss beams don't use the damage param. set it to 0 for bosses.
// flags have the DB_FLAG_ prefix.
// returns nothing.
stock DB_SpawnBeam(gameIdx, bool:isHero, Float:start[3], Float:end[3], Float:radius, color, damage, flags = 0)
// these two below are pretty much the same, but the first will determine an ideal firing position before calling the second
// spawns a rocket with all the parameters provided. see the character section for more info on patterns. they are DR_PATTERN_ in code.
// damage is not used by boss attacks, only hero attacks. set it to 0 for boss attacks.
// lifetime is an optional parameter that actually defaults to 10.0. it's the time before a rocket is removed from play.
// flags can do many unusual things with your projectiles. They are DR_FLAG_ in code. Do not set DR_FLAG_SPAWNER yourself.
// RETURNS: Entity for the rocket, or -1 on failure
stock DR_SpawnRocket(gameIdx, bool:isHero, Float:angles[3], Float:radius, Float:speed, color, damage, patternIdx, Float:param1, Float:param2, Float:lifetime = 0.0, flags = 0x0)
stock DR_SpawnRocketAt(gameIdx, bool:isHero, Float:spawnPos[3], Float:angles[3], Float:radius, Float:speed, color, damage, patternIdx, Float:param1, Float:param2, Float:lifetime = 0.0, flags = 0x0)
// these three freeze BOSS rockets only.
// frozen rockets will remain still for the duration and can still kill the hero, and will then resume moving as they were.
// the hit area for a freeze can be a rectangle, sphere, or the entire game.
stock DR_FreezeAllRocketsRectangle(gameIdx, Float:duration, Float:min[3], Float:max[3])
stock DR_FreezeAllRocketsRadius(gameIdx, Float:duration, Float:point[3], Float:radius)
stock DR_FreezeAllRockets(gameIdx, Float:duration)
// freezes the last rocket that spawned. it can be hero or boss rockets.
stock DR_FreezeLastRocket(gameIdx, Float:duration)
// set internal params for certain rocket patterns. as of 2015-07-08 the only rocket with an internal param is DR_PATTERN_ANGLE
// see the method ERR_OnUse in df_default_abilities for usage. it's used to allow angled rockets to fly in a diagonal formation, like a rocket ring.
stock DR_SetInternalParams(gameIdx, Float:param1, Float:param2 = 0.0, Float:param3 = 0.0, Float:param4 = 0.0, Float:param5 = 0.0, Float:param6 = 0.0)
// create a spawner at a position. note that spawners are actually a special type of rocket that spawn other rockets.
// spawners automatically have DR_FLAG_NO_COLLIDE applied.
// speed is the speed of the spawner, if your spawner moves around.
// modelIdx is the model index of your precached model. If this is -1, your spawner will have the default projectile model.
// see the character section for information about move and spawn patterns.
// rocket flags are flags for the spawner, which is a rocket. the most common flags are DR_FLAG_BOMB_PROOF and DR_FLAG_DISPOSE_PITCH
// RETURNS: entity index for the spawner. Only needed if you manage the movement and/or firing in your subplugin's code.
stock DS_SpawnSpawnerAt(gameIdx, bool:isHero, Float:spawnPos[3], Float:angles[3], Float:speed, modelIdx, movePattern, Float:moveParam1, Float:moveParam2, spawnPattern, Float:spawnInterval, Float:spawnParam1, Float:lifetimeOverride, rocketFlags)
// this must be called after the above. I split it into two methods since the other method is already enormous.
// parameters for the rockets that your spawner creates.
// if you manage the spawner yourself, these are all ignored.
public DS_SetSpawnerProjectileParams(gameIdx, Float:childRadius, childColor, Float:childSpeed, childDamage)
// set your boss' move speed.
// you will call this twice. once at the start of the ability if the boss' move speed should change.
// once again when the core is to believe your ability has ended. if moveSpeed is -1.0, it sets the boss to their default move speed.
stock DG_SetBossMoveSpeed(gameIdx, Float:moveSpeed = -1.0)
// you MUST call this when your ability ends. if you don't call this, your character will become stuck and unable to use any more abilities.
// only used for boss abilities.
// note that it's common to call this well before the ability stops doing things. this allows multiple abilities to be used at once.
stock DG_BossAbilityEnded(gameIdx)
// get the arena's area rectangle
// note that this rectangle includes the out of bounds area. you must apply DC_DESPAWN_RANGE to the values yourself
// to get to roughly where heroes and bosses can move around, you may also want to apply DC_RECOMMENDED_BUFFER
stock DG_GetArenaRectangle(gameIdx, Float:rect[2][3])
// get the min/max dimensions of the specified wall, along with the firing angle necessary to fire outward from that wall
// wallType is one of the six DG_WALL_ constants
// also don't forget that some maps may have the long axis be X, while others will have it be Y
// check random_wall_attack (RWA_) and patterned_wall_attack (PWA_) to see how the wall and ceiling attacks were done for Cirno
// Like the above, these do not account for the despawn area. Be sure to apply DC_DESPAWN_RANGE.
stock DG_GetWallMinMaxAndFiringAngle(gameIdx, wallType, Float:min[3], Float:max[3], Float:firingAngle[3])
// These two are frequently used together along with the above for wall attacks.
// Again, see Cirno's PWA_ ability for example usage.
stock DG_AdjustWallFiringPosition(Float:wallRect[2][3], const Float:angle[3], wallType, Float:offset = 1.0)
stock DG_GetIdealRectangle(const Float:wallRect[2][3], const Float:angle[3], wallType)
// I'm going to be honest, this method has a really terrible name. But it's locked in forever so I'd better explain it.
// You feed it an axis constant DG_AXIS_whatever and it returns a wall constant. This wall is the furthest from the hero on that axis.
// It's meant to be used for attacks where specifying a specific wall without a warning time would be unfair to the hero.
// RETURNS: A DG_WALL_ constant
stock DG_GetFurthestSpawnPattern(gameIdx, axis)
// If you've played the mod and seen how Cirno and Flandre both have attacks with a **CAUTION** message
// This method is how it's done.
// If the warning sound provided is invalid, it will default to the Administrator's warning voice clip.
stock DG_SetWarningMessage(gameIdx, String:warningText[MAX_CENTER_TEXT_LENGTH], String:warningSound[MAX_SOUND_FILE_LENGTH])
// Used by hero bombs to remove projectiles in various shapes: Sphere (radius), Rectangle, and Beam
// These must be called every frame. They're cleared at the end of the frame. (another reason I emphasize code execution order)
// Your bomb ability can use more than one of these at once.
// You can only have one of each type per frame, but these three can stack on each other.
stock DG_RadiusBombEffect(gameIdx, Float:radius, Float:heroPos[3])
stock DG_RectangleBombEffect(gameIdx, Float:point1[3], Float:point2[3])
stock DG_BeamBombEffect(gameIdx, Float:point1[3], Float:point2[3], Float:beamRadius)
// Get an ideal firing position for a client, be they hero or boss.
// Though you can actually safely pass a non-player entity as well.
// Just beware that this is one of few methods in this API whose first parameter isn't the game index.
stock DR_GetDefaultFiringPosition(clientIdx, Float:pos[3])
// Bosses should use this to determine where to direct projectiles.
// Flandre's rages especially make use of this, as at least 1 projectile per volley
// in most of her rages will directly target the hero.
stock DR_GetHeroHitPosition(gameIdx, Float:pos[3])
// These two are straightforward, but you'll rarely use them.
// If you devise some other damage source that your plugin manages on its own, you'll need to let the game know when hero/boss are damaged.
stock DG_OnHeroHit(gameIdx)
stock DG_OnBossHit(gameIdx, damage)
// these are all meant to be invoked inside DF_OnAbilityUsed()
// see df_default_abilities for example usage.
stock DF_GetArgInt(String:configName[MAX_CONFIG_NAME_LENGTH], abilityIdx, argIdx, defaultValue = 0)
stock Float:DF_GetArgFloat(String:configName[MAX_CONFIG_NAME_LENGTH], abilityIdx, argIdx, Float:defaultValue = 0.0)
stock bool:DF_GetArgString(String:configName[MAX_CONFIG_NAME_LENGTH], abilityIdx, argIdx, String:someStr[], length)
stock bool:DF_GetArgRectangle(String:configName[MAX_CONFIG_NAME_LENGTH], abilityIdx, argIdx, Float:rect[2][3])
stock DF_ReadSound(String:characterName[MAX_CONFIG_NAME_LENGTH], abilityIdx, argInt, String:soundFile[MAX_SOUND_FILE_LENGTH])
stock DF_ReadModel(String:characterName[MAX_CONFIG_NAME_LENGTH], abilityIdx, argInt, String:modelFile[MAX_MODEL_FILE_LENGTH])
stock DF_ReadModelToInt(String:characterName[MAX_CONFIG_NAME_LENGTH], abilityIdx, argInt)
just make a map that is 1 model, and put that in the skybox, set it as a prop_static and you're set
also, you really should make translucent sprites for the laser thingies (use additive mode so it simulates light blending).
Should also be some kind of hit indicator, or explosion when something hits.
You could probably do this with tempent particles.
I know you can parent tempents to entities, but it can be a bit buggy. Mabye worth a shot, I think the limit of tempents is something like 400 drawing at the same time or something...
__________________
Profile - Plugins
Add me on steam if you are seeking sp/map/model commissions.
just make a map that is 1 model, and put that in the skybox, set it as a prop_static and you're set
lol, it's a bit more involved than that. I'll have that third post finished in a few hours and/or tomorrow. This release has been exhausting as it is.
Quote:
Originally Posted by friagram
Should also be some kind of hit indicator, or explosion when something hits.
There are hitsounds (the touhou 12 "thump" for the hero and the tf2 "ding" for the boss) and when the hero is invincible, they flash. (though it's not as visible when hero is in third person, since they are a dot while in tp)
I have to be very VERY careful what I add in terms of aesthetics. This plugin stands at the top of a mesa, looking down, in terms of its entity usage.