Fighting games often have complicated series of steps to perform special moves, e.g. down, down & forward, forward & fire to perform Ken's Shoryuken fireball attack.
I wrote some code to see what it would require to add such moves into a control scheme. After that I realised I'd never use it anyway (I like writing strategy games, not action games).
The code below was written to accomodate:-
- charging up fire (or jump)
- step by step special moves
- rapid fire moves
I was going to add hold & release moves, e.g. hold down for 2 seconds then press up & fire, but that was when I got tired of writing code I wasn't going to use. ::)
Built into the routine are dash special moves (tap left or right quickly), a Dragonpunch in either direction (like Street Fighter 2) and rapid fire (like Chun Lee's Hundred Rending Kicks)
The code supports up to 10 joypads. This is in theory because I have the trial of GLBasic so can't test this more than making sure the syntax is correct. The code is a bit of an obscure mess and would best be tailored for particular needs. However, if anyone did want this done I would re-write the code for their needs.
First part below
SETSCREEN 640,480,0
LIMITFPS 60
LOADFONT "smallfont.png", 1 // A workaround because the default font won't load by, er, default
TYPE allpad
sfjoypad% // The joypad number that will be used. Can be 1 to 10. (Who has 10 joypads?!)
sfdirection% // The direction the joypad is in. 9 = no direction : 1 = up right : 2 = right : 3 = down right : 4 = down : 5 = down left : 6 = left : 7 = up left : 8 = up
sffire% // How long the FIRE button has been pressed. 1 = new bullet/punch/whatever. Numbers bigger than 1 used for build up to a super shot in R-Type or MegaMan where the fire button is held down to charge and released to fire.
sfjump% // How long the JUMP button has been pressed. Might be useful for super-jumps or whatnot.
sfspecial% // Number of special moves joypad n should be configured for
sfmaxmove% // Maximum steps in any one special move. Used for limiting dimensional array usage
sfmoves%[] // Array for holding the steps of special moves in.
sftypemove%[] // The type of special move. 1 = step by step. 2 = rapid fire. 3 = build up. The build up type hasn't been completed in this example
sftolerance%[] // Higher = easier to execute special moves. How long should slips of joypad be ignored before next step HAS to be completed?
sfmovewait%[] // higher = easier to execute special moves. How long can you be holding a direction before the step in a special move is considered void
sfmovestate%[] // which step is the special move currently in? 0 = not in any state
sfmovecounter%[] // Variable to check how long the move state has been in one state
sfmovetolerance%[] // Variable to check gow long the move been out of sequence
sfendmove%[] // Which is the final move in the combination? When reached, set sfmovestate to 255
sfcanignorefire%[] // Can you charge your gun while performing this move? If yes, then ignore fire & jump status while checking.
firebutton% // Set button used to shoot on Joypad - used for non-trial GLBasic only
jumpbutton% // Set button used to jump on Joypad - used for non-trial GLBasic only
keyright% // Set key for right
keydown% // Set key for down
keyleft% // Set key for left
keyup% // Set key for up
keyfire% // Set key for fire
keyjump% // Set key for jump
ENDTYPE
GLOBAL sfPad[] AS allpad // Possibility of up to 10 joysticks but only one used in this code
// Initialise the JoyPad settings. Any number of settings can be set for one joypad, though here I am just setting up one configuration
// Muliple settings is useful for games where the controls can change often. e.g. Street Fighter games, where the direction you are facing
// changes the kind of special moves that are available to you. It could also be useful for Megaman types games where weapons layouts change
LOCAL pla1
pla1 = sfPadInitialise(1,7,4) // pla1 will be configured for joypad 1, will have 6 special moves and 4 steps is the maximum number for any special move
// Define Controls for pla1, i.e. player 1
sfPadDefaultControls(pla1)
// Add pre-defined special moves into the 6 slots set out by sfPadInitialise(*,6,*)
// Obviously, don't try to add any moves or check for them if you haven't specified that the joypad configuration will need some
sfPadAddMove("right dash", pla1, 0) // tap right twice quickly
sfPadAddMove("left dash", pla1, 1) // tap left twice quickly
sfPadAddMove("right dragonpunch", pla1, 2) // right, down, down + right, right + fire
sfPadAddMove("right dragonpunch slip", pla1, 3) // included to make dragonpunch easier for noobs (e.g. my complaining gf)
sfPadAddMove("left dragonpunch", pla1, 4) // left, down, down + left, left + fire
sfPadAddMove("left dragonpunch slip", pla1, 5) // included to make dragonpunch easier for noobs
sfPadAddMove("fast fire", pla1, 6) // press fire repeatedly as quick as possible to active fast fire move
// Main test loop
spectimer% = 0
s$=""
WHILE TRUE
sfPadState(pla1) // Check what the state of the joypad and special moves are in
dire=sfPad[pla1].sfdirection
SELECT dire
CASE 9
PRINT "direction: no movement", 0, 0
CASE 1
PRINT "direction: up & right", 0, 0
CASE 2
PRINT "direction: right", 0, 0
CASE 3
PRINT "direction: down & right", 0, 0
CASE 4
PRINT "direction: down", 0, 0
CASE 5
PRINT "direction: down & left", 0, 0
CASE 6
PRINT "direction: left", 0, 0
CASE 7
PRINT "direction: up & left", 0, 0
CASE 8
PRINT "direction: up", 0, 0
DEFAULT
PRINT "direction: Whoops! An error.", 0, 0
ENDSELECT
PRINT "Imma chargin' mah lazer: " + sfPad[0].sffire, 0, 16
PRINT "Jump pressed for: " + sfPad[0].sfjump, 0, 32
IF spectimer > 0
DEC spectimer
PRINT "Special Move:" + s$, 0, 80
ENDIF
IF sfPad[pla1].sfmovestate[0]=255
spectimer = 120
s$="A dash to the right!"
ENDIF
IF sfPad[pla1].sfmovestate[1]=255
spectimer = 120
s$="A dash to the left!"
ENDIF
IF sfPad[pla1].sfmovestate[2]=255 OR sfPad[pla1].sfmovestate[3]=255
spectimer = 120
s$="Dragonpunch to the right!"
ENDIF
IF sfPad[pla1].sfmovestate[4]=255 OR sfPad[pla1].sfmovestate[5]=255
spectimer = 120
s$="Dragonpunch to the left!"
ENDIF
IF sfPad[pla1].sfendmove[6]=255
spectimer = 4
s$="Very fast hitting!"
ENDIF
SHOWSCREEN
WEND
END
Second part, the functions
// Beginning of functions
FUNCTION sfPadInitialise: padnumber%, specials% = 0, maxmoves% = 0
LOCAL p AS allpad
p.sfjoypad = padnumber-1 // joypads 1 to 10 are set as 0 to 9
p.sfdirection = 9 // 9 is always "no direction". 0 should never be seen.
p.sffire = 0
p.sfjump = 0
p.sfspecial = specials
p.sfmaxmove = maxmoves
IF specials = 0
// do nothing as no special moves need to be defined. sfPad[] will still show how long fire and/or jump have been pressed however
ELSE
REDIM p.sfmoves[specials][maxmoves][2]
REDIM p.sftypemove[specials]
REDIM p.sfmovestate[specials]
REDIM p.sfmovecounter[specials]
REDIM p.sfmovetolerance[specials]
REDIM p.sfendmove[specials]
REDIM p.sftolerance[specials]
REDIM p.sfmovewait[specials]
REDIM p.sfcanignorefire[specials]
ENDIF
DIMPUSH sfPad[], p // add the new joypad configuration
LOCAL padconfig_id
padconfig_id = LEN(sfPad[]) - 1
RETURN padconfig_id // return the Pad ID number. Necessary for keeping track of which pad is set which control scheme
ENDFUNCTION
FUNCTION sfPadAddMove: move$, padnumber, move
IF move$="right dash"
sfPad[padnumber].sftypemove[move]=1 // its a step by step kind of special move.
sfPad[padnumber].sfmovestate[move]=0 // clear registers
sfPad[padnumber].sfmovecounter[move]=0 // clear registers
sfPad[padnumber].sfmovetolerance[move]=0 // clear registers
sfPad[padnumber].sfmoves[move][0][0]=9 // first direction is no direction. player must be still
sfPad[padnumber].sfmoves[move][0][1]=0 // no fire or jump needed. fire = 1. jump = 2. fire & jump = 3
sfPad[padnumber].sfmoves[move][1][0]=2 // move right
sfPad[padnumber].sfmoves[move][1][1]=0 // no fire or jump
sfPad[padnumber].sfmoves[move][2][0]=9 // don't move
sfPad[padnumber].sfmoves[move][2][1]=0 // no fire or jump
sfPad[padnumber].sfmoves[move][3][0]=2 // move right again
sfPad[padnumber].sfmoves[move][3][1]=0 // no fire or jump
sfPad[padnumber].sfendmove[move]=4 // if player reaches step 4 they have successfully executed the dash to the right
sfPad[padnumber].sfcanignorefire[move]=1 // fire status can be ignored. You can still charge your laser
sfPad[padnumber].sftolerance[move]=0 // tolerate NO accidental joypad slips. If you can't double press right in quick succession, there is something wrong with you.
sfPad[padnumber].sfmovewait[move]=8 // tolerate 8 fps between changes of step
ENDIF
IF move$="left dash"
sfPad[padnumber].sftypemove[move]=1
sfPad[padnumber].sfmovestate[move]=0
sfPad[padnumber].sfmovecounter[move]=0
sfPad[padnumber].sfmovetolerance[move]=0
sfPad[padnumber].sfmoves[move][0][0]=9
sfPad[padnumber].sfmoves[move][0][1]=0
sfPad[padnumber].sfmoves[move][1][0]=6
sfPad[padnumber].sfmoves[move][1][1]=0
sfPad[padnumber].sfmoves[move][2][0]=9
sfPad[padnumber].sfmoves[move][2][1]=0
sfPad[padnumber].sfmoves[move][3][0]=6
sfPad[padnumber].sfmoves[move][3][1]=0
sfPad[padnumber].sfendmove[move]=4
sfPad[padnumber].sfcanignorefire[move]=1
sfPad[padnumber].sftolerance[move]=0
sfPad[padnumber].sfmovewait[move]=8
ENDIF
IF move$="right dragonpunch"
sfPad[padnumber].sftypemove[move]=1
sfPad[padnumber].sfmovestate[move]=0
sfPad[padnumber].sfmovecounter[move]=0
sfPad[padnumber].sfmovetolerance[move]=0
sfPad[padnumber].sfmoves[move][0][0]=2
sfPad[padnumber].sfmoves[move][0][1]=0
sfPad[padnumber].sfmoves[move][1][0]=4
sfPad[padnumber].sfmoves[move][1][1]=0
sfPad[padnumber].sfmoves[move][2][0]=3
sfPad[padnumber].sfmoves[move][2][1]=0
sfPad[padnumber].sfmoves[move][3][0]=2
sfPad[padnumber].sfmoves[move][3][1]=1
sfPad[padnumber].sfendmove[move]=4
sfPad[padnumber].sfcanignorefire[move]=0 // you cannot press fire until the right time (the end of the move) or the step by step sequence is void
sfPad[padnumber].sftolerance[move]=8
sfPad[padnumber].sfmovewait[move]=12 // tolerate 12 fps between changes of step. Very tolerant. Typical real world gameplay would be 8
ENDIF
IF move$="right dragonpunch slip" // Just added so dragonpunch is easier to perform. My gf kept messing up and complaining so I added this to make it easier to trigger
sfPad[padnumber].sftypemove[move]=1
sfPad[padnumber].sfmovestate[move]=0
sfPad[padnumber].sfmovecounter[move]=0
sfPad[padnumber].sfmovetolerance[move]=0
sfPad[padnumber].sfmoves[move][0][0]=2
sfPad[padnumber].sfmoves[move][0][1]=0
sfPad[padnumber].sfmoves[move][1][0]=9
sfPad[padnumber].sfmoves[move][1][1]=0
sfPad[padnumber].sfmoves[move][2][0]=3
sfPad[padnumber].sfmoves[move][2][1]=0
sfPad[padnumber].sfmoves[move][3][0]=2
sfPad[padnumber].sfmoves[move][3][1]=1
sfPad[padnumber].sfendmove[move]=4
sfPad[padnumber].sfcanignorefire[move]=0
sfPad[padnumber].sftolerance[move]=8
sfPad[padnumber].sfmovewait[move]=12
ENDIF
IF move$="left dragonpunch"
sfPad[padnumber].sftypemove[move]=1
sfPad[padnumber].sfmovestate[move]=0
sfPad[padnumber].sfmovecounter[move]=0
sfPad[padnumber].sfmovetolerance[move]=0
sfPad[padnumber].sfmoves[move][0][0]=6
sfPad[padnumber].sfmoves[move][0][1]=0
sfPad[padnumber].sfmoves[move][1][0]=4
sfPad[padnumber].sfmoves[move][1][1]=0
sfPad[padnumber].sfmoves[move][2][0]=5
sfPad[padnumber].sfmoves[move][2][1]=0
sfPad[padnumber].sfmoves[move][3][0]=6
sfPad[padnumber].sfmoves[move][3][1]=1
sfPad[padnumber].sfendmove[move]=4
sfPad[padnumber].sfcanignorefire[move]=0
sfPad[padnumber].sftolerance[move]=8
sfPad[padnumber].sfmovewait[move]=12
ENDIF
IF move$="left dragonpunch slip"
sfPad[padnumber].sftypemove[move]=1
sfPad[padnumber].sfmovestate[move]=0
sfPad[padnumber].sfmovecounter[move]=0
sfPad[padnumber].sfmovetolerance[move]=0
sfPad[padnumber].sfmoves[move][0][0]=6
sfPad[padnumber].sfmoves[move][0][1]=0
sfPad[padnumber].sfmoves[move][1][0]=9
sfPad[padnumber].sfmoves[move][1][1]=0
sfPad[padnumber].sfmoves[move][2][0]=5
sfPad[padnumber].sfmoves[move][2][1]=0
sfPad[padnumber].sfmoves[move][3][0]=6
sfPad[padnumber].sfmoves[move][3][1]=1
sfPad[padnumber].sfendmove[move]=4
sfPad[padnumber].sfcanignorefire[move]=0
sfPad[padnumber].sftolerance[move]=8
sfPad[padnumber].sfmovewait[move]=12
ENDIF
IF move$="fast fire"
sfPad[padnumber].sftypemove[move]=2 // This move is rapid dire, triggered when player hits fire quickly
sfPad[padnumber].sfmovestate[move]=0 // In this case, how many times has player hit fire quickly?
sfPad[padnumber].sfmovecounter[move]=0 // How long has fire button not been pressed?
sfPad[padnumber].sfmovetolerance[move]=0 // last count was fire? then 32. last count was not fire? then 64.
sfPad[padnumber].sfendmove[move]=0 // This is set to 255 when fast fire special move is activated
sfPad[padnumber].sfcanignorefire[move]=6 // How many times must player hit fire before special move is activated?
sfPad[padnumber].sftolerance[move]=6 // How long can fire be pressed for before you are too slow to activate move
sfPad[padnumber].sfmovewait[move]=6 // How long can a break between fire last before you are too slow to activate move
ENDIF
ENDFUNCTION
FUNCTION sfPadDefaultControls: padnumber
sfPad[padnumber].firebutton = 0 // fire button is first joypad button - only used if GLBasic is in full version
sfPad[padnumber].jumpbutton = 1 // jump button is second joypad button - only used is GLBasic is in full version
sfPad[padnumber].keyright = 205 // right arrow
sfPad[padnumber].keydown = 208 // down arrow
sfPad[padnumber].keyleft = 203 // left arrow
sfPad[padnumber].keyup = 200 // up arrow
sfPad[padnumber].keyfire = 29 // left CTRL
sfPad[padnumber].keyjump = 56 // Left ALT
ENDFUNCTION
// The main routine for checking joypad state and status of special moves
FUNCTION sfPadState: padnumber%, cancelspecials% = FALSE, cancelcharge% = FALSE
LOCAL isfire% = FALSE; LOCAL isjump% = FALSE; LOCAL isblock% = FALSE; LOCAL ismagic% = FALSE; LOCAL isup% = FALSE; LOCAL isdown% = FALSE; LOCAL isleft% = FALSE; LOCAL isright% = FALSE; LOCAL jumpfire% = 0
LOCAL jx = 0; LOCAL jy = 0; LOCAL ba = 0; LOCAL bb = 0; LOCAL bc = 0; LOCAL bd = 0
LOCAL state% = 0; LOCAL checkmove% = 0; LOCAL checkbutton% = 0; LOCAL checknextmove% = 0; LOCAL checknextbutton% = 0
// Read joypad & keyboard
JOYSTATE jx, jy, ba, bb // Works with trial version. Can't use more than one joypad! Tested with keyboard as second input and that works.
// If you have the full version of GLBasic then you can enable the following two lines of code and remove the line above
// Theoretically this routine could support up to 10 joypads, all with their own set of special moves.
// jx = GETJOYX(sfPad[padnumber].sfjoypad); jy = GETJOYY(sfPad[padnumber].sfjoypad)
// ba = GETJOYBUTTON(sfPad[padnumber].sfjoypad,sfPad[padnumber].firebutton); bb = GETJOYBUTTON(sfPad[padnumber].sfjoypad,sfPad[padnumber].jumpbutton)
IF KEY(sfPad[padnumber].keyfire) = 1 OR ba > 0
isfire = TRUE
jumpfire = jumpfire +1
ENDIF
IF KEY(sfPad[padnumber].keyjump) = 1 OR bb > 0
isjump = TRUE
jumpfire = jumpfire +2
ENDIF
IF KEY(sfPad[padnumber].keyup) = 1 OR jy < -0.8
isup = TRUE
ELSEIF KEY(sfPad[padnumber].keydown) = 1 OR jy > 0.8
isdown = TRUE
ENDIF
IF KEY(sfPad[padnumber].keyleft) = 1 OR jx < -0.8
isleft = TRUE
ELSEIF KEY(sfPad[padnumber].keyright) = 1 OR jx > 0.8
isright = TRUE
ENDIF
IF isfire
INC sfPad[padnumber].sffire
ELSE
sfPad[padnumber].sffire= 0 // sffire = 1 then fire. sffire > 1 then IMMA CHARGIN' MAH LAZER!
ENDIF
IF isjump
INC sfPad[padnumber].sfjump
ELSE
sfPad[padnumber].sfjump = 0 // sfjump = 1 then just pressed jump. sfjump > 1 then still holding it.
ENDIF
IF isup AND isright
sfPad[padnumber].sfdirection = 1
ELSEIF isup AND isleft
sfPad[padnumber].sfdirection = 7
ELSEIF isdown AND isright
sfPad[padnumber].sfdirection = 3
ELSEIF isdown AND isleft
sfPad[padnumber].sfdirection = 5
ELSEIF isup
sfPad[padnumber].sfdirection = 8
ELSEIF isdown
sfPad[padnumber].sfdirection = 4
ELSEIF isright
sfPad[padnumber].sfdirection = 2
ELSEIF isleft
sfPad[padnumber].sfdirection = 6
ELSE
sfPad[padnumber].sfdirection = 9
ENDIF
IF cancelcharge = TRUE THEN sfPad[padnumber].sffire = 0
// Check Special Moves
IF sfPad[padnumber].sfspecial = 0 OR cancelspecials = TRUE // Are there any special moves defined for this joypad? Are we even checking for special moves this cycle?
// do nothing
ELSE
FOR move = 0 TO sfPad[padnumber].sfspecial - 1
SELECT sfPad[padnumber].sftypemove[move]
CASE 1 // the type of move is a step by step move
IF sfPad[padnumber].sfmovestate[move] = 255 THEN sfPad[padnumber].sfmovestate[move] = 0 // if special move was triggered last cycle, remove the flag
state = sfPad[padnumber].sfmovestate[move]
IF state = 0 // here we have the start of lots of IF statements. My favourite! >:(
checkmove = sfPad[padnumber].sfmoves[move][0][0]
checkbutton = sfPad[padnumber].sfmoves[move][0][1]
IF sfPad[padnumber].sfcanignorefire[move] = 1
checkbutton = jumpfire
ENDIF
IF checkmove = sfPad[padnumber].sfdirection AND checkbutton = jumpfire
sfPad[padnumber].sfmovestate[move]=1
sfPad[padnumber].sfmovecounter[move]=0
sfPad[padnumber].sfmovetolerance[move]=0
ENDIF
ELSE
checkmove = sfPad[padnumber].sfmoves[move][state-1][0]
checkbutton = sfPad[padnumber].sfmoves[move][state-1][1]
checknextmove = sfPad[padnumber].sfmoves[move][state][0]
checknextbutton = sfPad[padnumber].sfmoves[move][state][1]
IF sfPad[padnumber].sfcanignorefire[move] = 1
checkbutton = jumpfire
checknextbutton = jumpfire
ENDIF
IF checkmove = sfPad[padnumber].sfdirection AND checkbutton = jumpfire
INC sfPad[padnumber].sfmovecounter[move]
IF sfPad[padnumber].sfmovecounter[move] > sfPad[padnumber].sfmovewait[move]
sfPad[padnumber].sfmovestate[move]=0
sfPad[padnumber].sfmovecounter[move]=0
sfPad[padnumber].sfmovetolerance[move]=0
ENDIF
ELSE
IF checknextmove = sfPad[padnumber].sfdirection AND checknextbutton = jumpfire
INC sfPad[padnumber].sfmovestate[move]
sfPad[padnumber].sfmovecounter[move]=0
sfPad[padnumber].sfmovetolerance[move]=0
ELSE
INC sfPad[padnumber].sfmovetolerance[move]
IF sfPad[padnumber].sfmovetolerance[move] > sfPad[padnumber].sftolerance[move]
sfPad[padnumber].sfmovestate[move]=0
sfPad[padnumber].sfmovecounter[move]=0
sfPad[padnumber].sfmovetolerance[move]=0
ENDIF
ENDIF
ENDIF
ENDIF
IF sfPad[padnumber].sfmovestate[move] = sfPad[padnumber].sfendmove[move] THEN sfPad[padnumber].sfmovestate[move] = 255
CASE 2 // the move is a rapid fire sequence.
state = sfPad[padnumber].sfmovestate[move]
sfPad[padnumber].sfendmove[move]=0
IF state = 0 AND isfire
sfPad[padnumber].sfmovestate[move]=1
sfPad[padnumber].sfmovetolerance[move]=32
sfPad[padnumber].sfmovecounter[move]=0
ENDIF
IF state > 0
IF sfPad[padnumber].sffire > sfPad[padnumber].sftolerance[move]
sfPad[padnumber].sfmovestate[move]=0 // if time fire is held is beyond tolerance, you are too slow!
ELSEIF sfPad[padnumber].sfmovecounter[move] > sfPad[padnumber].sfmovewait[move]
sfPad[padnumber].sfmovestate[move]=0
ENDIF
ENDIF
IF state > 0
IF sfPad[padnumber].sffire = 0 AND sfPad[padnumber].sfmovetolerance[move] = 32
INC sfPad[padnumber].sfmovestate[move]
sfPad[padnumber].sfmovetolerance[move] = 64
ELSEIF sfPad[padnumber].sffire > 0 AND sfPad[padnumber].sfmovetolerance[move] = 64
INC sfPad[padnumber].sfmovestate[move]
sfPad[padnumber].sfmovetolerance[move] = 32
sfPad[padnumber].sfmovecounter[move]=0
ELSEIF sfPad[padnumber].sfmovetolerance[move] = 64
INC sfPad[padnumber].sfmovecounter[move]
ENDIF
ENDIF
IF state > sfPad[padnumber].sfcanignorefire[move]
sfPad[padnumber].sfendmove[move] = 255
ENDIF
// Add hold and release special move routine here
// e.g. Guile's "Sonic Boom" move, where the joystick is held back for two seconds then moved forward and fire pressed.
ENDSELECT
NEXT
ENDIF
ENDFUNCTION
I should add, although the test screen I wrote is extremely basic, there is a lot behind this routine.
For example, some moves can ignore the state of firebutton, so you can charge your gun or whatever while performing special movements. Tolerance to mistakes or delays made by the player in trying to perform the special moves can be increased or diminished. This can help to change difficulty but is also good for adjusting to different fps. 60fps is fine but if the game was running on a device that could only manage 30fps it might be an idea to diminish the mistake & delay tolerance so special moves remain around the same difficult to do.
The same joypad can be set up for different sets of special moves. For example, in Street Fighter games many of the special moves depend on which direction you're facing. This could be easily achieved by setting two control schemes (or more) for the same controller:-
player1left = sfPadInitialise(j,n,m)
player1right = sfPadInitialise(j,n,m)
player1swimming = sfPadInitialise(j,n,m)
player1invehicle = sfPadInitialise(j,n,m)
Where j is the number of the joypad being used, n is the number of special moves for that control configuration and m the maximum number of steps in any special move
Ok, I'm done waffling about stuff no one will probably care about. ;/