Actions normally take longer than a tic, so splitting the firing into two actions makes a lot of sense, and I do it all the time. One action is the actor aiming, the other is the frame that shows the muzzleflash. This also allows for one frame to be shown longer than the other -- I will make the aiming frame display significantly longer than the flash frame. What you want to do is make the actor fire the projectiles and make the firing sound at the moment it transitions to the muzzleflash frame. Here is an example from the army ant of AA:
state antshootstate
ifaction ANTSHOOT
{
ifactioncount 1
{
ife bottarget -1 set botclip 10
ifg botclip 8
{
ai AIANTWAIT
set botclip 0
break
}
action ANTSHOOT2
add botclip 1
ifvarand initflags 32 state antshootpurple else
state antshootbullet
}
}
ifaction ANTSHOOT2
{
ifactioncount 1
action ANTSHOOT
}
ends
ANTSHOOT is the aiming frame. When it reaches actioncount 1, it checks to see if it has fired 9 rounds or if there is no longer a target. If either of those conditions is true it changes to AIANTWAIT and leaves the state, at which point it will reassess what to do next tic. If those conditions are not true it fires the next bullet (or the purple plasma if it's a special ant), increments the clip counter, and switches to the muzzleflash firing frame ANTSHOOT2. Once there it simply waits one actioncount and goes back to the aiming frame.
I removed the part where it checks to see if it needs to dodge something just to keep it relatively simple.
For further reference here is the state referred to above where the bullet firing happens:
state antshootbullet
sound M4FIRE
state hitscan_targetprep
zshoot zdist SHOTSPARK1
ends
The state hitscan_targetprep is a routine that aims the bullet up/down appropriately at the target and it takes into account additional factors. This isn't necessary if your actors will only be targeting the player though.