Cataclysm-DDA icon indicating copy to clipboard operation
Cataclysm-DDA copied to clipboard

Monsters attacks do not inherit cooldowns; are used in alphabetical order

Open Terrorforge opened this issue 1 year ago • 20 comments

Describe the bug

I was messing around with the zombie breaker (mon_zombie_concentration_ender) from MoM for #75649 when I discovered something weird; even if I completely remove the cooldown on zombie_anti_concentration_spell, if I'm in melee it will only cast the spell very rarely, something like once every few minutes, maybe as often as every 30 seconds. Instead it just tries to grab, over and over and over. Rearranging the list of special attacks didn't help, so it's not a matter of it just preferrentially using the first special attack in its entry. The only way I could get it to consistently cast the spell was to remove the grab entirely.

This severely hampers our ability to create zombies that do anything unique and interesting.

Attach save file

N/A

Steps to reproduce

  1. Mess about with zombie breaker (and other zombies with special attacks) as described and let it whale on you

Expected behavior

Ideally there would be some way to manually weight a monster's special attacks so that some occur more than others, but failing that they should all occur at a more or less equal rate.

Screenshots

image Spell with no cooldown, grab removed. It casts the spell over and over, as expected.

image image Spell with no cooldown, grab present. It only uses the spell every few minutes, maybe as often as once every 30 seconds. I even moved the grab to the end of list of special attacks just to see if it was prioritizing the first listed special attack, but nope. If it was just picking a random attack that's off cooldown, this should be 50/50 grabs and spells, right?

Versions and configuration

  • OS: Windows
    • OS Version: 10.0.19045.4651 (22H2)
  • Game Version: b8dba37 [64-bit]
  • Graphics Version: Tiles
  • Game Language: English [en]
  • Mods loaded: [ Dark Days Ahead [dda], Disable NPC Needs [no_npc_food], Portal Storms Ignore NPCs [personal_portal_storms], Slowdown Fungal Growth [no_fungal_growth], Mind Over Matter [mindovermatter], Bionic Slots [cbm_slots], Stats Through Kills [stats_through_kills] ]

Additional context

No response

Terrorforge avatar Aug 14 '24 09:08 Terrorforge

Could you reproduce with debug mode - DF_MATTACK filter and paste the log messages? The base attack has a 10-turn cooldown, they shouldn't be able to chain them like that.

Venera3 avatar Aug 14 '24 11:08 Venera3

Here you go: image And here's what it looks like after it successfully casts the spell: image

And just for good measure, I checked to see what happened if I manually add "cooldown": 10 to the breaker's grab. And yeah, it does now only occasionally grab and use the spell at about the intended cooldown: image image It looks to me like it only triggered because I failed to break the grab, which meant the grab attack didn't fire, and all the other valid attacks were on cooldown.

I also tried moving "grab" to the bottom of the breaker's list of special attacks to see if that would change the order of evaluation, but it did not. I noticed that the attacks seem to be listed in alphabetical order, so I added barbed_tentacle and cut_throat to the end of the entry to check, and lo and behold: image Now it's constantly using its barbed_tentacle attack every turn, despite the fact that it has a 5-turn cooldown defined in the base attack.

CONCLUSION:

  1. The game evaluates special attacks in alphabetical order. If the attack with the earliest alphabetical order is off cooldown and valid, it will always be used.
  2. Cooldowns are not preserved from base attacks, defaulting any monster attack without an explicitly defined cooldown to a cooldown of 0.

In combination, this means that if a monster has a valid low-alphabetical attack without a defined cooldown, it will use that attack to the exclusion of all other attacks. A quick tests confirms that basic zombies also just try to to grab repeatedly. I'm pretty sure that right now the only reason any zombie does literally anything other than grab is that grab has a failure mode (grab is already established).

Even if the cooldowns were passed down correctly, this seems like incorrect behavior to me. It wouldn't matter most of the time, but it means that if a monster has two attacks with the same cooldown it will always do them in the same order every time, and if it has two special attacks with no cooldown it will only ever use one of them.

Terrorforge avatar Aug 14 '24 13:08 Terrorforge

Oh and here's the .json for the final test version of the breaker with the extra attacks and such:

