CleverRaven / CleverRaven/Cataclysm-DDA
"Flinch", a mechanic for making speed penalties less deadly and annoying
- Dominant language
- C++
- Stars
- 13.2k
- Forks
- 4.6k
- Avg merge
- 2d 1h
- Merged PRs (30d)
- 205
Description
### Is your feature request related to a problem? Please describe.
Speed penalties are a necessity in the game. Lots of things, like pain and breathlessness and muscle strain, should lower your speed when doing things like swinging weapons... There's no easy way around this because there are only so many ways to alter character abilities as their resources change. However, speed penalties are disproportionately punishing and frequently lead to hard-to-understand failure cycles where you're getting hit by enemies more and more for each attack you make. This is not just frustrating but generally unfun, as it requires a lot of foreknowledge of the game engine to allow players to make logical assessments about their avatar's capabilities. One turn they seem to be on top of things, then they make a too-slow attack because of slight misjudgment and are now not one turn later, but two or three.
### Solution you would like.
Let's stop penalizing attack speed. Instead, anything that penalizes speed only affects movement speed (if it should even do that). Instead of penalizing attack speed, we get a new mechanic, Flinch.
Anything that could be susceptible to a speed penalty, such as melee attacks or smashing objects, makes a call to Flinch() instead. When your character Flinches, they delay for some short amount of time (10-50 ticks) and get a message about it. This means they now can make another decision based on knowing they are slowed down. It also means that faster attack weapons don't have as strong an advantage over slower, the flinch mechanic is the same no matter what.
#### Some ground rules.
1. You can only flinch once every two seconds (generally once per two turns). Whenever a Flinch() check returns >0, we record the "last flinched" time in a variable, and check it compared to now. This stops us from getting into an endless flinch cycle.
2. Whenever Flinch() is called, it returns either 0 or an int from 10-50 for number of ticks flinched. Remember, a tick is 1/100 of a second. We're not talking about long periods of the character picking their nose and watching the enemy, but enough time to matter. At its maximum you're flinching for 50 ticks out of every 200, which is very significant but not a stunlock.
3. If you flinch while aiming, you also lose a small amount of your aim meter as well as the time. You don't cancel aiming!
4. Flinching is not random. There's a `flinch_chance` score that builds up right before each `flinch()` check, and if it passes `flinch_threshold`, we get a flinch. The duration of the flinch is based on how high `flinch_chance` is.
#### The algorithm
`flinch()` to start would increment when attempting a melee attack, by an amount equal to:
* `(current_pain - 10) * pain_flinch_mod` (pain_flinch_mod is a global JSON setting, default 0.6)
* and: `100 - 100 * current_stamina / stamina_max * stamina_flinch_mod` (stamina_flinch_mod is a global JSON setting, default 1.0)
* `flinch_threshold` is another global JSON setting, default 300.
* When your flinch_chance reaches threshold, you flinch for a duration equal to `flinch_chance_gained_this_turn / 5` ticks, max 50, min 10. Then flinch_chance resets to 0.
* if time since last flinch > 10 seconds, reset flinch_chance to 0.
When I finish adding strain I'll redo these numbers to work it in but this should work for now.
In this model, you start flinching every two turns pretty often, but the duration only caps out if you're both tired and in pain. To get to the full 50 ticks of flinch per turn, you have to be accruing 250+ points of flinch_chance, which most of the time requires you to be in both a lot of pain and also out of breath.
The message given for flinching should probably suggest the things that are causing the flinch, eg:
`you flinch for an instant as you push past the pain.`
`you take a fraction of a second to gather your energy.`
Probably if you're both breathless and in pain, we should just randomly select an appropriate message? That way we can have alternative texts too. Or we could implement logic based on which of these is contributing the most to flinch_chance.
### Describe alternatives you have considered.
I am not sure about the 'every other turn' limit, but I don't want to keep flooding the player with flinch messages as they get more common. This keeps the spam down
Speed penalties are the other obvious alternative, and I hate them.
### Additional context
The effect should be subtle, but give the player a lot more control over their avatar's actions.
I think it's fairly easy to implement, but my time is limited again so I'm sort of hoping someone will get to it before I have a chance; It seems like it might help me to get the first pass of strain working, after testing it out
Contributor guide
Research direction
No files or tests are named. Start by locating the existing speed-penalty and melee-attack paths, then review how pain, stamina, aiming, and turn timing are represented. Done would require an agreed Flinch design, implementation across affected actions, player messages, configuration values, and tests for thresholds, cooldowns, duration, and aim effects.
Written by the indexing model from the issue text.
Assessment
- Domain
- game-dev
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 32/100