Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .run/Microbot Debug.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Microbot Debug" type="Remote">
<module name="Microbot-Hub.PestControlPlugin" />
<option name="USE_SOCKET_TRANSPORT" value="true" />
<option name="SERVER_MODE" value="false" />
<option name="SHMEM_ADDRESS" />
<option name="HOST" value="localhost" />
<option name="PORT" value="5005" />
<option name="AUTO_RESTART" value="false" />
<RunnerSettings RunnerId="Debug">
<option name="DEBUG_PORT" value="5005" />
<option name="LOCAL" value="false" />
</RunnerSettings>
<method v="2" />
</configuration>
</component>
230 changes: 217 additions & 13 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,223 @@
# Repository Guidelines
# agents.md

## Project Structure & Module Organization
Gradle drives builds through `build.gradle` plus helper scripts in `gradle/`. Plugin sources reside under `src/main/java/net/runelite/client/plugins/microbot/<PluginName>` with matching resources in `src/main/resources/<PluginName>`. Each plugin should also keep documentation inside `src/main/resources/<PluginName>/docs/` (README, optional `assets/`, and `dependencies.txt`). Shared web assets live in `public/`, while IDE/debug helpers and tests sit in `src/test/java`, notably `net.runelite.client.Microbot` for RuneLiteDebug sessions.
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Build, Test, and Development Commands
Use `./gradlew clean build` for a full compile, shading, and plugin detection report. During iteration, limit the scope via `./gradlew build -PpluginList=DailyTasksPlugin`. Execute unit tests with `./gradlew test`, and launch the client for manual verification through `./gradlew run --args='--debug'`.
## Overview

## Coding Style & Naming Conventions
Java code uses four-space indentation, Lombok annotations where they reduce boilerplate, and `PluginDescriptor` metadata referencing constants from `PluginConstants`. Keep plugin versions in a `static final String version = "x.y.z"` field. Packages remain lowercase, while class names mirror plugin folders in UpperCamelCase (e.g., `PestControlPlugin`). Runtime assets stay under each plugin’s resources subtree, and documentation should reuse existing tag constants.
Microbot Hub is a community plugin repository for the Microbot RuneLite client. It maintains a separation between core client functionality and community-contributed plugins, allowing rapid plugin development without affecting client stability. Each plugin is independently built, versioned, and packaged for GitHub Releases.

## Testing Guidelines
Place unit tests under `src/test/java` (or `src/test/generated_tests` for generated fixtures) with a `*Test` suffix. Tests should exercise automation logic headlessly, using deterministic mocks instead of live client calls. Add your plugin to `RuneLiteDebug.pluginsToDebug` in `src/test/java/net/runelite/client/Microbot.java` when interactive checks are necessary.
## Build System Architecture

## Commit & Pull Request Guidelines
Follow the conventional subject style, e.g., `feat: add PestControlPlugin (#241)`. Keep subjects under ~72 characters and mention the plugin or subsystem touched. Pull requests must link related issues, summarize behavior changes, list the commands used for verification (`./gradlew clean build`, `./gradlew test`, etc.), and include screenshots or GIFs for UI overlays. Limit each PR to a single plugin or feature and call out dependency or configuration updates explicitly.
The build system uses **Gradle with custom plugin discovery and packaging**:

## Security & Configuration Tips
Never commit credentials; load secrets from local `gradle.properties`. Prefer released dependency versions and justify additions inside each plugin’s `docs/README.md`. Use `PluginConstants.DEFAULT_PREFIX` and `DEFAULT_ENABLED`, and confirm `minClientVersion` against the Microbot client version logged during builds.
- **Dynamic Plugin Discovery**: `build.gradle` scans `src/main/java/net/runelite/client/plugins/microbot/` for directories containing `*Plugin.java` files
- **Per-Plugin Source Sets**: Each discovered plugin gets its own Gradle source set, compile task, and shadow JAR task
- **Gradle Helper Scripts**: Core build logic lives in:
- `gradle/project-config.gradle` - centralized configuration (JDK version, paths, GitHub release URLs, client version)
- `gradle/plugin-utils.gradle` - plugin discovery, descriptor parsing, JAR creation, SHA256 hashing

### Build Commands

