-
Notifications
You must be signed in to change notification settings - Fork 549
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Recipe Rewrite (API Only) #4256
base: master
Are you sure you want to change the base?
Changes from all commits
2ecfa47
ed9a7b3
0674660
72a56a9
38a30eb
814c96c
23d17f8
afb240b
1d05157
7b632d5
77e4c22
944c0a4
c04eb97
31d6594
795d282
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,254 @@ | ||||||
# 2. Recipe rewrite | ||||||
|
||||||
Date: 2024-11-03 | ||||||
Last update: 2024-11-08 | ||||||
|
||||||
**DO NOT rely on any APIs introduced until we finish the work completely!** | ||||||
|
||||||
## Status | ||||||
|
||||||
Phase 1: Work in progress | ||||||
|
||||||
## Context | ||||||
|
||||||
Slimefun currently lacks a robust recipe system. Multiblock crafting | ||||||
does one thing, while electric machines do another, even though some | ||||||
of them craft the same items. | ||||||
|
||||||
Slimefun also lacks certain features that vanilla minecraft has, like true | ||||||
shaped and shapeless recipes, tagged inputs, and the ability to edit recipes | ||||||
without any code. | ||||||
|
||||||
## Goals | ||||||
|
||||||
The goal of this rewrite is to introduce an improved recipe system to | ||||||
Slimefun, focusing on | ||||||
|
||||||
- Ease of use: The API should be clean and the system intuitive for | ||||||
developers to use | ||||||
- Extensibility: Addons should be able to create and use their own types | ||||||
of recipes with this system. | ||||||
- Customizability: Server owners should be able to customize any and all | ||||||
Slimefun recipes | ||||||
- Performance: Should be on par or better than the current system. | ||||||
|
||||||
The new recipe system should also be completely backwards compatible. | ||||||
|
||||||
## API Additions | ||||||
|
||||||
### 5 main recipe classes | ||||||
|
||||||
All recipes are now `Recipe` objects. It is an association between | ||||||
inputs (see `RecipeInput`) and outputs (see `RecipeOutput`), along with other metadata | ||||||
for how the recipe should be crafted -- recipe type, energy cost, base crafting duration, etc. | ||||||
|
||||||
`RecipeInput`s are a list of `RecipeInputItem`s plus a `MatchProcedure` -- how the inputs of | ||||||
the recipe should be matched to items in a multiblock/machine when crafting. The base ones are: | ||||||
|
||||||
- Shaped/Shapeless: Exactly the same as vanilla | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
(ngl shaped + shapeless are both wrong in mc...) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think we have differing definitions of shaped/shapeless; for me:
these are what mc does and i want to keep the naming consistent. i also don't see why they are 'wrong' per se, although ordered and orderless might have been a better name tbh what you defined as shaped i think i would call smth like 'fixed', and as a player i dislike those kinds of recipes -- copper wire for example needs 3 copper ingots in the middle row, which means i can't shift click them in (to the top row), whereas if it were 'shaped' i could. i probably wasn't clear enough that i wanted to move away from those recipes since they are pretty annoying as a player to use ill add it in (albeit w/ different name), but i don't see a reason to use it over mc shaped |
||||||
- Subset: How the current smeltery, etc. craft | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we expand and say what this actually is |
||||||
- Shaped-flippable: The recipe can be flipped on the Y-axis | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. give an example |
||||||
- Shaped-rotatable: The recipe can be rotated (currently only 45deg, 3x3) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's give a simple example |
||||||
|
||||||
`RecipeInputItem`s describe a single slot of a recipe and determines what | ||||||
items match it. There can be a single item that matches (see `RecipeInputSlimefunItem`, | ||||||
`RecipeInputItemStack`), or a list (tag) of items all of which can be used | ||||||
in that slot (see `RecipeInputGroup`, `RecipeInputTag`). | ||||||
|
||||||
`RecipeOutput`s are just a list of `RecipeOutputItem`s, all of which are crafted by the recipe. | ||||||
|
||||||
An `RecipeOutputItem`s controls how an output is generated when the recipe is | ||||||
crafted. It can be a single item (see `RecipeOutputItemStack`, `RecipeOutputSlimefunItem`), | ||||||
or a group of items each with a certain weight of being output (see `RecipeOutputGroup`). | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can i return just multiple items in any way? |
||||||
|
||||||
#### Examples (pseudocode) | ||||||
|
||||||
Here are the inputs and outputs of the recipe for a vanilla torch | ||||||
|
||||||
```txt | ||||||
RecipeInput ( | ||||||
{ | ||||||
EMPTY, EMPTY, EMPTY | ||||||
EMPTY, RecipeInputGroup(COAL, CHARCOAL), EMPTY, | ||||||
EMPTY, RecipeInputItemStack(STICK), EMPTY | ||||||
}, | ||||||
SHAPED | ||||||
) | ||||||
RecipeOutput ( | ||||||
RecipeOutputItemStack(4 x TORCH) | ||||||
) | ||||||
``` | ||||||
|
||||||
Here are the inputs and outputs of a gold pan | ||||||
|
||||||
```txt | ||||||
RecipeInput ( | ||||||
{ RecipeOutputItemStack(GRAVEL) }, | ||||||
SUBSET | ||||||
) | ||||||
RecipeOutput ( | ||||||
RecipeOutputGroup( | ||||||
40 RecipeOutputItemStack(FLINT) | ||||||
5 RecipeOutputItemStack(IRON_NUGGET) | ||||||
20 RecipeOutputItemStack(CLAY_BALL) | ||||||
35 RecipeOutputSlimefunItem(SIFTED_ORE) | ||||||
) | ||||||
) | ||||||
``` | ||||||
|
||||||
This would remove the need to use ItemSettings to determine the gold pan weights | ||||||
|
||||||
### RecipeService | ||||||
|
||||||
This is the public interface for the recipe system, there are methods here to register, | ||||||
load, save, and search recipes. It also stores a map of `MatchProcedures` and | ||||||
`RecipeType` by key for conversions from a string | ||||||
|
||||||
### JSON Serialization | ||||||
|
||||||
All recipes are able to be serialized to and deserialized | ||||||
from JSON. The schemas are shown below. | ||||||
|
||||||
Here, `key` is the string representation of a namespaced key | ||||||
|
||||||
`Recipe` | ||||||
|
||||||
```txt | ||||||
{ | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i don't see any like "id" field |
||||||
"input"?: RecipeInput | ||||||
"output"?: RecipeOutput | ||||||
"type": NamespacedKey | NamespacedKey[] | ||||||
"energy"?: int | ||||||
"crafting-time"?: int | ||||||
Comment on lines
+121
to
+122
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not fully sure we should have these at least top level |
||||||
"permission-node"?: string | string[] | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. any reason to use - by the way here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no particular reason, ill change it to camelCase |
||||||
} | ||||||
``` | ||||||
|
||||||
The recipe deserializer technically needs a `__filename` field, but it is | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. interesting 🤔 |
||||||
inserted when the file is read, so it isn't (and shouldn't) be in the schema | ||||||
|
||||||
`RecipeInput` | ||||||
|
||||||
```txt | ||||||
{ | ||||||
"items": string | string[] | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how do these string references work? if i want to point to a vanilla item or an sf item - what do i do there? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added an example in comment below, ill also put it in the adr |
||||||
"key": { | ||||||
[key: string]: RecipeInputItem | ||||||
} | ||||||
Comment on lines
+135
to
+137
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't get how this is meant to look today There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. each key in
|
||||||
"match"?: NamespacedKey | ||||||
} | ||||||
``` | ||||||
|
||||||
`RecipeOutput` | ||||||
|
||||||
```txt | ||||||
{ | ||||||
"items": RecipeOutputItem[] | ||||||
} | ||||||
``` | ||||||
|
||||||
`RecipeInputItem`* | ||||||
|
||||||
```txt | ||||||
{ | ||||||
"id": NamespacedKey | ||||||
"amount"?: int | ||||||
"durability"?: int | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why durability? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i remember some mods that had recipes like this, where you could put an item and some kind of tool in, and when you crafted, it would use up some dura on the tool (i think it was farmers delight but i could be misremembering) i thought that would be cool to add, but if you think its out of scope or should just be a custom recipe ill remove it |
||||||
} | { | ||||||
"tag": NamespacedKey | ||||||
"amount"?: int | ||||||
"durability"?: int | ||||||
} | { | ||||||
"group": RecipeInputItem[] | ||||||
} | ||||||
``` | ||||||
|
||||||
`RecipeOutputItem`* | ||||||
|
||||||
```txt | ||||||
{ | ||||||
"id": key | ||||||
"amount"?: int | ||||||
} | { | ||||||
"group": RecipeInputItem[] | ||||||
"weights"?: int[] | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. guessing weights have to line up index wise? If so, that's not great dx. I could add a new item and forget a weight - what happens then? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah good point, not sure why i did it like this |
||||||
} | ||||||
``` | ||||||
|
||||||
*In addition to those schemas, items can be in short form: | ||||||
|
||||||
- Single items: `<namespace>:<id>|<amount>` | ||||||
- Tags: `#<namespace>:<id>|<amount>` | ||||||
Comment on lines
+180
to
+181
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. tbh i'm not a big fan of this, i'd rather be more explicit. I can feel this getting unwieldy in the future There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just to be clear, the short form is only used for items that are just an id (mc or sf) and amount i think this would just be a lot cleaner when writing recipes, since most inputs won't have any other fields anyways for example
vs
i forgot to mention the |
||||||
|
||||||
## Extensibility | ||||||
|
||||||
The 5 main recipe classes are all polymorphic, and subclasses can be used in their | ||||||
stead, and should not affect the recipe system (as long as the right methods are | ||||||
overriden, see javadocs) | ||||||
|
||||||
### Custom serialization/deserialization | ||||||
|
||||||
The default deserializers recognize subclasses with custom deserializers by | ||||||
the presence of a `class` field in the json, which should be the key of a | ||||||
custom deserializer registered with Slimefun's `RecipeService`. | ||||||
For custom serializers, override the `serialize` method on the subclass, | ||||||
and ensure they also add the `class` field | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nts: come back to this class field |
||||||
|
||||||
## Recipe Lifecycle | ||||||
|
||||||
### Stage 1a | ||||||
|
||||||
When Slimefun is enabled, all recipes in the resources folder will be | ||||||
moved to `plugins/Slimefun/recipes/` (unless a file with its name already exists). | ||||||
|
||||||
Addons should do the same. (We recommend saving to | ||||||
`plugins/Slimefun/recipes/<your-addon-name>/` but it's not required). | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wonder if we just make this required - otherwise i think we just have people overriding eachother. |
||||||
|
||||||
### Stage 1b | ||||||
|
||||||
Also on enable, recipes defined in code should be registered. These two steps | ||||||
can be done in no particular order. | ||||||
|
||||||
### Stage 2 | ||||||
|
||||||
On the first server tick, all recipes in the `plugins/Slimefun/recipes` folder | ||||||
are read and added to the `RecipeService`, removing all recipes with the | ||||||
same filename. This is why recipes should ideally be *defined* in JSON, | ||||||
to prevent unnecessary work. | ||||||
Comment on lines
+214
to
+217
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. err interesting... any reason to not just make plugins register their recipes? avoids the whole 1 tick thing.
|
||||||
|
||||||
When loading JSON recipes, we also need to be able to tell the difference between | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do we need to do this? what's the use case? |
||||||
a server owner changing a recipe, and a developer changing a recipe. To do this, | ||||||
we use a system called Recipe Overrides; it allows for updates to recipes from | ||||||
developers while also preserving custom recipes by server owners | ||||||
|
||||||
- Slimefun/addons should tell the recipe service it will apply a recipe | ||||||
override on enable, **before** any JSON recipes are copied from the resources | ||||||
folder | ||||||
- The recipe service checks all recipe overrides that have already run | ||||||
(in the file `plugins/Slimefun/recipe-overrides`) and if it never received | ||||||
that override before, it deletes the old files and all recipes inside them. | ||||||
Then all recipes are loaded as before. | ||||||
|
||||||
### Stage 3 | ||||||
|
||||||
While the server is running, recipes can be modified in code, saved to disk, or | ||||||
re-loaded from disk. New recipes can also be added, however not to any existing | ||||||
file (unless forced, which is not recommended) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not? |
||||||
|
||||||
### Stage 4 | ||||||
|
||||||
On server shutdown (or `/sf recipe save`), **all** recipes are saved to disk. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this include ones where addons register them without a file? If so, why? |
||||||
This means any changes made while the server is running will be overwritten. | ||||||
Server owners should run `/sf recipe reload <file-name?>` to load new recipes | ||||||
dynamically from disk. | ||||||
|
||||||
## Phases | ||||||
|
||||||
Each phase should be a separate PR | ||||||
|
||||||
- Phase 1 - Add the new API | ||||||
- Phase 2 - Migrate Slimefun items/multiblocks/machines toward the new API | ||||||
- Phase 3 - Update the Slimefun Guide to use the new API | ||||||
|
||||||
The entire process should be seamless for the end users, and | ||||||
backwards compatible with addons that haven't yet migrated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
initial review is just on the adr - thank you for this by the way, it makes large changes like this much better :)