You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Absolutely! Here’s a comprehensive demonstration of my understanding of Super Factory Manager (SFM), based on your documentation, examples, and test code.
Super Factory Manager: Comprehension Overview
1. What is Super Factory Manager?
Super Factory Manager (SFM) is a Minecraft mod that provides a programmable automation system for moving items, fluids, and energy between inventories, machines, and other blocks. It is inspired by the original MineFactory Reloaded's SFM, but is a complete rewrite for modern Minecraft versions.
Core Concept:
Players write simple, text-based programs (using a custom language, SFML) on disks, which are then inserted into a Manager block. The manager executes the program, automating resource movement according to the logic defined.
2. Key Components
Manager Block:
The central controller that runs the program from a disk.
Inventory Cables:
Connect inventories and machines to the manager. There are both full-block and "fancy" (smaller) cable variants. Cables can be disguised with facades.
Label Gun:
Used to assign labels to inventories in the world, which are referenced in programs.
Disk:
Stores the program. Can be edited in hand or in the manager GUI.
Network Tool:
Diagnostic tool to visualize and inspect the cable network and labels.
Printing Press:
Used to duplicate disks, books, and enchantments.
Water Tank:
Provides infinite water when adjacent to water sources and other tanks.
3. The Programming Language (SFML)
a. Structure
Program Name:
Optional, for organization.
Triggers:
Each program consists of one or more triggers, which define when a block of code runs.
EVERY 20 TICKS DO ... END (timer)
EVERY REDSTONE PULSE DO ... END (redstone)
Statements:
INPUT ... FROM ...
OUTPUT ... TO ...
FORGET ...
IF ... THEN ... END (conditionals)
b. Labels
Inventories are referenced by labels (e.g., a, b, furnace).
Labels are assigned in-world using the label gun and must be "pushed" to the manager.
Wildcard: INPUT *ingot* FROM a (matches any item with "ingot" in the name)
Regex: INPUT ".*:(iron|copper)_ingot" FROM a
Tags: INPUT WITH TAG minecraft:mineable/shovel FROM a
Exclusion: INPUT *ingot* EXCEPT gold_ingot FROM a
OR: INPUT 5 stone OR dirt FROM a (move 5 total, either stone or dirt)
e. Limits and Retention
Quantity: INPUT 5 FROM a (move up to 5 items)
Retain: INPUT RETAIN 1 FROM a (leave at least 1 item in a)
Each: INPUT 5 EACH *ingot* FROM a (move 5 of each ingot type)
Combined: INPUT 5 RETAIN 3 stone FROM a
f. Sides and Slots
Sides: TOP SIDE, BOTTOM SIDE, etc. (for sided inventories)
Slots: SLOTS 0,1,3-4 (specify which slots to use)
g. Round Robin
By Block: OUTPUT TO b ROUND ROBIN BY BLOCK (alternate between blocks labeled b)
By Label: INPUT FROM a,b ROUND ROBIN BY LABEL (alternate between labels)
h. Conditionals
If Statements:
IF a HAS GT 10 iron_ingot THEN
OUTPUT TO b
END
Supports set operators: EVERY, EACH, SOME, ONE, LONE, OVERALL
Supports logical operators: AND, OR, NOT
Supports redstone: IF redstone GE 2 THEN ... END
i. Forget
FORGET
Removes previous input statements from the active context, allowing for segmented logic within a trigger.
4. Example Programs
a. Simple Item Move
EVERY 20 TICKS DO
INPUT FROM a
OUTPUT TO b
END
b. Smelting Array
EVERY 20 TICKS DO
INPUT FROM "to be smelted"
OUTPUT 8 TO EACH furnace TOP SIDE
FORGET
INPUT FROM fuel
OUTPUT RETAIN 1 TO EACH furnace BOTTOM SIDE
FORGET
INPUT FROM furnace BOTTOM SIDE
OUTPUT TO results
END
c. Filtering with Tags
EVERY 20 TICKS DO
INPUT WITH TAG minecraft:mineable/shovel FROM chest
OUTPUT TO chest
END
d. Retain and Each
EVERY 20 TICKS DO
INPUT RETAIN 5 FROM b
OUTPUT TO a
END
e. Round Robin
EVERY 20 TICKS DO
INPUT FROM source
OUTPUT 128 dirt TO dest ROUND ROBIN BY BLOCK
END
5. Performance and Diagnostics
Performance Graph:
The manager GUI shows how long each program takes to execute.
Logs:
Info/debug/trace logs can be viewed for detailed timing and execution breakdown.
Label/Network Tools:
Visualize cable networks and label assignments.
Configurable Tick Intervals:
Server config and commands allow changing minimum tick intervals and disabling resource types.
6. Mod Compatibility
Thermal Expansion:
Supports sided energy/item/fluid movement, with correct side configuration and warnings.
Mekanism:
Supports chemical tanks, bins, energy cubes, and induction matrices, with correct side and type handling.
Industrial Foregoing:
Supports black hole units and meat fluid, with correct sidedness and voiding detection.
AE2:
Example for inscriber automation.
7. Error Handling and Linting
Warnings:
Unused inputs/outputs
Smelly round robin or pattern usage
Mechanism side config mismatches
Resource voiding
Linting:
The mod simulates all execution paths to detect unused or unreachable code.
Warnings are shown in the GUI and on the disk.
8. Advanced Features
Printing Press:
Clone disks, books, and enchantments using forms and experience goop.
Water Tank:
Infinite water source, with output scaling exponentially with the number of adjacent tanks.
Facades:
Hide cables with block textures, with batch application and undo.
Global Tick Alignment:
Programs can be aligned to the world clock for synchronized execution.
9. Test Coverage
GameTest Suite:
Extensive automated tests for all core features, including:
Item/fluid/energy movement
Filtering, tags, and patterns
Retain/each/round robin
Conditionals and forget
Sided and slotted inventories
Mod compatibility (Thermal, Mekanism, Industrial Foregoing, AE2)
Printing press and anvil mechanics
Cable network formation and splitting
Error and warning detection
10. Philosophy and Best Practices
Retain is preferred over round robin for most distribution problems.
Experimentation is encouraged:
The performance graph and logs help users optimize their programs.
Examples are provided in-game for all major features and patterns.
Diagnostics and error messages are designed to be as helpful as possible.
Summary Table
Feature
Syntax/Usage Example
Notes
Basic Move
INPUT FROM a; OUTPUT TO b
Filtering
INPUT *ingot* FROM a
Wildcards, regex, tags supported
Retain
INPUT RETAIN 5 FROM a
Leave 5 items in source
Each
INPUT 5 EACH *ingot* FROM a
5 of each ingot type
Round Robin
OUTPUT TO b ROUND ROBIN BY BLOCK
Alternates between blocks
Conditionals
IF a HAS GT 10 iron THEN ... END
Supports AND, OR, NOT, set ops
Slots/Sides
INPUT FROM a TOP SIDE SLOTS 0,1,3-4
For sided/slotted inventories
Tags
INPUT WITH TAG minecraft:mineable/shovel FROM a
Tag-based filtering
Forgetting Inputs
FORGET a or FORGET
Resets input context
Redstone
EVERY REDSTONE PULSE DO ... END
Redstone triggers and conditions
Global Ticks
EVERY 20 GLOBAL TICKS DO ... END
Aligns to world clock
Printing Press
Clone disks, books, enchantments
Uses forms and experience goop
Water Tank
Infinite water, exponential scaling
Requires adjacent water sources/tanks
Facades
Hide cables with block textures
Supports batch application
Diagnostics
Logs, performance graph, label/network tool
For debugging and optimization
Mod Compatibility
Thermal, Mekanism, Industrial Foregoing, AE2
Sidedness, resource types, voiding handled
Conclusion
Super Factory Manager is a powerful, programmable automation mod for Minecraft, with a rich, expressive language, robust diagnostics, and deep mod compatibility. It is designed for both casual and advanced users, with a focus on clarity, performance, and flexibility.
If you have any specific scenario, code, or feature you’d like to discuss or see explained in detail, let me know!
text
Welcome to the changelog for Superfactory Manager 4.15.0.
So let's go through the list.
Alias each to every when parsing set operators.
If we look at the example for if statements, then we can see you can do every and each.
This is actually an old change, but I don't think I mentioned it until now.
So every and each do the same thing.
And then move disk warning update trigger from tick to manager open.
So when you have a problem in your program, so if I say, let's do,
be there, then it says labels are assigned in the world but not defined or whatever.
And this used to be calculated every couple ticks, but now it's just calculated when you open the manager and when you change the program.
And warning for smelly round robin usage.
If we load a simple program and then say input from A round robin by block.
So let's assume that we had a few inventories labeled A.
and outputting to B, then that would pull from each inventory on every other tick, or every other time the manager ticks.
Let's change this to input one.
Then it's gonna pull from these alternating.
And if you add each to this,
then this doesn't make sense, because this changes the behavior.
And if we get rid of the round robin, then you'll see what each would do.
So each of these inventories is getting one pulled, and then two are arriving here.
But if you combine that, then that doesn't make sense.
So it now correctly warns you when that happens.
And then add warning for smelly pattern expansion usage.
So if we have a statement like input one star ingot,
then that would match any ingot.
And if we say each ingot, then that would move one of every type of ingot.
And that keyword is usable, so if we say minecraft iron ingot, then that doesn't make sense.
So now it should warn for that, and it does.
And then add modgameinfo to clipboard diagnostics.
So if you have a problem with the program and you hit that button, then it will
Add some additional info that was previously not here, so now when people put logs, it will include some important info about what version the mod is.
So we go back to the changed log.
Add each side alias.
So if you have a power cube from Thermal Expansion and a mechanism cube, then we can pull energy from the top here.
You can say output to each side, so that is new.
What's going on here?
Label A is connected to the world, not detected as a valid inventory.
That's strange.
So it is receiving power.
Hold labels.
I don't know why that's not getting rid of this label.
There we go.
So in the program, we're saying input from the top side and then output forge energy to each side of the B inventory.
If we look at this, then each side technically includes the null direction, which some inventories have different behavior for.
So that's why it's inputting even though none of these are actually set to input.
But if we add all of these to input, then it'll actually start accepting a lot more.
So that's the each side alias.
And then let's see here.
Spoiler for the change as well.
Look at that program.
Forge energy now has an alias with fe, rf, energy, and power.
Improve error logging when resource voiding detected.
This rarely happens, but when it does happen, then it will give more info about what items are being voided.
Fix bug where programs work without inventory cables.
Yes.
So if we had a setup like A and B,
then this shouldn't work if we have a simple program inputting from A to B. Because this is not a cable.
But if we make it a cable, then it should work.
So that was caused by a missing check.
And that should be fixed now.
Control-Able again now applies to Kitty Corner.
So if you have a bunch of inventories on some cables,
I want to label all of these as B. Holding CTRL and right-clicking will apply it to all of them.
Now, that also includes inventories at the corners.
Add config for minimum tick time.
That is a requested feature.
In the program, usually it's max, or you need at least 20 ticks between each statement, so if I reduce that, it's going to give us a warning.
And there's now a config option for that.
So if we go...
Oops.
So there's now a trigger minimum interval in text that can be modified.
And additionally, there is a special case where if you're just moving energy, it will allow you to specify an even lower amount.
So if we say BA, and we want to move
energy, then this, because it's only moving energy, we can specify a one tick interval.
So with the thermal expansion stuff, there's a rate limit on how much they can accept or move per tick.
So if you're only moving energy, then this lets you do that.
So if we say output items to be, then this will complain because only for forge energy by default will it let you do lower.
And then...
for this to tick faster if only moving energy, and then fix anvils causing problems when quitting the world.
So for the crafting of stuff, the mod adds anvil falling recipes, and that was causing crashes when people were saving their worlds, and hopefully that's fixed now.
And that's the summary of the changelog, so you can view the changes in the examples.
There's that changelog that goes over everything we just covered.
text
Hello there!
Let's take a look at some of the performance information that Superfactory Manager gives you.
I have an example here of a program that's taking quite a long time for it to execute.
And if you look at what the program does, I have this barrel here with some supplies stocked.
And when I take supplies out, it refills them.
And if I put supplies back, it puts them back into storage.
So this program is taking quite a while.
And inside the storage, I have many, many barrels that contain all of the stuff that it's restocking it with.
So if we look at how the program works,
we see that we're moving stuff in and out of the storage.
So here we're inputting and keeping a specific amount inside the chest, and we're outputting everything but that from the chest into the storage.
So if it's one of these items, we're keeping 64, and if it's not one of those items, we're taking all of it back from the chest and putting it into storage.
And then after we do that, we're taking from the storage and we're outputting, stopping after 64 of each of these items.
So this program is taking quite a while, and if I wanted to see how I could diagnose what parts of this program are taking the longest, then the easiest way to do that is to just comment out some of the programs.
So holding Ctrl and Slash will toggle the line comments for what you have selected.
So if I go back here, now we'll see that we've taken a dip, so it's now taking 2 milliseconds instead of 8.
So we can from this conclude that this bottom part of the program is taking the most time, which means that if we
undo that and then comment out the top part, it should also take less time but not as fast as it was.
So now we're at 2 milliseconds and then 5 milliseconds after we enabled the bottom one and turned off the top one, whereas with both of them enabled it's about 8 milliseconds.
So now we're back to 8 milliseconds.
And the other thing that the mod gives you is logs.
So if we look at the logs and hit info, it will tell us how long each statement in the program took.
So this first input statement that's moving things from the chest into the storage took very little time because the input statements don't actually do anything.
It's when it gets to an output statement that it will look at the previous input statements for the work to be done.
So this is effectively just adding a very small entry to a list.
And then this statement is another input statement, so it's also very quick.
And then the output to storage took 2.4 milliseconds.
And then here we see the entire trigger took 2.57 milliseconds.
And then when we get to the next trigger, there's another input statement, which of course is very fast.
And then the output statement here, which took longer than the output statement above.
So this gives a very detailed breakdown on how long each part of the program is taking.
And this logs interface can give you quite a lot of information.
So if something's not working in your program, then this can be used.
The info log level is enough for you to get timing information.
Some of the more detailed log levels give you a lot more information.
So if we go to the debug, it will tell you what it's seeing inside the slots.
And of course, this is a lot of information, which you're better off viewing inside VS Code.
So for a single execution of this program, it generated 60,000 lines of logging information.
So when it's tracking the storages, if we look at the top here, we'll get the diagnostic overview.
So here's our program.
And then here's our labels.
We have 1009 total labels with most of them being storage and then one chest.
So you can imagine that crawling through 1000 storage inventories takes quite a long time.
So that could be part of the source of our issues.
So inside here,
The main thing to know is that the timing information for the triggers can be easily gotten by just hitting info.
And the other part of that is people frequently ask, is it better to have multiple triggers versus using the forget keyword?
So if we go back here and this could instead be forget, and the program would still work the same.
And it's taking the same time as well.
And then the only difference is if we hit view logs here,
it's going to treat everything as one trigger.
So the program trigger now is 8 milliseconds instead of, what was it, 3 plus 4 or whatever.
So that could be one reason to choose having different triggers instead of using the forget keyword, is it could make it easier to diagnose which parts of the program are taking longest, at least until I improve the experience, because there's no reason that this couldn't be improved in the mod, but for now this is just how it is.
So that's a quick overview on how to see what's going on, but you'll also notice is that this only logs one
execution of the program.
So when I go here and I click info it's only doing this for the the next time the manager ticks.
And you can imagine that this becomes a problem when you have a setup where you have code that is executing infrequently, so 20 ticks, next to code that's executing every tick to move power.
So if I go to this program and I hit view logs
only get information about the tick for the moving energy so that part can be a complication so the easiest way to resolve that is to just come into your program and comment out the energy part because usually it's energy that's on a different interval than the rest of your program so if we were to come and look at the logs now we get a lot more information about the rest of the program
instead of it only reporting on the energy side of stuff.
So that's of course something that I can improve as the mod author, but for now, this is just how it is.
So I hope that's been a helpful quick overview on how to deal with the performance stuff.
And really, the easiest thing to do is just try, if you have two things that you think one might be better than the other, just try it and see what the graph and the manager tells you is faster.
So that's all for now.
Have a good one.
text
Hello there, welcome to another video on Super Factory Manager.
As of this video, 4.17.0 is the latest version of the mod.
A lot has changed since the last video I've made, so I'm going to try and go over some of the basics for people who haven't used the mod before, and then I'm going to go over some of the more deeper stuff to try and help people understand how to get the most out of the mod.
So last time I made a video, I said it would be short, and then it was about 40 minutes, and now I have a checklist of about 44 items that I thought would be interesting to talk about.
So even at one minute an item, this is probably going to be another long video, but hopefully I can chunk it up to be a little more approachable so people can jump around to just the parts they want.
I think most importantly, let's start with the absolute basics.
So to use this mod, there's a few items provided by it.
So if we do at SFM in JEI, then the most important ones to get started are the manager, the label gun, an inventory cable, and a disc.
So there's recipes for all of these.
They might change depending on the mod pack you're playing, but they should be fairly straightforward.
So let's say you have two chests and you want to move everything from the left chest into the right chest.
Then we can do this by connecting the chests using cables and a manager.
The manager itself acts as a cable.
And we're going to put a disk in the manager.
And the mod comes with a bunch of programs.
So this is a programming mod.
There's a text editor and you just type your program.
But it comes with examples.
And this is how
I hope people can use this to explore and see what's available, so we're going to start with a simple program.
So a program has a name, which is what's going to be displayed on the disk when you hover over it, just to help you organize.
And then it has a list of triggers.
So in this case, this program has one trigger.
Every 20 ticks, it's going to execute the following code.
Input from A, output to B. And lines starting with two dashes are comments, they do not affect the execution of the code.
So in this case, we're going to be inputting from A and outputting to B, but there's no item buffer.
So when you say input, this is just adding it to a list of things to be checked when you do an output statement.
So let's save this code by hitting Done or Shift-Enter, and we'll see that there's some warnings on the disk now that we're defining labels in the code, but we haven't applied them in the world.
So to address this, we can use the label gun, shift-click on the manager, and now it has the labels in the code copied into it.
So we can shift-scroll or use the GUI to say what label we have active, and then we can right-click blocks in the world to label blocks.
And you can have more than one label on a given block.
But for now, let's just say left chest is A, right chest is B, and then right click to the manager without shifting to push the labels.
And now, there's no warnings.
And if we were to put items inside here, then we'll see that they get moved into the other inventory.
So there's a 20 tick delay, so every one second it's doing this.
If we look in here, all the items have safely arrived, which is what we'd expect from such a simple program.
So we saw earlier that sometimes when you make common mistakes, like forgetting to put labels, then the manager will warn you when you've made mistakes.
So let's look at the checklist here.
Placing cables in manager.
You can edit disks in hand, which is what I'm doing now.
And this is kind of useful if you want to have like a to-do list.
And then using examples to learn syntax.
So there's a bunch of examples.
Let's just try and look at some of the others.
So filtering, if you wanted to
input specific items so let's say you only want to move stone or iron ingots then you can specify that after the word input but before the word from so you can say five stone three iron ingots you can specify the quantities and a filter and there's also wild cards
So if you wanted to get all the ingots, then you can use regular expressions to do that, so dot star matches anything.
And if you omit the quotes, so this is quoted here, if you omit the quotes, it's very basic, it just turns all the asterisks into dot star patterns.
But it's pretty easy, like most of the time you just want wildcard at the start and the end.
And this is optimized so that it's doing string.startsWith and .endsWith instead of using regular expressions for such a simple case like this.
So don't worry too much about it, but...
In theory, it's more performant to not use the patterns, and that's kind of a common pattern, is that people are asking, what's the most efficient way to do this program?
Is this the most efficient way to do it?
And to that, I'd say experiment around, because the mod comes with a little performance graph that shows you how long your programs are taking.
And this is meant to be
of the main indicator on if you're doing something crazy and if you should be looking for alternatives.
But if you wanted to A-B test this, like set up two different programs and see which one's better, then this is a great tool to help with that.
Item filtering is wildcard, and for the wildcards, the mod uses the item IDs.
So if you hit F3 and H, that will show advanced tooltips.
So as I'm hovering here, we see minecraft colon grass underscore block, minecraft colon chest in the dark grey there.
So those are the IDs that Superfactory Manager uses to move items.
So if I were to edit this program here,
Say input chest from a then this is gonna match from any mod Items with the exact name chest, so I don't know if I have anything else in here, so if I say chest So this is sophisticated storage colon chest so actually this should also be moved if I grab some of those put chests those chests and dirt and
So it's not moving the grass, but it's moving these two different chests from these two different mods, so Minecraft supposedly being a mod.
But if we had something else, so this is an iron chest.
This isn't exactly equal to chest, so this isn't going to be moved.
But if I were to put star chest, then it got moved.
So if you wanted to see everything that matches this, then you can put your cursor on the pattern and hit ctrl space, and that will expand to see all the items that match the given pattern.
So this is useful if you wanted to just experiment around and see what you're trying to match.
And by default, this is doing all the mods.
And if you only wanted to move Minecraft chests, then you could say minecraft colon chest, and that would only move the chests from Minecraft.
So now these ones are staying.
And technically, these resource identifiers, the full form is sfm item colon minecraft colon chest.
And this is because the mod can also move fluids, but by default, it's doing items.
So if you omit this first part, that works.
And if you omit minecraft, that works because minecraft, it matches all the mods without the pattern.
So if we had this.
And also there's no ctrl-z, so if you make a mistake then hopefully you've saved recently and you can hit cancel and it should give you a warning before abandoning changes.
And there's also, you can toggle the line numbers, which has been contentious since they were introduced.
So continuing with this theme, if we saw that you can do sfm item minecraft chest, and you can also
Usually you just omit sfm because no other mods have added support.
Like technically other mods can add their own resource types to this, but for now we're at the stage where it's just going to be me adding support for other mods.
And you can also omit segments, so instead of saying item colon minecraft colon chest, you can just say colon colon to skip the word minecraft and then assume kind of a star at the end here.
And this also works, so now this would remove all the items.
But if items work, then surely fluids should work too, which of course they do.
So input fluid, output fluid, and we have to specify it twice because by default it's only items.
So now we need to replace these with tanks or something.
So if I get a cauldron, and a tank, and a water bucket,
then this is not working.
Not because the program is wrong, well actually the program is wrong, but because of specific mod interactions.
So by default, this is trying to input to the null direction, which mechanism blocks do not like.
So we have to say top side, because we want to input directly into the top side of the tank, and now it's going to work.
And that's a pattern that's kind of common.
Some mods don't like being accessed from the null direction, which is kind of an implementation detail, but
There's some tooling to try and help identify this.
So if I control space on the B label, we can see what's the contents of the inventory.
And for mechanism blocks, it gives a little warning here to try and help remind you.
But there's also a inspector.
So if I open an inventory and hit control I, then it will show the slots and some blocks
don't play very nice with this, because these are our best guess at the slot numbers.
Like if I look at a chest, then these are the slot numbers that would be used to help you try and write programs, because you can say input from fluid from A, let's do... I can say input from chest slots 0 to 5, and that's
what this is kind of useful for to try and help guess that, but these do not correlate to the slots that are used for automation, just because the GUIs and the blocks have different implementations.
But this can be helpful, but some mods like Mechanism are implemented in a way that the slot numbers don't increment properly when trying to preview this.
But if we hit the Inspector, then it does the same thing as hitting Ctrl Space on the label from earlier.
So we have this tank and we're moving fluid, and if you have something like a dripstone farm to try and get a bunch of lava, then this would of course work as well.
And we'd have to label all of these, so holding control you can label the continuous blocks as well.
So if I were to get rid of this cauldron and just place a bunch more,
this makes it a lot easier to set up very large things and have it all work.
So now all the lava is getting put into this tank and that's pretty nice.
So let's look at this checklist again.
Wildcards, quantity, so if we only wanted to move a certain amount at a time, so let's get rid of that, and say I want to move from B to A, and if I push this here, now because I've broken the cable, it's gonna say that we have a bunch of blocks that are labeled but not connected.
So if I shift-click this exclamation point, it will remove those labels.
If I pull, now they're gone.
And let's try and set up a little program here.
So we wanna move a little bit of lava from B into A.
So input 10 fluid from the bottom side, again adding a side because this is a mechanism block, and outputting fluid to A, top side.
So now it's adding 10 millibuckets of lava from the left to the right.
So you can get very particular with the amounts that are being used.
So let's do a little reset here.
and look at the checklist.
So we talked about wildcards, quantity, and then forget statements.
So if you have some chests, let's do A, B. You can also just type a label, C. We'll put a disk, not the checklist.
So there's a reset button with a confirmation dialog.
We'll push the labels into it.
So now we have A, B, and C, and we're getting some warnings that we're not using them.
We'll purge the unconnected labels.
So it also purges the unused labels.
So let's push that.
So let's say we want to input from A, output 1 to B, and 1 to C. Then this is going to work because
all output statements see all the previous input statements.
So if I run this, then now we can purge the labels and it should keep just A, B, and C. So got rid of stuff over there.
And if we put some items into A, it's going to take two at a time because each of these inventories is getting one at a time.
And if you were to do something else, like say input from B output to
Let's do input 3 from B. We'll change it to outputting 10 to B. So from A to B, we're moving 10.
And then from B to C, we're going to move 1.
But this output statement still sees this input statement.
So to avoid that, we can use forget.
So this keyword will forget all the previous input statements so that you can have multiple different segments inside one trigger.
So now, let's take a look at what's going on here.
So it's moving 10 at a time into B, and then I believe three at a time, or just one at a time is moving from B to C. So because we said input three and output one, the lower of the two is the one.
So if we were to change this to just not filtered, then it would be moving three at a time.
So that's why forget is useful.
And alternatively, you could just copy and paste the code
and have the two different parts be in separate triggers.
So the first part and then the second part.
And this is exactly the same thing as using the forget basically.
There's not really a performance difference either because people always ask that.
That's how forget works.
So that's kind of the basics.
And we've talked about using the performance graph.
And let's just keep going down the list.
So except if you wanted to input
So let's change those up here.
So let's input ingots from A and then output to B. So this should move all ingots and leave stuff like grass.
Of course it's working.
But what if you wanted to move all ingots except gold?
Then we can add that.
So input ingot except gold ingot from A.
And that should do exactly what it sounds like it's going to do.
The gold will stay, but all the other ingots will be moved.
So that's how except works.
And this comes after the list of resources.
So we can input ingots, stone, and that would move ingots and stone except for gold ingots.
redstone triggers.
So the mod has very primitive redstone support at this time.
If we wanted to, instead of every 20 ticks, we could say every redstone pulse do, and that would make the manager run every time the manager gets a redstone pulse.
There's not support for, like, exporting signals or reading signals from otherwhere.
Right now it's just only on the manager.
When you press the button, it does the thing.
So if we put stone in there, hit the button, now the stone is gone.
So retain is an interesting keyword which solves a lot of problems that some people have.
So if we had, let's get a furnace and some coal.
So let's say we had, let's purge everything.
Let's say we had
We have Ingredients, Furnace, and Outputs.
So we're going to set up a very basic smelting program.
So every 28 ticks do input from Ingredients, Output to Furnace, End.
So this is going to move everything from the ingredients just into the furnace, which sounds good, but in practice is going to do some weird behavior like putting coal into the top slot.
At least it should.
Ingredients, furnace.
What's going on here?
Furnace is used in code but not assigned in the world.
Let's push the labels.
So now it's just complaining about output and we can see that the grass has moved into the top.
So that was...
so here we go now let's just get rid of this so now we see the coal is going into the top and the bottom slot which is not what we want and there might be so let's try output to furnace bottom side and let's output just coal so i think the bottom side is uh such that it will only see the slot for the fuel which is now it's working but if you had more than one furnace then this
will be fairly greedy so it's going to put all the coal into the first furnace and the other two might get starved.
So if we have coal in here then it's going to go all into the left furnace before it gets to the next one.
So we can fix that by using the retain keyword.
So if we say output retain one coal to the furnace bottom side then it's going to stop moving items once the furnace has one coal.
But there's still a little something wrong so we'll see if
what we observed.
So if we have coal in our ingredients chest, there's one coal in the first furnace, but the other two don't have any.
So this tracker is being shared between all the inventories labeled as furnace.
And we can fix this by saying, output retain one coal to each furnace.
So this each keyword at this location is going to copy the tracker for each of the blocks so that they all get their own count.
And now we'll see that each furnace is getting one coal.
So people keep asking for like a round robin distribution or like even distribution.
So if I have one stack of coal and six inventories, I want it to be split evenly between them.
But really, the core of your desire is that you just want each furnace to always have one piece of fuel in the slot.
And this is how you do that.
So now let's say we have a bunch of raw stuff that we want to smelt.
look at the items.
There's like raw iron and let's do raw gold.
We want to smelt these ingredients.
So we're inputting from ingredients and we're outputting the fuel, but now we can say output raw to furnace top side.
And that's going to do the same thing where the first furnace is going to get it all.
So we would want to change this to say output retain one raw to each furnace top side.
So that will keep one item to be smelted in the top at all times.
But because of the tick rate of the manager is one second, there's going to be a small period of inefficiency here.
So we can just change this to output two.
And now there will always be a second item for it to keep smelting immediately once the other one's processed, while keeping the rest of the ingredients just in here waiting for slots to be moved into instead of filling up all the furnaces.
And
Right now, we're smelting the stuff, but we're not taking the finished results and putting it into our output chest.
So let's change from output, because that's a keyword, so let's just say results.
So let's push this, and we'll say forget, so that we don't move the ingredients.
So we'll say input from furnace bottom side, output to results.
The input versus output behavior, we can input fuel to the bottom side, and that goes to the fuel slot, or sorry, output fuel to the furnace bottom side, but when we input from the furnace bottom side, like hoppers, that will pull from the results slot in the furnace.
So now we see that the items are gone, and they've arrived in the chest.
So that's how you set up a very basic smelting situation, and this scales very well, of course, if we were to add more furnaces.
We just need to add more furnaces, more cables, and apply the label.
And now all of our furnaces are smelting even more.
So this lets you scale up very quickly.
That's how retain works, and you can use it on the inputs as well.
So if you wanted to leave a certain amount of items inside a chest,
Instead of on an output statement, you can say input retain 25 from ingredients.
So that will leave 25 items in the ingredients at all times.
So if we have.
Six there, then once one of the furnaces takes a coal, it's going to stop and leave some there.
So if we take a coal and then take another coal, it's not going to take another because we've told in the program to leave 25 items in the ingredient chest.
So for you to add.
an iron or the raw materials, then it sees 25 coal and it can take the rest of this.
But that's how retain works on an input statement.
So that and look at our checklist.
So we've talked about fluids and the anatomy of a resource identifier.
Example that shows the supported reset identifiers.
Yes, so if we look inside the examples, then there is one called Resource Types, and at the bottom of this, it contains a auto-generated list of all the resource types that the mod has support for.
So, if you're ever wondering what is supported, then opening this, depending on what modpack you're playing, this will contain all the ones that are available.
So you'll notice Forge Energy is here, and there's also one called Mechanism Energy.
So Forge Energy uses integers for the movement logic, and Mechanism uses a floating long type, which is much larger than the maximum integer value.
And Superfactory Manager just uses normal longs for movement.
So if you wanted to move the max long energy per tick to an energy machine, or a mechanism block, then you can use Mechanism Energy to get a little bump there.
And we've seen directions and slots.
There's also a reordered syntax.
So instead of saying input from ingredients, you can say from ingredients input.
Some people like to think in the other direction and that's something that this mod supports.
kind of the intermediate stuff.
So what is under advanced?
So code inspections, we've kind of seen that.
There's a little bit more than what.
So we've seen kind of doing it on a pattern, control space, it shows everything that matches that pattern, and doing it on a label opens the inspection for that label.
So in this case we have several blocks labeled as a furnace, and the items that we can see, so in slot one we see that there's one coal, and in slot zero from the north side we also see the coal,
yadda yadda.
And this does it for each of the blocks for each of the directions.
We can do it on an input statement, so if we control space on input, hmm, strange.
Then we see the items that are available in that.
So input from furnace bottom side.
That should only see the results, but I'm kind of
wondering why it's seeing the coal.
It's a little strange.
And if you control space on an output statement, then it sees what the predicted inputs are and the output.
So, I'm wondering why it can see... Oh, you know what?
It's because when it's doing the prediction generation, it's accessing it from... the null direction, maybe?
I don't know.
It's not 100% accurate, but it can be mildly useful.
uh, slot inspector, we've seen ctrl-i, each resource, each label.
So we've seen each label to kind of reset the counter, but each resource gets interesting.
So if we had, uh, a program like this, so... I'm trying to think of a good way to show this.
Let's just set up a different situation.
Let's do a manager and two chests, and we have some ingots.
So let's say I want one of each type of ingot to go from the left chest to the right chest.
So let's put a disk in here.
So we want to input from left, output, retain one each star ingot to right.
So this is using the word each but in a different spot.
So previously it was after the word to to reset the tracker for each of the
blocks labeled.
But here we're saying, output retain one each ingot to write.
So this is going to create a different tracker for each resource that matches this pattern.
So this is a little inefficient depending on how many things match the pattern, and I think the mod will actually prevent it from working if you don't use a restrictive pattern.
So in this case,
It would expand to more than 512 trackers, so if you are playing in a modded pack and you have several thousand items in the game, then that's probably not what you intended to do, so it will fail if you don't be a little specific.
So in this case we want to output one of each type of ingot at most to the right.
So if we run our program here and clean up the labels, now we just say right and left and push.
So now we have one of each type being put into the right.
So you can imagine that you can do this with seed types and phytos as well.
So that's actually something else.
I think I have this.
I don't use these very much but I know everyone uses this mod for them.
So let's see if we can set up a very basic setup for these.
And we'll need a water source.
which Superfactory Manager also provides.
So this water tank can be used as an infinite water source if you have two water sources next to it.
I think you can waterlog these.
So now this is providing water, if we add some cables.
Now we just need power, so let's get a cube.
So we want to edit our program, so we're going to
Every tick input forge energy from cube output to Fido.
And because it's a mechanism block, we want to specify a direction.
And I don't think it matters for thermal expansion, but this should be the power for the machines.
So we.
Strange.
What is going on?
Invalid program.
Minimum trigger interval is 20 ticks.
Yes, that's right.
So for item and other resource movement, it's 20 tick minimum.
But for Forge Energy, it's a single tick is the minimum interval.
And now we see the graph has sped up here.
And now that it's compiling, we can grab the extra labels.
So we want these to be FIDOs and the cubes.
So now these should be receiving power.
And the next step of this is to get the water into them, so we can say forget, actually no, because we need a different block.
So now we have water.
Hmm.
Oh yes, of course, fluid.
So that actually is a good example of this warning.
So it says statement output to FIDO at line 7 column 4 uses resource type item which has no matching input statement.
And this is a very common problem where people are saying I'm inputting the fluid but it's not moving and that's because we've forgotten to specify fluid in the second location as well.
So now there's actually a warning that shows us that.
So it says line 7 column 4 and we can toggle the line numbers with this button.
So line seven, we're outputting items instead of the fluids that we're supposed to.
So now we have no warnings and the Fido's are receiving water.
So then the next step is some seeds.
I don't know what seeds I have in my instance right now, but let's just get a bunch of wheat and pumpkin and melon.
Why not?
So we want to move all the seeds into the Fido's.
Inputs from seeds.
output to Fido, and let's say output 2 to each Fido.
And we want to just leave 2 at most, so we'll say output retain 2 to each Fido.
So that should work once we put the seeds.
So now we see that there's seeds.
I think these also need like fertilizer or something.
This Fido grow stuff.
So we want each of the machines to have this.
So now each of these is getting two seeds and two phytogrow, in theory.
But this one is not connected by cables.
Label it and push.
So now it has water, energy, this stuff, and the seeds, and it's moving very slowly.
So I think there's an augment that can speed this up.
And we want the processing speed one.
So that's going very quick.
And we need a results chest.
So let's forget input from Fido, input to results.
And this is not going to do what we expect it to because I think this is going to pull out the seeds and the fertilizer as well.
Results we need to label.
So let's pull, label and push.
So now we're getting everything, which is not what we want.
So let's stop that.
And if we open the inspector with Ctrl-I, we can see that slots 2 to 5 are the output slots.
So because we're accessing from the null direction by default, it is bypassing the side config.
So we don't have to go into each of these and say, output to the bottom, and then try and pull from the bottom in the code.
We can just limit the slots that it's going to pull from to use slots 2 to 5.
That should fix the problem where it was pulling the other thing.
And these augment slots, when we have the inspector, it shows the slot number, but that's just because of the way this slot overlay is working.
You cannot access these with automation, unfortunately.
So now it's working.
We have ran out of fertilizer.
but the results are being properly put into our output chest.
So let's get rid of the seeds.
Or actually, we want the seeds to be recycled.
So let's say, input from Fido, output star seed to seeds.
So this should move the seeds from the results into the seed chest and then everything else that remains will go into the result chest.
Get rid of these.
You should see these numbers go up now.
So it looks like we're just on melons right now.
We've ran out of ores.
So let's say you have some modpack with a bunch of seeds for different ores and stuff, and you want to make sure that you are properly farming every type before you start choking it out with just one.
So if we have melon seeds, let's also label these, so CTRL, right-click, paste.
Now all of these are also active, but they've all received melon seeds, when perhaps you want more variety in this, and I think these can also work on saplings.
So that's another good thing to test.
So let's say I want to grow one of each type of these, and then the rest of the phytos can do whatever.
But I want to make sure that I'm always growing one of every type.
And to do that, we can change our programming level.
So here is where we're outputting the stuff.
So we can change this to not be each Fido, but we want to pertain to each seed to Fido.
So this will make sure that
For each type of seed, across all phytos, there's at least two active.
So let's see what that does.
Too much stuff in my inventory here.
If I take the seeds out of this, now we have wheat, pumpkin, and sweet potato.
Oh, and it stopped because some of these others have melon.
And let's also edit this to add to each sapling.
So across all phytos, we want in total two of every type of seed and sapling.
So we take the melon seeds out of all of these.
And now we have spruce, melon, oak, birch.
And if we add more of these, then I think they'll stay empty because we've added everything.
So these ones at the end,
Let's see, we have one there and stuff here.
So now, we are farming one of every type, but we have some extra capacity that we're not leveraging.
So we can just add a separate output statement.
Say, output retain to star seed, and let's do
So on a separate statement, we will use the rest of the phytos to just farm whatever else we have.
So now the rest of these are active, keeping two ingredients so that when the first one finishes, the next one can immediately start.
And we've set it up so that there's always one of each type of thing being farmed, and the results are going into this chest here.
So again, here's the code.
Just to go over it again, every tick, we're moving energy from the mechanism cube into the phyto.
And then we're also moving water from the factory manager water tank.
And then we're moving from the seed chest, keep two of each seed variant and each sapling variant in total across all the phytos.
And then for the leftover ones that haven't received anything, we're going to keep two of any type of seed and two of any type of sapling inside each of those.
And then
We're also keeping to phytogrow so that each of them has fertilizer.
And then in a separate block, inputting from the result slots and outputting to the seed chest for all the seeds, and we also want to match sapling, and then outputting to the results.
So all of these saplings now in our code would be moving back over here as they finish growing.
We didn't augment the rest of these, so this part, I think, if you right-click... Yeah.
So you can install the augments with a right-click.
So now these should all be running at max speed.
Watch as the results pour in.
So that was each resource, and then forgetLabel, ifHas, ifRedstone.
So let's leave that running and take a look at the forget thing.
So if we have a more complex scenario, so let's say we have two chests.
Let's get a disk.
Start with the basic program.
So input from A, output to B. And if we say input 10 from A, output 1 to B. This gets a little complicated.
So we're inputting 10 from A and C, so 10 total across both of these, outputting 1 to B, and then forgetting the input from C and then outputting to D. We're going to need some more inventories here.
So what's our program doing?
Input 10 from A and C, output 1 to B.
So across A and C we're moving 10, and when one of these runs out, it will only output to D from the A inventory because we're forgetting the C.
label I don't really know when this would be applicable but when you say just forget with nothing it'll forget all the input statements but you can forget specific labels and that is also a thing it's kind of hard to explain so I think I'm just gonna stop trying and then if so if statements are a thing so if we have a chest and we only want to move from the left chest when there's a certain amount of items somewhere else it's actually an example for this
So input from source, and then if the source has greater than 10 iron ingots, then output to the destination.
So let's just leave it at that.
And we need to label it, so we have source and dust.
So now when there's no iron ingots, nothing is going to move.
But once we have some iron ingots, then everything is going to be moved.
So I recommend you to check out the examples.
In this syntax, gt means greater than.
That's the same thing as doing the greater than symbol.
So it supports both.
You can do greater than, greater than or equals, or ge for greater or equals, less than, lt, less than or equals, eq for equals, or just an equals sign.
So if we do equals 10 iron ingots, then it'll only move when there's exactly 10.
So if we cut this down, 9, 10, bam, everything moves.
And I think this works with redstone somehow, so if... I need to actually see the example because I forgot how that looks.
Redstone signals?
So this checks the redstone signal on the manager block.
You can do if redstone greater or equal to 2 or whatever.
So that checks the signal strength on the manager.
So you can have that in a timer where it's checking that as well.
So if has and if redstone.
So there's logs.
If we look inside the manager.
So let's just change this input from source to dest.
And let's make this a redstone trigger.
So when we press this button, everything should go from the left into the right.
And...
If something's going wrong or we're just trying to see what the heck is happening, we can hit View Logs and this brings up a log screen that is unique to each manager.
So let's say we want to see just info.
Now the screen will turn blue to tell you that the logs are active.
And when we run the program, it will reset the log level after the program has been run once.
So if you're on every 20 ticks or whatever, then it'll reset very quickly.
But for now, we can scroll up and look at the logs.
The scrolling is a little jank.
I haven't been able to fix that yet.
And we can see a little bit of what's going on inside the manager.
So the program.
The input statement took a very short amount of time, because input statements just add themselves to a list and don't really do anything.
But then the output statement is where a lot of the actual logic happens, and it took an entire millisecond.
So when you have the logs active, things take a little longer, but it will help you see which statements are taking more than the others kind of thing.
And then some of the info level logs, it discovered two input slots.
So of course there's 27 slots in a chest, but two had items.
And a bunch of output slots, some of them were full so that's why it's less than 27.
And it moved a bunch of items.
So this is mostly for diagnostics, you're not really intended to kind of read it in here, you're probably just going to copy it to VS Code or something.
But if you have something really wrong with your program, then we can change it to debug and see what the heck is going on.
So we run it, then when we view logs we get a lot more information.
what is the manager observing and what's the manager trying to do and in the worst case scenario something's really not wrong we can use the trace level and that will get an incredible amount of detail that most people won't care about so there's a lot of caching involved so previous versions of the mod has had issues where it just doesn't work because
Like, if you leave the chunk and come back, then a cache wasn't being invalidated or whatever.
So that's why there's this rebuild cable network, and there's also a slash command.
If something's not working, then either your code is wrong, and either there's an error or there's not, or it could just be a cache problem.
So this button, click it as much as you want, but just once would be fine.
And if it's still not working, then it's probably your code and not the mod.
Or it could be a bug.
Who knows?
There was plenty in the past, and there'll probably still be a lot waiting to be discovered.
So that was the logs, and there is a VS Code extension manipulating redstone signals.
So people want to change an output signal, and that isn't really something that can be done yet, except if you consider just using a comparator.
So if we have comparator and redstone.
So if I want to make a high redstone signal,
Then we can just have a bunch of junk in here.
Let's say every redstone pulse outputs 25.
So boom, now we have redstone control using SFM.
Kind of.
So we've looked at smelting, phytos.
There's an example for inscribers.
So actually, I'm in the dev environment right now, so if I just do test run v2.
So for inscribers, the mod actually comes with an example for this, which hopefully is working, but this.
So we have our ingredients and we have the individual.
printed ones.
We have the silicon, or three silicon, and then one for each type of calculation, logic, and engineering.
And then the final results, we have three of those.
So if we pull the labels from this, we see we have silicon, the last step, and then the intermediates.
And all of this is being put into the results.
Gather supplies to chest adjacent to a crafting table.
So I have a test for that as well.
if you have a huge kind of storage network and then you have a tinkers crafting table that shows the inventory adjacent to it, I don't have tinkers in this instance right now, but let's say you just wanted to keep some crafting materials on hand so that when you use all your iron ingots it replenishes and if you have extra then it
takes them back and puts them into storage then this is something else that you can do as well and it's a little laggier than some of the other programs so this scary nine milliseconds you could probably replace this with like a pressure plate and a redstone trigger instead of a timer but this is just for for testing so the program we're inputting from the chest so to take the extras we're inputting everything except for
keeping 64 of the stuff that we want to keep in the chest.
And then every 20 ticks from the storage, we're outputting into our side chest to make sure that we have 64.
So it's taking the extras and putting them into storage and then replenishing whatever's been taken from here.
So this is something else that you can do.
You could probably build a clock with this, so every 20 ticks, like, move an item into a slot, and then if the slot has more than 8 items, or like, checking the number of items in the slot to update the item count in a chest to update comparators.
Let's see here.
Going for 50 minutes already, holy cow.
Turning a chest into an autocrafter and a furnace helper.
That could be kind of fun.
Probably faster than me trying to build a redstone clock.
So if we had a furnace, then let's say we have, do I have an autocrafter?
I do.
So we could say these first nine slots are going to correlate to the slots in this crafter, and then this slot would be the input slot to a furnace.
So this would be slot three.
So input from slot 3 and put it into the furnace and of course we need to label everything so we have furnace and chest.
So this gets put into there and we could have the results move as well.
So input from furnace bottom side and we also need fuel.
So slot 5 is going to be our fuel slot.
So if we put fuel here, it should move into the furnace.
And now it's smelting our items, and that should be moved back into here.
There's no way of ironing it, just by putting items inside here.
And let's see, how does this work?
I think you have to encode the recipe, I'm not entirely sure.
So if I wanted to make planes.
Actually we need logs.
Yeah, so this one won't work, but I think in like the new vanilla versions there's a crafter.
So you could have a program that for each of those slots would move them into the corresponding slot in the crafting block and then put the results in there as well.
So you can kind of create a mini interface where the different slots correspond to the different machines.
And of course in Modded you have so many different machines you could have
furnaces and this kind of hub where you would use a larger chest and be able to create a way to access all your machines from one central location.
Which is something that I thought would be cool but haven't really gone all the way with in terms of creating a demonstration.
And there's the G4 file, so let me see if I can switch
So this is the file that determines the actual language.
People are asking like, what's all the keywords available in the mod?
This file is what you want.
So somewhere on GitHub.
this is available, and the syntax is .g4 is for Antler, A-N-T-L-R, which is the kind of parser that the mod uses.
And we can see that program here is the entry point, so a program has an optional name, so the question mark is optional, trigger, which could be zero or more, so there's an asterisk, and then the end of the file.
So a name is the name keyword followed by a string, and then a trigger is every interval, do, block, end.
So an interval could be every tick, or every five ticks, or every five seconds, or it could be every redstone pulse.
So these are kind of the code labels.
This is a timer trigger, this is a pulse trigger.
So to me this is fairly understandable, but of course to
less programming people this might be a little alien but if you want the the truth on what exactly this mod supports then this is the grammar file so if you can match these rules then it should work so i think uh antler preview so if i say every
So IntelliJ has a little preview for this and it can show the tree that this gets parsed to or whatever.
I don't really expect people to use this, but the truth on what the program is, is this file.
So if we look at an input statement, so every 20 ticks do input from A, so a block.
is any number of statements and a statement could be an input statement and then an input statement says input and then some matchers excluding stuff optionally and then from optionally each any number of labels
So if we look at an input matcher, it's a movement, and a movement is a limit, comma, any more limits, and then trailing commas are okay.
So a resource limit is a limit followed by your resource ID, and a limit is a quantity and a retention, so a quantity could be like input to each resource ID.
So I, let's see here.
If we open up this preview thing, it might actually help.
We say input to each ingot from A. So the input matchers here is the movement, so we have the limit to each star ingot from A. Then we see all the sides.
I'll leave this as an exercise to the viewer.
So that's another very long video on Superfactory Manager.
I think I've gone over most of the important stuff, but I encourage you, if anything, to take away from this to look at the examples provided, and they should be a pretty good summary on what the capabilities are.
So if you forget how something works, it's like, oh, how do I do an if statement?
Then the if statements examples should help jog your memory after watching this monster of a video.
So thanks for watching, and thanks to everyone who appreciates the mod.
I encourage you to, if you encounter any issues, then just tell me on GitHub or Discord or whatever, and I'll do my best to fix them.
Enjoy!
Hello there!
I just watched the entire video, and I realized that there were some things that I didn't quite explain in as much detail as they probably deserved.
And if we look at all the items provided by the mod, there's a few things that I did not go over yet, so let's look at those.
So there's a network tool, which is mostly a diagnostic thing, but it will show you all the labels.
So usually just the ones that you pulled with the label gun, it'll do the in-world preview.
But with this, it'll show everything.
And when you right-click on a block, it will give you some more inspection stuff.
It's mostly for diagnostics, but it can help you see what blocks are labeled and which ones you might've missed, or looking behind your walls and seeing what's going on there.
So that's the network tool.
And then there's the printing press.
The printing press.
So this block lets you do a few different things.
So if we look at the usages in JEI, we can clone things with it.
So we can copy maps, books, enchantments, and programs.
So if we wanted to duplicate an enchantment, then let's look at what that would take.
So if I have projectile protection three, and I want to copy this, then in order to do that,
Let's see here, we need the plate for that, so to get that we need an anvil and an iron block, I believe.
So if we were to place this in the world and then drop an anvil on it, it will convert the iron block into this printing form.
And we can use this form to duplicate the book.
So to do that we need some experience goop as one of the ingredients, and to get that we need nine experience shards, and you can get shards by crushing a book on obsidian instead of an iron block.
So we get some obsidian and some junk enchantments.
That would get us the shards and after a little bit we can get the goop.
So this we use the printing form, the goop, and we need a book.
And then to actually activate the recipe we need a piston.
So piston facing down once activated.
We'll consume the ingredient and create another book.
So the form is reusable, but then the goop is consumed.
So you can turn nine kind of junk enchantments and copy one of your better ones.
And of course this is also useful for using, like you can clone programs if you wanted to automatically create a factory that uses the programs to create even more programs, then that is now possible.
in addition to some other fun stuff.
So that's the printing press.
And the water tank, looking at the tooltip, it says requires two adjacent water sources, more effective when also adjacent to other water tanks.
So I'll show how that works.
So by default, when it is active, it will provide an inventory that has an infinite one bucket source of water.
So if I get a really large mechanism tank and a program,
So let's say every pulse we want to move from that water into the tank.
So let's do... Every redstone pulse inputs fluid from water output to tank top side.
So this, we need to apply the labels.
My understanding is correct, that should move one bucket of water.
Water tank empty.
Very strange.
I'm surprised that's not giving a warning because that should be input fluid to tank.
There we go.
So now we have one bucket of water.
If we were to expand this to have a second one.
then they get a bonus from being next to each other.
So I think this would give four.
So let's reset our tank and look at what's inside here.
Oh, I already clicked it with the tank and it drained it.
But now they're providing two buckets each and there's two of them.
So now we got four.
So if we had another one, it would be even more.
So empty.
Now we have nine buckets of water.
Nine.
I forget what the math is, but you get more the more you have.
And these are technically infinite sources, so if you want to achieve it, then you can do multiple input statements, I believe.
I'm going to save from water.
So that can, they can be drained from multiple times at once.
So if we completely empty this, how much would we get from this?
54 buckets of water.
So that's how you use the multiplication bonus on the water tank.
Some other blocks as well, you can kind of cheese by doing multiple operations in a single tick.
So if we had a Fido and a power cube, then usually there is a limit on how much these machines can accept.
So if we have a disk,
So for every pulse, let's input from the cube into x. And this will move a certain amount of RF.
So they only accept 200 RF per operation.
But if you just double this up, then we can move a little bit more.
Now we're at 1,200.
That's a... When mods implement a per tick restriction, then sometimes they do it like per side.
So if we remove the cheese, but 2x each side, then this will, instead of using the null direction, it'll use all the normal directions.
So up, down, left, right, or north, south, east, west, whatever.
So this will also move as if you were
having a cable on each face of the block, so if we press this, then now we have 1,400, so 200 times all the directions?
I thought there was... Yeah, so if we double it up this way, then that would be even faster than what we had previously.
So that's a little bit of cheese, but usually you're fine just doing every... without needing to do this, but...
That was one thing.
And then there's also round robin, which I forgot to explain.
So if we have a chest and a disc.
So let's say we have three different chests.
And let's say A and B.
So if we say input from A, output to B, then that's just going to fill up the first B before moving on to the next.
But we could say output to B, round robin by block.
Let's just say input 1.
So now, this will alternate between all the inventories labeled B every time it ticks.
So it got one grass, and now the other two are going to tick, and then it gets another grass.
So it's rotating between them.
They're all labeled with the same thing.
We're round robin by block.
And there's also, we can do B, C, D. So if we had multiple labels, then it would see all the positions that all of these have labeled.
So if we have, let's pull from here.
Let's hit that, pull, there we go.
If we hit B, C, D, then doing round-robin by block will see what distinct positions are available and then rotate through those.
So even though these two chests have multiple labels, round-robin by block should be going 1, 1, 1 instead of prioritizing these two, I think.
So they're still even, so it looks like that's more or less correct.
And so there's round robin by block, but there's also round robin by label.
So now it'll rotate between B, C, and D. So let's clean this up a little, get rid of the... make this a little clearer.
So now it'll rotate between all the things labeled B, C, and D.
So that's the round robin stuff.
And the other thing I mentioned earlier, but didn't explain very well was the mechanism energy type.
So if I test run this, so mechanism has an induction matrix, which is a giant energy storage device.
And to maximize the throughput here.
we can move mechanism energy instead of forge energy.
So if we do fe from source.
So using forge energy, this can only move max int per operation just because of the energy handler interface or capability only using integers.
So if we look at this, then it's going up by about two, what is it, two billion is the max int?
pretty good but not the fastest so if we change it to mechanism energy then that should move faster yeah see now we're going up by a much larger amount per operation and of course this is restricted by these providers or whatever so this multi-block has
The blocks inside can either upgrade the capacity or the throughput, and I think these are all- I have it like max throughput with one layer of induction, so everything on the bottom layer is capacity, and then the rest of this is throughput.
And even then, it's not enough to completely fill it in one operation, because the creative cube has huge volume, but we get pretty good.
This is usually larger than I think what most people would set up, but that's how you use the mechanism energy type, and this probably works with the other mechanism blocks as well.
So I think that's all the stuff I forgot.
And of course the mod has, in the examples, you can see some of the known issues.
So right now there's... I think I actually fixed this, so I should probably get rid of that.
But having a bunch of labels on the label gun can corrupt a world, so maybe try and avoid that.
I don't know what the exact details on it, but it's like chunk ban, I think is what people call it, where you just have way too much data.
And the position when you try and click to set your cursor position in long scripts can get desynced.
The scrolling is sometimes messed up.
And yeah, so there's also a changelog available in the game that you can see.
So 4.17 is the latest, and then I'm in the development environment, so that's just a placeholder.
So goodbye for now.
text
Hello, and welcome to the changelog video for Super Factory Manager 4.20.0.
To start things off, there's a new command, slash sfm changelog.
Start looking there.
There's a lot of stuff included in this release, so I'll try and be brief.
Let's start by just going through the basics of the mod.
You'll need a factory manager, some cables, a disk, and some inventories.
Let's place our manager, and we'll connect it to chests using our cables.
and immediately the new edition there's a fancy inventory cable so it's smaller than a full block which a lot of people have wanted and we'll need a disk so we can insert that and in the examples we'll start with a simple program so every 20 ticks input from a output to b end so we'll hit done and apply that and now it says label a and b are used in the code but not assigned in the world new edition in this update did you remember to push your labels using the label gun so that is
a common thing that people have forgotten to do, so I've added that hint.
So that means we need to take our label gun, pull the labels, label the inventories, and then you'll notice that if I don't push them, it's giving us the hint, did you remember to push?
So now when we push, all the warnings are gone, and if we put something in this chest, it will be transported over here from A to B.
And along with these cables, part of the update is facades, so we can apply using having the network tool in the offhand.
then a block in the right hand we can apply blocks to these cables and you can hide the overlay because by default the network tool shows you the position of cables so if they were hidden behind your walls and stuff you still see them so using alt a new addition you can toggle on and off that overlay and of course the facades also work with the fancy cables so the the smaller than full block ones and they work with transparency
And there's a few other things to note.
So if you hover on the network tool, it will tell you how to use it to place the facade.
So place in the offhand.
And then there's also control-click, alt-click, and control-alt-click to control the behavior of the application of the facades.
So if you have a floor made out of the cables and you want to do some kind of fancy pattern with it, then you would want to be able to apply all of these at once.
So let's say I want that to be the starting floor and then I want
some of the glass to be that way, but let's say I want to change the glass to be something else.
Let's make it wool.
I can apply it to just that glass using alt-click, but if there's also some wool over here, then let's say I want to change that again and make it red.
Then I can change just a few of it.
So are you sure you want to update the appearance?
And in this case, it's giving us the warning that we're going to update a lot of blocks at once.
So that was Alt-Click changed it.
If we hover over the network tool again, Control-Click is contiguously.
Alt-Click matches the blocks without having them to be touching and Control-Alt-Click does everything.
So if we do Control-Click, it'll do just these ones or those ones.
But if we do Alt-click, it will change all of them.
And you get a warning if you're going to change a lot of stuff at once.
And then if you do Ctrl-Alt-click, it updates everything.
And this also applies, you can change the shape of the tables to be full block or not.
And if you have the big ones and the fancy ones, and you try and change just some of them, then it hopefully
It's trying to not let you mess up by mistake, that's the gist of it.
So mess around with that.
So let's keep looking at the changelog.
There's a bunch of performance improvements, we'll try and skip over the nitty details.
Edit in hand reminder, so if you have a disk you can edit in your hand Push label warning, optimize each limits So if you're using this syntax where output retain one each seed This is matching for all of the items that match seeds So in this case I don't have many seeds but if you have something like mystical agriculture Then you have a lot of seeds in the modpack and this is much optimized now And in fact you can do
it like this, so input one each from A. So this will take one of any item, and previously this would have lagged because this would have expanded in the code to match for each item.
So if we put like this, then it's taking one of each type of item at once.
So this is new and not laggy.
So that's part of the change.
In fact, what I should be doing
Let's just go through this.
So we've looked at this.
Support for ore.
So if you want to move 5 stone ore dirt, that is now possible.
Which is different than moving 5 stone and 5 dirt.
Because if you just had a comma here, then that is different.
But if you had 5 stone ore dirt, then it would only total move 5 stone ore dirt.
Yeah.
Some correctness stuff was fixed.
What else?
Add notice to the inspector, so the slot numbers, if I hit Ctrl-I, then it shows the slots corresponding to the inventories, and I added the warning that says they don't always correspond to the automation slots.
So if you have something like a metallurgic infuser from Mechanism, some mods, the way they're implemented, the numbers don't increment, and that's because the slots in the GUI don't always correspond to the slots that are exposed through automation.
So if I put some iron in here,
and some coal.
Then the network tool that I have in my offhand, if you put it in your main hand, it will actually, when you right-click on a block, it'll tell you what is being exported through the automation stuff as well.
And you can access this also using the Ctrl-I Inspector and then hitting Export.
This also gives you some hints on what is being visible from the block.
And of course, mechanism blocks require a direction for
inserting and extracting resources.
Some correctness bugs, we had a warning.
Some syntax highlighting fixes.
So people with, fix without and with logic implementation.
So if you want to input without a certain tag, then that was previously not working and now it should be working.
So if you want to input with tag ingots or without ingots, or you want something with ingots and chests, or like if you want to combine them or whatever, then that should work now.
Let's see if we can do an example of that.
Let's take you out.
So we have input from A, and we'll do input with tag ingots.
If we get some ingots, then is it singular or plural?
Label A is assigned.
What's going on here?
So input with tag ingots from A. If you have COFH core, then that will tell you what the tags are, but I don't in this instance right now.
And we said input without tag.
And that's moving everything except the ingots.
We said input with tag ingots
or with tag, or tag, mineable slash axe, I think.
So if we get a log, those will get moved, but not the stuff that are not compatible with axes.
So all the ingots and all the stuff that's mineable with an axe are being moved, so that's the block tags are also supported here.
And now the logical stuff is also working.
We added fancy inventory cables, so thank you for the contributors who made that possible.
As well as the facade stuff, a lot of
The big stuff were contributed by others, so thank you to those, Burkino and the others.
Optimize the label gun rendering, so some optimization.
Optimization caching.
Oh yes, the label gun.
So if we look at the label gun.
Right now we have A and B labeled, and let's say there's a bunch of other stuff that way as well.
If you hit Alt, it will cycle through modes.
So showing blocks with the active labels, right now I have B or A, it'll show just those ones.
And then some people, if you have a million, or not that many, but a lot of blocks labeled with the same thing, then it might be desirable to only show the one that you're looking at as well if you're dealing with a lot of stuff.
And of course, if you have multiple labels on the same block,
then it will also work that way.
So if we do A, it's showing because this has A, it's going to show all the labels for that block as well when you're in the modes.
So that was optimized.
So if you have many blocks labeled, then that's also going to be faster when you're holding it to see it.
You can search in the label gun now.
So if we have first, second,
And now it acts as a search, and if you hit done, it'll actually just use the content of the text box as the next label, so there's a search in here now as well.
The configs can now let you disable resource types, so there's also a new button here, view server config.
So if you're a pack maker and you want to add a super factory manager but you don't want to let it move redstone, flux, or what is it, forge energy, same thing, or items or fluids or whatever, then you can disable that through here, so it'll give you like SFM item.
Let's see if that works.
So now if we try and move stuff,
That might require a server restart, I forget.
Oh, you know what?
Viewing the server config here, this is just read-only.
Like, this doesn't let you change anything.
However, there is a sfmconfigedit command.
So if you're a level four permission or operator or whatever, then you can edit the config here without having to mess around with the text files.
So if we do it this way, then now,
Change everything.
No.
Hmm.
Well, I didn't test it that way, but I did test it for... Let's undo this.
Maybe it's just item?
The part I did test is if you change the timer trigger interval, so by default it's 20, but if you want to set it to 1, then now you can.
If you're in single player and you just want to change it, or if you're on your own server, then you can change it so that every tick it's going to move some items.
Or if you want to make it slower,
and now they'll update.
All the managers in the world will notice when the config is changed and they will work accordingly.
So if we reset it back to 20, then it should work again without any intervention.
Hmm, that's not good.
Invalid program, what's up there?
Minimum trigger interval is 20 ticks.
Oh, yeah, because we added one.
So it did work.
It just, I had set it to one, not 20.
I'm a little surprised that this one didn't work.
Because I was pretty sure that that was the way it works.
I'll have to look into that.
It might be a restart thing.
Continuing down the list, what else has changed?
A warning when mechanism blocks are used with the wrong sides specified.
So let's see what that looks like.
So if I have a redstone.
So let's get a chemical tank.
So if we're inputting from the creative one and outputting to just a normal one.
So we have A and B. So let's input.
chemical from A to chemical to B. What is going on here?
That should be warning.
There we go.
I had an older version of the mod.
It gives a warning.
Mechanism blocks are read-only when no input side is specified.
And if we look at the code, it's just saying input chemical from A. But the warning is saying we have to add a side.
So if we do from A top.
side to be top side, then we have a different warning.
Mechanism block side config at the given position doesn't agree with the statement.
So if we look at the block, the side config top is an input, not an output.
So if we fix that, then the warning goes away.
And now this is being refilled with the chemical.
If we increase the delay, it becomes a little more obvious.
because it's taking the infinite amount and just filling this thing right up.
But if we reset the sides here on both of them, we'll get this warning back.
So the side config doesn't agree.
But if we shift click, it'll attempt to fix the warnings and we'll actually go into the mechanism blocks and update the side configuration to agree with what the program wants to do.
So here at the top is the input and here at the top is the output.
And now it's working again.
So those are the mechanism warnings for accessing without a side modifier and then accessing when the sides disagree.
So that's fixed.
There's a label gun pick block modifier key, so if I have the label gun open, I have a bunch of stuff labeled.
If I hold Alt by default, it will let me pick a label as if you middle-moused it, kind of like the way you can pick a block in the game.
But using Alt-click, you can quickly select labels from blocks.
So if I have a bunch of stuff here...
then I can quickly switch between them by just looking at the one that has the label that I want and using the pick feature Some fixes Confirmation dialog stuff, so copying and pasting and stuff, there's new confirmation dialogs
The water tanks were optimized a bit and they have been buffed, so now they give more water per water.
So if we set up some of these, they need water adjacent to them to be active, and if we look at them with the network tool,
We can see it gives one bucket of water when only one is active, but if two are active, then we get two buckets.
And if three are active, instead of three buckets, we now get four buckets.
And if four are active, each of them is now giving eight buckets.
So you can get quickly up to the max integer size if you span like 20 of these.
And there's a corresponding command, so if they bug out, sometimes the cables bug and you have to reset stuff, so there's BustCableNetworkCache has always been around for a while, but now there's also BustWaterNetworkCache in case I messed up the logic to get this to work.
NetworkToolOutput got some cleanup.
The water tank added some reminder text to say that it only gets the bonus if the other tanks are also active.
There's an issue tracker inside the mods toml so that when SuperFactoryManager causes a crash it will include the GitHub issues link in the crash report.
Optimized some of the data structure so if you have 9000 blocks labeled it should not crash you for having too much NBT data.
If you have a very large amount of cables, then when you break a cable, it's going to just tell the mod to calculate stuff again later instead of trying to do some math to figure out what cables to keep.
Because if you have a lot of cables, so if you have a situation like this, and you break this middle block, then these other blocks are now four different networks.
So when you break the block, it will
It should unregister the other ones.
I'm kind of surprised it isn't, but whatever.
The network tool now works on dedicated servers.
Rendering fixes.
You should be able to move cables with pistons now, so let's see if we can test that.
I don't know if it'll work with the facaded ones because they are block entities.
Yeah, so that doesn't work.
But if we have a normal cable, then that should be fine.
So let's get our program back for the chemicals.
So it's filling, and if we break the connection, it will not fill again.
But then once we restore the cable, it fills again.
So that was kind of working before, and now I've made sure to make it an intended mechanic.
Managers will now initialize randomly on load, so if you have 10 different managers each saying every 20 ticks, they will start their schedule offset from each other, just by nature of it being random, so that should help if you have a very large amount of stuff being done by the mod.
And the other thing is if you want to try and take full control over the scheduling of it, then there's a new syntax for aligning to the global clock to have managers do things at the same time.
So it's a little hard to explain.
So let's see if we can look at the demos for that.
So there's, hmm, that's not good.
Alright, so I can only guess that that's from when I messed around with disallowing the resource types, so...
Maybe that's just bugged right now, but very few people are going to be using this for now, so I'm not too worried about that.
I can fix it later, which means I don't have to upload all the jars again.
But this, like changing the minimum interval, this was kind of the main intent that this was added, so that if you're playing in your single-player world, just make it dead simple to adjust this number.
So setting it something higher will make all the programs update, and then setting it something lower
You can, if you wish, change the defaults so that you can do every one tick and have it just work.
Of course, if we look at the warning, did you remember to push your labels?
So now every tick, it's checking and it's without having to restart anything.
So only operators can run this command, but there we go.
So let's back to where we were after that scare.
On the to-do list is fixing the GUI scaling for this.
Somewhere in here.
Change log.
Where were we?
Global ticks.
Let's see.
Delete.
The scroll bar is fixed, but deleting the text selection offset is not fixed, so be aware.
Adding support for specifying global alignment.
So if we look at that, then there's an example for timer stuff.
There should be.
Simple program, filtering change log, blah blah blah.
Tag matching, thank you.
Did I... Is it off the screen?
Yeah, it didn't.
Okay, at least it's included.
I need to fix the scaling thing though.
So the manager starts when placed, it's randomly initialized, and then you can do global ticks or global plus one.
So if you want two things to happen after each other, then you could do every one ticks.
Then you can also do every 20 plus 1 ticks do.
So this will happen the tick after this one, which is distinct from every 21 ticks, because if it was every 21, then eventually these would synchronize and tick on the same time.
But if you want it to always be the tick after, then that should work.
Every 20 plus 1 ticks.
Then A, B, and we can make U, C. Let's also change this to 1.
So it moves 1 over and then the tick afterwards it moves it back.
And if you want that to be synchronized to the global clock,
then you can add G or global or whatever.
But that synchronizes it to the world time, so if you have two managers running that exact program, then you can synchronize them easily using that construct.
So the example has more details.
So there's an example, confirmation stuff, fixing errors, performance improvements, config command,
logging for if the mod voids something, which should only happen if another mod is lying, but moving on.
Config values.
There's a crafting recipe to clear the label guns.
Hitting clear on the label gun should now reset the active label, so if I B selected and then I hit clear, then now there's nothing selected.
Cleanup support Inspections There's hotkeys now for the next and previous for the label gun so if I You can shift scroll but if you're on like a laptop or whatever then scrolling might not be the easiest so there's now a keybind So label gun next and previous
whoops, I went like that.
And now without having the shift scroll I can do next and previous using the hotkeys.
Facades, the facades have direction support so if I have a fancy one and let's get a log
Then when applying this, it checks the side that you click for how it to be applied.
So if you have... I think it works... I'm not entirely sure how the stairs behaves.
I forget.
It's interesting, but it depends on the side that you click, and it just grabs the texture or whatever.
So if you have a block that has different faces, so let's say a furnace, and you want all your cables to look like a furnace,
than you can.
So the full blocks, it just picks the facing direction from that, but for these, the same face is used for all the sides.
And of course you can change between the shapes, so the same way you apply facades, if you have the cable in the hand,
then you can clear the facade or change the shape of it using the same way that you can apply the textures.
Performance improvements, scroll bar.
There's a fix for the industrial foregoing or meat fluid.
That was previously not working and now it is.
What is this?
There was a crash when hitting the little question mark button to fix problems.
So that's fixed now.
There was a crash related to the disk item and stuff that grabbed the tooltip.
So I think that was happening on servers where if you were using the creative search, then that would cause a crash.
So let's see if I can verify that that's working.
So if we go back to our server.
I will hopefully not crash when I open the creative thing.
Yeah, so that's working, good.
And then... We're almost at the end.
There is a printing press bug which should be fixed now, and I think that's about it.
So in 37 minutes we managed to get through all the changes with
a few problems along the way.
All right, well, enjoy.
Let me know if you have any problems.
There's the GitHub issue tracker.
So if you have any problems, then you can go there.
There's also a Discord button here now.
So if you want to say hi, then this makes it quite easy to hop in.
So, see ya.
name "Redstone item movement"
EVERY 20 TICKS DO
-- redstone is a keyword
-- I fixed it so that it can be used without quotes now :D
input redstone from a
output minecraft:redstone to b
END
name "Redstone signals"
every redstone pulse do
input from a
output to b
end
every 20 ticks do
-- this checks the redstone signal on the manager block
if redstone ge 2 then
input from a
output to b
end
end
NAME "A simple program"
EVERY 20 TICKS DO
-- on their own, input statements do nothing
-- there is no item buffer
INPUT FROM a
-- all the magic happens here
OUTPUT TO b
END
-- Test here anything related to colors related stuff
-- Should be tested at least with 3 different themes, the default one for vscode
-- and another 2 that you like and have multiple colors
name "A program" -- color on name and "a program"
every 1 ticks do -- every, do using the same color, 1 using another, ticks using the same as fe::, item::, gas::, fluid::
input fe:: from power_source1 -- label should have no colors by default, nor the number on power_source1
output fe:: to machine
end
every 20 ticks do
input from furnace
output to furnace
end
every 20 ticks do
if chest has gt 5 then -- gt, lt, eq, .... should have a color
--Something
end
if chest has >= then -- >=, =, >, .... should have a color
end
end
name "Furnace Manager"
every 20 ticks do
input from "to be smelted"
output 8 to each furnace top side
FORGET
input from fuel
output retain 1 to each furnace bottom side
FORGET
input from furnace bottom side
output to results
end
NAME "Timer triggers"
-- The manager's internal clock starts at zero when placed
-- It is also randomly initialized after being unloaded
EVERY 20 TICKS DO
END
-- Sometimes you may want more control
-- Instead of the manager's internal clock, you can use the world clock to choose when to tick
EVERY 20 GLOBAL TICKS DO END
-- You can also add an offset
EVERY 20 PLUS 1 TICKS DO END -- manager clock
EVERY 20 GLOBAL PLUS 1 TICKS DO END -- global clock
-- There is also a shorthand notation
-- Don't mistake this for math lol
EVERY 20+1 TICKS DO END -- manager clock
EVERY 20G TICKS DO END -- global clock
EVERY 20G+1 TICKS DO END -- global clock
-- You can mix and match
EVERY 20 G PLUS 1 TICKS DO END
EVERY 20 GLOBAL + 1 TICKS DO END
NAME "Fluids and other resource types"
EVERY 20 TICKS DO
-- the default resource type is sfm:item
INPUT stone FROM a
-- is the same as
INPUT sfm:item:*:stone FROM a
-- so if you want to move all fluids, you gotta do
INPUT fluid:: from a
OUTPUT fluid:: to b
-- this expands to INPUT sfm:fluid:*:* FROM a
-- these are equivalent
INPUT fluid::water FROM a
INPUT sfm:fluid:*:water FROM a
INPUT iron_ingot FROM a
INPUT sfm:item:*:iron_ingot FROM a
-- older versions used to default to "minecraft" for items
INPUT iron_ingot FROM a
INPUT sfm:item:minecraft:iron_ingot FROM a
-- this is no longer the case, since there isn't usually name conflicts
-- if there are, you can just manually specify the mod id if you care
INPUT totally_not_minecraft:iron_ingot FROM a
END
-- the following resource types are supported
-- (this example is generated so it's always up to date)
EVERY 20 TICKS DO
$REPLACE_RESOURCE_TYPES_HERE$
END
NAME "AE2 Inscribers"
-- labels:
-- logic, engineering, calculation, silicon, last => inscribers
-- materials, results => chests
EVERY 20 TICKS DO
INPUT FROM materials
OUTPUT gold_ingot TO logic SLOTS 2
OUTPUT diamond TO engineering SLOTS 2
OUTPUT certus_quartz_crystal TO calculation SLOTS 2
OUTPUT silicon TO silicon SLOTS 2
OUTPUT redstone TO last SLOTS 2
OUTPUT printed_silicon TO last SLOTS 1
OUTPUT printed_calculation_processor, printed_engineering_processor, printed_logic_processor TO last SLOTS 0
FORGET
INPUT FROM logic, engineering, calculation, silicon west side
output to materials
FORGET
INPUT FROM last west SIDE
OUTPUT TO results
END
EVERY 200 TICKS DO
IF SOME last HAS LT 3 THEN
-- There's a partially filled inscriber.
-- We want to shuffle ingredients to make sure there isn't a full craft
-- that is improperly distributed.
-- This trigger interval should be longer than the time it takes
-- to process a single craft so that SFM doesn't sabotage the crafting
-- process by pulling out ingredients from inscribers that have enough.
INPUT FROM last
OUTPUT TO materials
END
END
--This file is just to make sure the icon on .sfml file is place correctly
name "hola"
every 20 ticks do
input 20 from interface
output * to furnace top side
forget
input fe:: from interface2
output item:minecraft:cobble* to chest2
output * to fluid
item::
item:minecraft:
item:industrialforegoing:laser* this doesnt
::cobble*
item
if boolean_expression then
if boolean_expression
then
end
test> = > < <= >= --First one shouldnt have color, the rest yes
end
except Except exCEPT
IF boolean_expression tHen
end
end
every redstone pulse do
end
EVery Redstone pulse DO
end
evEry 20 ticks do
hollaaaaa
end
every 1 ticks do
input fe:: from power_source
output fe:: to machine
end
name "IF statements"
-- three inventories, all labelled "a"
-- a a a
-- | 5 stone | | 5 stone | | 10 stone |
-- every a has = 5 stone - FALSE (not all of them do!)
-- each a has >= 5 stone - TRUE (alias for "every")
-- some a has eq 5 stone - TRUE
-- one a has = 5 stone - FALSE (more than one does!)
-- lone a has = 5 stone - FALSE (more than zero or one does!)
-- overall a has eq 5 stone - FALSE (there are 20 stone in total, not 5)
-- a has eq 5 stone - FALSE (default behaviour is 'overall')
every 20 ticks do
INPUT FROM source
if some source has gt 10 minecraft:iron_ingot then
OUTPUT TO dest1
end
if
not source has > 20 stone
and source has ge 20 cobblestone
then
output 20 to dest2
end
end
--This example comes from the github itself, currently it does 5/7 cases (blame the last forget)
EVERY 20 TICKS DO
INPUT fluid:mek: FROM tank1
OUTPUT TO tank2
-- missing fluid output
FORGET
INPUT FROM thing1
OUTPUT fluid::* TO thing2
FORGET
INPUT FROM tank1
OUTPUT fluid:: TO tank2
-- missing fluid input
FORGET
INPUT FROM chest
INPUT fluid:: FROM tank1
OUTPUT TO chest
-- missing fluid output
FORGET
IF chest HAS > 0 stone THEN
INPUT FROM chest
INPUT fluid:: FROM tank
END
OUTPUT TO chest
-- missing fluid input
END
EVERY 20 TICKS DO
OUTPUT TO chest
-- missing input
FORGET
INPUT FROM chest
-- missing output
END
EVERY 20 TICKS DO
INPUT 5 FROM a,b,c
OUTPUT 1 to z1
FORGET b,c
OUTPUT TO z2
-- no output uses input from b,c
END
-- Here you can test snippets (keep these 3 lines and delete the rest before commiting)
-- Currently there are 7 snippets (change number and text below if added more):
-- every, if, ifelse, ifelseif, input, output, basic and energy
NAME "Tag Matching"
EVERY 20 TICKS DO
-- these are equivalent
INPUT WITH #forge:gems FROM chest
INPUT WITH TAG forge:gems FROM chest
INPUT WITH TAG #forge:gems FROM chest
-- you can omit the namespace to match all namespaces
-- these are equivalent
INPUT WITH #ingots FROM chest
INPUT WITH TAG *:ingots FROM chest
-- some tags have multiple path elements
INPUT WITH TAG refinedstorage:disks/items/* FROM chest
INPUT WITH #tools/*/*_matter FROM chest
-- block tags work for items too
INPUT *a* WITH TAG minecraft:mineable/shovel FROM chest
-- matching all remaining segments is possible
INPUT WITH #the_bumblezone:essence/** FROM chest
-- should match:
-- #the_bumblezone:essence/life/grow_plants
-- #the_bumblezone:essence/calming_arena/drowned_bonus_held_item"
-- #the_bumblezone:essence/knowing/block_entity_forced_highlighting"
-- combine with other stuff too
INPUT
3 RETAIN 5 WITH TAG #forge:ingots,
4 dirt,
WITH #mineable/axe,
minecraft: WITH #ingots,
stone,
EXCEPT sand, gold_ingot
FROM chest
OUTPUT TO other_chest
END
NAME "Known issues"
-- Official SFM Discord:
-- https://discord.gg/5mbUY3mu6m
-- Official SFM issue tracker:
-- https://github.com/TeamDman/SuperFactoryManager/issues
-- JEI support is missing from some versions
-- Sometimes managers stop working for 'no reason'
---- "Rebuild cable network" gui button to try fix single
---- "/sfm bust_cable_network_cache" to try fix all
---- If it happens once, it will probably come back :(
---- I have no idea why this happens
---- If you can reproduce this, pls tell me how
-- Having like 9000 labels in a label gun corrupts world
---- because the NBT is too big
---- the network tool may cause similar situations
-- mouse drag selection desyncs with mouse position in long scripts
-- pressing tab jumps to the bottom of long scripts
-- outputting to composters (1.20.3+) without specifying a side
-- will skip the check for if the item is compostable
-- turning the composter into a trash can
-- "Press Ctrl+E to edit" and other tooltips show in edit screen
-- scroll bar in the logs gui becomes inaccurate as logs grow
-- CoFH inventories have integer overflow when moving items in large quantities
EVERY 20 TICKS DO
INPUT seeds FROM mekanism_creative_bin
OUTPUT TO phyto SLOTS 0
-- sfm warning that resource loss has occurred because cofh returns erroneous values
END
-- Sorry for any inconveniences :(
--This file is just to make sure the icon on .sfml file is place correctly
name "phytos!!!!!"
every 20 ticks do
if redstone eq 15 then
input fluid:: from water
output fluid:: to phyto
forget
input * except phytogro from drawer top side
output retain 20 to each phyto slots 0
forget
input phytogro from drawer top side
output * retain 4 phytogro to each phyto slots 1
forget
input * from phyto slots 2-5
output * to drawer top side
else
input * from phyto
output * to drawer top side
end
end
every 2 ticks do
input fe:: from powah top side
output fe:: to phyto top side
end
NAME "Round Robin"
EVERY 20 TICKS DO
-- you probably don't need round robin
-- instead, try retain!
INPUT FROM source
OUTPUT RETAIN 1000 fluid::water TO thingy
-- don't put more than 1 bucket of water in each block labelled "thingy"
FORGET
INPUT RETAIN 5 stone FROM first
OUTPUT TO second
-- don't take the last 5 stone
END
-- alternatively, there is some round robin support
-- it will rotate the each time the statement ticks, so it isn't the fastest
EVERY 20 TICKS DO
INPUT FROM source
OUTPUT 128 dirt TO dest ROUND ROBIN BY BLOCK
-- instead of outputting to all blocks labelled "dest"
-- when this statement executes it will pick only one block
-- the chosen block rotates each time this statement is executed
FORGET
INPUT FROM "storage a", "storage b" ROUND ROBIN BY LABEL
OUTPUT 8 bone_meal TO EACH dispenser
-- instead of outputting to all the labels
-- one label will be chosen each time the statement executes
-- basically alternating between
-- INPUT FROM "storage a"
-- and
-- INPUT FROM "storage b"
END
name "Filtering"
-- basic filtering with limits
EVERY 20 TICKS DO
INPUT
5 stone,
3 iron_ingot,
cobblestone, -- trailing comma is fine
FROM a
OUTPUT TO b
END
-- use an asterisk to fuzzy match
every 20 ticks do
-- quoted patterns use full regex
OUTPUT ".*ingot.*" to b
-- without quotes, "*" gets converted to ".*"
-- this is shorter, I prefer it
INPUT *ingot* from a
end
-- you can exclude items too
EVERY 20 TICKS DO
INPUT *ingot* EXCEPT iron_ingot, gold_ingot FROM a
INPUT EXCEPT cobblestone FROM z
OUTPUT 5 EXCEPT copper_ingot TO b
END
NAME "Changelog"
-- Official SFM Discord:
-- https://discord.gg/5mbUY3mu6m
-- Official SFM Donation Page:
-- https://ko-fi.com/teamdman
---- 4.21.0 ----
-- Add intellisense, disabled by default - backslash to accept suggestion
-- Bottom left button in program edit screen now opens editor config
-- Fix missing focus highlight from tab-navigation of buttons in manager screen
-- Fix syntax highlighting for "SECOND" keyword
-- Fix escaped quotes in strings containing the escaping backslash
-- Add reminder when SFM crashes the game that there's a config to turn off managers
-- Add hotkey reminder to ctrl+i overlay
-- Fix disconnect issue when clearing facades from cables
---- 4.20.0 ----
-- Fix disks having no name when program has no name for 1.21+
-- VSCode extension updates, thanks @Titop54 for the help <3
-- Fix max program length ungraceful crash, thanks @Crazy
---- Set MAX_PROGRAM_LENGTH to ~32,300 because of vanilla constraints
-- Renamed resource types example to include "fluid" in the title
-- Add edit-in-hand reminder text to disk when no program present
-- Add reminder to push labels when warnings present
-- Optimize EACH limits
EVERY 20 TICKS DO
INPUT FROM chest
OUTPUT RETAIN 1 EACH *seed* TO phyto
-- this no longer lags the more items match lol
END
-- Add support for OR
EVERY 20 TICKS DO
INPUT
5 stone OR dirt, -- move 5 total (stone|dirt)
cobblestone -- move all cobble
FROM chest
END
-- Fix correctness bug
---- With multiple 'b' inventories, each one would keep 5 instead of 5 across them total
EVERY 20 TICKS DO
input retain 5 from b
output to a
END
-- Update round robin example to advocate for using "retain" instead
-- Add notice to inspector that slot numbers may not correspond with automation slots
-- Fix syntax highlighting for "without", thanks @AkaiPandaTV
-- Fix WITHOUT and WITH logic implementation
EVERY 20 TICKS DO
INPUT minecraft: FROM left
OUTPUT WITH TAG ingots OR tag chests EXCEPT iron_ingot TO right
OUTPUT WITHOUT TAG ingots OR TAG nuggets TO "top"
END
-- Add fancy inventory cable, cable with non-full-block model, thanks @kevinrpb
-- Optimized label gun and network tool rendering, thanks @Burkino <3
-- Add label gun render-only-selected gui button added, thanks @Burkino
-- Add label gun gui searching, thanks @Burkino
-- Add cable facades, thanks @Burkino
---- Network tool in off-hand will replace cable texture with block in main hand on right click
-- Add resource type disallowing via config for pack makers, thanks @Burkino
-- Add warning when using Mekanism blocks in IO statements with no side specified
-- Add label gun pick-block modifier key for right clicking
-- Fix label gun with no active label failing to shift-right-click to clear labels on blocks
-- Remove confirmation dialog when reset is pressed with blank program and no labels
-- Add confirmation screen when pasting
-- Optimized water tank logic, thanks @WanderinWolf for the bug report
---- Capacity scaling has been buffed from n to 2^n
---- Added `/sfm bust_water_network_cache` in case I screwed up the cache logic lol
-- Update network tool output; hide capabilities that aren't present, added newline
-- Update water tank reminder text to mention that other tanks must be active for bonus
-- Add issue tracker url to mods.toml, shown when SFM is suspected of causing a crash
-- Add CompressedBlockPosSet data structure to fix packet length issues with network tool and label gun
-- Invalidate network cache instead of recomputing when removing cables from large networks
-- Fix network tool not working on dedicated servers, thanks @RacerDelux
-- Fix only rendering one of label gun and network tool when both held at once
-- Fix network tool not refreshing render data properly when in off-hand
-- Fix cables being moved by pistons not updating the cable network
-- Remove unused keyword tokens: WORLD, PROGRAM, MOVE
-- Add random initialization of manager internal clock on load from world
-- Add support for specifying global alignment and offset for timer intervals
EVERY 20 GLOBAL TICKS DO END
EVERY 20 GLOBAL PLUS 5 TICKS DO END
EVERY 20G PLUS 5 TICKS DO END
EVERY 20G + 5 TICKS DO END
EVERY 20G+5 TICKS DO END
-- Add timer_triggers.sfml example
-- Fix manager asking for confirmation when pasting over empty disks
-- Fix missing truncation on ctrl+space actions for label and output tokens
-- Performance improvements by switching to imperative loops, thanks @Qther
-- Add `/sfm config show` (anyone) and `/sfm config edit` (op) commands
-- Add "show config" button to manager gui
-- Improve resource loss logging
-- Add config variable to disable config loss logging in console
-- Add "(SFM)" to mod name in mods.toml so it shows up in searches for "SFM"
-- Add crafting recipe to clear label guns
-- Fix label gun clear not resetting active label
-- Add label cleanup support for labels on blocks that are not detected as valid inventories
-- Fix inspection results missing line endings when multiple resource types present
-- Add exports for all side to network tool, listing selected side first
-- Add hotkeys for label gun next/prev for people with no scroll wheel, unbound by default
-- Add facades to fancy cables, HUGE thanks to CoFH for their RetexturedBakedQuad class <3
-- Add direction support to fancy cable facades
-- Add `/sfm changelog` command
-- Add swapping between fancy and normal cables with network tool
-- Performance improvements by reducing memory allocations iterating block neighbours
-- Fix inaccurate scrollbar
-- Fix industrial foregoing ore meat fluid not moving because SFM mishandling fluid tags
---- Thanks @luxxiz for the report!
-- Fix modifying disk label data in ServerboundManagerFixPacket failing due to immutability (#212)
-- Fix DiskItem calling client method on server when producing tooltip contents (#210)
-- Fix pasting large program causing packet too large error (#211)
-- Fix printing press form crash bug (#209)
-- Add label gun view mode to only show a subset of labels
---- 4.19.1 ----
-- Fix 1.19.4 build failures due to Gradle hating me
---- 4.19.0 ----
-- Fix uppercase resource identifiers not being auto-lowercased internally
-- Add WITH TAG support, check examples for more info
EVERY 20 TICKS DO
INPUT minecraft:* WITH TAG minecraft:mineable/shovel FROM chest
OUTPUT TO chest
END
-- Add WIP hotkey for copying item NBT to clipboard, doesn't work on JEI hovered items
-- Fix tooltips for manager screen showing over the program edit screen... again
-- Add `/sfm export_info` client command to dump item and recipe info
-- Update SFM 1.21.1 build to fix broken Mekanism compatibility
---- gas::, infusion::, etc are now aliases for chemical::
---- 4.18.0 ----
-- Add 1.21.0 build with Mekanism support
-- Set up a Ko-fi for donations
---- Thanks to @micrwvae for being first!
-- Restore Mekanism support for Minecraft 1.20.4
-- Add syntax highlighting for SECONDS keyword
-- Improve performance iterating over EXCLUDE resources
---- 4.17.0 ----
-- fix cache not being invalidated when chunk unloaded
---- this was caused by other mods not invalidating capabilities
---- this caused managers to stop working (rejoining a server, etc)
-- fix label guns sharing internal objects with each other and disks
---- this caused disks in managers to get updated without pushing
---- sometimes manager disk didn't have labels you thought you pushed
-- fix reset button not purging labels
-- add confirmation screen to reset button
-- remove confirmation screen when overwriting empty program
-- add client config for showing line numbers
---- add toggle button in program edit screen
-- reduce log spam from syntax errors
---- build process now properly stops when parsing fails
-- fix partial forgets resetting trackers
EVERY 20 TICKS DO
INPUT 10 FROM a,b
OUTPUT 1 to z
FORGET b
OUTPUT to z -- this used to move 10, should be/is now 9
END
-- add warnings
EVERY 20 TICKS DO
INPUT fluid:: FROM a -- fluid:: never gets used
OUTPUT TO b -- item:: never get inputted
END
---- 4.16.0 ----
-- fix crashes using ctrl+space
---- happened when using on `Gas::` or `fe::` or `bruh::`
---- Thanks @crystalivel for the report <3
-- add manager screen button to rebuild network
---- this will do until I can squash the bug
---- I'm sorry for the headaches this has caused
-- add known issues example program
-- fix INPUT statement limits not being respected
---- occurred when using 2+ outputs with one input
---- https://imgur.com/a/sfm-unintended-behaviour-gBkNyae
---- Thanks @Lewi for the report <3
-- fix recipes to use item tags for signs and rods
---- Thanks @pddshk for the suggestion (#127) <3
---- Thanks Blase for the reminder <3
-- add mekanism_energy:mekanism:energy resource type
---- This can move MAX_LONG per operation where supported
-- make "EVERY TICK DO" valid syntax
---- this still only allows forge_energy by default
-- optimize more common regex (*seed, seed*, *seed*)
-- add line numbers to program edit screen
---- Thanks @Lewi and @Reko for the suggestion <3
-- remove admin requirement for /sfm bust_cable_network_cache
---- logs whoever uses the command
-- add logging for individual managers
-- program edit screen no longer pauses the game in single player
-- more confirmation dialogs when overwriting programs
---- Thanks @DeeJayh for the report, sorry for ur loss <3
-- make FORGET keyword blue
-- make NOT,AND,OR keywords gold
-- allow `redstone` as a label
-- fix missing dash in changelog
-- support `IF chest HAS EQ 0 THEN`
---- previously only `IF chest HAS EQ 0 item:: THEN`
-- add ctrl+space action for IfStatement and BoolExpr
-- add git tag action to release process documentation
-- add GitHub release to release process documentation
-- update AE2 example to accommodate case where ingredients are spread between inscribers
---- Thanks @Kem for the idea <3
---- 4.15.1 ----
-- Fix crash bug where variable-size inventories (composters) crashed
-- Fix modloader=forge missing for the mc=1.20.1 CurseForge uploads
---- 4.15.0 ----
-- alias "each" to "every" when parsing set operators
---- (e.g., "IF EACH chest HAS GT 1 iron_ingot THEN")
---- this is an old change but I forgot to mention till now
-- move disk warning update trigger from tick to manager open
-- add warnings for smelly round robin usage
-- add warning for smelly pattern expansion usage
-- add mod+game info to clipboard diagnostics
-- add EACH SIDE alias
---- OUTPUT forge_energy:: TO "flux cell" EACH SIDE
-- improve error logging when resource voiding detected
-- fix bug where programs worked without inventory cables
-- ctrl+labelgun now applies to kitty corner
-- add config for minimum tick time
-- allow programs to tick faster if only moving energy
-- alias forge_energy:: <= fe::, rf::, energy::, power::
-- fix anvils causing problems when quitting world
---- 4.14.3 ----
-- add support for 1.20.2
-- add support for 1.20.3
-- add support for 1.20.4
---- 4.14.2 ----
-- fix capability caching to avoid resource voiding and programs not working
---- 4.14.1 ----
-- fix label gun not working on servers
---- 4.14.0 ----
-- switch to milliseconds instead of nanoseconds
-- add FORGET keyword
---- FORGET a,b,c -- remove these from inputs
---- FORGET -- remove all inputs
-- add ROUND ROBIN keyword, works in inputs and outputs
---- INPUT FROM a,b ROUND ROBIN BY BLOCK
---- OUTPUT TO a,b,c ROUND ROBIN BY LABEL
-- fix REDSTONE keyword not being allowed as a resource id
---- INPUT redstone FROM a -- works now :D
-- add /sfm op command for diagnosing cable network problems
-- performance improvements :D
---- breaking many cables at once now faster
---- fastutils in cable network makes programs faster too
---- shifted pattern compilation to build time instead of first inference
-- improved GitHub README
-- test suite improvements
-- fix inv overlay (Ctrl+i) failing with big payloads
-- double inspection payload max size before truncation to 20480
-- add ae2 inscriber example program
-- add network tool item
---- 4.13.0 ----
-- add ctrl+slash hotkey to toggle comments
-- add warning when exiting program without saving
-- add 1.20.1 mekanism support
-- add emotes to discord server
-- add role to discord server to be pinged about updates
---- 4.12.0 ----
-- add changelog to templates
-- add ctrl+space hotkey for code inspection
-- add `EACH` modifier on quantities and retentions
-- add resource exclusion patterns
-- add missing syntax highlighting
-- cleaner templates
-- improved export inspector output (Ctrl+I)
name "Limits"
every 20 ticks do
-- This is hard to explain since there's so many variations
-- This is more a collection of samples than a coherent program
-- Guess what you think each statement does, try them out on your own!
INPUT FROM a
INPUT 1 FROM a
INPUT stone FROM a
INPUT 3 stone FROM a
INPUT 5 RETAIN 3 stone FROM a
INPUT 5 *ingot FROM a
INPUT 5 EACH *ingot FROM a
INPUT 5 RETAIN 1 EACH *ingot FROM a
-- quantity and retention can both be expanded with the EACH keyword
-- this only makes sense if your resource id is a pattern
INPUT 5 EACH RETAIN 3 EACH *ingot* FROM a
OUTPUT TO b
INPUT 1 "redstone" FROM EACH a
OUTPUT 1 TO EACH b
OUTPUT 5 TO b
OUTPUT 5 stone TO b
OUTPUT RETAIN 1 coal TO furnace BOTTOM SIDE
OUTPUT 8 EACH *ingot* to b
OUTPUT RETAIN 8 EACH *ingot* to b
OUTPUT 8 EACH retain 8 EACH *ingot* to b
end
NAME "Forget"
EVERY 20 TICKS DO
INPUT FROM a
OUTPUT 1 to b
INPUT FROM c
OUTPUT TO d -- the "a" input is still active!!!
END
EVERY 20 TICKS DO
INPUT FROM a
OUTPUT 1 to b
FORGET a
-- or just
FORGET
INPUT FROM c
OUTPUT TO d -- only the "c" input is active :D
END
name "energy"
EVERY 20 TICKS DO
INPUT 1000 sfm:forge_energy:forge:energy from a BOTTOM SIDE
OUTPUT sfm:forge_energy:forge:energy to b TOP SIDE
END
---
every 20 ticks do
input from a top side
output to b top side
end
name "ATO"
every 20 ticks do
input from coal top side
input from alum top side
output coal to furn bottom side
output 2 alltheores:raw_aluminum to each furn top side
end
every 20 ticks do
-- when not using quotes, "*" gets replaced by ".*" for convenience
-- then the identifiers are treated as regular expressions
input *:*ingot* from furn
output to out
end
name "patterns"
every 20 ticks do
-- since we are using quotes, we must use ".*" instead of just "*" if we want to match all mods
input ".*:(iron|copper)_ingot" from a
output to b
end
every 20 ticks do
input gas:mekanism:* from a bottom side
output gas:mekanism:* to b bottom side
end
---
every 20 ticks do
input infusion:mekanism:* from a bottom side
output infusion:mekanism:* to b bottom side
end
---
every 20 ticks do
input pigment:mekanism:* from a bottom side
output pigment:mekanism:* to b bottom side
end
---
every 20 ticks do
input slurry:alltheores:* from a bottom side
output slurry:*:* to b bottom side
end
name "condition"
every 20 ticks do
INPUT FROM a
-- since more than one inventory can have the same label, it might be useful
-- to be able to be specific for conditionals.
-- if every a has gt 10 iron_ingot then -- all inventories must match
-- if some a has gt 10 iron_ingot then -- at least one inventory must match
-- if one a has gt 10 iron_ingot then -- exactly one inventory must match
-- if lone a has gt 10 iron_ingot then -- exactly zero or one inventory must match
-- if overall a has gt 10 iron_ingot then -- counting items from all inventories instead of individually, must match
if a has gt 10 minecraft:iron_ingot then
-- defaults to "overall" if set qualifier not specified
-- "minecraft:" namespace prefix is default and can be omitted
OUTPUT TO b
end
if not d has gt 20 stone and a has ge 20 stone then
output 20 to d
end
-- this will resolve to "true" and will always run
if (false and true) or (false or true) or not true then
-- "not", "and", and "or" operators all have same precidence
-- use parentheses to ensure things work as you expect
output to c
end
end
-- if this program were running while "a" had 9*iron_ingot:
-- "b" would receive 0*iron_ingot
-- "d" would receive 9*iron_ingot
-- if this program were running while "a" had 30*iron_ingot:
-- "b" would receive 30*iron_ingot
-- "d" would receive 0*iron_ingot
-- if this program were running while "a" had 64*stone:
-- "d" would receive 20*stone
-- "c" would receive 44*stone
-- if this program were running while "a" had 16*stone:
-- "d" would receive 0*stone
-- "c" would receive 16*stone
-- if this program were running while "a" had (2*64)*stone:
-- "d" would receive 20*stone
-- "c" would receive (64+44)*stone
--------------------
every 20 ticks do
if a has ge 32 iron_ingot then
input 1 cobblestone from a
else if a has ge 16 iron_ingot then
input 1 stone from a
else
input 1 red_sandstone from a
end
output to b
end
name "retention"
every 20 ticks do
-- move all items except 1 from "a" to "b"
input retain 1 from a
output to b
end
every 20 ticks do
-- move 25 items except 1 from "a" to "b"
input 25 retain 1 from a
output to b
end
every 20 ticks do
-- move 25 red_sand items except 1 from "a" to "b"
input 25 retain 1 red_sand from a
output to b
end
every 20 ticks do
-- move 20 red sand and all coal
-- but leave 1 red sand
input
20 retain 1 red_sand,
retain 1 coal
from a
output to b
end
every 20 ticks do
input from a
output retain 1 to b
-- only output until "b" has 1 item
end
every 20 ticks do
input from a
output 5 retain 20 red_sand to b
-- accept 5 red sand at once, with "a" max of 20 in "b"
end
every 20 ticks do
input
15 retain 5 iron_ingot,
12 retain 3 stone
from "a" top side
output
2 iron_ingot,
64 retain 10 stone
to b
-- moves all but 5 iron ingots to "b", 15 at a time
-- moves up to 10 stone to "b", ensuring "a" keeps 3 stone
-- "b" can accept up to 64 stone but "a" can only provide 12 at a time
end
name "move items" -- name declaration is optional
-- use two dashes for a comment
-- keywords are case insensitive
-- more than one trigger can be used in a program
every 20 ticks do
-- each trigger block can contain multiple statements
INPUT FROM a
OUTPUT TO b
-- "a" and "b" are inventory labels
-- use a label gun to identify them in world
end
-- The previous trigger will move all items, but for instructional
-- purposes, more triggers will be included here
every 20 ticks do
INPUT 1 from a -- only extract 1 item at a time
OUTPUT to b
end
every 20 ticks do
input from a
output 1 to b -- only insert 1 item at a time
-- if multiple inventories are labelled "b",
-- only one will receive an item
end
every 20 ticks do
input from a
output 1 to each b -- every "b" inventory will receive 1 item
end
every 20 ticks do
input 1 from each a -- each "a" inventory will only have 1 item removed,
-- instead of 1 item total being moved
output to b
end
every 20 ticks do
input iron_ingot from a -- only move iron ingots, "minecraft:" namespace is assumed
output to b
end
every 20 ticks do
input thaumcraft:iron_nugget from a -- only move thaumcraft iron nuggets
output to b
end
every 20 ticks do
input "redstone" from a -- redstone is a keyword for future use, so must be surrounded in quotes
output to b
end
every 20 ticks do
input "minecraft:redstone" from a -- redstone is a keyword, so must be surrounded in quotes
output to b
end
every 20 ticks do
if a has gt 0 "redstone" then -- again, redstone must be in quotes
input from a
output to b
end
end
name "smelting"
every 20 ticks do
input from chest
output
minecraft:oak_log -- items can be specified
to furnace top side
output
coal, -- namespace defaults to "minecraft:"
1 retain 1 oak_log, -- limits are per-item
charcoal
to furnace bottom side
end
every 20 ticks do
input from furnace bottom side
output to chest
end
name "slots"
every 20 ticks do
-- multiple slots can be specified, comma separated, also supporting ranges
input from a top side slots 0,1,3-4, 5
output to a slots 2
end
name "each"
-- using every inventory labelled "c", move 5 items total
-- "b" will receive 5 items, or whatever is available if less than 5.
every 20 ticks do
input 5 from c
output to b
end
-- for every inventory labelled "a", 5 items will be moved
-- "b" will receive |a| * 5 items
every 20 ticks do
input 5 from each a -- note the "each" keyword
output to b
end
packageca.teamdman.sfm.gametest;
importca.teamdman.sfm.SFM;
importca.teamdman.sfm.common.blockentity.ManagerBlockEntity;
importca.teamdman.sfm.common.item.DiskItem;
importca.teamdman.sfm.common.localization.LocalizationKeys;
importca.teamdman.sfm.common.net.ServerboundOutputInspectionRequestPacket;
importca.teamdman.sfm.common.program.LabelPositionHolder;
importca.teamdman.sfm.common.program.ProgramContext;
importca.teamdman.sfm.common.program.linting.GatherWarningsProgramBehaviour;
importca.teamdman.sfm.common.registry.SFMBlocks;
importca.teamdman.sfm.common.registry.SFMItems;
importca.teamdman.sfml.ast.OutputStatement;
importca.teamdman.sfml.ast.Program;
importnet.minecraft.core.BlockPos;
importnet.minecraft.gametest.framework.GameTest;
importnet.minecraft.gametest.framework.GameTestHelper;
importnet.minecraft.world.Container;
importnet.minecraft.world.item.ItemStack;
importnet.minecraft.world.item.Items;
importnet.minecraft.world.level.block.Blocks;
importnet.minecraftforge.gametest.GameTestHolder;
importjava.util.ArrayList;
importjava.util.List;
@SuppressWarnings({
"RedundantSuppression",
"DataFlowIssue",
"deprecation",
"OptionalGetWithoutIsPresent",
"DuplicatedCode",
})
@GameTestHolder(SFM.MOD_ID)
publicclassSFMIfStatementGameTestsextendsSFMGameTestBase {
@GameTest(template = "3x2x1")
publicstaticvoidcomparison_gt(GameTestHelperhelper) {
varleftPos = newBlockPos(2, 2, 0);
varrightPos = newBlockPos(0, 2, 0);
varmanagerPos = newBlockPos(1, 2, 0);
helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());
helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
varleft = (Container) helper.getBlockEntity(leftPos);
varright = (Container) helper.getBlockEntity(rightPos);
varmanager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
left.setItem(0, newItemStack(Items.DIAMOND, 64));
left.setItem(1, newItemStack(Items.DIAMOND, 64));
left.setItem(2, newItemStack(Items.IRON_INGOT, 12));
right.setItem(0, newItemStack(Items.STICK, 13));
right.setItem(1, newItemStack(Items.STICK, 64));
right.setItem(2, newItemStack(Items.DIRT, 1));
manager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
manager.setProgram(""" NAME "comparison_gt test" EVERY 20 TICKS DO IF left HAS GT 100 diamond THEN -- should happen INPUT diamond FROM left OUTPUT diamond TO right END IF left HAS GT 300 iron_ingot THEN -- should not happen INPUT iron_ingot FROM left OUTPUT iron_ingot TO right END IF right HAS > 10 stick THEN -- should happen INPUT stick FROM right OUTPUT stick TO left END if right has > 0 dirt then -- should happen input dirt from right output dirt to left end END """.stripTrailing().stripIndent());
// set the labelsLabelPositionHolder.empty()
.add("left", helper.absolutePos(leftPos))
.add("right", helper.absolutePos(rightPos))
.save(manager.getDisk());
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
intleftDiamondCount = count(left, Items.DIAMOND);
intleftIronCount = count(left, Items.IRON_INGOT);
intleftStickCount = count(left, Items.STICK);
intleftDirtCount = count(left, Items.DIRT);
intrightDiamondCount = count(right, Items.DIAMOND);
intrightIronCount = count(right, Items.IRON_INGOT);
intrightStickCount = count(right, Items.STICK);
intrightDirtCount = count(right, Items.DIRT);
// the diamonds should have moved from left to rightassertTrue(leftDiamondCount == 0, "left should have no diamonds");
assertTrue(rightDiamondCount == 64 * 2, "right should have 100 diamonds");
// the iron should have stayed in leftassertTrue(leftIronCount == 12, "left should have 12 iron ingots");
assertTrue(rightIronCount == 0, "right should have no iron ingots");
// the sticks should have moved from right to leftassertTrue(rightStickCount == 0, "right should have no sticks");
assertTrue(leftStickCount == 77, "left should have 77 sticks");
// the dirt should have moved from right to leftassertTrue(rightDirtCount == 0, "right should have no dirt");
assertTrue(leftDirtCount == 1, "left should have 1 dirt");
});
}
@GameTest(template = "3x2x1")
publicstaticvoidcomparison_ge(GameTestHelperhelper) {
varleftPos = newBlockPos(2, 2, 0);
varrightPos = newBlockPos(0, 2, 0);
varmanagerPos = newBlockPos(1, 2, 0);
helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());
helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
varleft = (Container) helper.getBlockEntity(leftPos);
varright = (Container) helper.getBlockEntity(rightPos);
varmanager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
left.setItem(0, newItemStack(Items.DIAMOND, 64));
left.setItem(1, newItemStack(Items.DIAMOND, 64));
left.setItem(2, newItemStack(Items.IRON_INGOT, 12));
right.setItem(0, newItemStack(Items.STICK, 13));
right.setItem(1, newItemStack(Items.STICK, 64));
right.setItem(2, newItemStack(Items.DIRT, 1));
manager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
manager.setProgram(""" NAME "comparison_ge test" EVERY 20 TICKS DO IF left HAS GE 129 diamond THEN -- should not happen INPUT diamond FROM left OUTPUT diamond TO right END IF left HAS GE 12 iron_ingot THEN -- should happen INPUT iron_ingot FROM left OUTPUT iron_ingot TO right END IF right HAS >= 13 stick THEN -- should happen INPUT stick FROM right OUTPUT stick TO left END if right has >= 1 dirt then -- should happen input dirt from right output dirt to left end END """.stripTrailing().stripIndent());
// set the labelsLabelPositionHolder.empty()
.add("left", helper.absolutePos(leftPos))
.add("right", helper.absolutePos(rightPos))
.save(manager.getDisk());
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
intleftDiamondCount = count(left, Items.DIAMOND);
intleftIronCount = count(left, Items.IRON_INGOT);
intleftStickCount = count(left, Items.STICK);
intleftDirtCount = count(left, Items.DIRT);
intrightDiamondCount = count(right, Items.DIAMOND);
intrightIronCount = count(right, Items.IRON_INGOT);
intrightStickCount = count(right, Items.STICK);
intrightDirtCount = count(right, Items.DIRT);
// the diamonds should have moved from left to rightassertTrue(leftDiamondCount == 64 * 2, "left should have 128 diamonds");
assertTrue(rightDiamondCount == 0, "right should have no diamonds");
// the iron should have moved from left to rightassertTrue(leftIronCount == 0, "left should have no iron ingots");
assertTrue(rightIronCount == 12, "right should have 12 iron ingots");
// the sticks should have moved from right to leftassertTrue(rightStickCount == 0, "right should have no sticks");
assertTrue(leftStickCount == 77, "left should have 77 sticks");
// the dirt should have moved from right to leftassertTrue(rightDirtCount == 0, "right should have no dirt");
assertTrue(leftDirtCount == 1, "left should have 1 dirt");
});
}
@GameTest(template = "3x2x1")
publicstaticvoidcomparison_eq(GameTestHelperhelper) {
varleftPos = newBlockPos(2, 2, 0);
varrightPos = newBlockPos(0, 2, 0);
varmanagerPos = newBlockPos(1, 2, 0);
helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());
helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
varleft = (Container) helper.getBlockEntity(leftPos);
varright = (Container) helper.getBlockEntity(rightPos);
varmanager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
left.setItem(0, newItemStack(Items.DIAMOND, 64));
left.setItem(1, newItemStack(Items.DIAMOND, 64));
left.setItem(2, newItemStack(Items.IRON_INGOT, 12));
right.setItem(0, newItemStack(Items.STICK, 13));
right.setItem(1, newItemStack(Items.STICK, 64));
right.setItem(2, newItemStack(Items.DIRT, 1));
manager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
manager.setProgram(""" NAME "comparison_eq test" EVERY 20 TICKS DO IF left HAS eq 129 diamond THEN -- should not happen INPUT diamond FROM left OUTPUT diamond TO right END IF left HAS = 12 iron_ingot THEN -- should happen INPUT iron_ingot FROM left OUTPUT iron_ingot TO right END IF right HAS eq 77 stick THEN -- should happen INPUT stick FROM right OUTPUT stick TO left END if right has = 1 dirt then -- should happen input dirt from right output dirt to left end END """.stripTrailing().stripIndent());
// set the labelsLabelPositionHolder.empty()
.add("left", helper.absolutePos(leftPos))
.add("right", helper.absolutePos(rightPos))
.save(manager.getDisk());
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
intleftDiamondCount = count(left, Items.DIAMOND);
intleftIronCount = count(left, Items.IRON_INGOT);
intleftStickCount = count(left, Items.STICK);
intleftDirtCount = count(left, Items.DIRT);
intrightDiamondCount = count(right, Items.DIAMOND);
intrightIronCount = count(right, Items.IRON_INGOT);
intrightStickCount = count(right, Items.STICK);
intrightDirtCount = count(right, Items.DIRT);
// the diamonds should have moved from left to rightassertTrue(leftDiamondCount == 64 * 2, "left should have 128 diamonds");
assertTrue(rightDiamondCount == 0, "right should have no diamonds");
// the iron should have moved from left to rightassertTrue(leftIronCount == 0, "left should have no iron ingots");
assertTrue(rightIronCount == 12, "right should have 12 iron ingots");
// the sticks should have moved from right to leftassertTrue(rightStickCount == 0, "right should have no sticks");
assertTrue(leftStickCount == 77, "left should have 77 sticks");
// the dirt should have moved from right to leftassertTrue(rightDirtCount == 0, "right should have no dirt");
assertTrue(leftDirtCount == 1, "left should have 1 dirt");
});
}
@GameTest(template = "3x2x1")
publicstaticvoidcomparison_lt(GameTestHelperhelper) {
varleftPos = newBlockPos(2, 2, 0);
varrightPos = newBlockPos(0, 2, 0);
varmanagerPos = newBlockPos(1, 2, 0);
helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());
helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
varleft = (Container) helper.getBlockEntity(leftPos);
varright = (Container) helper.getBlockEntity(rightPos);
varmanager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
left.setItem(0, newItemStack(Items.DIAMOND, 64));
left.setItem(1, newItemStack(Items.DIAMOND, 64));
left.setItem(2, newItemStack(Items.IRON_INGOT, 12));
right.setItem(0, newItemStack(Items.STICK, 13));
right.setItem(1, newItemStack(Items.STICK, 64));
right.setItem(2, newItemStack(Items.DIRT, 1));
manager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
manager.setProgram(""" NAME "comparison_lt test" EVERY 20 TICKS DO IF left HAS lt 10 diamond THEN -- should not happen INPUT diamond FROM left OUTPUT diamond TO right END IF left HAS < 200 iron_ingot THEN -- should happen INPUT iron_ingot FROM left OUTPUT iron_ingot TO right END IF right HAS < 78 stick THEN -- should happen INPUT stick FROM right OUTPUT stick TO left END if right has < 3 dirt then -- should happen input dirt from right output dirt to left end END """.stripTrailing().stripIndent());
// set the labelsLabelPositionHolder.empty()
.add("left", helper.absolutePos(leftPos))
.add("right", helper.absolutePos(rightPos))
.save(manager.getDisk());
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
intleftDiamondCount = count(left, Items.DIAMOND);
intleftIronCount = count(left, Items.IRON_INGOT);
intleftStickCount = count(left, Items.STICK);
intleftDirtCount = count(left, Items.DIRT);
intrightDiamondCount = count(right, Items.DIAMOND);
intrightIronCount = count(right, Items.IRON_INGOT);
intrightStickCount = count(right, Items.STICK);
intrightDirtCount = count(right, Items.DIRT);
// the diamonds should have moved from left to rightassertTrue(leftDiamondCount == 64 * 2, "left should have 128 diamonds");
assertTrue(rightDiamondCount == 0, "right should have no diamonds");
// the iron should have moved from left to rightassertTrue(leftIronCount == 0, "left should have no iron ingots");
assertTrue(rightIronCount == 12, "right should have 12 iron ingots");
// the sticks should have moved from right to leftassertTrue(rightStickCount == 0, "right should have no sticks");
assertTrue(leftStickCount == 77, "left should have 77 sticks");
// the dirt should have moved from right to leftassertTrue(rightDirtCount == 0, "right should have no dirt");
assertTrue(leftDirtCount == 1, "left should have 1 dirt");
});
}
@GameTest(template = "3x2x1")
publicstaticvoidcomparison_le(GameTestHelperhelper) {
varleftPos = newBlockPos(2, 2, 0);
varrightPos = newBlockPos(0, 2, 0);
varmanagerPos = newBlockPos(1, 2, 0);
helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());
helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
varleft = (Container) helper.getBlockEntity(leftPos);
varright = (Container) helper.getBlockEntity(rightPos);
varmanager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
left.setItem(0, newItemStack(Items.DIAMOND, 64));
left.setItem(1, newItemStack(Items.DIAMOND, 64));
left.setItem(2, newItemStack(Items.IRON_INGOT, 12));
right.setItem(0, newItemStack(Items.STICK, 13));
right.setItem(1, newItemStack(Items.STICK, 64));
right.setItem(2, newItemStack(Items.DIRT, 1));
manager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
manager.setProgram(""" NAME "comparison_le test" EVERY 20 TICKS DO IF left HAS le 10 diamond THEN -- should not happen INPUT diamond FROM left OUTPUT diamond TO right END IF left HAS <= 12 iron_ingot THEN -- should happen INPUT iron_ingot FROM left OUTPUT iron_ingot TO right END IF right HAS le 77 stick THEN -- should happen INPUT stick FROM right OUTPUT stick TO left END if right has <= 1 dirt then -- should happen input dirt from right output dirt to left end END """.stripTrailing().stripIndent());
// set the labelsLabelPositionHolder.empty()
.add("left", helper.absolutePos(leftPos))
.add("right", helper.absolutePos(rightPos))
.save(manager.getDisk());
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
intleftDiamondCount = count(left, Items.DIAMOND);
intleftIronCount = count(left, Items.IRON_INGOT);
intleftStickCount = count(left, Items.STICK);
intleftDirtCount = count(left, Items.DIRT);
intrightDiamondCount = count(right, Items.DIAMOND);
intrightIronCount = count(right, Items.IRON_INGOT);
intrightStickCount = count(right, Items.STICK);
intrightDirtCount = count(right, Items.DIRT);
// the diamonds should have moved from left to rightassertTrue(leftDiamondCount == 64 * 2, "left should have 128 diamonds");
assertTrue(rightDiamondCount == 0, "right should have no diamonds");
// the iron should have moved from left to rightassertTrue(leftIronCount == 0, "left should have no iron ingots");
assertTrue(rightIronCount == 12, "right should have 12 iron ingots");
// the sticks should have moved from right to leftassertTrue(rightStickCount == 0, "right should have no sticks");
assertTrue(leftStickCount == 77, "left should have 77 sticks");
// the dirt should have moved from right to leftassertTrue(rightDirtCount == 0, "right should have no dirt");
assertTrue(leftDirtCount == 1, "left should have 1 dirt");
});
}
@GameTest(template = "3x2x1")
publicstaticvoidforget_1(GameTestHelperhelper) {
helper.setBlock(newBlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
BlockPosrightPos = newBlockPos(0, 2, 0);
helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
BlockPosleftPos = newBlockPos(2, 2, 0);
helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());
varrightChest = getItemHandler(helper, rightPos);
varleftChest = getItemHandler(helper, leftPos);
leftChest.insertItem(0, newItemStack(Blocks.DIRT, 64), false);
ManagerBlockEntitymanager = (ManagerBlockEntity) helper.getBlockEntity(newBlockPos(1, 2, 0));
manager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
manager.setProgram(""" EVERY 20 TICKS DO INPUT FROM a IF a has gt 0 dirt THEN FORGET a END OUTPUT TO b -- nothing happens :D END """.stripTrailing().stripIndent());
// set the labelsLabelPositionHolder.empty()
.add("a", helper.absolutePos(leftPos))
.add("b", helper.absolutePos(rightPos))
.save(manager.getDisk());
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
assertTrue(leftChest.getStackInSlot(0).getCount() == 64, "Dirt should not depart");
assertTrue(rightChest.getStackInSlot(0).isEmpty(), "Dirt should not arrive");
});
}
@GameTest(template = "3x2x1")
publicstaticvoidforget_2(GameTestHelperhelper) {
helper.setBlock(newBlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
BlockPosrightPos = newBlockPos(0, 2, 0);
helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
BlockPosleftPos = newBlockPos(2, 2, 0);
helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());
varrightChest = getItemHandler(helper, rightPos);
varleftChest = getItemHandler(helper, leftPos);
leftChest.insertItem(0, newItemStack(Blocks.DIRT, 64), false);
ManagerBlockEntitymanager = (ManagerBlockEntity) helper.getBlockEntity(newBlockPos(1, 2, 0));
manager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
manager.setProgram(""" EVERY 20 TICKS DO INPUT FROM a,b,c IF a has gt 0 dirt THEN FORGET END OUTPUT TO dest -- nothing happens :D END """.stripTrailing().stripIndent());
// set the labelsLabelPositionHolder.empty()
.add("a", helper.absolutePos(leftPos))
.add("b", helper.absolutePos(leftPos))
.add("c", helper.absolutePos(leftPos))
.add("dest", helper.absolutePos(rightPos))
.save(manager.getDisk());
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
assertTrue(leftChest.getStackInSlot(0).getCount() == 64, "Dirt should not depart");
assertTrue(rightChest.getStackInSlot(0).isEmpty(), "Dirt should not arrive");
});
}
@GameTest(template = "3x2x1")
publicstaticvoidforget_slot(GameTestHelperhelper) {
helper.setBlock(newBlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
BlockPosrightPos = newBlockPos(0, 2, 0);
helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
BlockPosleftPos = newBlockPos(2, 2, 0);
helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());
varrightChest = getItemHandler(helper, rightPos);
varleftChest = getItemHandler(helper, leftPos);
leftChest.insertItem(0, newItemStack(Blocks.DIRT, 64), false);
leftChest.insertItem(1, newItemStack(Items.IRON_INGOT, 64), false);
ManagerBlockEntitymanager = (ManagerBlockEntity) helper.getBlockEntity(newBlockPos(1, 2, 0));
manager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
manager.setProgram(""" EVERY 20 TICKS DO INPUT FROM a SLOTS 1-26 -- iron ingot INPUT FROM c SLOTS 0 -- dirt blocks IF a SLOTS 0 has gt 0 dirt THEN FORGET a -- forgets the iron ingot END OUTPUT TO b -- will move the dirt block, the next tick the ingots END """.stripTrailing().stripIndent());
// set the labelsLabelPositionHolder.empty()
.add("a", helper.absolutePos(leftPos))
.add("c", helper.absolutePos(leftPos))
.add("b", helper.absolutePos(rightPos))
.save(manager.getDisk());
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
assertTrue(leftChest.getStackInSlot(0).isEmpty(), "Dirt should depart");
assertTrue(leftChest.getStackInSlot(1).getCount() == 64, "Iron ingots should not depart");
assertTrue(rightChest.getStackInSlot(0).getCount() == 64, "Dirt should arrive in size");
assertTrue(rightChest.getStackInSlot(0).getItem() == Items.DIRT, "Dirt should arrive in type");
});
}
@GameTest(template = "3x2x1", batch = "linting")
publicstaticvoidcount_execution_paths_conditional_1(GameTestHelperhelper) {
// place inventorieshelper.setBlock(newBlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
BlockPosrightPos = newBlockPos(0, 2, 0);
helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
BlockPosleftPos = newBlockPos(2, 2, 0);
helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());
// place managerManagerBlockEntitymanager = (ManagerBlockEntity) helper.getBlockEntity(newBlockPos(1, 2, 0));
manager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
// set the labelsLabelPositionHolderlabelPositionHolder = LabelPositionHolder.empty()
.add("left", helper.absolutePos(leftPos))
.add("right", helper.absolutePos(rightPos))
.save(manager.getDisk());
// load the programmanager.setProgram(""" EVERY 20 TICKS DO IF left HAS gt 0 stone THEN INPUT FROM left END OUTPUT TO right END """.stripTrailing().stripIndent());
assertManagerRunning(manager);
varprogram = manager.getProgram();
// ensure no warningsvarwarnings = DiskItem.getWarnings(manager.getDisk());
assertTrue(warnings.isEmpty(), "expected 0 warning, got " + warnings.size());
// count the execution pathsGatherWarningsProgramBehavioursimulation = newGatherWarningsProgramBehaviour(warnings::addAll);
program.tick(ProgramContext.createSimulationContext(
program,
labelPositionHolder,
0,
simulation
));
List<Integer> expectedPathSizes = newArrayList<>(List.of(1, 2));
assertTrue(
simulation.getSeenPaths().size() == expectedPathSizes.size(),
"expected " + expectedPathSizes.size() + " execution paths, got " + simulation.getSeenPaths().size()
);
int[] actualPathIOSizes = simulation.getSeenIOStatementCountForEachPath();
// don't assume the order, just that each path size has occurred the specified number of timesfor (inti = 0; i < actualPathIOSizes.length; i++) {
intpathSize = actualPathIOSizes[i];
if (!expectedPathSizes.remove((Integer) pathSize)) {
helper.fail("unexpected path size " + pathSize + " at index " + i + " of " + simulation
.getSeenPaths()
.size() + " paths");
}
}
helper.succeed();
}
@GameTest(template = "3x2x1", batch = "linting")
publicstaticvoidcount_execution_paths_conditional_1b(GameTestHelperhelper) {
// place inventorieshelper.setBlock(newBlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
BlockPosrightPos = newBlockPos(0, 2, 0);
helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
BlockPosleftPos = newBlockPos(2, 2, 0);
helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());
// place managerManagerBlockEntitymanager = (ManagerBlockEntity) helper.getBlockEntity(newBlockPos(1, 2, 0));
manager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
// set the labelsLabelPositionHolder.empty()
.add("left", helper.absolutePos(leftPos))
.save(manager.getDisk());
// load the programmanager.setProgram(""" EVERY 20 TICKS DO IF left HAS gt 0 stone THEN INPUT FROM left END END """.stripTrailing().stripIndent());
assertManagerRunning(manager);
// assert expected warningsvarwarnings = DiskItem.getWarnings(manager.getDisk());
assertTrue(warnings.size() == 1, "expected 1 warning, got " + warnings.size());
assertTrue(warnings
.get(0)
.getKey()
.equals(LocalizationKeys.PROGRAM_WARNING_UNUSED_INPUT_LABEL// should be unused input
.key()
.get()), "expected output without matching input warning");
helper.succeed();
}
@GameTest(template = "3x2x1", batch = "linting")
publicstaticvoidcount_execution_paths_conditional_2(GameTestHelperhelper) {
// place inventorieshelper.setBlock(newBlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
BlockPosrightPos = newBlockPos(0, 2, 0);
helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
BlockPosleftPos = newBlockPos(2, 2, 0);
helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());
// place managerManagerBlockEntitymanager = (ManagerBlockEntity) helper.getBlockEntity(newBlockPos(1, 2, 0));
manager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
// set the labelsLabelPositionHolderlabelPositionHolder = LabelPositionHolder.empty()
.add("left1", helper.absolutePos(leftPos))
.add("left2", helper.absolutePos(leftPos))
.add("right", helper.absolutePos(rightPos))
.save(manager.getDisk());
// load the programmanager.setProgram(""" EVERY 20 TICKS DO IF left2 HAS gt 0 stone THEN INPUT FROM left1 END IF left1 HAS gt 0 stone THEN INPUT FROM left2 END OUTPUT TO right END """.stripTrailing().stripIndent());
assertManagerRunning(manager);
varprogram = manager.getProgram();
// ensure no warningsvarwarnings = DiskItem.getWarnings(manager.getDisk());
assertTrue(warnings.isEmpty(), "expected 0 warning, got " + warnings.size());
// count the execution pathsGatherWarningsProgramBehavioursimulation = newGatherWarningsProgramBehaviour(warnings::addAll);
program.tick(ProgramContext.createSimulationContext(
program,
labelPositionHolder,
0,
simulation
));
List<Integer> expectedPathSizes = newArrayList<>(List.of(1, 2, 2, 3));
assertTrue(
simulation.getSeenPaths().size() == expectedPathSizes.size(),
"expected " + expectedPathSizes.size() + " execution paths, got " + simulation.getSeenPaths().size()
);
int[] actualPathIOSizes = simulation.getSeenIOStatementCountForEachPath();
// don't assume the order, just that each path size has occurred the specified number of timesfor (inti = 0; i < actualPathIOSizes.length; i++) {
intpathSize = actualPathIOSizes[i];
if (!expectedPathSizes.remove((Integer) pathSize)) {
helper.fail("unexpected path size " + pathSize + " at index " + i + " of " + simulation
.getSeenPaths()
.size() + " paths");
}
}
helper.succeed();
}
@GameTest(template = "3x2x1", batch = "linting")
publicstaticvoidconditional_output_inspection(GameTestHelperhelper) {
helper.setBlock(newBlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
BlockPosrightPos = newBlockPos(0, 2, 0);
helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
BlockPosleftPos = newBlockPos(2, 2, 0);
helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());
varrightChest = getItemHandler(helper, rightPos);
varleftChest = getItemHandler(helper, leftPos);
leftChest.insertItem(0, newItemStack(Blocks.DIRT, 64), false);
ManagerBlockEntitymanager = (ManagerBlockEntity) helper.getBlockEntity(newBlockPos(1, 2, 0));
manager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
// set the labelsLabelPositionHolder.empty()
.add("a", helper.absolutePos(leftPos))
.add("b", helper.absolutePos(rightPos))
.save(manager.getDisk());
// set the programStringcode = """ EVERY 20 TICKS DO IF a HAS = 64 dirt THEN INPUT RETAIN 32 FROM a END OUTPUT TO b END """.stripTrailing().stripIndent();
manager.setProgram(code);
assertManagerRunning(manager);
// compile new program for inspectionProgramprogram = compile(code);
OutputStatementoutputStatement = (OutputStatement) program
.triggers()
.get(0)
.getBlock()
.getStatements()
.get(1);
StringinspectionResults = ServerboundOutputInspectionRequestPacket.getOutputStatementInspectionResultsString(
manager,
program,
outputStatement
);
//noinspection TrailingWhitespacesInTextBlockStringexpected = """ OUTPUT TO b -- predictions may differ from actual execution results -- POSSIBILITY 0 -- all false OVERALL a HAS = 64 dirt -- false -- predicted inputs: none -- predicted outputs: none -- POSSIBILITY 1 -- all true OVERALL a HAS = 64 dirt -- true -- predicted inputs: INPUT 32 minecraft:dirt FROM a SLOTS 0 -- predicted outputs: OUTPUT 32 minecraft:dirt TO b """.stripLeading().stripIndent().stripTrailing();
if (!inspectionResults.equals(expected)) {
System.out.println("Received results:");
System.out.println(inspectionResults);
System.out.println("Expected:");
System.out.println(expected);
// get the position of the difference and show itfor (inti = 0; i < inspectionResults.length(); i++) {
if (inspectionResults.charAt(i) != expected.charAt(i)) {
System.out.println("Difference at position "
+ i
+ ":"
+ inspectionResults.charAt(i)
+ " vs "
+ expected.charAt(i));
break;
}
}
helper.fail("inspection didn't match results");
}
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
assertTrue(leftChest.getStackInSlot(0).getCount() == 32, "Dirt did not depart");
assertTrue(rightChest.getStackInSlot(0).getCount() == 32, "Dirt did not arrive");
});
}
@GameTest(template = "3x4x3")
publicstaticvoidmove_if_powered(GameTestHelperhelper) {
BlockPosmanagerPos = newBlockPos(1, 2, 1);
helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
BlockPosleftPos = managerPos.east();
helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());
BlockPosrightPos = managerPos.west();
helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
BlockPostopPos = managerPos.above();
helper.setBlock(topPos, SFMBlocks.TEST_BARREL_BLOCK.get());
BlockPosleverPos = managerPos.north();
helper.setBlock(leverPos, Blocks.LEVER);
helper.pullLever(leverPos);
varrightChest = getItemHandler(helper, rightPos);
varleftChest = getItemHandler(helper, leftPos);
leftChest.insertItem(0, newItemStack(Items.DIRT, 64), false);
leftChest.insertItem(1, newItemStack(Items.DIRT, 64), false);
leftChest.insertItem(2, newItemStack(Items.STONE, 64), false);
leftChest.insertItem(3, newItemStack(Items.IRON_INGOT, 64), false);
leftChest.insertItem(4, newItemStack(Items.GOLD_INGOT, 64), false);
leftChest.insertItem(5, newItemStack(Items.GOLD_NUGGET, 64), false);
ManagerBlockEntitymanager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
manager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
manager.setProgram(""" EVERY 20 TICKS DO IF redstone GT 0 THEN INPUT FROM left OUTPUT TO right END END """.stripTrailing().stripIndent());
// set the labelsLabelPositionHolder.empty()
.add("left", helper.absolutePos(leftPos))
.add("right", helper.absolutePos(rightPos))
.save(manager.getDisk());
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
assertTrue(count(leftChest, null) == 0, "everything should depart");
assertTrue(count(rightChest, Items.GOLD_NUGGET) == 64, "gold nuggets should arrive");
assertTrue(count(rightChest, Items.IRON_INGOT) == 64, "iron ingots should arrive");
assertTrue(count(rightChest, Items.GOLD_INGOT) == 64, "gold ingots should arrive");
assertTrue(count(rightChest, Items.DIRT) == 64 * 2, "dirt should arrive");
assertTrue(count(rightChest, Items.STONE) == 64, "stone should arrive");
});
}
@GameTest(template = "3x2x1")
publicstaticvoidhas_or(GameTestHelperhelper) {
varleftPos = newBlockPos(2, 2, 0);
varrightPos = newBlockPos(0, 2, 0);
varmanagerPos = newBlockPos(1, 2, 0);
helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get());
helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get());
helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
varleft = (Container) helper.getBlockEntity(leftPos);
varright = (Container) helper.getBlockEntity(rightPos);
varmanager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
left.setItem(0, newItemStack(Items.DIAMOND, 64));
left.setItem(1, newItemStack(Items.DIAMOND, 64));
left.setItem(2, newItemStack(Items.IRON_INGOT, 12));
manager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
manager.setProgram(""" NAME "has_or test" EVERY 20 TICKS DO IF left HAS LT 150 diamond OR iron_ingot THEN INPUT FROM left OUTPUT TO right END END """.stripTrailing().stripIndent());
// set the labelsLabelPositionHolder.empty()
.add("left", helper.absolutePos(leftPos))
.add("right", helper.absolutePos(rightPos))
.save(manager.getDisk());
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
// left should be emptyassertTrue(count(left, Items.DIAMOND) == 0, "left should have no diamonds");
assertTrue(count(left, Items.IRON_INGOT) == 0, "left should have no iron ingots");
// right should have all the itemsassertTrue(count(right, Items.DIAMOND) == 64 * 2, "right should have 128 diamonds");
assertTrue(count(right, Items.IRON_INGOT) == 12, "right should have 12 iron ingots");
});
}
}
packageca.teamdman.sfm.gametest;
importca.teamdman.sfm.SFM;
importca.teamdman.sfm.common.blockentity.ManagerBlockEntity;
importca.teamdman.sfm.common.item.DiskItem;
importca.teamdman.sfm.common.program.LabelPositionHolder;
importca.teamdman.sfm.common.registry.SFMBlocks;
importca.teamdman.sfm.common.registry.SFMItems;
importnet.minecraft.core.BlockPos;
importnet.minecraft.gametest.framework.GameTest;
importnet.minecraft.gametest.framework.GameTestHelper;
importnet.minecraft.world.item.Item;
importnet.minecraft.world.item.ItemStack;
importnet.minecraft.world.item.Items;
importnet.minecraft.world.level.block.Blocks;
importnet.minecraft.world.level.block.entity.BarrelBlockEntity;
importnet.minecraftforge.gametest.GameTestHolder;
importnet.minecraftforge.gametest.PrefixGameTestTemplate;
importjava.util.ArrayList;
importjava.util.List;
importjava.util.concurrent.atomic.AtomicInteger;
@SuppressWarnings({"DataFlowIssue", "DuplicatedCode", "DefaultAnnotationParam"})
@GameTestHolder(SFM.MOD_ID)
@PrefixGameTestTemplate(value=true)
publicclassSFMPerformanceGameTestsextendsSFMGameTestBase {
@GameTest(template = "25x3x25", batch = "laggy")
publicstaticvoidmove_many_inventories(GameTestHelperhelper) {
// fill the platform with cables and barrelsvarsourceBlocks = newArrayList<BlockPos>();
vardestBlocks = newArrayList<BlockPos>();
for (intx = 0; x < 25; x++) {
// for (int z = 0; z < 25; z++) {for (intz = 0; z < 24; z++) {
helper.setBlock(newBlockPos(x, 2, z), SFMBlocks.CABLE_BLOCK.get());
helper.setBlock(newBlockPos(x, 3, z), SFMBlocks.TEST_BARREL_BLOCK.get());
if (z % 2 == 0) {
sourceBlocks.add(newBlockPos(x, 3, z));
// fill the source chests with ingotsBarrelBlockEntitybarrel = (BarrelBlockEntity) helper.getBlockEntity(newBlockPos(x, 3, z));
for (inti = 0; i < barrel.getContainerSize(); i++) {
barrel.setItem(i, newItemStack(Items.IRON_INGOT, 64));
}
} else {
destBlocks.add(newBlockPos(x, 3, z));
}
}
}
// fill in the blocks needed for the testhelper.setBlock(newBlockPos(0, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
ManagerBlockEntitymanager = (ManagerBlockEntity) helper.getBlockEntity(newBlockPos(0, 2, 0));
manager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
// create the programvarprogram = """ NAME "many inventory lag test" EVERY 20 TICKS DO INPUT FROM a OUTPUT TO b END """.stripTrailing().stripIndent();
// set the labelsLabelPositionHolder.empty()
.addAll("a", sourceBlocks.stream().map(helper::absolutePos).toList())
.addAll("b", destBlocks.stream().map(helper::absolutePos).toList())
.save(manager.getDisk());
// load the programmanager.setProgram(program);
assertTrue(
manager.getState() == ManagerBlockEntity.State.RUNNING,
"Program did not start running " + DiskItem.getErrors(manager.getDisk())
);
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
// ensure all the source chests are emptysourceBlocks.forEach(pos -> {
BarrelBlockEntitybarrel = (BarrelBlockEntity) helper.getBlockEntity(pos);
for (inti = 0; i < barrel.getContainerSize(); i++) {
ItemStackfound = barrel.getItem(i);
assertTrue(
found.isEmpty(),
"Items did not leave, pos=" + helper.absolutePos(pos) + " i=" + i + " found=" + found
);
}
});
// ensure all the dest chests are fulldestBlocks.forEach(pos -> {
BarrelBlockEntitybarrel = (BarrelBlockEntity) helper.getBlockEntity(pos);
for (inti = 0; i < barrel.getContainerSize(); i++) {
assertTrue(barrel.getItem(i).getCount() == 64, "Items did not arrive");
}
});
});
}
@GameTest(template = "25x3x25", batch = "laggy")
publicstaticvoidmove_many_full(GameTestHelperhelper) {
// fill the platform with cables and barrelsvarsourceBlocks = newArrayList<BlockPos>();
vardestBlocks = newArrayList<BlockPos>();
for (intx = 0; x < 25; x++) {
// for (int z = 0; z < 25; z++) {for (intz = 0; z < 24; z++) {
helper.setBlock(newBlockPos(x, 2, z), SFMBlocks.CABLE_BLOCK.get());
helper.setBlock(newBlockPos(x, 3, z), SFMBlocks.TEST_BARREL_BLOCK.get());
if (z % 2 == 0) {
sourceBlocks.add(newBlockPos(x, 3, z));
} else {
destBlocks.add(newBlockPos(x, 3, z));
}
// fill the source chests with ingotsBarrelBlockEntitybarrel = (BarrelBlockEntity) helper.getBlockEntity(newBlockPos(x, 3, z));
for (inti = 0; i < barrel.getContainerSize(); i++) {
barrel.setItem(i, newItemStack(Items.IRON_INGOT, 64));
}
}
}
// fill in the blocks needed for the testhelper.setBlock(newBlockPos(0, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
ManagerBlockEntitymanager = (ManagerBlockEntity) helper.getBlockEntity(newBlockPos(0, 2, 0));
manager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
// create the programvarprogram = """ NAME "move many full" EVERY 20 TICKS DO INPUT FROM a OUTPUT TO b END """.stripTrailing().stripIndent();
// set the labelsLabelPositionHolder.empty()
.addAll("a", sourceBlocks.stream().map(helper::absolutePos).toList())
.addAll("b", destBlocks.stream().map(helper::absolutePos).toList())
.save(manager.getDisk());
// load the programmanager.setProgram(program);
assertTrue(
manager.getState() == ManagerBlockEntity.State.RUNNING,
"Program did not start running " + DiskItem.getErrors(manager.getDisk())
);
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
// ensure all the source chests are fullsourceBlocks.forEach(pos -> {
BarrelBlockEntitybarrel = (BarrelBlockEntity) helper.getBlockEntity(pos);
for (inti = 0; i < barrel.getContainerSize(); i++) {
assertTrue(barrel.getItem(i).getCount() == 64, "Items did not stay");
}
});
// ensure all the dest chests are fulldestBlocks.forEach(pos -> {
BarrelBlockEntitybarrel = (BarrelBlockEntity) helper.getBlockEntity(pos);
for (inti = 0; i < barrel.getContainerSize(); i++) {
assertTrue(barrel.getItem(i).getCount() == 64, "Items did not arrive");
}
});
});
}
/** * Creates many inventories. * Half of them will be full, the other half will be empty. * The half that is full will have three different items, one type per row: * - iron ingots * - gold ingots * - diamonds * The program should use a regular expression to match only ingots in the form *:*_ingot */@GameTest(template = "25x3x25", batch = "laggy")
publicstaticvoidmove_many_regex(GameTestHelperhelper) {
// fill the platform with cables and barrelsvarsourceBlocks = newArrayList<BlockPos>();
vardestBlocks = newArrayList<BlockPos>();
varironIngots = newAtomicInteger(0);
vargoldIngots = newAtomicInteger(0);
vardiamonds = newAtomicInteger(0);
for (intx = 0; x < 25; x++) {
for (intz = 0; z < 24; z++) { // make sure we have an even number to split// place a cable belowhelper.setBlock(newBlockPos(x, 2, z), SFMBlocks.CABLE_BLOCK.get());
// place the barrel on tophelper.setBlock(newBlockPos(x, 3, z), SFMBlocks.TEST_BARREL_BLOCK.get());
if (z % 2 == 0) {
sourceBlocks.add(newBlockPos(x, 3, z));
BarrelBlockEntitybarrel = (BarrelBlockEntity) helper.getBlockEntity(newBlockPos(x, 3, z));
for (inti = 0; i < barrel.getContainerSize(); i++) {
if (i % 3 == 0) {
barrel.setItem(i, newItemStack(Items.IRON_INGOT, 64));
ironIngots.addAndGet(64);
} elseif (i % 3 == 1) {
barrel.setItem(i, newItemStack(Items.GOLD_INGOT, 64));
goldIngots.addAndGet(64);
} else {
barrel.setItem(i, newItemStack(Items.DIAMOND, 64));
diamonds.addAndGet(64);
}
}
} else {
destBlocks.add(newBlockPos(x, 3, z));
}
}
}
// create the manager block and add the diskhelper.setBlock(newBlockPos(0, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
ManagerBlockEntitymanager = (ManagerBlockEntity) helper.getBlockEntity(newBlockPos(0, 2, 0));
manager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
// create the programvarprogram = """ NAME "move many regex" EVERY 20 TICKS DO INPUT *:*_ingot FROM a OUTPUT TO b END """.stripTrailing().stripIndent();
// set the labelsLabelPositionHolder.empty()
.addAll("a", sourceBlocks.stream().map(helper::absolutePos).toList())
.addAll("b", destBlocks.stream().map(helper::absolutePos).toList())
.save(manager.getDisk());
// load the programmanager.setProgram(program);
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
// ensure the source chests only have the non-ingot itemssourceBlocks.forEach(pos -> {
BarrelBlockEntitybarrel = (BarrelBlockEntity) helper.getBlockEntity(pos);
for (inti = 0; i < barrel.getContainerSize(); i++) {
if (i % 3 == 0) {
assertTrue(barrel.getItem(i).isEmpty(), "Items did not depart");
} elseif (i % 3 == 1) {
assertTrue(barrel.getItem(i).isEmpty(), "Items did not depart");
} else {
assertTrue(barrel.getItem(i).getItem() == Items.DIAMOND, "Non-matching didn't stay");
}
}
});
// ensure the destination chests only have the ingot itemsintdiamondStart = diamonds.get();
destBlocks.forEach(pos -> {
BarrelBlockEntitybarrel = (BarrelBlockEntity) helper.getBlockEntity(pos);
for (inti = 0; i < barrel.getContainerSize(); i++) {
Itemitem = barrel.getItem(i).getItem();
if (item == Items.IRON_INGOT) {
ironIngots.addAndGet(-barrel.getItem(i).getCount());
} elseif (item == Items.GOLD_INGOT) {
goldIngots.addAndGet(-barrel.getItem(i).getCount());
} elseif (item == Items.DIAMOND) {
diamonds.addAndGet(-barrel.getItem(i).getCount());
}
}
});
assertTrue(ironIngots.get() == 0, "Iron ingots did not arrive");
assertTrue(goldIngots.get() == 0, "Gold ingots did not arrive");
assertTrue(diamonds.get() == diamondStart, "Diamonds did not stay");
});
}
/** * Creates four chests: a, b, c, d * a: 9x64 iron ingots * b: 9x64 gold ingots * c: 9x64 diamonds * d: 26x64 cobblestone, 1x64 copper ingot * <p> * Every 20 ticks, the program should: * - move all ingots from a to b, b to c, c to d, d to a * <p> * The program should use a regular expression to match only ingots in the form *:*_ingot */@GameTest(template = "3x4x3", batch = "laggy")
publicstaticvoidmove_regex_circle(GameTestHelperhelper) {
varmanagerPos = newBlockPos(1, 2, 1);
varaPos = newBlockPos(1, 2, 0);
varbPos = newBlockPos(2, 2, 1);
varcPos = newBlockPos(1, 2, 2);
vardPos = newBlockPos(0, 2, 1);
// place and fill the chestshelper.setBlock(aPos, SFMBlocks.TEST_BARREL_BLOCK.get());
helper.setBlock(bPos, SFMBlocks.TEST_BARREL_BLOCK.get());
helper.setBlock(cPos, SFMBlocks.TEST_BARREL_BLOCK.get());
helper.setBlock(dPos, SFMBlocks.TEST_BARREL_BLOCK.get());
vara = (BarrelBlockEntity) helper.getBlockEntity(aPos);
varb = (BarrelBlockEntity) helper.getBlockEntity(bPos);
varc = (BarrelBlockEntity) helper.getBlockEntity(cPos);
vard = (BarrelBlockEntity) helper.getBlockEntity(dPos);
for (inti = 0; i < 27; i++) {
if (i < 9) {
a.setItem(i, newItemStack(Items.IRON_INGOT, 64));
b.setItem(i, newItemStack(Items.GOLD_INGOT, 64));
c.setItem(i, newItemStack(Items.DIAMOND, 64));
}
d.setItem(i, newItemStack(Items.COBBLESTONE, 64));
}
d.setItem(26, newItemStack(Items.COPPER_INGOT, 64));
// create the manager block and add the diskhelper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
ManagerBlockEntitymanager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
manager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
// create the programvarprogram = """ NAME "move regex circle" EVERY 20 TICKS DO INPUT *:*_ingot FROM a OUTPUT TO b END EVERY 20 TICKS DO INPUT *:*_ingot FROM b OUTPUT TO c END EVERY 20 TICKS DO INPUT *:*_ingot FROM c OUTPUT TO d END EVERY 20 TICKS DO INPUT *:*_ingot FROM d OUTPUT TO a END """.stripTrailing().stripIndent();
// set the labelsLabelPositionHolder.empty()
.add("a", helper.absolutePos(aPos))
.add("b", helper.absolutePos(bPos))
.add("c", helper.absolutePos(cPos))
.add("d", helper.absolutePos(dPos))
.save(manager.getDisk());
// load the programmanager.setProgram(program);
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
});
}
/** * In FTB Skies, I was generating resources using sieves and gravel. * This would give a bunch of raw metals, which I would then smelt into ingots. * Those ingots would be moved into gold barrels; very large inventories; and I had a lot of them. * Then I had a crafting station with its own adjacent gold barrel. * I wrote a program to keep that gold barrel stocked with essential ingredients. */@GameTest(template = "25x4x25")
publicstaticvoidgather_supplies(GameTestHelperhelper) {
varitems = newItem[]{
Items.GOLD_INGOT,
Items.GOLD_BLOCK,
Items.IRON_INGOT,
Items.IRON_BLOCK,
Items.DIAMOND,
Items.DIAMOND_BLOCK,
Items.EMERALD,
Items.EMERALD_BLOCK,
Items.LAPIS_LAZULI,
Items.LAPIS_BLOCK,
Items.REDSTONE,
Items.REDSTONE_BLOCK,
Items.COAL,
Items.COAL_BLOCK,
Items.NETHERITE_INGOT,
Items.NETHERITE_BLOCK,
Items.TORCH,
Items.BUCKET,
Items.CHEST,
Items.CRAFTING_TABLE,
Items.FURNACE,
Items.COBBLESTONE,
Items.OAK_LOG,
Items.OAK_PLANKS
};
// create a bunch of storage inventoriesList<BlockPos> storage = newArrayList<>();
intitemIndex = 0;
for (inti = 0; i < 24; i++) {
for (intj = 3; j < 25; j++) {
for (intk = 0; k < 3; k++) {
BlockPospos = newBlockPos(i, k + 2, j);
if (j == 3) {
if (k == 0) {
helper.setBlock(pos, SFMBlocks.CABLE_BLOCK.get());
}
} else {
if (i % 3 == 0 || i % 3 == 2) {
helper.setBlock(pos, SFMBlocks.TEST_BARREL_BLOCK.get());
// fill the barrel with some itemsBarrelBlockEntitybarrel = (BarrelBlockEntity) helper.getBlockEntity(pos);
for (intslot = 0; slot < barrel.getContainerSize(); slot++) {
barrel.setItem(slot, newItemStack(items[itemIndex++ % items.length], 64));
}
storage.add(pos);
} else {
helper.setBlock(pos, SFMBlocks.CABLE_BLOCK.get());
}
}
}
}
}
// add the crafting stationhelper.setBlock(newBlockPos(0, 2, 1), Blocks.CRAFTING_TABLE);
helper.setBlock(newBlockPos(0, 2, 0), SFMBlocks.TEST_BARREL_BLOCK.get());
helper.setBlock(newBlockPos(1, 2, 0), SFMBlocks.CABLE_BLOCK.get());
helper.setBlock(newBlockPos(1, 2, 1), SFMBlocks.CABLE_BLOCK.get());
helper.setBlock(newBlockPos(1, 2, 2), SFMBlocks.CABLE_BLOCK.get());
// add the managerhelper.setBlock(newBlockPos(2, 2, 0), SFMBlocks.MANAGER_BLOCK.get());
varmanager = (ManagerBlockEntity) helper.getBlockEntity(newBlockPos(2, 2, 0));
manager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
// create the programvarprogram = """ NAME "gather supplies" EVERY 20 TICKS DO INPUT retain 64 gold_ingot, retain 64 gold_block, retain 64 iron_ingot, retain 64 iron_block, retain 64 diamond, retain 64 diamond_block, retain 64 emerald, retain 64 emerald_block, retain 64 lapis_lazuli, retain 64 lapis_block, retain 64 "redstone", retain 64 redstone_block, retain 64 coal, retain 64 coal_block, retain 64 netherite_ingot, retain 64 netherite_block, retain 64 torch, retain 16 bucket, retain 64 chest, retain 64 crafting_table, retain 64 furnace, retain 64 cobblestone, retain 64 *:*_log, retain 64 *:*_planks FROM chest INPUT EXCEPT gold_ingot, gold_block, iron_ingot, iron_block, diamond, diamond_block, emerald, emerald_block, lapis_lazuli, lapis_block, "redstone", redstone_block, coal, coal_block, netherite_ingot, netherite_block, torch, bucket, chest, crafting_table, furnace, cobblestone, *:*_log, *:*_planks FROM chest OUTPUT TO storage END EVERY 20 TICKS DO INPUT FROM storage OUTPUT retain 64 gold_ingot, retain 64 gold_block, retain 64 iron_ingot, retain 64 iron_block, retain 64 diamond, retain 64 diamond_block, retain 64 emerald, retain 64 emerald_block, retain 64 lapis_lazuli, retain 64 lapis_block, retain 64 "redstone", retain 64 redstone_block, retain 64 coal, retain 64 coal_block, retain 64 netherite_ingot, retain 64 netherite_block, retain 64 torch, retain 16 bucket, retain 64 chest, retain 64 crafting_table, retain 64 furnace, retain 64 cobblestone, retain 64 *:*_log, retain 64 *:*_planks TO chest END """.stripTrailing().stripIndent();
// set the labelsLabelPositionHolder.empty()
.addAll("storage", storage.stream().map(helper::absolutePos).toList())
.add("chest", helper.absolutePos(newBlockPos(0, 2, 0)))
.save(manager.getDisk());
// load the programmanager.setProgram(program);
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
// the inventory should be stocked with a stack of each itemBarrelBlockEntitybarrel = (BarrelBlockEntity) helper.getBlockEntity(newBlockPos(0, 2, 0));
for (Itemitem : items) {
for (intslot = 0; slot < barrel.getContainerSize(); slot++) {
ItemStackstack = barrel.getItem(slot);
if (stack.getItem() == item) {
assertTrue(
stack.getCount() == stack.getMaxStackSize(),
"Item " + item + " is not fully stocked"
);
}
}
}
});
}
}
packageca.teamdman.sfm.gametest;
importca.teamdman.sfm.SFM;
importca.teamdman.sfm.common.item.DiskItem;
importca.teamdman.sfm.common.localization.LocalizationKeys;
importca.teamdman.sfm.common.registry.SFMBlocks;
importca.teamdman.sfm.common.registry.SFMItems;
importnet.minecraft.core.BlockPos;
importnet.minecraft.gametest.framework.GameTest;
importnet.minecraft.gametest.framework.GameTestHelper;
importnet.minecraft.world.item.ItemStack;
importnet.minecraftforge.gametest.GameTestHolder;
@GameTestHolder(SFM.MOD_ID)
publicclassSFMProgramLinterGameTestsextendsSFMGameTestBase {
@GameTest(template = "3x2x1")
publicstaticvoidmekanism_null_io_direction(GameTestHelperhelper) {
helper.succeed();
// new SFMChestTestBuilder(helper)// .addChest();
}
@GameTest(template = "1x2x1")
publicstaticvoiddisk_name(GameTestHelperhelper) {
BlockPoschestPos = newBlockPos(0, 2, 0);
helper.setBlock(chestPos, SFMBlocks.TEST_BARREL_BLOCK.get());
varchest = getItemHandler(helper, chestPos);
{
ItemStackdisk = newItemStack(SFMItems.DISK_ITEM.get());
StringprogramString = """ NAME "bruh" EVERY 20 TICKS DO END """;
DiskItem.setProgram(disk, programString);
DiskItem.compileAndUpdateErrorsAndWarnings(disk, null);
chest.insertItem(0, disk, false);
assertTrue(DiskItem.getProgramName(disk).equals("bruh"), "program name should be bruh for disk 1");
assertTrue(DiskItem.getWarnings(disk).isEmpty(), "there should be no warnings on disk 1");
assertTrue(DiskItem.getErrors(disk).isEmpty(), "there should be no errors on disk 1");
assertTrue(disk.getHoverName().getString().equals("bruh"), "display name should be \"bruh\" for disk 1");
}
{
ItemStackdisk = newItemStack(SFMItems.DISK_ITEM.get());
StringprogramString = """ EVERY 20 TICKS DO END """;
DiskItem.setProgram(disk, programString);
DiskItem.compileAndUpdateErrorsAndWarnings(disk, null);
chest.insertItem(1, disk, false);
assertTrue(DiskItem.getProgramName(disk).isEmpty(), "program name should be empty for disk 2");
assertTrue(DiskItem.getWarnings(disk).isEmpty(), "there should be no warnings on disk 2");
assertTrue(DiskItem.getErrors(disk).isEmpty(), "there should be no errors on disk 2");
assertTrue(
disk.getHoverName().contains(LocalizationKeys.DISK_ITEM.getComponent()),
"display name should be default for disk 2"
);
}
helper.succeed();
}
}
packageca.teamdman.sfm.gametest.compat.mekanism;
importca.teamdman.sfm.SFM;
importca.teamdman.sfm.common.blockentity.ManagerBlockEntity;
importca.teamdman.sfm.common.compat.SFMMekanismCompat;
importca.teamdman.sfm.common.program.LabelPositionHolder;
importca.teamdman.sfm.common.registry.SFMBlocks;
importca.teamdman.sfm.common.registry.SFMItems;
importca.teamdman.sfm.gametest.SFMGameTestBase;
importca.teamdman.sfm.gametest.declarative.SFMDeclarativeTestBuilder;
importca.teamdman.sfm.gametest.declarative.SFMTestSpec;
importca.teamdman.sfm.gametest.declarative.TestBlockDef;
importmekanism.api.RelativeSide;
importmekanism.api.chemical.infuse.InfusionStack;
importmekanism.api.math.FloatingLong;
importmekanism.common.lib.transmitter.TransmissionType;
importmekanism.common.registries.MekanismBlocks;
importmekanism.common.registries.MekanismInfuseTypes;
importmekanism.common.tier.BinTier;
importmekanism.common.tier.ChemicalTankTier;
importmekanism.common.tier.EnergyCubeTier;
importmekanism.common.tile.TileEntityBin;
importmekanism.common.tile.TileEntityChemicalTank;
importmekanism.common.tile.TileEntityEnergyCube;
importmekanism.common.tile.component.config.DataType;
importmekanism.common.tile.multiblock.TileEntityInductionPort;
importmekanism.common.util.UnitDisplayUtils;
importnet.minecraft.core.BlockPos;
importnet.minecraft.core.Direction;
importnet.minecraft.gametest.framework.GameTest;
importnet.minecraft.gametest.framework.GameTestAssertException;
importnet.minecraft.gametest.framework.GameTestHelper;
importnet.minecraft.world.item.ItemStack;
importnet.minecraft.world.item.Items;
importnet.minecraft.world.level.block.Block;
importnet.minecraft.world.level.block.Blocks;
importnet.minecraft.world.level.material.Fluids;
importnet.minecraftforge.common.capabilities.ForgeCapabilities;
importnet.minecraftforge.fluids.FluidStack;
importnet.minecraftforge.fluids.capability.IFluidHandler;
importnet.minecraftforge.gametest.GameTestHolder;
importjava.util.ArrayList;
importjava.util.List;
importjava.util.Objects;
@SuppressWarnings({"DuplicatedCode", "DataFlowIssue"})
@GameTestHolder(SFM.MOD_ID)
publicclassSFMMekanismCompatGameTestsextendsSFMGameTestBase {
@GameTest(template = "3x2x1")
publicstaticvoidmek_chemtank_infusion_empty(GameTestHelperhelper) {
// designate positionsvarleftPos = newBlockPos(2, 2, 0);
varrightPos = newBlockPos(0, 2, 0);
varmanagerPos = newBlockPos(1, 2, 0);
// set up the worldhelper.setBlock(leftPos, MekanismBlocks.ULTIMATE_CHEMICAL_TANK.getBlock());
varleftTank = ((TileEntityChemicalTank) helper.getBlockEntity(leftPos));
helper.setBlock(rightPos, MekanismBlocks.ULTIMATE_CHEMICAL_TANK.getBlock());
varrightTank = ((TileEntityChemicalTank) helper.getBlockEntity(rightPos));
helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
varmanager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));
// set up the programmanager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
manager.setProgram(""" EVERY 20 TICKS DO INPUT infusion:*:* FROM a NORTH SIDE -- mek can extract from front by default OUTPUT infusion:*:* TO b TOP SIDE -- mek can insert to top by default END """.stripIndent());
// set the labelsLabelPositionHolder.empty()
.add("a", helper.absolutePos(leftPos))
.add("b", helper.absolutePos(rightPos))
.save(manager.getDisk());
// ensure it can move into an empty tankleftTank.getInfusionTank().setStack(newInfusionStack(MekanismInfuseTypes.REDSTONE.get(), 1_000_000L));
rightTank.getInfusionTank().setStack(InfusionStack.EMPTY);
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
assertTrue(leftTank.getInfusionTank().getStack().isEmpty(), "Contents did not depart");
assertTrue(rightTank.getInfusionTank().getStack().getAmount() == 1_000_000L, "Contents did not arrive");
});
}
@GameTest(template = "3x2x1")
publicstaticvoidmek_chemtank_infusion_some(GameTestHelperhelper) {
// designate positionsvarleftPos = newBlockPos(2, 2, 0);
varrightPos = newBlockPos(0, 2, 0);
varmanagerPos = newBlockPos(1, 2, 0);
// set up the worldhelper.setBlock(leftPos, MekanismBlocks.ULTIMATE_CHEMICAL_TANK.getBlock());
varleftTank = ((TileEntityChemicalTank) helper.getBlockEntity(leftPos));
helper.setBlock(rightPos, MekanismBlocks.ULTIMATE_CHEMICAL_TANK.getBlock());
varrightTank = ((TileEntityChemicalTank) helper.getBlockEntity(rightPos));
helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
varmanager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));
// set up the programmanager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
manager.setProgram(""" EVERY 20 TICKS DO INPUT infusion:*:* FROM a NORTH SIDE -- mek can extract from front by default OUTPUT infusion:*:* TO b TOP SIDE -- mek can insert to top by default END """.stripIndent());
// set the labelsLabelPositionHolder.empty()
.add("a", helper.absolutePos(leftPos))
.add("b", helper.absolutePos(rightPos))
.save(manager.getDisk());
// ensure it can move when there's already some in the destinationleftTank.getInfusionTank().setStack(newInfusionStack(MekanismInfuseTypes.REDSTONE.get(), 1_000_000L));
rightTank.getInfusionTank().setStack(newInfusionStack(MekanismInfuseTypes.REDSTONE.get(), 1_000_000L));
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
assertTrue(leftTank.getInfusionTank().getStack().isEmpty(), "Contents did not depart");
assertTrue(rightTank.getInfusionTank().getStack().getAmount() == 2_000_000L, "Contents did not arrive");
});
}
@GameTest(template = "3x2x1")
publicstaticvoidmek_chemtank_infusion_full(GameTestHelperhelper) {
// designate positionsvarleftPos = newBlockPos(2, 2, 0);
varrightPos = newBlockPos(0, 2, 0);
varmanagerPos = newBlockPos(1, 2, 0);
// set up the worldhelper.setBlock(leftPos, MekanismBlocks.ULTIMATE_CHEMICAL_TANK.getBlock());
varleftTank = ((TileEntityChemicalTank) helper.getBlockEntity(leftPos));
helper.setBlock(rightPos, MekanismBlocks.ULTIMATE_CHEMICAL_TANK.getBlock());
varrightTank = ((TileEntityChemicalTank) helper.getBlockEntity(rightPos));
helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
varmanager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));
// set up the programmanager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
manager.setProgram(""" EVERY 20 TICKS DO INPUT infusion:*:* FROM a NORTH SIDE -- mek can extract from front by default OUTPUT infusion:*:* TO b TOP SIDE -- mek can insert to top by default END """.stripIndent());
// set the labelsLabelPositionHolder.empty()
.add("a", helper.absolutePos(leftPos))
.add("b", helper.absolutePos(rightPos))
.save(manager.getDisk());
// ensure it can move into a nearly full tankleftTank.getInfusionTank().setStack(newInfusionStack(MekanismInfuseTypes.REDSTONE.get(), 2_000_000L));
rightTank
.getInfusionTank()
.setStack(newInfusionStack(
MekanismInfuseTypes.REDSTONE.get(),
ChemicalTankTier.ULTIMATE.getStorage() - 1_000_000L
));
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
assertTrue(leftTank.getInfusionTank().getStack().getAmount() == 1_000_000L, "Contents did not depart");
assertTrue(
rightTank.getInfusionTank().getStack().getAmount() == ChemicalTankTier.ULTIMATE.getStorage(),
"Contents did not arrive"
);
});
}
@GameTest(template = "3x2x1")
publicstaticvoidmek_bin_empty(GameTestHelperhelper) {
// designate positionsvarleftPos = newBlockPos(2, 2, 0);
varrightPos = newBlockPos(0, 2, 0);
varmanagerPos = newBlockPos(1, 2, 0);
// set up the worldhelper.setBlock(leftPos, MekanismBlocks.ULTIMATE_BIN.getBlock());
varleft = ((TileEntityBin) helper.getBlockEntity(leftPos));
helper.setBlock(rightPos, MekanismBlocks.ULTIMATE_BIN.getBlock());
varright = ((TileEntityBin) helper.getBlockEntity(rightPos));
helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
varmanager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));
// set up the programmanager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
manager.setProgram(""" EVERY 20 TICKS DO INPUT FROM a NORTH SIDE OUTPUT TO b TOP SIDE END """.stripIndent());
// set the labelsLabelPositionHolder.empty()
.add("a", helper.absolutePos(leftPos))
.add("b", helper.absolutePos(rightPos))
.save(manager.getDisk());
left.getBinSlot().setStack(newItemStack(Items.COAL, BinTier.ULTIMATE.getStorage()));
right.getBinSlot().setEmpty();
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
assertTrue(left.getBinSlot().getCount() == BinTier.ULTIMATE.getStorage() - 64, "Contents did not depart");
assertTrue(right.getBinSlot().getCount() == 64, "Contents did not arrive");
assertTrue(right.getBinSlot().getStack().getItem() == Items.COAL, "Contents wrong type");
});
}
@GameTest(template = "3x2x1")
publicstaticvoidmek_bin_some(GameTestHelperhelper) {
// designate positionsvarleftPos = newBlockPos(2, 2, 0);
varrightPos = newBlockPos(0, 2, 0);
varmanagerPos = newBlockPos(1, 2, 0);
// set up the worldhelper.setBlock(leftPos, MekanismBlocks.ULTIMATE_BIN.getBlock());
varleft = ((TileEntityBin) helper.getBlockEntity(leftPos));
helper.setBlock(rightPos, MekanismBlocks.ULTIMATE_BIN.getBlock());
varright = ((TileEntityBin) helper.getBlockEntity(rightPos));
helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
varmanager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));
// set up the programmanager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
manager.setProgram(""" EVERY 20 TICKS DO INPUT FROM a NORTH SIDE OUTPUT TO b TOP SIDE END """.stripIndent());
// set the labelsLabelPositionHolder.empty()
.add("a", helper.absolutePos(leftPos))
.add("b", helper.absolutePos(rightPos))
.save(manager.getDisk());
left.getBinSlot().setStack(newItemStack(Items.DIAMOND, 100));
right.getBinSlot().setStack(newItemStack(Items.DIAMOND, 100));
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
assertTrue(left.getBinSlot().getCount() == 100 - 64, "Contents did not depart");
assertTrue(right.getBinSlot().getCount() == 100 + 64, "Contents did not arrive");
assertTrue(right.getBinSlot().getStack().getItem() == Items.DIAMOND, "Contents wrong type");
});
}
@GameTest(template = "3x2x1")
publicstaticvoidmek_bin_full(GameTestHelperhelper) {
// designate positionsvarleftPos = newBlockPos(2, 2, 0);
varrightPos = newBlockPos(0, 2, 0);
varmanagerPos = newBlockPos(1, 2, 0);
// set up the worldhelper.setBlock(leftPos, MekanismBlocks.ULTIMATE_BIN.getBlock());
varleft = ((TileEntityBin) helper.getBlockEntity(leftPos));
helper.setBlock(rightPos, MekanismBlocks.ULTIMATE_BIN.getBlock());
varright = ((TileEntityBin) helper.getBlockEntity(rightPos));
helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
varmanager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));
// set up the programmanager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
manager.setProgram(""" EVERY 20 TICKS DO INPUT FROM a NORTH SIDE OUTPUT TO b TOP SIDE END """.stripIndent());
// set the labelsLabelPositionHolder.empty()
.add("a", helper.absolutePos(leftPos))
.add("b", helper.absolutePos(rightPos))
.save(manager.getDisk());
left.getBinSlot().setStack(newItemStack(Items.STICK, BinTier.ULTIMATE.getStorage()));
right.getBinSlot().setStack(newItemStack(Items.STICK, BinTier.ULTIMATE.getStorage() - 32));
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
assertTrue(left.getBinSlot().getCount() == BinTier.ULTIMATE.getStorage() - 32, "Contents did not depart");
assertTrue(right.getBinSlot().getCount() == BinTier.ULTIMATE.getStorage(), "Contents did not arrive");
assertTrue(right.getBinSlot().getStack().getItem() == Items.STICK, "Contents wrong type");
});
}
@GameTest(template = "3x2x1")
publicstaticvoidmek_energy_empty(GameTestHelperhelper) {
// designate positionsvarleftPos = newBlockPos(2, 2, 0);
varrightPos = newBlockPos(0, 2, 0);
varmanagerPos = newBlockPos(1, 2, 0);
// set up the worldhelper.setBlock(leftPos, MekanismBlocks.ULTIMATE_ENERGY_CUBE.getBlock());
varleft = ((TileEntityEnergyCube) helper.getBlockEntity(leftPos));
helper.setBlock(rightPos, MekanismBlocks.ULTIMATE_ENERGY_CUBE.getBlock());
varright = ((TileEntityEnergyCube) helper.getBlockEntity(rightPos));
helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
varmanager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));
// set up the programmanager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
manager.setProgram(""" EVERY 20 TICKS DO INPUT forge_energy:forge:energy FROM a NORTH SIDE OUTPUT forge_energy:forge:energy TO b TOP SIDE END """.stripIndent());
// set the labelsLabelPositionHolder.empty()
.add("a", helper.absolutePos(leftPos))
.add("b", helper.absolutePos(rightPos))
.save(manager.getDisk());
left.setEnergy(0, EnergyCubeTier.ULTIMATE.getMaxEnergy());
right.setEnergy(0, FloatingLong.ZERO);
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
assertTrue(left.getEnergy(0).equals(FloatingLong.ZERO), "Contents did not depart");
assertTrue(right.getEnergy(0).equals(EnergyCubeTier.ULTIMATE.getMaxEnergy()), "Contents did not arrive");
});
}
@GameTest(template = "3x2x1")
publicstaticvoidmek_energy_some(GameTestHelperhelper) {
// designate positionsvarleftPos = newBlockPos(2, 2, 0);
varrightPos = newBlockPos(0, 2, 0);
varmanagerPos = newBlockPos(1, 2, 0);
// set up the worldhelper.setBlock(leftPos, MekanismBlocks.ULTIMATE_ENERGY_CUBE.getBlock());
varleft = ((TileEntityEnergyCube) helper.getBlockEntity(leftPos));
helper.setBlock(rightPos, MekanismBlocks.ULTIMATE_ENERGY_CUBE.getBlock());
varright = ((TileEntityEnergyCube) helper.getBlockEntity(rightPos));
helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
varmanager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));
// set up the programmanager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
manager.setProgram(""" EVERY 20 TICKS DO INPUT forge_energy:forge:energy FROM a NORTH SIDE OUTPUT forge_energy:forge:energy TO b TOP SIDE END """.stripIndent());
// set the labelsLabelPositionHolder.empty()
.add("a", helper.absolutePos(leftPos))
.add("b", helper.absolutePos(rightPos))
.save(manager.getDisk());
left.setEnergy(0, FloatingLong.create(1_000));
right.setEnergy(0, FloatingLong.create(1_000));
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
assertTrue(left.getEnergy(0).equals(FloatingLong.ZERO), "Contents did not depart");
assertTrue(right.getEnergy(0).equals(FloatingLong.create(2_000)), "Contents did not arrive");
});
}
@GameTest(template = "3x2x1")
publicstaticvoidmek_energy_full(GameTestHelperhelper) {
// designate positionsvarleftPos = newBlockPos(2, 2, 0);
varrightPos = newBlockPos(0, 2, 0);
varmanagerPos = newBlockPos(1, 2, 0);
// set up the worldhelper.setBlock(leftPos, MekanismBlocks.ULTIMATE_ENERGY_CUBE.getBlock());
varleft = ((TileEntityEnergyCube) helper.getBlockEntity(leftPos));
helper.setBlock(rightPos, MekanismBlocks.ULTIMATE_ENERGY_CUBE.getBlock());
varright = ((TileEntityEnergyCube) helper.getBlockEntity(rightPos));
helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
varmanager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));
// set up the programmanager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
manager.setProgram(""" EVERY 20 TICKS DO INPUT forge_energy:forge:energy FROM a NORTH SIDE OUTPUT forge_energy:forge:energy TO b TOP SIDE END """.stripIndent());
// set the labelsLabelPositionHolder.empty()
.add("a", helper.absolutePos(leftPos))
.add("b", helper.absolutePos(rightPos))
.save(manager.getDisk());
left.setEnergy(0, EnergyCubeTier.ULTIMATE.getMaxEnergy());
right.setEnergy(0, EnergyCubeTier.ULTIMATE.getMaxEnergy().subtract(1_000));
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
assertTrue(
left.getEnergy(0).equals(EnergyCubeTier.ULTIMATE.getMaxEnergy().subtract(1_000)),
"Contents did not depart"
);
assertTrue(right.getEnergy(0).equals(EnergyCubeTier.ULTIMATE.getMaxEnergy()), "Contents did not arrive");
});
}
@GameTest(template = "3x2x1")
publicstaticvoidmek_energy_one(GameTestHelperhelper) {
// designate positionsvarleftPos = newBlockPos(2, 2, 0);
varrightPos = newBlockPos(0, 2, 0);
varmanagerPos = newBlockPos(1, 2, 0);
// set up the worldhelper.setBlock(leftPos, MekanismBlocks.ULTIMATE_ENERGY_CUBE.getBlock());
varleft = ((TileEntityEnergyCube) helper.getBlockEntity(leftPos));
helper.setBlock(rightPos, MekanismBlocks.ULTIMATE_ENERGY_CUBE.getBlock());
varright = ((TileEntityEnergyCube) helper.getBlockEntity(rightPos));
helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
varmanager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));
// set up the programmanager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
manager.setProgram(""" EVERY 20 TICKS DO INPUT 1 forge_energy:forge:energy FROM a NORTH SIDE OUTPUT forge_energy:forge:energy TO b TOP SIDE END """.stripIndent());
// set the labelsLabelPositionHolder.empty()
.add("a", helper.absolutePos(leftPos))
.add("b", helper.absolutePos(rightPos))
.save(manager.getDisk());
left.setEnergy(0, FloatingLong.create(100));
right.setEnergy(0, FloatingLong.ZERO);
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
assertTrue(
left
.getEnergy(0)
.equals(FloatingLong
.create(100)
.subtract(UnitDisplayUtils.EnergyUnit.FORGE_ENERGY.convertFrom(1))),
"Contents did not depart"
);
assertTrue(
right.getEnergy(0).equals(UnitDisplayUtils.EnergyUnit.FORGE_ENERGY.convertFrom(1)),
"Contents did not arrive"
);
});
}
@GameTest(template = "25x3x25")
publicstaticvoidmany_lava_cauldrons(GameTestHelperhelper) {
// designate positionsvarsourceBlocks = newArrayList<BlockPos>();
vardestBlocks = newArrayList<BlockPos>();
varmanagerPos = newBlockPos(0, 2, 0);
// set up cauldronsfor (intx = 0; x < 25; x++) {
for (intz = 1; z < 25; z++) {
helper.setBlock(newBlockPos(x, 2, z), SFMBlocks.CABLE_BLOCK.get());
helper.setBlock(newBlockPos(x, 3, z), Blocks.LAVA_CAULDRON);
sourceBlocks.add(newBlockPos(x, 3, z));
}
}
// set up tanksfor (inti = 1; i < 25; i++) {
BlockPostankPos = newBlockPos(i, 2, 0);
helper.setBlock(tankPos, MekanismBlocks.BASIC_FLUID_TANK.getBlock());
destBlocks.add(tankPos);
}
// set up the managerhelper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
ManagerBlockEntitymanager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
manager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
// create the programvarprogram = """ NAME "many inventory lag test" EVERY 20 TICKS DO INPUT fluid:*:* FROM source OUTPUT fluid:*:* TO dest TOP SIDE END """;
// set the labelsLabelPositionHolder.empty()
.addAll("source", sourceBlocks.stream().map(helper::absolutePos).toList())
.addAll("dest", destBlocks.stream().map(helper::absolutePos).toList())
.save(manager.getDisk());
// load the programmanager.setProgram(program);
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
sourceBlocks.forEach(pos -> helper.assertBlock(
pos,
Blocks.CAULDRON::equals,
() -> "Cauldron did not empty"
));
intfound = destBlocks
.stream()
.map(helper::getBlockEntity)
.map(be -> be.getCapability(ForgeCapabilities.FLUID_HANDLER))
.map(x -> x.orElse(null))
.peek(Objects::requireNonNull)
.map(x -> x.getFluidInTank(0))
.mapToInt(FluidStack::getAmount)
.sum();
assertTrue(found == 1000 * 25 * 24, "Not all fluids were moved (found " + found + ")");
});
}
@GameTest(template = "3x4x3")
publicstaticvoidmulti_fluid(GameTestHelperhelper) {
vara1Pos = newBlockPos(2, 2, 1);
vara2Pos = newBlockPos(1, 2, 0);
varb1Pos = newBlockPos(1, 2, 2);
varb2Pos = newBlockPos(0, 2, 1);
varmanagerPos = newBlockPos(1, 2, 1);
helper.setBlock(a1Pos, MekanismBlocks.BASIC_FLUID_TANK.getBlock());
helper.setBlock(a2Pos, MekanismBlocks.BASIC_FLUID_TANK.getBlock());
helper.setBlock(b1Pos, MekanismBlocks.BASIC_FLUID_TANK.getBlock());
helper.setBlock(b2Pos, MekanismBlocks.BASIC_FLUID_TANK.getBlock());
vara1 = helper
.getBlockEntity(a1Pos)
.getCapability(ForgeCapabilities.FLUID_HANDLER, Direction.NORTH)
.orElse(null);
vara2 = helper
.getBlockEntity(a2Pos)
.getCapability(ForgeCapabilities.FLUID_HANDLER, Direction.NORTH)
.orElse(null);
varb1 = helper
.getBlockEntity(b1Pos)
.getCapability(ForgeCapabilities.FLUID_HANDLER, Direction.NORTH)
.orElse(null);
varb2 = helper
.getBlockEntity(b2Pos)
.getCapability(ForgeCapabilities.FLUID_HANDLER, Direction.NORTH)
.orElse(null);
a1.fill(newFluidStack(Fluids.WATER, 3000), IFluidHandler.FluidAction.EXECUTE);
a2.fill(newFluidStack(Fluids.LAVA, 3000), IFluidHandler.FluidAction.EXECUTE);
helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
varmanager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));
manager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
manager.setProgram(""" EVERY 20 TICKS DO INPUT fluid:: FROM a NORTH SIDE OUTPUT fluid::lava, fluid::water TO b TOP SIDE END """.stripIndent());
// set the labelsLabelPositionHolder.empty()
.add("a", helper.absolutePos(a1Pos))
.add("a", helper.absolutePos(a2Pos))
.add("b", helper.absolutePos(b1Pos))
.add("b", helper.absolutePos(b2Pos))
.save(manager.getDisk());
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
assertTrue(a1.getFluidInTank(0).isEmpty(), "a1 did not empty");
assertTrue(a2.getFluidInTank(0).isEmpty(), "a2 did not empty");
assertTrue(b1.getFluidInTank(0).getFluid() == Fluids.WATER, "b1 did not fill with water");
assertTrue(b2.getFluidInTank(0).getFluid() == Fluids.LAVA, "b2 did not fill with lava");
});
}
@GameTest(template = "25x3x25")
publicstaticvoidmek_induction(GameTestHelperhelper) {
// designate positionsvarmanagerPos = newBlockPos(1, 3, 0);
varpowerCubePos = newBlockPos(1, 2, 0);
varinductionBeginPos = newBlockPos(0, 2, 1);
varinductionInput = newBlockPos(1, 3, 1);
// set up induction matrixfor (intx = 0; x < 18; x++) {
for (intz = 0; z < 18; z++) {
for (inty = 0; y < 18; y++) {
//noinspection ExtractMethodRecommenderbooleanisOutside = x == 0 || x == 17 || z == 0 || z == 17 || y == 0 || y == 17;
Blockblock;
if (isOutside) {
block = MekanismBlocks.INDUCTION_CASING.getBlock();
} else {
if (y == 1) {
block = MekanismBlocks.ULTIMATE_INDUCTION_CELL.getBlock();
} else {
block = MekanismBlocks.ULTIMATE_INDUCTION_PROVIDER.getBlock();
}
}
helper.setBlock(inductionBeginPos.offset(x, y, z), block);
}
}
}
helper.setBlock(inductionInput, MekanismBlocks.INDUCTION_PORT.getBlock());
varinductionPort = (TileEntityInductionPort) helper.getBlockEntity(inductionInput);
// set up the energy sourcehelper.setBlock(powerCubePos, MekanismBlocks.CREATIVE_ENERGY_CUBE.getBlock());
TileEntityEnergyCubepowerCube = (TileEntityEnergyCube) helper.getBlockEntity(powerCubePos);
powerCube.setEnergy(0, EnergyCubeTier.CREATIVE.getMaxEnergy());
// set up the managerhelper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
ManagerBlockEntitymanager = (ManagerBlockEntity) helper.getBlockEntity(managerPos);
manager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
// create the programlongincr = 10_000_000_000L;
varstartingAmount = FloatingLong.create(0L);
varprogram = """ NAME "induction matrix test" EVERY 20 TICKS DO INPUT %d mekanism_energy:: FROM source NORTH SIDE OUTPUT mekanism_energy:: TO dest NORTH SIDE END """.formatted(incr);
// set the labelsLabelPositionHolder.empty()
.addAll("source", List.of(helper.absolutePos(powerCubePos)))
.addAll("dest", List.of(helper.absolutePos(inductionInput)))
.save(manager.getDisk());
// we can't prefill since we can't wait a delay AND use succeedIfManagerDidThing// pre-fill the matrix by a little bit// we want to make sure SFM doesn't have problems inserting beyond MAX_INT// var startingAmount = FloatingLong.create(Integer.MAX_VALUE + incr);// inductionPort.insertEnergy(startingAmount, Action.EXECUTE);// launch the programmanager.setProgram(program);
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
if (!inductionPort.getMultiblock().isFormed()) {
thrownewGameTestAssertException("Induction matrix did not form");
}
varexpected = startingAmount.add(incr);
FloatingLongenergy = inductionPort.getEnergy(0);
booleansuccess = energy.equals(expected);
assertTrue(
success,
"Expected energy did not match"
);
});
}
@GameTest(template = "3x2x1")
publicstaticvoidmek_cube(GameTestHelperhelper) {
SFMTestSpecspec = newSFMTestSpec()
.setProgram(""" EVERY 20 TICKS DO INPUT fe:: FROM a BOTTOM SIDE OUTPUT fe:: TO b TOP SIDE END """)
.addBlock(TestBlockDef.<TileEntityEnergyCube>of(
"a",
newBlockPos(1, 0, 0),
mekanism.common.registries.MekanismBlocks.BASIC_ENERGY_CUBE.getBlock(),
(tileEntityCube) -> {
tileEntityCube.setEnergy(0, SFMMekanismCompat.createForgeEnergy(1000));
SFMMekanismCompat.configureExclusiveIO(
tileEntityCube,
TransmissionType.ENERGY,
RelativeSide.BOTTOM,
DataType.OUTPUT
);
}
))
.addBlock(TestBlockDef.<TileEntityEnergyCube>of(
"b",
newBlockPos(-1, 0, 0),
mekanism.common.registries.MekanismBlocks.BASIC_ENERGY_CUBE.getBlock(),
(tileEntityCube) -> SFMMekanismCompat.configureExclusiveIO(
tileEntityCube,
TransmissionType.ENERGY,
RelativeSide.TOP,
DataType.INPUT
)
))
.preCondition("ONE a HAS EQ 1000 fe::")
.preCondition("ONE b HAS EQ 0 fe::")
.postCondition("ONE a HAS EQ 0 fe::")
.postCondition("ONE b HAS EQ 1000 fe::");
newSFMDeclarativeTestBuilder(helper, spec).run();
}
}
packageca.teamdman.sfm.gametest.compat.industrialforegoing;
importca.teamdman.sfm.SFM;
importca.teamdman.sfm.common.blockentity.ManagerBlockEntity;
importca.teamdman.sfm.common.program.LabelPositionHolder;
importca.teamdman.sfm.common.registry.SFMBlocks;
importca.teamdman.sfm.common.registry.SFMItems;
importca.teamdman.sfm.gametest.SFMGameTestBase;
importcom.buuz135.industrial.module.ModuleCore;
importcom.buuz135.industrial.module.ModuleTransportStorage;
importcom.buuz135.industrial.utils.BlockUtils;
importnet.minecraft.core.BlockPos;
importnet.minecraft.gametest.framework.GameTest;
importnet.minecraft.gametest.framework.GameTestHelper;
importnet.minecraft.world.item.ItemStack;
importnet.minecraft.world.item.Items;
importnet.minecraftforge.common.capabilities.ForgeCapabilities;
importnet.minecraftforge.gametest.GameTestHolder;
@SuppressWarnings({"DuplicatedCode", "DataFlowIssue"})
@GameTestHolder(SFM.MOD_ID)
publicclassSFMIndustrialForegoingCompatGameTestsextendsSFMGameTestBase {
@GameTest(template = "3x2x1")
publicstaticvoidindustrialforegoing_blackhole_empty(GameTestHelperhelper) {
// designate positionsvarleftPos = newBlockPos(2, 2, 0);
varrightPos = newBlockPos(0, 2, 0);
varmanagerPos = newBlockPos(1, 2, 0);
// set up the worldhelper.setBlock(leftPos, ModuleTransportStorage.BLACK_HOLE_UNIT_SUPREME.getLeft().get());
varleft = helper.getBlockEntity(leftPos).getCapability(ForgeCapabilities.ITEM_HANDLER).orElse(null);
helper.setBlock(rightPos, ModuleTransportStorage.BLACK_HOLE_UNIT_SUPREME.getLeft().get());
varright = helper.getBlockEntity(rightPos).getCapability(ForgeCapabilities.ITEM_HANDLER).orElse(null);
helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
varmanager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));
// set up the programmanager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
manager.setProgram(""" EVERY 20 TICKS DO INPUT FROM a NORTH SIDE OUTPUT TO b TOP SIDE END """.stripIndent());
// set the labelsLabelPositionHolder.empty()
.add("a", helper.absolutePos(leftPos))
.add("b", helper.absolutePos(rightPos))
.save(manager.getDisk());
intfullCount = BlockUtils.getStackAmountByRarity(ModuleCore.SUPREME_RARITY);
assertTrue(fullCount > 0, "expected full count to be greater than 0");
assertTrue(left.insertItem(0, newItemStack(Items.COAL, 64), false).isEmpty(), "couldn't prep left");
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
assertTrue(left.getStackInSlot(0).isEmpty(), "Contents did not depart properly");
assertTrue(right.getStackInSlot(0).getCount() == 64, "Contents did not arrive");
});
}
@GameTest(template = "3x2x1")
publicstaticvoidindustrialforegoing_blackhole_some(GameTestHelperhelper) {
// designate positionsvarleftPos = newBlockPos(2, 2, 0);
varrightPos = newBlockPos(0, 2, 0);
varmanagerPos = newBlockPos(1, 2, 0);
// set up the worldhelper.setBlock(leftPos, ModuleTransportStorage.BLACK_HOLE_UNIT_SUPREME.getLeft().get());
varleft = helper.getBlockEntity(leftPos).getCapability(ForgeCapabilities.ITEM_HANDLER).orElse(null);
helper.setBlock(rightPos, ModuleTransportStorage.BLACK_HOLE_UNIT_SUPREME.getLeft().get());
varright = helper.getBlockEntity(rightPos).getCapability(ForgeCapabilities.ITEM_HANDLER).orElse(null);
helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
varmanager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));
// set up the programmanager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
manager.setProgram(""" EVERY 20 TICKS DO INPUT FROM a NORTH SIDE OUTPUT TO b TOP SIDE END """.stripIndent());
// set the labelsLabelPositionHolder.empty()
.add("a", helper.absolutePos(leftPos))
.add("b", helper.absolutePos(rightPos))
.save(manager.getDisk());
// we need to insert a normal stack last for the rendering to work in IFassertTrue(left.insertItem(0, newItemStack(Items.COAL, 5000 - 64), false).isEmpty(), "couldn't prep left");
assertTrue(left.insertItem(0, newItemStack(Items.COAL, 64), false).isEmpty(), "couldn't prep left");
assertTrue(right.insertItem(0, newItemStack(Items.COAL, 5000 - 64), false).isEmpty(), "couldn't prep left");
assertTrue(right.insertItem(0, newItemStack(Items.COAL, 64), false).isEmpty(), "couldn't prep right");
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
assertTrue(left.getStackInSlot(0).getCount() == 5_000 - 64, "Contents did not depart properly");
assertTrue(right.getStackInSlot(0).getCount() == 5_000 + 64, "Contents did not arrive");
});
}
@GameTest(template = "3x2x1")
publicstaticvoidindustrialforegoing_blackhole_full(GameTestHelperhelper) {
// designate positionsvarleftPos = newBlockPos(2, 2, 0);
varrightPos = newBlockPos(0, 2, 0);
varmanagerPos = newBlockPos(1, 2, 0);
// set up the worldhelper.setBlock(leftPos, ModuleTransportStorage.BLACK_HOLE_UNIT_SUPREME.getLeft().get());
varleft = helper.getBlockEntity(leftPos).getCapability(ForgeCapabilities.ITEM_HANDLER).orElse(null);
helper.setBlock(rightPos, ModuleTransportStorage.BLACK_HOLE_UNIT_SUPREME.getLeft().get());
varright = helper.getBlockEntity(rightPos).getCapability(ForgeCapabilities.ITEM_HANDLER).orElse(null);
helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
varmanager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));
// set up the programmanager.setItem(0, newItemStack(SFMItems.DISK_ITEM.get()));
manager.setProgram(""" EVERY 20 TICKS DO INPUT FROM a NORTH SIDE OUTPUT TO b TOP SIDE END """.stripIndent());
// set the labelsLabelPositionHolder.empty()
.add("a", helper.absolutePos(leftPos))
.add("b", helper.absolutePos(rightPos))
.save(manager.getDisk());
intfullCount = BlockUtils.getStackAmountByRarity(ModuleCore.SUPREME_RARITY);
assertTrue(fullCount > 0, "expected full count to be greater than 0");
assertTrue(left.insertItem(0, newItemStack(Items.COAL, 64), false).isEmpty(), "couldn't prep left");
assertTrue(left.insertItem(0, newItemStack(Items.COAL, 1), false).isEmpty(), "couldn't prep left");
assertTrue(
right.insertItem(0, newItemStack(Items.COAL, fullCount - 2), false).isEmpty(),
"couldn't prep right"
);
assertTrue(right.insertItem(0, newItemStack(Items.COAL, 1), false).isEmpty(), "couldn't prep right");
succeedIfManagerDidThingWithoutLagging(helper, manager, () -> {
// black hole units have voiding=true by default// the final insertion operation that causes the thing to fill will cause the items that don't fit to be voided// this test should cause a stack of 64 to be used to insert even though only 1 is needed to finish fillingassertTrue(left.getStackInSlot(0).getCount() == 1, "Contents did not depart properly");
assertTrue(right.getStackInSlot(0).getCount() == fullCount, "Contents did not arrive");
});
}
}
grammarSFML;
@header {package ca.teamdman.langs;}
@lexer::members { public boolean INCLUDE_UNUSED = false; // we want syntax highlighting to not break on unexpected tokens}program : name? trigger* EOF;
name: NAME string ;
//// TRIGGERS//trigger : EVERY interval DO block END #TimerTrigger
| EVERYREDSTONEPULSEDO block END #PulseTrigger
;
interval: NUMBER? GLOBAL? (PLUSNUMBER)? (TICKS | TICK | SECONDS | SECOND) # IntervalSpace
| NUMBER_WITH_G_SUFFIX (PLUSNUMBER)? (TICKS | TICK | SECONDS | SECOND) # IntervalNoSpace;
//// BLOCK STATEMENT//block : statement* ;
statement : inputStatement
| outputStatement
| ifStatement
| forgetStatement
;
// IO STATEMENTforgetStatement : FORGET label? (COMMA label)* COMMA?;
inputStatement : INPUT inputResourceLimits? resourceExclusion? FROMEACH? labelAccess
| FROMEACH? labelAccess INPUT inputResourceLimits? resourceExclusion?
;
outputStatement : OUTPUT outputResourceLimits? resourceExclusion? TOEACH? labelAccess
| TOEACH? labelAccess OUTPUT outputResourceLimits? resourceExclusion?
;
inputResourceLimits : resourceLimitList; // separate for different defaultsoutputResourceLimits : resourceLimitList; // separate for different defaultsresourceLimitList : resourceLimit (COMMA resourceLimit)* COMMA?;
resourceLimit : limit? resourceIdDisjunction with?
| limit with?
| with
;
limit : quantity retention #QuantityRetentionLimit
| retention #RetentionLimit
| quantity #QuantityLimit
;
quantity : number EACH?;
retention : RETAIN number EACH?;
resourceExclusion : EXCEPT resourceIdList;
resourceId : (identifier) (COLON (identifier)? (COLON (identifier)? (COLON (identifier)?)?)?)? # Resource
| string # StringResource
;
resourceIdList : resourceId (COMMA resourceId)* COMMA?;
resourceIdDisjunction : resourceId (OR resourceId)* OR?;
with : WITH withClause
| WITHOUT withClause
;
withClause : LPAREN withClause RPAREN # WithParen
| NOT withClause # WithNegation
| withClause AND withClause # WithConjunction
| withClause OR withClause # WithDisjunction
| (TAGHASHTAG?|HASHTAG) tagMatcher # WithTag
;
tagMatcher : identifier COLON identifier (SLASH identifier)*
| identifier (SLASH identifier)*
;
sidequalifier : EACHSIDE #EachSide
| side(COMMA side)* SIDE #ListedSides
;
side : TOP
| BOTTOM
| NORTH
| EAST
| SOUTH
| WEST
;
slotqualifier : SLOTS rangeset;
rangeset : range (COMMA range)*;
range : number (DASH number)? ;
ifStatement : IF boolexpr THEN block (ELSEIF boolexpr THEN block)* (ELSE block)? END;
boolexpr : TRUE #BooleanTrue
| FALSE #BooleanFalse
| LPAREN boolexpr RPAREN #BooleanParen
| NOT boolexpr #BooleanNegation
| boolexpr AND boolexpr #BooleanConjunction
| boolexpr OR boolexpr #BooleanDisjunction
| setOp? labelAccess HAS comparisonOp number resourceIdDisjunction? with? (EXCEPT resourceIdList)? #BooleanHas
| REDSTONE (comparisonOp number)? #BooleanRedstone
;
comparisonOp : GT
| LT
| EQ
| LE
| GE
| GT_SYMBOL
| LT_SYMBOL
| EQ_SYMBOL
| LE_SYMBOL
| GE_SYMBOL
;
setOp : OVERALL
| SOME
| EVERY
| EACH
| ONE
| LONE
;
//// IO HELPERS//labelAccess : label (COMMA label)* roundrobin? sidequalifier? slotqualifier?;
roundrobin : ROUNDROBINBY (LABEL | BLOCK);
label : (identifier) #RawLabel
| string #StringLabel
;
identifier : (IDENTIFIER | REDSTONE | GLOBAL | SECOND | SECONDS) ;
// GENERALstring: STRING ;
number: NUMBER ;
//// LEXER//// IF STATEMENTIF : IF ;
THEN : THEN ;
ELSE : ELSE ;
HAS : HAS ;
OVERALL : OVERALL ;
SOME : SOME ;
ONE : ONE ;
LONE : LONE ;
// BOOLEAN LOGICTRUE : TRUE ;
FALSE : FALSE ;
NOT : NOT ;
AND : AND ;
OR : OR ;
// QUANTITY LOGICGT : GT ;
GT_SYMBOL : '>' ;
LT : LT ;
LT_SYMBOL : '<' ;
EQ : EQ ;
EQ_SYMBOL : '=' ;
LE : LE ;
LE_SYMBOL : '<=' ;
GE : GE ;
GE_SYMBOL : '>=' ;
// IO LOGICFROM : FROM ;
TO : TO ;
INPUT : INPUT ;
OUTPUT : OUTPUT ;
WHERE : WHERE ;
SLOTS : SLOTS ;
RETAIN : RETAIN ;
EACH : EACH ;
EXCEPT : EXCEPT ;
FORGET : FORGET ;
// WITH LOGICWITHOUT : WITHOUT;
WITH : WITH ;
TAG : TAG ;
HASHTAG : '#' ;
// ROUND ROBINROUND : ROUND ;
ROBIN : ROBIN ;
BY : BY ;
LABEL : LABEL ;
BLOCK : BLOCK ;
// SIDE LOGICTOP : TOP ;
BOTTOM : BOTTOM ;
NORTH : NORTH ;
EAST : EAST ;
SOUTH : SOUTH ;
WEST : WEST ;
SIDE : SIDE ;
// TIMER TRIGGERSTICKS : TICKS ;
TICK : TICK ;
SECONDS : SECONDS ;
SECOND : SECOND ;
GLOBAL : (GLOBAL) | G;
PLUS : '+' | PLUS;
// REDSTONE TRIGGERREDSTONE : REDSTONE ;
PULSE : PULSE;
// PROGRAM SYMBOLSDO : DO ;
END : END ;
NAME : NAME ;
// GENERAL SYMBOLS// used by triggers and as a set operatorEVERY : EVERY ;
COMMA : ',';
COLON : ':';
SLASH : '/';
DASH : '-';
LPAREN : '(';
RPAREN : ')';
NUMBER_WITH_G_SUFFIX : [0-9]+[gG] ;
NUMBER : [0-9]+ ;
IDENTIFIER : [a-zA-Z_*][a-zA-Z0-9_*]* | '*'; // Note that the * in the square brackets is a literlSTRING : '"' (~'"'|'\\"')* '"' ;
LINE_COMMENT : '--' ~[\r\n]* -> channel(HIDDEN);
//LINE_COMMENT : '--' ~[\r\n]* (EOF|'\r'? '\n');WS
: [ \r\t\n]+ -> channel(HIDDEN)
;
UNUSED
: {INCLUDE_UNUSED}? . -> channel(HIDDEN)
;
fragmentA :('a' | 'A') ;
fragmentB :('b' | 'B') ;
fragmentC :('c' | 'C') ;
fragmentD :('d' | 'D') ;
fragmentE :('e' | 'E') ;
fragmentF :('f' | 'F') ;
fragmentG :('g' | 'G') ;
fragmentH :('h' | 'H') ;
fragmentI :('i' | 'I') ;
fragmentJ :('j' | 'J') ;
fragmentK :('k' | 'K') ;
fragmentL :('l' | 'L') ;
fragmentM :('m' | 'M') ;
fragmentN :('n' | 'N') ;
fragmentO :('o' | 'O') ;
fragmentP :('p' | 'P') ;
fragmentQ :('q' | 'Q') ;
fragmentR :('r' | 'R') ;
fragmentS :('s' | 'S') ;
fragmentT :('t' | 'T') ;
fragmentU :('u' | 'U') ;
fragmentV :('v' | 'V') ;
fragmentW :('w' | 'W') ;
fragmentX :('x' | 'X') ;
fragmentY :('y' | 'Y') ;
fragmentZ :('z' | 'Z') ;
===
===
That is a summary of my Minecraft mod, Super Factory Manager.
I've included the transcripts of some videos I've made, the example programs, and the test code.
Please demonstrate to me your comprehension of the mod.