```bash
# Build all plugins
./gradlew clean build

# Build specific plugin(s) only (much faster for iteration)
./gradlew build -PpluginList=PestControlPlugin
./gradlew build -PpluginList=PestControlPlugin,AutoMiningPlugin

# Run tests (tests have access to all plugin source sets)
./gradlew test

# Generate plugins.json metadata file with SHA256 hashes (requires exact JDK 11)
./gradlew generatePluginsJson

# Copy plugin documentation to public/docs/
./gradlew copyPluginDocs

# Launch RuneLite debug session with plugins from Microbot.java
./gradlew run --args='--debug'

# Validate JDK version
./gradlew validateJdkVersion
```

## Plugin Structure

Each plugin lives in its own package under `src/main/java/net/runelite/client/plugins/microbot/<pluginname>/`:

```
<pluginname>/
├── <PluginName>Plugin.java # Main plugin class with @PluginDescriptor
├── <PluginName>Script.java # Script logic extending Script class
├── <PluginName>Config.java # Configuration interface (optional)
├── <PluginName>Overlay.java # UI overlay (optional)
└── Additional support classes
```

Matching resources under `src/main/resources/net/runelite/client/plugins/microbot/<pluginname>/`:

```
<pluginname>/
├── dependencies.txt # Maven coordinates (optional)
└── docs/
├── README.md # Plugin documentation
└── assets/ # Screenshots, icons, etc.
```

## Plugin Descriptor Anatomy

Every plugin **must** have a `@PluginDescriptor` annotation with these **required** fields:

- `name` - Display name (use `PluginConstants.DEFAULT_PREFIX` or create custom prefix)
- `version` - Semantic version string (store in `static final String version` field)
- `minClientVersion` - Minimum Microbot client version required

Important **optional** fields:

- `authors` - Array of author names
- `description` - Brief description shown in plugin panel
- `tags` - Array of tags for categorization
- `iconUrl` - URL to icon image (shown in client hub)
- `cardUrl` - URL to card image (shown on website)
- `enabledByDefault` - Use `PluginConstants.DEFAULT_ENABLED` (currently `false`)
- `isExternal` - Use `PluginConstants.IS_EXTERNAL` (currently `true`)

Example:
```java
@PluginDescriptor(
name = PluginConstants.MOCROSOFT + "Pest Control",
description = "Supports all boats, portals, and shields.",
tags = {"pest control", "minigames"},
authors = { "Mocrosoft" },
version = PestControlPlugin.version,
minClientVersion = "1.9.6",
iconUrl = "https://chsami.github.io/Microbot-Hub/PestControlPlugin/assets/icon.png",
cardUrl = "https://chsami.github.io/Microbot-Hub/PestControlPlugin/assets/card.png",
enabledByDefault = PluginConstants.DEFAULT_ENABLED,
isExternal = PluginConstants.IS_EXTERNAL
)
@Slf4j
public class PestControlPlugin extends Plugin {
static final String version = "2.2.7";
// ...
}
```

## PluginConstants

The `PluginConstants.java` file is **shared across all plugins** (included in each JAR during build). It contains:

- Standardized plugin name prefixes (e.g., `DEFAULT_PREFIX`, `MOCROSOFT`, `BOLADO`)
- Global defaults: `DEFAULT_ENABLED = false`, `IS_EXTERNAL = true`

When creating a new plugin prefix, add it to `PluginConstants.java` for consistency.

## Adding External Dependencies

If a plugin needs additional libraries beyond the Microbot client:

1. Create `src/main/resources/net/runelite/client/plugins/microbot/<pluginname>/dependencies.txt`
2. Add Maven coordinates, one per line:
```
com.google.guava:guava:33.2.0-jre
org.apache.commons:commons-lang3:3.14.0
```
3. The build system automatically includes these in the plugin's shadow JAR

## Testing and Debugging Plugins

### Running Plugins in Debug Mode

1. Edit `src/test/java/net/runelite/client/Microbot.java`
2. Add your plugin class to the `debugPlugins` array:
```java
private static final Class<?>[] debugPlugins = {
YourPlugin.class,
AutoLoginPlugin.class
};
```
3. Run `./gradlew run --args='--debug'` or use your IDE's run configuration

### Running Tests

- Tests live in `src/test/java/`
- Test classes have access to all plugin source sets (configured in `build.gradle`)
- Use `./gradlew test` to run all tests

