Albeoris / Memoria

Final Fantasy IX tools
MIT License
246 stars 36 forks source link

Suggestion: Convenient battle configuration framework #403

Open Albeoris opened 3 weeks ago

Albeoris commented 3 weeks ago

@snouz , @Tirlititi

Souls-like bosses.

Since Alternate Fantasy is the gold standard right now, my idea is about that, as there's no point in making multiple massive mods that change the difficulty of the game.

However, it seems to me that to implement this idea, changes are required in Memoria: I would like to have a convenient framework for customizing all fights (especially boss fights) and using it to make the battles less standard.

For example, let's take the boss in the evil forest. The boss has two phases, in the second phase Blank joins the team. Let's expand on this: during the first phase, every time the boss is hit with fire, he emits a scream that attracts a minion into the battle (up to 4 minions on the battlefield). In the second phase, the boss starts screaming constantly and attracts 2 minions every turn. But if he is hit again with fire, he emits a particularly powerful scream that confuses the minions.

{
  "battleManager": {
    "formatVersion": 1,
    "initialEnemies": ["boss"],
    "phases": [
      {
        "phase": 1,
        "comment": "Initial phase of the battle.",
        "triggers": [
          {
            "condition": "bossHitByFire",
            "action": {
              "type": "summonCreature",
              "minionType": "minion",
              "amount": 1
              "max": 4,
              "comment": "Plant Brain emits a scream, summoning a minion."
            }
          },
          {
            "condition": "bossHpIsZero",
            "action": {
              "type": "changePhase",
              "phase": 2
            }
          }
        ]
      },
      {
        "phase": 2,
        "comment": "Phase when Blank joins the team.",
        "triggers": [
          {
            "condition": "onPhaseStart",
            "action": {
              "type": "joinAlly",
              "allyName": "Blank",
              "comment": "Blank joins the battle."
            }
          },
          {
            "condition": "onTurnStart",
            "action": {
              "type": "summonCreature",
              "minionType": "minion",
              "amount": 2
              "max": 4,
              "effect": "Plant Brain screams continuously, summoning two minions every turn."
            }
          },
          {
            "condition": "bossHitByFire",
            "action": {
              "comment": "TODO: Plant Brain emits a powerful scream, confusing all minions."
            }
          }
        ]
      }
    ]
  },
  "creatures":
  {
    "boss": {
        "inheritsFrom": "Plant Brain",
        "health": 458,
    },
    "minion": {
        "name": "$Minion",
        "inheritsFrom": "Plant Spider",
        "scale": 0.6
        "health": 50,
        "description": "$MinionDescription",
        "comment": "A little spider inherited from Plant Spider with 50 HP."
    },
  },
  "conditions": {
    "bossHpIsZero": {
      "comment": "This condition is met when the boss dies first time.",
      "type": "attributeCheck",
      "check": {
        "target": "boss",
        "attribute": "hp",
        "value": "0",
      }
    },
    "bossHitByFire": {
      "comment": "This condition is met when the boss receives damage from any source that includes the fire element.",
      "type": "damageCondition",
      "check": {
        "target": "boss",
        "source": "any",
        "attribute": "damageElement",
        "value": "fire",
      }
    }
  },
  "text":
  {
      "$Minion": {
          "en": "Newborn spider",
          "jp": "生まれたばかりのクモ",
          "ru": "Новорожденный паук",
      },
      "$MinionDescription": {
          "en": "Unused description for bestiary",
          "jp": "Unused description for bestiary",
          "ru": "Unused description for bestiary",
      }
  }
}

In order not to write this manually, we can write a separate tool for configuring battles (similar to how Hades does it). The main idea is to offer modders a convenient and flexible configuration mechanism, all-in-one, where you can easily create new enemies, import models and sounds, create events, localize text into different languages, etc., using a convenient interface that provides Memoria, and not limited to the functions that the developers included in EventEngine.

RuslanMikhlevskiy commented 3 weeks ago

I fully support this, such a toolkit is sorely lacking.

Tirlititi commented 2 weeks ago

Thanks a bunch for the compliment 😊

I never played Dark Souls (even less modded it) so I'm not sure of what you mean by "Souls-like bosses". I played Sekiro though if that helps.

I was actually thinking of something like that for field scripts instead, in order to make it easier to add cutscenes / treasures / interactive objects and to modify the existing ones. But in both cases (battle scripts and field scripts), I'm not sure whether it should be a whole alternative system to the existing EventEngine or an extension that compiles into EventEngine code. I have started to extend the capabilities of EventEngine so we are not limited anymore to the native functions that the developers implemented (that's these two commits: 935e207 and 91e94a6, there's a video showing for example how a shop can be changed dynamically). Also, I think it would be very difficult to have two different event engines running in parallel. Thus, I think that it would be better to design these to be external tools that compile into EventEngine code.

The example you give could be done with Hades Workshop, except having 5 enemies at once (which would require a whole re-writing of the battle system anyways) and the extended language support. Actually, if Hades Workshop wasn't in old C++ relying on wxWidgets, I would suggest to simply add this kind of editor inside Hades Workshop directly.

If that's easier, it is also possible to have a tool that generates HW script and then use the command-line mode of HW to generate EventEngine code from that. So either way is possible:

I talk of the script there because that's the main part, but it'd also be important to generate enemy / ability datas and that must be done in binary format anyways (that's much easier than generating scripts in the binary format though).

In short, I think the systems we have are already flexible enough to make almost everything we want. It's the "convenient" part that I agree should be worked on.

Albeoris commented 2 weeks ago

@Tirlititi looks like this's time to share my project. :)

I added you and @snouz as collaborators if you are interesting. This completely overlaps with what you wrote above. I love C#, so instead of using a pseudo-language I'm trying to get C# to be completely valid and compile.

I am not considering the option of creating a virtual machine to interpret C# byte code (this would be strange, to say the least). Therefore, the project has two directions of development: a completely separate EventEngine, which provides the engine with the necessary dependencies and allows the compiled C# code of the scene to be executed (a very ambitious goal), and compiling the C# code back into native bytecode. This will allow you to use all the features of the IDE for editing scenes and at the same time is not a super-complicated task, since in essence, I need to parse the syntax tree using Roslyn and generate bytecode based on it.

To test the correctness of the compiled bytecode, a lot of unit-tests are needed, but as a first approximation, we can limit ourselves to repeated decompilation and comparison with the source file. Yes, in this case we will not be able to execute any C# code (although we can try to do IL inserts via BinaryFormatter :D), but we will already be able to conveniently navigate through the code.

If you are interested, feel free to join. I've tried very hard to keep the code clean, so it shouldn't be too difficult to figure out. Location files for tests are linked in releases in the repository.