{ "id": "mon_zombie_concentration_ender", "copy-from": "mon_zombie_base", "type": "MONSTER", "name": { "str": "zombie breaker" }, "description": "An enormous hump takes up most of this zombie's back, fusing its head directly into its torso. It moves slowly but seems more aware than most of its kind, constantly looking around rather than staring blankly ahead. When you look directly at it, you feel a cold chill at the base of your skull.", "species": [ "ZOMBIE", "HUMAN", "PSI_NULL" ], "proportional": { "hp": 2 }, "symbol": "Z", "color": "dark_gray_white", "scents_tracked": [ "sc_human", "sc_fetid" ], "melee_skill": 4, "dodge": 1, "bleed_rate": 50, "vision_day": 35, "vision_night": 5, "emit_fields": [ { "emit_id": "emit_anti_psi", "delay": "1 s" } ], "grab_strength": 30, "special_attacks": [ { "id": "grab" }, { "id": "bite_humanoid", "cooldown": 5 }, { "id": "scratch", "cooldown": { "math": [ "5 + rand(13)" ] }, "damage_max_instance": [ { "damage_type": "bash", "amount": 6, "armor_multiplier": 0.8 } ], "hit_dmg_u": "%1$s strikes at your %2$s!", "hit_dmg_npc": "%1$s strikes at <npcname>!", "miss_msg_u": "%1$s strikes at you, but you dodge!", "miss_msg_npc": "%1$s tries to strike at <npcname>, but they dodge!", "no_dmg_msg_u": "%1$s strikes at your %2$s, but fails to penetrate armor.", "no_dmg_msg_npc": "%1$s tries to strike at <npcname>, but fails to penetrate armor." }, { "id": "zombie_anti_concentration_spell", "type": "spell", "spell_data": { "id": "null_break_concentration_spell" }, "cooldown": { "math": [ "7 + rand(19)" ] }, "monster_message": "%1$s looks at %3$s and the static on the edge of your vision flickers." },{ "id": "barbed_tentacle" }, { "id": "cut_throat" }

Terrorforge avatar Aug 14 '24 13:08 Terrorforge

From a cursory I'm-at-work search I'd imagine #74139 moving the cooldown over to variable objects doesn't play nice with the mattack loading code. I'm not gonna be in a position to bisect it properly in the near future so pinging @Ramza13 - could this be related or was it broken before?

Venera3 avatar Aug 14 '24 14:08 Venera3

Note sure if this is the same or a different bug, I just noticed this:

Untitled

The mi-go used three psionic powers and a physical attack all within one second.

Standing-Storm avatar Sep 18 '24 01:09 Standing-Storm

Why isn't this listed as a release blocker?

tenehea avatar Sep 21 '24 09:09 tenehea

@tenehea because 74139 that introduced the issue was merged after 0.H diverge, it never was in release candidate in the first place

GuardianDll avatar Sep 27 '24 23:09 GuardianDll

So what, to fix it, all that is needed is to add cooldown to all grabbing attacks?

GuardianDll avatar Oct 03 '24 22:10 GuardianDll

To all special attacks of every kind

tenehea avatar Oct 03 '24 22:10 tenehea

No, the fix is to fix mattack loading.

Venera3 avatar Oct 15 '24 07:10 Venera3

what exactly should be fixed then?

GuardianDll avatar Oct 15 '24 07:10 GuardianDll

Monster attacks not loading their cooldown from their base attack. It's probably a consequence of special_attacks supporting two different sorts of attack syntax and hence not living in generic factory properly - you can't make cooldown a mandatory member since old monattacks don't define a "cooldown" as such, and just slappin a cooldown into every monster definition just turns it into a contributor trap.

Venera3 avatar Oct 15 '24 07:10 Venera3

If what, i think Kevin told that it is pretty much desired for zeds to try to grab you as often as possible IMG_20241015_110504 https://discord.com/channels/598523535169945603/598523535169945607/1291521408890834954

GuardianDll avatar Oct 15 '24 09:10 GuardianDll

A major bug is not a feature

tenehea avatar Oct 15 '24 13:10 tenehea

...i realised point raised by Kevin is not related to this bug, my bad

GuardianDll avatar Oct 15 '24 13:10 GuardianDll

Grabs are completely incidental in the context of this bug (beyond being the one relevant monster attack in the game, apperently). Every monster with an inherited -cooldown attack will use them too often.

Venera3 avatar Oct 16 '24 07:10 Venera3

Monster attacks not loading their cooldown from their base attack. It's probably a consequence of special_attacks supporting two different sorts of attack syntax and hence not living in generic factory properly - you can't make cooldown a mandatory member since old monattacks don't define a "cooldown" as such, and just slappin a cooldown into every monster definition just turns it into a contributor trap.

I'm too stoopid to understand what this means and want to fix this so do you mind explaining a bit more? (apologies if what I've said below makes no sense)

Monster attacks not loading their cooldown from their base attack.

So are you saying special attacks are meant to use base attack cooldowns if unspecified or am I misunderstanding? I can't see anywhere in the docs or code where special attacks cooldown values would be based off of the base attack cooldown value, from both I'd expect it to default to 0.

you can't make cooldown a mandatory member since old monattacks don't define a "cooldown" as such, and just slappin a cooldown into every monster definition just turns it into a contributor trap.

Why are there even 2 coexisting formats of writing special attacks? Wouldn't making the old style special attacks be read through the same function by just checking against a unordered set of the old method ids make sense? So like

"special_attacks": [
    [ "ACID", 10 ],
    { "type": "leap", "cooldown": 8, "max_range": 4 }
]

->

"special_attacks": [
    { "type": "ACID", "cooldown": 10 },
    { "type": "leap", "cooldown": 8, "max_range": 4 }
]

Is the old version 10 not the same as the new version 10 or something? Then you could just make cooldown mandatory and slap appropriate ones everywhere, it feels like they're a big enough deal to warrant fine tuning imo rather than just defaulting to anything.

Procyonae avatar Oct 23 '24 22:10 Procyonae

How cooldown should work

Yes, the monster (m)attack should inherit everything not defined in the mtype from the base attack, that's a pretty core functionality of them - apperently core enough in my eyes that I didn't document it back when I wrote the docs. I wouldn't go with mandatorifying cooldown in the monster definition, personally, but it also wouldn't be a dealbreaker.

Why do we use two concurrent definitions in DDA vol. 3221

You'd have to ask 2016 Coolthulu for the definitive answer, my guess would be "let's not break 3rd party mods". The whole mattack system laid around mostly-unused and completely undocumented until 2021ish when I stumbled across it and started messing around for my beloved bugs, so it's a pretty weird part of the codebase.

Long-term I'd have liked legacy monattacks to atrophy away/get moved over to the JSON mattack system but there hasn't been a concerted push towards that.

I'd have to dig into the current loading code properly but spooling old monattacks off into a separate legacy_attack or similar load path would let you move the mattack_actor stuff over to the generic factory and hook up an implicit copy-from thing from the base attack definition.

Venera3 avatar Oct 24 '24 03:10 Venera3

Is there any issue written about moving away from hardcoded monattacks? Because i don't think we would be able to get rid of them completely, some are simply impossible to generify to their own json monattacks

GuardianDll avatar Oct 24 '24 06:10 GuardianDll

Changed the title to more accurately describe the actual problems. Just to summarize:

  1. Monster attacks do not inherit cooldowns from the base definition, so even though e.g. grab has a default cooldown of 10, when you give a monster "grab" without explicitly specifying a cooldown in the monster definition, it will have a cooldown of 0 and be indefinitely spammable.
  2. Monster attacks are always evaluated in alphabetical order. This means that they will always use their attacks in a specific order, and if they happen to have a 0-cooldown attack the comes early in alphabetical order, they will just spam that attack over and over to the exclusion of doing anything else.

Terrorforge avatar Nov 28 '24 10:11 Terrorforge

Is this going anywhere? It's severely intrusive and breaks most of the game

tenehea avatar Dec 08 '24 22:12 tenehea

It's less easy to fix than it sounds unless I were going about it wrong

Procyonae avatar Dec 09 '24 00:12 Procyonae

Hello, I believe someone has come up with a partial fix for this in https://github.com/Cataclysm-TLG/Cataclysm-TLG/pull/457

It compiles and runs on my machine, and juggernauts no longer spam cut_throat, but instead select valid moves randomly as intended.

This does not fix cooldown inheritance, but they're two separate issues so I think a partial fix is probably a good idea if yall want to port this.

worm-girl avatar Mar 09 '25 13:03 worm-girl

Just to be clear, the order of evaluation for attacks is not an issue. Alphabetical, reverse alphabetical, fixed, randomized, it really doesn't matter. The issue here is cooldowns.

RenechCDDA avatar Mar 16 '25 06:03 RenechCDDA

What this default cooldown should be, and where it is/should be defined? My understanding is that right now it is a zero, in which case monster spamming attack makes total sense

GuardianDll avatar Mar 16 '25 10:03 GuardianDll

The default is here, in the definition for the grab attack:

https://github.com/CleverRaven/Cataclysm-DDA/blob/c6a738a6686797b705b38e30f9508b82bcc70e9f/data/json/monster_special_attacks/monster_attacks.json#L60-L63

My understanding was that the attacks defined in that file were all meant to be referenced in monster definitions and everything not explicitly changed by the monster definition should inherit from the default.

Standing-Storm avatar Mar 16 '25 14:03 Standing-Storm

Just to be clear, the order of evaluation for attacks is not an issue. Alphabetical, reverse alphabetical, fixed, randomized, it really doesn't matter. The issue here is cooldowns.

Weren't both issues a problem, which was leading to things like juggernauts spamming cut_throat and rarely or never using their other attacks? Just fixing the cooldowns won't entirely fix that issue if there are any zero cooldown attacks.

worm-girl avatar Mar 16 '25 19:03 worm-girl

They shouldn't have a zero cooldown attack unless it's intended that they use it every turn. At which point... that's exactly what it's doing.

Kevin sums up my opinion on this matter: Image

Behavior trees and all that is very firmly in the realm of suggestions and desired features. It is not something that exists right now, and it's not relevant to this bug report.

RenechCDDA avatar Mar 17 '25 06:03 RenechCDDA

Happy 1 year anniversary 😅

tenehea avatar Jun 02 '25 01:06 tenehea

@tenehea Please refrain from whatever your last comment was supposed to be. If you do not have anything useful to add in solving a confirmed issue you do not need to post. Posting when you do not have anything to contribute is just spam wasting the time of people who actually want to fix it.

RenechCDDA avatar Jun 02 '25 08:06 RenechCDDA