Saplings-Projects / 1M_sub

Game project for Fauna's 1M sub : a dungeon crawler
GNU General Public License v3.0
2 stars 12 forks source link

Enemy attack pattern #127

Open Turtyo opened 3 weeks ago

Turtyo commented 3 weeks ago

Feature Description

Provide each enemy with a customizable set of attacks and actions.

Motivation

The enemy attack is currently only a single attack with one damage, the same for everyone. This is neither interesting nor practical to edit.

Acceptance Criteria

Proposed Solution

Use the current card system to give each enemy a set of card which is its attack pattern. Changing the proportion of each card naturally changes an enemy amount of each attack. This way, adding a new attack to an enemy means adding a new card to a deck (which is easy to do with the current card system, using the inspector inside Godot). This way, every time we add an effect to an enemy or the player, it becomes available to the other (so it's re-usable).

Implementation Steps (Optional)

Additional Context

We might add a functionality to show the next enemy attack later on. With a deck, we just need to peek at the next card in the deck. This is not to be done in this implementation, but keep it in mind.

Thought it might be easier to just do it right now, so if it makes sense it could be done with this issue.

JonaLam commented 2 weeks ago

I'm a bit confused with the implementation, should we give the enemy a deck? Wouldn't it be simpler to just give them a bunch of card that the enemy plays at random or something like that?

Turtyo commented 2 weeks ago

Well we already have the entire system to manage cards with the decks. Making it so that enemies use a deck makes it a standard, ie the same thing everywhere.

Another solution would be to give the mob just cards and an array of probabilities, but directly using a deck already does this

JonaLam commented 1 week ago

Does this system let us order the deck how we want? As long as the solution is modular enough it's probably fine.

Turtyo commented 1 week ago

By ordering the deck, do you mean changing the proportion of each attack ? Or do you mean specifically choosing an order in which the attacks would happen ?

  1. Yes, just add more cards of the attack / skill you want the enemy to use
  2. No, it would be random
JonaLam commented 1 week ago

I meant ordering, and I think that it might be good to at least have the option to order them slightly or other stuff like that in case we want to giver enemies interesting attack patterns

Turtyo commented 1 week ago

mmh we could decide to not shuffle cards for the enemies, which would allow choosing the order of cards

JonaLam commented 1 week ago

Yeah, I'm just worried using the a deck system wil be constricting in the futere, in case we want to do more interesting things with the enemy attacks, though this might not become too relevant if we don't go down that direction. being able to order I think wil matter tho

Turtyo commented 1 week ago

It's currently the best system we can have because it allows easy re-use of effects and a system for ordering the attacks. If later we want to provoke a certain sequence of attack or force an attack after a certain condition is met, we would need to move the corresponding card at the top of the pile of the mob deck

If you have a better option, I would be glad to hear it though !

JonaLam commented 1 week ago

I don't really have a better solution than give the enemy a pool of cards and make them chose based of off the behaviour we give them. The reason I want this solution is becuse it would be easy to give them behaviours as we wish, but if the deck solution also gives us easy behaviours then I think the solution you have works well

Turtyo commented 4 days ago

If we use an array of cards instead of a true deck, and we do the picking by randomly selecting a card in the array, it has a few advantages:

I still think we should use cards, because that allows easy re-use of effects and to change the enemy attack directly in the editor (by adding effects onto cards), instead of having to change those in code.

If we do it with an array, building onto the idea of the array and being able to choose the next card being played, we can even do something a bit more precise; we build a table where it tells you the probability of getting a certain action after having done another action on the previous turn.

attack: 25% buff, 25% defense, 25% attack, 25% debuff,
buff: 100% attack,
debuff: 75% attack, 25% buff,
defense: 25% defense, 75% attack 

or something like that; what do you think ?

JonaLam commented 4 days ago

It's an interesting way to do it, I'm not sure if it's better than just the copying what spire does tho. It makes it a lot harder to predict and plan what to do, which in turn makes reactive cards like blocking a lot worse. The simple solution is to just make the enemy chose an action at the start of the player turn which it wil do at the end.

Turtyo commented 4 days ago

Yes this is exactly how this would work, but it just tells you which action is possible after a given action, with what probability. For example after an attack, the enemy chooses the next action, it picks between a buff, defense, attack and debuff with 25% each. So we would still be able to display the next action to the player.

After a buff for example, we know we will make an attack (and it will be displayed to the player)

JonaLam commented 4 days ago

Then it seems good. Since we're acounting for potentioal diffrent behaviours, we should probably have a base class which the diffrent behaviours inheret from. from there we should have something like if it's an enemy whose attacks are determined from the start we could just use an array, but for the functionality you're sugesting, we could use a dictionary, but we would need multiple based of off the cards the player plays. Scince the player can play multiple cards per turn, I'm not sure if the attack is based on the last card the player played or the majority, but regardless, we would need diffrent tables for each type

Turtyo commented 3 days ago

Do we want a table simply by attack type, or do we want the table to be for each card the next possible cards ? option 2 would give more control but be longer to do. What base behavior are you think of ? The next attack of the enemy doesn't depend on the actions of the player, so what the player does has 0 impact on what the enemy does, the table is for the enemy alone.

JonaLam commented 3 days ago

We probably want a table for each card just in case we need it. When I said base behaviour I think all of the things all enemies need like displaying attacks and such, while which attack they display is chosen by scripts inherenting from the base. There are definatly other ways to gives us options for attack which might be better though.

Turtyo commented 3 days ago

What other ways are you thinking of ? i'm not sure to follow what you mean by "while which attack they display is chosen by scripts inherenting from the base" That would be chosen by the number in each table (which we might change via an export to not have to do all by code)

JonaLam commented 3 hours ago

What I though was that the base class would have a simple function like for example choose_attack(CardBase attack) and then that function would handle displaying the attack for the player to see. and then the child scripts would handle how to chose the attacks, either with the way we've been discussing, or a set order which would have to be done in script. This allows later for a enemy which would only attack if you attack on your turn or something like that. It's definatly not certan we would need that, but it's nice to have the option

Turtyo commented 2 hours ago

The child script don't need to handle how to choose the next attack, here is a bit of pseudo-code on what i would see it like:

@export next_action_table {
attack: (25, buff), (25, defense), (25, attack), (25, debuff),
buff: (100, attack),
debuff: (75, attack), (25, buff),
defense: (25, defense), (75, attack), 
}

var next_action = attack

on_turn_start():
    do next_action
    choose_next_action()
    display_next_action()

choose_next_action():
    check line corresponding to the next_action
    previously did an attack for example,
    next_action  = randomly select between buff, defense, attack, debuff with 25% chance each

display_next_action():
    show icon corresponding to next_action

I'm not sure to see why we would need child scripts that choose the next action differently than using the table. Yes it could allow for an enemy that could do it differently, but that's a special case. We can say that the table is the default implementation, and if we ever need something without the table, then we can make child scripts and override those functions.

Making the default implementation like that doesn't stop us from making child scripts later, but that would be if we need it I think.

JonaLam commented 1 hour ago

Ye, that's actually probably better, is there anything else here to be discussed?

Turtyo commented 1 hour ago

well if we agree on the format, the only question left is probably if we need a class for the (% chance, card) thing and we agree it's one line per attack attack card of the monster. We'll also need to be sure every attack is reachable, for example you could have a situation like that:

attack: 100 buff,
buff: 100 attack,
defense: 100 debuff,
debuff: 100 defense,

Of course here it's very visible, but with more cards it might be harder to see. I would say enemies shouldn't have too many attacks to start easy, so it's easier to stop this kind of thing

JonaLam commented 1 hour ago

Yeah, having a class would probably be a good idea so that it's easy for others to make enemy behaviour. I'm not sure if this is what you meant, but I think each attack should have their own table for the sake of flexibility. For attacks being unreachable, we can make tests for that, it would probably be a bit involved, but that should be fine. You would probably have to go through each type and make a tree of each type it connects to, and then just check if all trees have every type. Alternativly can we just let it be, I don't think it wil be to much of a problem as long as we play test the enemies

Turtyo commented 1 hour ago

Mmh it's basically a standard method of state checking, making sure every state is reachable. That should be doable, I'll see what I do when I write the tests. Or we can just be confident in playtest, but I would rather not.

Alright for the class, i'll start soon then I think