## Version Management

- **Always increment the plugin version** when making changes (even small fixes)
- Store version in a static field: `static final String version = "1.2.3";`
- Follow semantic versioning: `MAJOR.MINOR.PATCH`
- The version is used for JAR naming, GitHub release assets, and `plugins.json` generation

## Git Workflow

Based on recent commits:

- Use conventional commit prefixes: `fix:`, `feat:`, `docs:`, etc.
- Include PR references when applicable: `fix: description (#123)`
- Work on feature branches, merge to `development`, create PRs to `main`
- Current branch: `development`, main branch: `main`

## Publishing Workflow

1. Build plugins: `./gradlew build`
2. Generate metadata: `./gradlew generatePluginsJson` (requires JDK 11 exactly)
3. Copy documentation: `./gradlew copyPluginDocs`
4. Upload `build/libs/<pluginname>-<version>.jar` and updated `public/docs/plugins.json` as assets on the GitHub release tagged with `<version>` (or `latest-release` for the stable tag): `https://github.com/chsami/Microbot-Hub/releases/download/<tag>/<pluginname>-<version>.jar`

## Important Implementation Details

- **Java Version**: JDK 11 (configured in `project-config.gradle` with `TARGET_JDK_VERSION = 11`, vendor `ADOPTIUM`)
- **Microbot Client Dependency**: Defaults to the latest version resolved via `https://microbot.cloud/api/version/client`, falling back to `2.0.61` if lookup fails. Artifacts come from GitHub Releases (`https://github.com/chsami/Microbot/releases/download/<version>/microbot-<version>.jar`). Override with `-PmicrobotClientVersion=<version>` or `-PmicrobotClientVersion=latest`, or supply a local JAR for offline work via `-PmicrobotClientPath=/absolute/path/to/microbot-<version>.jar`
- **Plugin Release Tag**: `plugins.json` uses a stable release tag (`latest-release`) so download URLs stay constant: `https://github.com/chsami/Microbot-Hub/releases/download/latest-release/<plugin>-<version>.jar`. Override with `-PpluginsReleaseTag=<tag>` if needed.
- **Shadow JAR Excludes**: Common exclusions defined in `plugin-utils.gradle` include `docs/**`, `dependencies.txt`, metadata files, and module-info
- **Reproducible Builds**: JAR tasks disable file timestamps, use reproducible file order, and normalize file permissions to `0644`
- **Descriptor Parsing**: Build system uses regex to extract plugin metadata from Java source files (see `getPluginDescriptorInfo` in `plugin-utils.gradle`)

## Plugin Discovery Logic

When you run `./gradlew build`:

1. Scans `src/main/java/net/runelite/client/plugins/microbot/` for directories
2. Finds directories containing a file matching `*Plugin.java`
3. Creates a plugin object with: `name` (class name without .java), `sourceSetName` (directory name), `dir`, `javaFile`
4. Filters by `-PpluginList` if provided
5. For each plugin:
- Creates dedicated source set
- Configures compilation classpath with Microbot client
- Creates shadow JAR task with plugin-specific dependencies
- Parses `@PluginDescriptor` for metadata
- Computes SHA256 hash of JAR for `plugins.json`

## Common Patterns

- Plugins extending `SchedulablePlugin` implement `getStartCondition()` and `getStopCondition()` for scheduler integration
- Use `@Inject` for dependency injection (configs, overlays, scripts)
- Config classes use `@Provides` methods to register with `ConfigManager`
- Overlays are registered in `startUp()`, unregistered in `shutDown()`
- Use `@Subscribe` for event handling (ChatMessage, GameTick, etc.)

## Threading

Scripts run on a scheduled executor thread, but certain RuneLite API calls (widgets, game objects, etc.) must run on the client thread:

```java
// Use invoke() for client thread operations
TrialInfo info = Microbot.getClientThread().invoke(() -> TrialInfo.getCurrent(client));

// For void operations
Microbot.getClientThread().invoke(() -> {
// client thread code here
});
```

