Skip to content

Commit bf49c92

Browse files
committed
Merge remote-tracking branch 'origin/master-1.20-lts' into master-1.21-lts
2 parents 33462db + e6db5f0 commit bf49c92

14 files changed

Lines changed: 1372 additions & 0 deletions

AGENTS.md

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
# AGENTS.md - Developer and AI Agent Guide
2+
3+
This document provides essential information for developers and AI agents working on this Minecraft mod's codebase.
4+
5+
## Project Overview
6+
7+
This is a Minecraft mod. See README.md for a description of what this mod does. The project is written in Java and uses Gradle as its build system.
8+
9+
## Multi-Loader Architecture
10+
11+
**This repo only uses a multi-loader architecture if you can find a `loader-common` directory in the root of this repo!**
12+
13+
In Minecraft 1.21 and above, this repository uses a **multi-loader setup**, meaning the mod is available on multiple Minecraft mod loaders.
14+
The Minecraft version can be found in `gradle.properties`.
15+
Understanding this architecture is crucial when making changes:
16+
17+
### Directory Structure
18+
19+
- **`loader-common/`**: Contains source code that is common across all mod loaders. Most shared functionality should be implemented here.
20+
- **`loader-fabric/`**: Fabric-specific implementation and integration code.
21+
- **`loader-forge/`**: Forge-specific implementation and integration code (for older Minecraft versions).
22+
- **`loader-neoforge/`**: NeoForge-specific implementation and integration code (for newer Minecraft versions).
23+
24+
### Making Changes in Multi-loader Setups
25+
26+
When adding features or fixing bugs:
27+
1. Place shared logic in `loader-common/` whenever possible
28+
2. Only add loader-specific code to the respective `loader-*` directories when platform-specific APIs are required
29+
3. Ensure your changes work across all supported loaders
30+
31+
## Testing
32+
33+
This mod uses two types of tests:
34+
35+
### 1. Unit Tests
36+
37+
**Location**: `src/test/java/`
38+
39+
Traditional JUnit tests for testing isolated functionality without requiring a full Minecraft instance.
40+
41+
**Running unit tests**:
42+
```bash
43+
./gradlew test
44+
```
45+
46+
Unit tests are automatically executed when running the `build` command.
47+
48+
### 2. Game Tests
49+
50+
**Location**: Within normal sources, typically in the `org/cyclops/*/gametest` package (e.g., `loader-common/src/main/java/org/cyclops/cyclopscore/gametest/`)
51+
52+
Game tests run an actual Minecraft instance to test code with real game logic. These are essential for testing features that interact with Minecraft's gameplay systems.
53+
Game tests only exist in Minecraft 1.21 and higher.
54+
55+
**Running game tests**:
56+
```bash
57+
./gradlew runGameTestServer
58+
```
59+
60+
**Important**:
61+
- Game tests are **NOT** run automatically during the build process
62+
- For Minecraft 1.21 and above, game tests must be run manually before committing
63+
- Game tests must pass before finalizing your changes
64+
65+
### When to Add Tests
66+
67+
When adding new features or fixing bugs:
68+
- **Always** add unit tests when possible for isolated logic
69+
- **Always** add game tests when the feature interacts with Minecraft gameplay systems
70+
- Look at existing tests in the respective directories for examples of test patterns and conventions
71+
72+
## Building the Project
73+
74+
### Prerequisites
75+
76+
- Java version is specified in `gradle.properties` (otherwise, default to version 17)
77+
- Gradle (use the provided wrapper: `./gradlew`)
78+
79+
### Build Command
80+
81+
Before every commit, ensure the project builds successfully:
82+
83+
```bash
84+
./gradlew build
85+
```
86+
87+
This command will:
88+
- Compile all source code
89+
- Run unit tests automatically
90+
- Generate build artifacts
91+
92+
### Full Pre-Commit Validation
93+
94+
Run build:
95+
96+
```bash
97+
./gradlew build
98+
```
99+
100+
Only for Minecraft 1.21 and above, also run game tests:
101+
```bash
102+
./gradlew runGameTestServer
103+
```
104+
105+
Both must pass before committing changes.
106+
107+
## Code Formatting
108+
109+
This project uses Spotless for code formatting:
110+
111+
```bash
112+
./gradlew spotlessApply
113+
```
114+
115+
The pre-commit script in `scripts/pre-commit` automatically formats staged files. Consider setting it up as a Git hook:
116+
117+
```bash
118+
ln -s ../../scripts/pre-commit .git/hooks/pre-commit
119+
```
120+
121+
## Development Workflow
122+
123+
1. **Understand the change**: Read the issue/feature request thoroughly
124+
2. **Explore the codebase**: Use tools like `grep` to find relevant code
125+
3. **Make minimal changes**: Focus on the specific issue/feature
126+
4. **Add tests**: Write unit tests and/or game tests as appropriate
127+
5. **Build and test**:
128+
```bash
129+
./gradlew build
130+
./gradlew runGameTestServer # For MC 1.21+
131+
```
132+
6. **Format code**: `./gradlew spotlessApply`
133+
7. **Commit**: Use clear, descriptive commit messages
134+
135+
## Project Dependencies
136+
137+
## Release Management
138+
139+
Version bumping and release management helper scripts are available in the [CyclopsMC/ReleaseHelpers](https://github.com/CyclopsMC/ReleaseHelpers) repository. These bash scripts assist with:
140+
- Version bumping across all loaders
141+
- Changelog management
142+
- Release preparation
143+
144+
## Gradle Tasks Reference
145+
146+
Common Gradle tasks for development:
147+
148+
| Task | Purpose |
149+
|------|---------|
150+
| `./gradlew build` | Build the project and run unit tests |
151+
| `./gradlew test` | Run unit tests only |
152+
| `./gradlew runGameTestServer` | Run game tests (manual, required for MC 1.21+) |
153+
| `./gradlew spotlessApply` | Format code according to project standards |
154+
| `./gradlew publishToMavenLocal` | Publish to local Maven for testing in other projects |
155+
156+
If for any reason gradle fails due to internet connection issues, try running offline instead by running the gradle command with the `--offline` flag.
157+
158+
## CI/CD
159+
160+
GitHub Actions automatically:
161+
- Builds the project on every push and pull request
162+
- Runs unit tests
163+
- Runs game tests (including `runGameTestServer`)
164+
- Generates coverage reports
165+
- Deploys to CurseForge, Modrinth, and Maven on appropriate branches/tags
166+
167+
See `.github/workflows/ci.yml` for the full CI configuration.
168+
169+
## Additional Resources
170+
171+
- **README.md**: Project overview and usage information
172+
- **CONTRIBUTING.md**: Contribution guidelines and issue reporting
173+
- **Build configuration**: `build.gradle` and loader-specific build files
174+
- **Project properties**: `gradle.properties` (Minecraft version, mod version, etc.)
175+
176+
## Key Principles
177+
178+
1. **Minimal changes**: Make the smallest possible changes to achieve your goal
179+
2. **Test coverage**: Always try to add tests for new features and bug fixes (may be difficult or impossible for things related to GUIs)
180+
3. **Multi-loader compatibility**: Ensure changes work across all supported loaders
181+
4. **Build validation**: Never commit without running `build` (and `runGameTestServer` for MC 1.21+)
182+
5. **Code quality**: Follow existing patterns and conventions in the codebase

loader-common/src/main/java/org/cyclops/cyclopscore/client/gui/component/input/WidgetNumberField.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ public int getMinValue() {
5959
*/
6060
public void setMinValue(int minValue) {
6161
this.minValue = minValue;
62+
try {
63+
if (this.minValue > Integer.parseInt(getValue())) {
64+
setValue(Integer.toString(this.minValue));
65+
}
66+
} catch (NumberFormatException e) {
67+
setValue(Integer.toString(this.minValue));
68+
}
6269
updateArrowsState();
6370
}
6471

loader-neoforge/src/main/java/org/cyclops/cyclopscore/nbt/path/NbtPath.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,17 @@ public class NbtPath {
2121
new NbtPathExpressionParseHandlerListElement(),
2222
new NbtPathExpressionParseHandlerListSlice(),
2323
new NbtPathExpressionParseHandlerUnion(),
24+
new NbtPathExpressionParseHandlerGrouping(),
2425
new NbtPathExpressionParseHandlerBooleanRelationalLessThan(),
2526
new NbtPathExpressionParseHandlerBooleanRelationalLessThanOrEqual(),
2627
new NbtPathExpressionParseHandlerBooleanRelationalGreaterThan(),
2728
new NbtPathExpressionParseHandlerBooleanRelationalGreaterThanOrEqual(),
29+
new NbtPathExpressionParseHandlerBooleanRelationalNotEqual(),
2830
new NbtPathExpressionParseHandlerBooleanRelationalEqual(),
2931
new NbtPathExpressionParseHandlerStringEqual(),
32+
new NbtPathExpressionParseHandlerBooleanLogicalAnd(),
33+
new NbtPathExpressionParseHandlerBooleanLogicalOr(),
34+
new NbtPathExpressionParseHandlerBooleanLogicalNot(),
3035
new NbtPathExpressionParseHandlerFilterExpression()
3136
);
3237

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package org.cyclops.cyclopscore.nbt.path.parse;
2+
3+
import net.minecraft.nbt.ByteTag;
4+
import net.minecraft.nbt.Tag;
5+
import org.cyclops.cyclopscore.nbt.path.INbtPathExpression;
6+
import org.cyclops.cyclopscore.nbt.path.NbtParseException;
7+
import org.cyclops.cyclopscore.nbt.path.NbtPath;
8+
import org.cyclops.cyclopscore.nbt.path.NbtPathExpressionMatches;
9+
10+
import javax.annotation.Nullable;
11+
import java.util.function.Supplier;
12+
import java.util.regex.Matcher;
13+
import java.util.regex.Pattern;
14+
import java.util.stream.Stream;
15+
16+
/**
17+
* @author rubensworks
18+
*/
19+
public abstract class NbtPathExpressionParseHandlerBooleanLogicalAdapter implements INbtPathExpressionParseHandler {
20+
21+
private final Pattern regex;
22+
23+
protected NbtPathExpressionParseHandlerBooleanLogicalAdapter(String relation) {
24+
this.regex = Pattern.compile("^ *" + relation + " *");
25+
}
26+
27+
protected abstract boolean getLogicalValue(boolean left, Supplier<Boolean> right);
28+
29+
/**
30+
* Determine if a tag is truthy.
31+
* ByteTag with value 1 is true, 0 is false.
32+
* Any other non-null tag is considered true.
33+
* This follows the same logic as {@link org.cyclops.cyclopscore.nbt.path.INbtPathExpression#test(Tag)}.
34+
*
35+
* @param tag The tag to check
36+
* @return true if the tag is truthy, false otherwise
37+
*/
38+
public static boolean isTruthy(Tag tag) {
39+
if (tag == null) {
40+
return false;
41+
}
42+
if (tag.getId() == Tag.TAG_BYTE) {
43+
return ((ByteTag) tag).getAsByte() == (byte) 1;
44+
}
45+
// Non-null non-ByteTags are truthy
46+
return true;
47+
}
48+
49+
/**
50+
* Find the end position of an expression, stopping at logical operators or closing parenthesis.
51+
* This method is shared by logical operator handlers to identify expression boundaries.
52+
*
53+
* @param expression The full expression string
54+
* @param start The starting position to search from
55+
* @return The position where the expression ends
56+
*/
57+
public static int findExpressionEnd(String expression, int start) {
58+
int depth = 0;
59+
for (int i = start; i < expression.length(); i++) {
60+
char c = expression.charAt(i);
61+
62+
if (c == '(') {
63+
depth++;
64+
} else if (c == ')') {
65+
if (depth == 0) {
66+
return i;
67+
}
68+
depth--;
69+
} else if (depth == 0) {
70+
// Check for logical operators at top level
71+
if (i + 1 < expression.length()) {
72+
String twoChar = expression.substring(i, i + 2);
73+
if (twoChar.equals("&&") || twoChar.equals("||")) {
74+
return i;
75+
}
76+
}
77+
// Check for NOT operator (but not != which is handled differently)
78+
if (c == '!' && (i + 1 >= expression.length() || expression.charAt(i + 1) != '=')) {
79+
return i;
80+
}
81+
}
82+
}
83+
return expression.length();
84+
}
85+
86+
@Nullable
87+
@Override
88+
public HandleResult handlePrefixOf(String nbtPathExpression, int pos) {
89+
Matcher matcher = this.regex
90+
.matcher(nbtPathExpression)
91+
.region(pos, nbtPathExpression.length());
92+
if (!matcher.find()) {
93+
return HandleResult.INVALID;
94+
}
95+
96+
// Parse the right-hand side expression
97+
int rightPos = pos + matcher.group().length();
98+
if (rightPos >= nbtPathExpression.length()) {
99+
return HandleResult.INVALID;
100+
}
101+
102+
// Find the end of the right expression
103+
int endPos = findExpressionEnd(nbtPathExpression, rightPos);
104+
if (endPos == rightPos) {
105+
return HandleResult.INVALID;
106+
}
107+
108+
String rightExpressionString = nbtPathExpression.substring(rightPos, endPos);
109+
try {
110+
INbtPathExpression rightExpression = NbtPath.parse(rightExpressionString.trim());
111+
return new HandleResult(new Expression(rightExpression, this),
112+
matcher.group().length() + rightExpressionString.length());
113+
} catch (NbtParseException e) {
114+
return HandleResult.INVALID;
115+
}
116+
}
117+
118+
public static class Expression implements INbtPathExpression {
119+
120+
protected final INbtPathExpression expression;
121+
protected final NbtPathExpressionParseHandlerBooleanLogicalAdapter handler;
122+
123+
public Expression(INbtPathExpression expression, NbtPathExpressionParseHandlerBooleanLogicalAdapter handler) {
124+
this.expression = expression;
125+
this.handler = handler;
126+
}
127+
128+
@Override
129+
public NbtPathExpressionMatches matchContexts(Stream<NbtPathExpressionExecutionContext> executionContexts) {
130+
return new NbtPathExpressionMatches(executionContexts
131+
.map(executionContext -> {
132+
Tag currentTag = executionContext.getCurrentTag();
133+
134+
// The left side is the current tag (should be a boolean result from previous expression)
135+
boolean leftValue = isTruthy(currentTag);
136+
137+
// Evaluate the right expression against the root context
138+
// This ensures both sides of the expression are evaluated against the same base context
139+
Tag rootTag = executionContext.getRootContext().getCurrentTag();
140+
141+
// AND operation
142+
boolean result = handler.getLogicalValue(leftValue, () -> expression.test(rootTag));
143+
144+
return new NbtPathExpressionExecutionContext(ByteTag.valueOf(result), executionContext);
145+
})
146+
);
147+
}
148+
149+
}
150+
151+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.cyclops.cyclopscore.nbt.path.parse;
2+
3+
import java.util.function.Supplier;
4+
5+
/**
6+
* A handler that handles boolean AND expressions in the form of "expression1 {@literal &&} expression2".
7+
*/
8+
public class NbtPathExpressionParseHandlerBooleanLogicalAnd extends NbtPathExpressionParseHandlerBooleanLogicalAdapter {
9+
10+
public NbtPathExpressionParseHandlerBooleanLogicalAnd() {
11+
super("&&");
12+
}
13+
14+
@Override
15+
protected boolean getLogicalValue(boolean left, Supplier<Boolean> right) {
16+
return left && right.get();
17+
}
18+
}

0 commit comments

Comments
 (0)