**Always use `Microbot.getClientThread().invoke()`** when accessing:
- Widgets (`client.getWidget()`, `widget.isHidden()`)
- Game objects that aren't cached
- Player world view (`client.getLocalPlayer().getWorldView()`)
- `BoatLocation.fromLocal()` - accesses player world view internally
- `TrialInfo.getCurrent()` - accesses widgets internally
- Any RuneLite API that throws "must be called on client thread"
71 changes: 71 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,74 @@ When you run `./gradlew build`:
- Config classes use `@Provides` methods to register with `ConfigManager`
- Overlays are registered in `startUp()`, unregistered in `shutDown()`
- Use `@Subscribe` for event handling (ChatMessage, GameTick, etc.)

## Threading

Scripts run on a scheduled executor thread, but certain RuneLite API calls (widgets, game objects, etc.) must run on the client thread:

```java
// Use invoke() for client thread operations
TrialInfo info = Microbot.getClientThread().invoke(() -> TrialInfo.getCurrent(client));

// For void operations
Microbot.getClientThread().invoke(() -> {
// client thread code here
});
```

**Always use `Microbot.getClientThread().invoke()`** when accessing:
- Widgets (`client.getWidget()`, `widget.isHidden()`)
- Game objects that aren't cached
- Player world view (`client.getLocalPlayer().getWorldView()`)
- Varbits (`client.getVarbitValue()`)
- `BoatLocation.fromLocal()` - accesses player world view internally
- `TrialInfo.getCurrent()` - accesses widgets internally
- `Rs2BoatCache.getLocalBoat()` - accesses player world view
- `Rs2BoatModel.isNavigating()` - accesses varbits
- `Rs2BoatModel.isMovingForward()` - accesses varbits
- `Rs2BoatModel.getHeading()` - accesses varbits
- Any RuneLite API that throws "must be called on client thread"

## Event Subscription from Scripts

**Important:** `@Subscribe` event handlers (`onGameTick`, `onClientTick`, `onVarbitChanged`, `onGameObjectSpawned`, etc.) **run on the client thread automatically**. You do NOT need to wrap client API calls in `Microbot.getClientThread().invoke()` inside these handlers.

Scripts can subscribe to RuneLite events by injecting the EventBus:

```java
@Slf4j
public class MyScript {
private final EventBus eventBus;

@Inject
public MyScript(EventBus eventBus) {
this.eventBus = eventBus;
}

public void register() {
eventBus.register(this);
}

public void unregister() {
eventBus.unregister(this);
}

@Subscribe
public void onGameTick(GameTick event) {
// Handle game tick
}
}
```

In the plugin, call `register()` in `startUp()` and `unregister()` in `shutDown()`:

```java
@Override
protected void startUp() {
myScript.register();
}

protected void shutDown() {
myScript.unregister();
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public Dimension render(Graphics2D graphics) {
.build());

panelComponent.getChildren().add(LineComponent.builder()
.left("Bait: " + (Rs2Inventory.hasItem("fish chunks") ? String.valueOf(Rs2Inventory.get("fish chunks").getQuantity()) : "Not Present"))
.left("Bait: " + (Rs2Inventory.hasItem("fish chunks", "fish offcuts") ? String.valueOf(Rs2Inventory.get("fish chunks").getQuantity()) : "Not Present"))
.build());

panelComponent.getChildren().add(LineComponent.builder().build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
isExternal = PluginConstants.IS_EXTERNAL
)
public class AerialFishingPlugin extends Plugin {
public static final String version = "1.1.1";
public static final String version = "1.1.2";
@Inject
private Client client;
@Inject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public boolean run(AerialFishingConfig config) {
Rs2AntibanSettings.microBreakDurationLow = 1;
Rs2AntibanSettings.microBreakDurationHigh = 5;
mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> {
if (!super.run() || !Microbot.isLoggedIn() || !Rs2Inventory.hasItem("fish chunks", "king worm") || (!Rs2Equipment.isWearing(ItemID.AERIAL_FISHING_GLOVES_NO_BIRD) && !Rs2Equipment.isWearing(ItemID.AERIAL_FISHING_GLOVES_BIRD))) {
if (!super.run() || !Microbot.isLoggedIn() || !Rs2Inventory.hasItem("fish chunks", "king worm", "fish offcuts") || (!Rs2Equipment.isWearing(ItemID.AERIAL_FISHING_GLOVES_NO_BIRD) && !Rs2Equipment.isWearing(ItemID.AERIAL_FISHING_GLOVES_BIRD))) {
return;
}

Expand Down
Loading