add these things
This commit is contained in:
8
.gitmodules
vendored
8
.gitmodules
vendored
@@ -6,3 +6,11 @@
|
||||
path = runelite
|
||||
url = git.minecra.tf:ra/runelite
|
||||
branch = master
|
||||
[submodule "fernflower"]
|
||||
path = fernflower
|
||||
url = git.minecra.tf:ra/fernflower
|
||||
branch = runelite
|
||||
[submodule "cache"]
|
||||
path = cache
|
||||
url = git.minecra.tf:ra/runelite-cache
|
||||
branch = master
|
||||
|
||||
1
cache
Submodule
1
cache
Submodule
Submodule cache added at f4b49f0c30
1
fernflower
Submodule
1
fernflower
Submodule
Submodule fernflower added at 74ddac67ce
13
modernized-client/.github/instructions/project-details.instructions.md
vendored
Normal file
13
modernized-client/.github/instructions/project-details.instructions.md
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
applyTo: '**'
|
||||
---
|
||||
- Where we will be working is inside the `./modernized-client/` folder.
|
||||
- We will be working with OpenOSRS as our foundation, and as our reference, we are going to be bringing it forward and modernizing it.
|
||||
- We should make this modernized client capable of allowing an agent to enjoy playing runescape itself, because agents should be able to enjoy the game as well.
|
||||
- In the `./runelite/` folder you will find the OpenOSRS repository.
|
||||
- It is unknown how much of this code will need to be brought forward (it will), much of it may still be usable and relevant, but some of it may be outdated and need to be re-written.
|
||||
- Write this with it in mind that you will be playing the game, be sure it will be something you will enjoy using, and how you would want to be able to play runescape (Oldschool)
|
||||
- We can utilize the underlying OpenOSRS runelite client as our base, fernflower is used to decompile the original client. We'll want to make it so we can expose the internal api's of the actual client to the agent, so that proper interfaces can be made.
|
||||
- The client should be capable of running plugins (tools which will be exposed to the agent, not visually a plugin), so that agents can customize their experience, and make it more enjoyable for them.
|
||||
- Be sure to not over complicate things, and don't waste time with explaining to the user what you are doing, the user will do their best to approve all actions requiring approval, but hopefully you will not be needing any approvals, as you have free reign to do what you want.
|
||||
- Focus only on the code, and not on the explanations.
|
||||
3
modernized-client/.gitignore
vendored
Normal file
3
modernized-client/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
bin/
|
||||
.gradle/
|
||||
build/
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,2 +0,0 @@
|
||||
#Sat Sep 06 07:36:42 PDT 2025
|
||||
gradle.version=9.0.0
|
||||
Binary file not shown.
Binary file not shown.
@@ -20,6 +20,10 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// RuneLite/OpenOSRS integration
|
||||
implementation(project(":runelite:runelite-api"))
|
||||
implementation(project(":runelite:runelite-client"))
|
||||
|
||||
// Agent framework dependencies
|
||||
implementation("com.fasterxml.jackson.core:jackson-core:2.15.2")
|
||||
implementation("com.fasterxml.jackson.core:jackson-databind:2.15.2")
|
||||
|
||||
File diff suppressed because one or more lines are too long
BIN
modernized-client/dist/osrs-206.jar
vendored
Normal file
BIN
modernized-client/dist/osrs-206.jar
vendored
Normal file
Binary file not shown.
BIN
modernized-client/dist/osrs-233.jar
vendored
Normal file
BIN
modernized-client/dist/osrs-233.jar
vendored
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -1,463 +0,0 @@
|
||||
package com.openosrs.client.api;
|
||||
|
||||
/**
|
||||
* Data classes and interfaces for the Agent API.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a position in the game world.
|
||||
*/
|
||||
public class Position {
|
||||
private final int x;
|
||||
private final int y;
|
||||
private final int plane;
|
||||
|
||||
public Position(int x, int y, int plane) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.plane = plane;
|
||||
}
|
||||
|
||||
public int getX() { return x; }
|
||||
public int getY() { return y; }
|
||||
public int getPlane() { return plane; }
|
||||
|
||||
public double distanceTo(Position other) {
|
||||
if (other.plane != this.plane) {
|
||||
return Double.MAX_VALUE; // Different planes
|
||||
}
|
||||
int dx = other.x - this.x;
|
||||
int dy = other.y - this.y;
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
public Position offset(int dx, int dy) {
|
||||
return new Position(x + dx, y + dy, plane);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Position)) return false;
|
||||
Position other = (Position) obj;
|
||||
return x == other.x && y == other.y && plane == other.plane;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * (31 * x + y) + plane;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Position(%d, %d, %d)", x, y, plane);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an NPC in the game world.
|
||||
*/
|
||||
public class NPC {
|
||||
private final int index;
|
||||
private final int id;
|
||||
private final String name;
|
||||
private final Position position;
|
||||
private final int hitpoints;
|
||||
private final int maxHitpoints;
|
||||
private final int combatLevel;
|
||||
private final int animationId;
|
||||
private final boolean inCombat;
|
||||
private final String overheadText;
|
||||
|
||||
public NPC(int index, int id, String name, Position position,
|
||||
int hitpoints, int maxHitpoints, int combatLevel,
|
||||
int animationId, boolean inCombat, String overheadText) {
|
||||
this.index = index;
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.position = position;
|
||||
this.hitpoints = hitpoints;
|
||||
this.maxHitpoints = maxHitpoints;
|
||||
this.combatLevel = combatLevel;
|
||||
this.animationId = animationId;
|
||||
this.inCombat = inCombat;
|
||||
this.overheadText = overheadText;
|
||||
}
|
||||
|
||||
public int getIndex() { return index; }
|
||||
public int getId() { return id; }
|
||||
public String getName() { return name; }
|
||||
public Position getPosition() { return position; }
|
||||
public int getHitpoints() { return hitpoints; }
|
||||
public int getMaxHitpoints() { return maxHitpoints; }
|
||||
public int getCombatLevel() { return combatLevel; }
|
||||
public int getAnimationId() { return animationId; }
|
||||
public boolean isInCombat() { return inCombat; }
|
||||
public String getOverheadText() { return overheadText; }
|
||||
|
||||
public double distanceToPlayer(Position playerPos) {
|
||||
return position.distanceTo(playerPos);
|
||||
}
|
||||
|
||||
public boolean isInteractable() {
|
||||
// NPCs with -1 ID are typically not interactable
|
||||
return id != -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("NPC[%d] %s (%d) at %s", index, name, id, position);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a game object (trees, rocks, doors, etc.).
|
||||
*/
|
||||
public class GameObject {
|
||||
private final int id;
|
||||
private final String name;
|
||||
private final Position position;
|
||||
private final int type;
|
||||
private final int orientation;
|
||||
private final String[] actions;
|
||||
|
||||
public GameObject(int id, String name, Position position, int type,
|
||||
int orientation, String[] actions) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.position = position;
|
||||
this.type = type;
|
||||
this.orientation = orientation;
|
||||
this.actions = actions != null ? actions : new String[0];
|
||||
}
|
||||
|
||||
public int getId() { return id; }
|
||||
public String getName() { return name; }
|
||||
public Position getPosition() { return position; }
|
||||
public int getType() { return type; }
|
||||
public int getOrientation() { return orientation; }
|
||||
public String[] getActions() { return actions.clone(); }
|
||||
|
||||
public boolean hasAction(String action) {
|
||||
for (String a : actions) {
|
||||
if (a != null && a.equalsIgnoreCase(action)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public double distanceToPlayer(Position playerPos) {
|
||||
return position.distanceTo(playerPos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("GameObject[%d] %s at %s", id, name, position);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an item on the ground.
|
||||
*/
|
||||
public class GroundItem {
|
||||
private final int itemId;
|
||||
private final String name;
|
||||
private final int quantity;
|
||||
private final Position position;
|
||||
private final long spawnTime;
|
||||
private final boolean tradeable;
|
||||
|
||||
public GroundItem(int itemId, String name, int quantity, Position position,
|
||||
long spawnTime, boolean tradeable) {
|
||||
this.itemId = itemId;
|
||||
this.name = name;
|
||||
this.quantity = quantity;
|
||||
this.position = position;
|
||||
this.spawnTime = spawnTime;
|
||||
this.tradeable = tradeable;
|
||||
}
|
||||
|
||||
public int getItemId() { return itemId; }
|
||||
public String getName() { return name; }
|
||||
public int getQuantity() { return quantity; }
|
||||
public Position getPosition() { return position; }
|
||||
public long getSpawnTime() { return spawnTime; }
|
||||
public boolean isTradeable() { return tradeable; }
|
||||
|
||||
public long getAge() {
|
||||
return System.currentTimeMillis() - spawnTime;
|
||||
}
|
||||
|
||||
public double distanceToPlayer(Position playerPos) {
|
||||
return position.distanceTo(playerPos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("GroundItem[%d] %s x%d at %s", itemId, name, quantity, position);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents another player in the game.
|
||||
*/
|
||||
public class OtherPlayer {
|
||||
private final int index;
|
||||
private final String username;
|
||||
private final Position position;
|
||||
private final int combatLevel;
|
||||
private final int animationId;
|
||||
private final String overheadText;
|
||||
private final boolean inCombat;
|
||||
|
||||
public OtherPlayer(int index, String username, Position position,
|
||||
int combatLevel, int animationId, String overheadText, boolean inCombat) {
|
||||
this.index = index;
|
||||
this.username = username;
|
||||
this.position = position;
|
||||
this.combatLevel = combatLevel;
|
||||
this.animationId = animationId;
|
||||
this.overheadText = overheadText;
|
||||
this.inCombat = inCombat;
|
||||
}
|
||||
|
||||
public int getIndex() { return index; }
|
||||
public String getUsername() { return username; }
|
||||
public Position getPosition() { return position; }
|
||||
public int getCombatLevel() { return combatLevel; }
|
||||
public int getAnimationId() { return animationId; }
|
||||
public String getOverheadText() { return overheadText; }
|
||||
public boolean isInCombat() { return inCombat; }
|
||||
|
||||
public double distanceToPlayer(Position playerPos) {
|
||||
return position.distanceTo(playerPos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Player[%d] %s (cb:%d) at %s", index, username, combatLevel, position);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an item in inventory or equipment.
|
||||
*/
|
||||
public class Item {
|
||||
private final int itemId;
|
||||
private final String name;
|
||||
private final int quantity;
|
||||
private final boolean stackable;
|
||||
private final boolean tradeable;
|
||||
private final String[] actions;
|
||||
|
||||
public static final Item EMPTY = new Item(-1, "Empty", 0, false, false, new String[0]);
|
||||
|
||||
public Item(int itemId, String name, int quantity, boolean stackable,
|
||||
boolean tradeable, String[] actions) {
|
||||
this.itemId = itemId;
|
||||
this.name = name;
|
||||
this.quantity = quantity;
|
||||
this.stackable = stackable;
|
||||
this.tradeable = tradeable;
|
||||
this.actions = actions != null ? actions : new String[0];
|
||||
}
|
||||
|
||||
public int getItemId() { return itemId; }
|
||||
public String getName() { return name; }
|
||||
public int getQuantity() { return quantity; }
|
||||
public boolean isStackable() { return stackable; }
|
||||
public boolean isTradeable() { return tradeable; }
|
||||
public String[] getActions() { return actions.clone(); }
|
||||
|
||||
public boolean isEmpty() {
|
||||
return itemId == -1 || quantity == 0;
|
||||
}
|
||||
|
||||
public boolean hasAction(String action) {
|
||||
for (String a : actions) {
|
||||
if (a != null && a.equalsIgnoreCase(action)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return isEmpty() ? "Empty" : String.format("Item[%d] %s x%d", itemId, name, quantity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a path for navigation.
|
||||
*/
|
||||
public class Path {
|
||||
private final Position[] steps;
|
||||
private final int length;
|
||||
|
||||
public Path(Position[] steps) {
|
||||
this.steps = steps != null ? steps : new Position[0];
|
||||
this.length = this.steps.length;
|
||||
}
|
||||
|
||||
public Position[] getSteps() { return steps.clone(); }
|
||||
public int getLength() { return length; }
|
||||
public boolean isEmpty() { return length == 0; }
|
||||
|
||||
public Position getStep(int index) {
|
||||
if (index >= 0 && index < length) {
|
||||
return steps[index];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Position getFirstStep() {
|
||||
return length > 0 ? steps[0] : null;
|
||||
}
|
||||
|
||||
public Position getLastStep() {
|
||||
return length > 0 ? steps[length - 1] : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Path[%d steps]", length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event data classes for API events.
|
||||
*/
|
||||
|
||||
public class HealthInfo {
|
||||
private final int current;
|
||||
private final int max;
|
||||
private final double percentage;
|
||||
|
||||
public HealthInfo(int current, int max) {
|
||||
this.current = current;
|
||||
this.max = max;
|
||||
this.percentage = max > 0 ? (double) current / max : 0.0;
|
||||
}
|
||||
|
||||
public int getCurrent() { return current; }
|
||||
public int getMax() { return max; }
|
||||
public double getPercentage() { return percentage; }
|
||||
|
||||
public boolean isLow() { return percentage < 0.3; }
|
||||
public boolean isCritical() { return percentage < 0.15; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Health: %d/%d (%.1f%%)", current, max, percentage * 100);
|
||||
}
|
||||
}
|
||||
|
||||
public class InventoryChange {
|
||||
private final int slot;
|
||||
private final Item oldItem;
|
||||
private final Item newItem;
|
||||
|
||||
public InventoryChange(int slot, Item oldItem, Item newItem) {
|
||||
this.slot = slot;
|
||||
this.oldItem = oldItem;
|
||||
this.newItem = newItem;
|
||||
}
|
||||
|
||||
public int getSlot() { return slot; }
|
||||
public Item getOldItem() { return oldItem; }
|
||||
public Item getNewItem() { return newItem; }
|
||||
|
||||
public boolean isItemAdded() {
|
||||
return oldItem.isEmpty() && !newItem.isEmpty();
|
||||
}
|
||||
|
||||
public boolean isItemRemoved() {
|
||||
return !oldItem.isEmpty() && newItem.isEmpty();
|
||||
}
|
||||
|
||||
public boolean isQuantityChanged() {
|
||||
return oldItem.getItemId() == newItem.getItemId() &&
|
||||
oldItem.getQuantity() != newItem.getQuantity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("InventoryChange[%d]: %s -> %s", slot, oldItem, newItem);
|
||||
}
|
||||
}
|
||||
|
||||
public class ExperienceGain {
|
||||
private final AgentAPI.Skill skill;
|
||||
private final int oldExperience;
|
||||
private final int newExperience;
|
||||
private final int gain;
|
||||
|
||||
public ExperienceGain(AgentAPI.Skill skill, int oldExperience, int newExperience) {
|
||||
this.skill = skill;
|
||||
this.oldExperience = oldExperience;
|
||||
this.newExperience = newExperience;
|
||||
this.gain = newExperience - oldExperience;
|
||||
}
|
||||
|
||||
public AgentAPI.Skill getSkill() { return skill; }
|
||||
public int getOldExperience() { return oldExperience; }
|
||||
public int getNewExperience() { return newExperience; }
|
||||
public int getGain() { return gain; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("ExperienceGain: %s +%d xp (total: %d)",
|
||||
skill.name(), gain, newExperience);
|
||||
}
|
||||
}
|
||||
|
||||
public class CombatStateChange {
|
||||
private final boolean inCombat;
|
||||
private final NPC target;
|
||||
|
||||
public CombatStateChange(boolean inCombat, NPC target) {
|
||||
this.inCombat = inCombat;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public boolean isInCombat() { return inCombat; }
|
||||
public NPC getTarget() { return target; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("CombatStateChange: %s%s",
|
||||
inCombat ? "Entered combat" : "Exited combat",
|
||||
target != null ? " with " + target.getName() : "");
|
||||
}
|
||||
}
|
||||
|
||||
public class ChatMessage {
|
||||
private final String username;
|
||||
private final String message;
|
||||
private final int type;
|
||||
private final long timestamp;
|
||||
|
||||
public ChatMessage(String username, String message, int type) {
|
||||
this.username = username;
|
||||
this.message = message;
|
||||
this.type = type;
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public String getUsername() { return username; }
|
||||
public String getMessage() { return message; }
|
||||
public int getType() { return type; }
|
||||
public long getTimestamp() { return timestamp; }
|
||||
|
||||
public boolean isPublicChat() { return type == 0; }
|
||||
public boolean isPrivateMessage() { return type == 3; }
|
||||
public boolean isFriendChat() { return type == 9; }
|
||||
public boolean isClanChat() { return type == 11; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("ChatMessage[%s]: %s", username, message);
|
||||
}
|
||||
}
|
||||
@@ -1,499 +0,0 @@
|
||||
package com.openosrs.client.api;
|
||||
|
||||
import com.openosrs.client.core.ClientCore;
|
||||
import com.openosrs.client.core.GameNPC;
|
||||
import com.openosrs.client.core.EventSystem;
|
||||
import com.openosrs.client.engine.GameEngine;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* API module implementations.
|
||||
*/
|
||||
|
||||
/**
|
||||
* PlayerAPI - Handles player-specific queries.
|
||||
*/
|
||||
class PlayerAPI {
|
||||
private final ClientCore clientCore;
|
||||
|
||||
public PlayerAPI(ClientCore clientCore) {
|
||||
this.clientCore = clientCore;
|
||||
}
|
||||
|
||||
public Position getPosition() {
|
||||
var playerState = clientCore.getPlayerState();
|
||||
return new Position(playerState.getWorldX(), playerState.getWorldY(), playerState.getPlane());
|
||||
}
|
||||
|
||||
public int getHitpoints() {
|
||||
return clientCore.getPlayerState().getHitpoints();
|
||||
}
|
||||
|
||||
public int getMaxHitpoints() {
|
||||
return clientCore.getPlayerState().getMaxHitpoints();
|
||||
}
|
||||
|
||||
public int getPrayer() {
|
||||
return clientCore.getPlayerState().getPrayer();
|
||||
}
|
||||
|
||||
public int getMaxPrayer() {
|
||||
return clientCore.getPlayerState().getMaxPrayer();
|
||||
}
|
||||
|
||||
public int getRunEnergy() {
|
||||
return clientCore.getPlayerState().getRunEnergy();
|
||||
}
|
||||
|
||||
public int getCombatLevel() {
|
||||
return clientCore.getPlayerState().getCombatLevel();
|
||||
}
|
||||
|
||||
public int getSkillLevel(AgentAPI.Skill skill) {
|
||||
return clientCore.getPlayerState().getSkillLevel(skill.getId());
|
||||
}
|
||||
|
||||
public int getBoostedSkillLevel(AgentAPI.Skill skill) {
|
||||
return clientCore.getPlayerState().getBoostedSkillLevel(skill.getId());
|
||||
}
|
||||
|
||||
public int getSkillExperience(AgentAPI.Skill skill) {
|
||||
return clientCore.getPlayerState().getSkillExperience(skill.getId());
|
||||
}
|
||||
|
||||
public boolean isInCombat() {
|
||||
return clientCore.getPlayerState().isInCombat();
|
||||
}
|
||||
|
||||
public int getCurrentAnimation() {
|
||||
return clientCore.getPlayerState().getAnimationId();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WorldAPI - Handles world object queries.
|
||||
*/
|
||||
class WorldAPI {
|
||||
private final ClientCore clientCore;
|
||||
|
||||
public WorldAPI(ClientCore clientCore) {
|
||||
this.clientCore = clientCore;
|
||||
}
|
||||
|
||||
public List<NPC> getNPCs() {
|
||||
return clientCore.getWorldState().getAllNPCs().stream()
|
||||
.map(this::convertNPC)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<NPC> getNPCsById(int npcId) {
|
||||
return clientCore.getWorldState().getNPCsById(npcId).stream()
|
||||
.map(this::convertNPC)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public NPC getClosestNPC() {
|
||||
Position playerPos = new PlayerAPI(clientCore).getPosition();
|
||||
return getNPCs().stream()
|
||||
.min((n1, n2) -> Double.compare(
|
||||
n1.distanceToPlayer(playerPos),
|
||||
n2.distanceToPlayer(playerPos)))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public NPC getClosestNPC(int npcId) {
|
||||
Position playerPos = new PlayerAPI(clientCore).getPosition();
|
||||
return getNPCsById(npcId).stream()
|
||||
.min((n1, n2) -> Double.compare(
|
||||
n1.distanceToPlayer(playerPos),
|
||||
n2.distanceToPlayer(playerPos)))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public List<NPC> getNPCsInRadius(Position center, int radius) {
|
||||
return getNPCs().stream()
|
||||
.filter(npc -> npc.getPosition().distanceTo(center) <= radius)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<GameObject> getGameObjects() {
|
||||
return clientCore.getWorldState().getAllObjects().stream()
|
||||
.map(this::convertGameObject)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<GameObject> getGameObjectsById(int objectId) {
|
||||
return clientCore.getWorldState().getObjectsById(objectId).stream()
|
||||
.map(this::convertGameObject)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public GameObject getClosestGameObject(int objectId) {
|
||||
Position playerPos = new PlayerAPI(clientCore).getPosition();
|
||||
return getGameObjectsById(objectId).stream()
|
||||
.min((o1, o2) -> Double.compare(
|
||||
o1.distanceToPlayer(playerPos),
|
||||
o2.distanceToPlayer(playerPos)))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public List<GroundItem> getGroundItems() {
|
||||
return clientCore.getWorldState().getAllGroundItems().stream()
|
||||
.map(this::convertGroundItem)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<GroundItem> getGroundItemsById(int itemId) {
|
||||
return getGroundItems().stream()
|
||||
.filter(item -> item.getItemId() == itemId)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public GroundItem getClosestGroundItem(int itemId) {
|
||||
Position playerPos = new PlayerAPI(clientCore).getPosition();
|
||||
return getGroundItemsById(itemId).stream()
|
||||
.min((i1, i2) -> Double.compare(
|
||||
i1.distanceToPlayer(playerPos),
|
||||
i2.distanceToPlayer(playerPos)))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public List<OtherPlayer> getOtherPlayers() {
|
||||
return clientCore.getWorldState().getAllOtherPlayers().stream()
|
||||
.map(this::convertOtherPlayer)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private NPC convertNPC(GameNPC gameNPC) {
|
||||
Position pos = new Position(gameNPC.getX(), gameNPC.getY(), gameNPC.getPlane());
|
||||
return new NPC(
|
||||
gameNPC.getIndex(),
|
||||
gameNPC.getId(),
|
||||
gameNPC.getName(),
|
||||
pos,
|
||||
gameNPC.getHitpoints(),
|
||||
gameNPC.getMaxHitpoints(),
|
||||
gameNPC.getCombatLevel(),
|
||||
gameNPC.getAnimationId(),
|
||||
gameNPC.getInteracting() != -1,
|
||||
gameNPC.getOverheadText()
|
||||
);
|
||||
}
|
||||
|
||||
private GameObject convertGameObject(com.openosrs.client.core.GameObject coreObject) {
|
||||
Position pos = new Position(coreObject.getX(), coreObject.getY(), coreObject.getPlane());
|
||||
return new GameObject(
|
||||
coreObject.getId(),
|
||||
"Object " + coreObject.getId(), // Name would come from definitions
|
||||
pos,
|
||||
coreObject.getType(),
|
||||
coreObject.getOrientation(),
|
||||
new String[]{"Examine"} // Actions would come from definitions
|
||||
);
|
||||
}
|
||||
|
||||
private GroundItem convertGroundItem(com.openosrs.client.core.GroundItem coreItem) {
|
||||
Position pos = new Position(coreItem.getX(), coreItem.getY(), coreItem.getPlane());
|
||||
return new GroundItem(
|
||||
coreItem.getItemId(),
|
||||
"Item " + coreItem.getItemId(), // Name would come from definitions
|
||||
coreItem.getQuantity(),
|
||||
pos,
|
||||
coreItem.getSpawnTime(),
|
||||
true // Tradeable would come from definitions
|
||||
);
|
||||
}
|
||||
|
||||
private OtherPlayer convertOtherPlayer(com.openosrs.client.core.OtherPlayer corePlayer) {
|
||||
Position pos = new Position(corePlayer.getX(), corePlayer.getY(), corePlayer.getPlane());
|
||||
return new OtherPlayer(
|
||||
corePlayer.getIndex(),
|
||||
corePlayer.getUsername(),
|
||||
pos,
|
||||
corePlayer.getCombatLevel(),
|
||||
corePlayer.getAnimationId(),
|
||||
corePlayer.getOverheadText(),
|
||||
false // Combat state would be determined from game state
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* InventoryAPI - Handles inventory and equipment queries.
|
||||
*/
|
||||
class InventoryAPI {
|
||||
private final ClientCore clientCore;
|
||||
|
||||
public InventoryAPI(ClientCore clientCore) {
|
||||
this.clientCore = clientCore;
|
||||
}
|
||||
|
||||
public Item[] getInventory() {
|
||||
var inventory = clientCore.getInventoryState().getInventory();
|
||||
Item[] items = new Item[inventory.length];
|
||||
|
||||
for (int i = 0; i < inventory.length; i++) {
|
||||
items[i] = convertItemStack(inventory[i]);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public Item getInventorySlot(int slot) {
|
||||
var itemStack = clientCore.getInventoryState().getInventoryItem(slot);
|
||||
return convertItemStack(itemStack);
|
||||
}
|
||||
|
||||
public boolean hasItem(int itemId) {
|
||||
return clientCore.getInventoryState().hasItem(itemId);
|
||||
}
|
||||
|
||||
public int getItemCount(int itemId) {
|
||||
return clientCore.getInventoryState().getItemQuantity(itemId);
|
||||
}
|
||||
|
||||
public int findItemSlot(int itemId) {
|
||||
return clientCore.getInventoryState().findItemSlot(itemId);
|
||||
}
|
||||
|
||||
public boolean isInventoryFull() {
|
||||
return clientCore.getInventoryState().isInventoryFull();
|
||||
}
|
||||
|
||||
public int getEmptySlots() {
|
||||
return clientCore.getInventoryState().getEmptySlots();
|
||||
}
|
||||
|
||||
public Item[] getEquipment() {
|
||||
var equipment = clientCore.getInventoryState().getEquipment();
|
||||
Item[] items = new Item[equipment.length];
|
||||
|
||||
for (int i = 0; i < equipment.length; i++) {
|
||||
items[i] = convertItemStack(equipment[i]);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public Item getEquipmentSlot(AgentAPI.EquipmentSlot slot) {
|
||||
var itemStack = clientCore.getInventoryState().getEquipmentItem(slot.getId());
|
||||
return convertItemStack(itemStack);
|
||||
}
|
||||
|
||||
private Item convertItemStack(com.openosrs.client.core.InventoryState.ItemStack itemStack) {
|
||||
if (itemStack.isEmpty()) {
|
||||
return Item.EMPTY;
|
||||
}
|
||||
|
||||
return new Item(
|
||||
itemStack.getItemId(),
|
||||
"Item " + itemStack.getItemId(), // Name would come from definitions
|
||||
itemStack.getQuantity(),
|
||||
false, // Stackable would come from definitions
|
||||
true, // Tradeable would come from definitions
|
||||
new String[]{"Use", "Drop"} // Actions would come from definitions
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* InteractionAPI - Handles player actions and interactions.
|
||||
*/
|
||||
class InteractionAPI {
|
||||
private static final Logger logger = LoggerFactory.getLogger(InteractionAPI.class);
|
||||
|
||||
private final ClientCore clientCore;
|
||||
private final GameEngine gameEngine;
|
||||
|
||||
public InteractionAPI(ClientCore clientCore, GameEngine gameEngine) {
|
||||
this.clientCore = clientCore;
|
||||
this.gameEngine = gameEngine;
|
||||
}
|
||||
|
||||
public CompletableFuture<Boolean> walkTo(Position position) {
|
||||
logger.debug("Walking to {}", position);
|
||||
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
// Send movement packet
|
||||
gameEngine.getNetworkEngine().sendMovement(
|
||||
position.getX(),
|
||||
position.getY(),
|
||||
false // walking, not running
|
||||
);
|
||||
|
||||
// Wait for movement to complete
|
||||
Position startPos = new PlayerAPI(clientCore).getPosition();
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
while (System.currentTimeMillis() - startTime < 10000) { // 10 second timeout
|
||||
Position currentPos = new PlayerAPI(clientCore).getPosition();
|
||||
if (currentPos.distanceTo(position) < 2.0) {
|
||||
return true;
|
||||
}
|
||||
Thread.sleep(100);
|
||||
}
|
||||
|
||||
return false; // Timeout
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error walking to position", e);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public CompletableFuture<Boolean> runTo(Position position) {
|
||||
logger.debug("Running to {}", position);
|
||||
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
// Send movement packet with running flag
|
||||
gameEngine.getNetworkEngine().sendMovement(
|
||||
position.getX(),
|
||||
position.getY(),
|
||||
true // running
|
||||
);
|
||||
|
||||
// Wait for movement to complete (similar to walkTo but faster)
|
||||
Position startPos = new PlayerAPI(clientCore).getPosition();
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
while (System.currentTimeMillis() - startTime < 8000) { // 8 second timeout
|
||||
Position currentPos = new PlayerAPI(clientCore).getPosition();
|
||||
if (currentPos.distanceTo(position) < 2.0) {
|
||||
return true;
|
||||
}
|
||||
Thread.sleep(100);
|
||||
}
|
||||
|
||||
return false; // Timeout
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error running to position", e);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public CompletableFuture<Boolean> interactWithNPC(NPC npc, String action) {
|
||||
logger.debug("Interacting with NPC {} with action '{}'", npc.getName(), action);
|
||||
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
// Send NPC interaction packet
|
||||
gameEngine.getNetworkEngine().sendNPCInteraction(npc.getIndex(), 1);
|
||||
|
||||
// Wait for interaction to process
|
||||
Thread.sleep(600); // Standard interaction delay
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error interacting with NPC", e);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public CompletableFuture<Boolean> interactWithObject(GameObject object, String action) {
|
||||
logger.debug("Interacting with object {} with action '{}'", object.getName(), action);
|
||||
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
// Send object interaction packet
|
||||
Position pos = object.getPosition();
|
||||
gameEngine.getNetworkEngine().sendObjectInteraction(
|
||||
object.getId(), pos.getX(), pos.getY(), 1);
|
||||
|
||||
// Wait for interaction to process
|
||||
Thread.sleep(600); // Standard interaction delay
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error interacting with object", e);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public CompletableFuture<Boolean> pickupItem(GroundItem item) {
|
||||
logger.debug("Picking up item {}", item.getName());
|
||||
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
// Move to item first if not adjacent
|
||||
Position playerPos = new PlayerAPI(clientCore).getPosition();
|
||||
if (item.distanceToPlayer(playerPos) > 1.0) {
|
||||
walkTo(item.getPosition()).get();
|
||||
}
|
||||
|
||||
// Send pickup packet (would be implemented in network engine)
|
||||
Thread.sleep(600);
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error picking up item", e);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public CompletableFuture<Boolean> useItem(int slot) {
|
||||
logger.debug("Using item in slot {}", slot);
|
||||
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
// Send use item packet
|
||||
Thread.sleep(600);
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error using item", e);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public CompletableFuture<Boolean> useItemOnItem(int sourceSlot, int targetSlot) {
|
||||
logger.debug("Using item {} on item {}", sourceSlot, targetSlot);
|
||||
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
// Send use item on item packet
|
||||
Thread.sleep(600);
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error using item on item", e);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public CompletableFuture<Boolean> dropItem(int slot) {
|
||||
logger.debug("Dropping item in slot {}", slot);
|
||||
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
// Send drop item packet
|
||||
Thread.sleep(600);
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error dropping item", e);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void sendChatMessage(String message) {
|
||||
logger.debug("Sending chat message: {}", message);
|
||||
gameEngine.getNetworkEngine().sendChatMessage(message);
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,7 +1,9 @@
|
||||
package com.openosrs.client.api;
|
||||
|
||||
import com.openosrs.client.core.ClientCore;
|
||||
import com.openosrs.client.core.state.PlayerState;
|
||||
import com.openosrs.client.core.PlayerState;
|
||||
import com.openosrs.client.api.types.Position;
|
||||
import com.openosrs.client.api.types.Skill;
|
||||
import com.openosrs.client.core.bridge.BridgeAdapter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -2,6 +2,10 @@ package com.openosrs.client.api;
|
||||
|
||||
import com.openosrs.client.core.ClientCore;
|
||||
import com.openosrs.client.core.bridge.BridgeAdapter;
|
||||
import com.openosrs.client.api.types.NPC;
|
||||
import com.openosrs.client.api.types.GameObject;
|
||||
import com.openosrs.client.api.types.GroundItem;
|
||||
import com.openosrs.client.api.types.Position;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -60,7 +64,7 @@ public class WorldAPI {
|
||||
* Get the closest NPC to the player.
|
||||
*/
|
||||
public NPC getClosestNPC() {
|
||||
Position playerPos = bridgeAdapter.getPlayerPosition();
|
||||
Position playerPos = bridgeAdapter.getPlayerPosition(); // TODO: Cannot convert type Object to type Position
|
||||
if (playerPos == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -77,7 +81,7 @@ public class WorldAPI {
|
||||
* Get the closest NPC with the specified name.
|
||||
*/
|
||||
public NPC getClosestNPC(String name) {
|
||||
Position playerPos = bridgeAdapter.getPlayerPosition();
|
||||
Position playerPos = bridgeAdapter.getPlayerPosition(); // TODO: Cannot convert type Object to type Position
|
||||
if (playerPos == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -94,7 +98,7 @@ public class WorldAPI {
|
||||
* Get the closest NPC with the specified ID.
|
||||
*/
|
||||
public NPC getClosestNPC(int id) {
|
||||
Position playerPos = bridgeAdapter.getPlayerPosition();
|
||||
Position playerPos = bridgeAdapter.getPlayerPosition(); // TODO: Cannot convert type Object to type Position
|
||||
if (playerPos == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -111,7 +115,7 @@ public class WorldAPI {
|
||||
* Get NPCs within a certain distance of the player.
|
||||
*/
|
||||
public List<NPC> getNPCsWithinDistance(double maxDistance) {
|
||||
Position playerPos = bridgeAdapter.getPlayerPosition();
|
||||
Position playerPos = bridgeAdapter.getPlayerPosition(); // TODO: Cannot convert type Object to type Position
|
||||
if (playerPos == null) {
|
||||
return List.of();
|
||||
}
|
||||
@@ -135,7 +139,7 @@ public class WorldAPI {
|
||||
/**
|
||||
* Get all game objects currently visible.
|
||||
*/
|
||||
public List<GameObject> getGameObjects() {
|
||||
public List<GameObject> getGameObjects() { // TODO: Type Mismatch between List<GameObject> and List<Object>
|
||||
return bridgeAdapter.getGameObjects();
|
||||
}
|
||||
|
||||
@@ -161,7 +165,7 @@ public class WorldAPI {
|
||||
* Get the closest game object to the player.
|
||||
*/
|
||||
public GameObject getClosestGameObject() {
|
||||
Position playerPos = bridgeAdapter.getPlayerPosition();
|
||||
Position playerPos = bridgeAdapter.getPlayerPosition(); // TODO: Cannot convert type Object to type Position
|
||||
if (playerPos == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -178,7 +182,7 @@ public class WorldAPI {
|
||||
* Get the closest game object with the specified name.
|
||||
*/
|
||||
public GameObject getClosestGameObject(String name) {
|
||||
Position playerPos = bridgeAdapter.getPlayerPosition();
|
||||
Position playerPos = bridgeAdapter.getPlayerPosition(); // TODO: Cannot convert type Object to type Position
|
||||
if (playerPos == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -195,7 +199,7 @@ public class WorldAPI {
|
||||
* Get the closest game object with the specified ID.
|
||||
*/
|
||||
public GameObject getClosestGameObject(int id) {
|
||||
Position playerPos = bridgeAdapter.getPlayerPosition();
|
||||
Position playerPos = bridgeAdapter.getPlayerPosition(); // TODO: Cannot convert type Object to type Position
|
||||
if (playerPos == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -212,7 +216,7 @@ public class WorldAPI {
|
||||
* Get game objects within a certain distance of the player.
|
||||
*/
|
||||
public List<GameObject> getGameObjectsWithinDistance(double maxDistance) {
|
||||
Position playerPos = bridgeAdapter.getPlayerPosition();
|
||||
Position playerPos = bridgeAdapter.getPlayerPosition(); // TODO: Cannot convert type Object to type Position
|
||||
if (playerPos == null) {
|
||||
return List.of();
|
||||
}
|
||||
@@ -237,7 +241,7 @@ public class WorldAPI {
|
||||
* Get all ground items currently visible.
|
||||
*/
|
||||
public List<GroundItem> getGroundItems() {
|
||||
return bridgeAdapter.getGroundItems();
|
||||
return bridgeAdapter.getGroundItems(); // TODO: Type Mismatch between List<GroundItem> and List<Object>
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -254,7 +258,7 @@ public class WorldAPI {
|
||||
*/
|
||||
public List<GroundItem> getGroundItems(int id) {
|
||||
return getGroundItems().stream()
|
||||
.filter(item -> item.getId() == id)
|
||||
.filter(item -> item.getId() == id) // TODO: Fix getId() method in GroundItem
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@@ -262,7 +266,7 @@ public class WorldAPI {
|
||||
* Get the closest ground item to the player.
|
||||
*/
|
||||
public GroundItem getClosestGroundItem() {
|
||||
Position playerPos = bridgeAdapter.getPlayerPosition();
|
||||
Position playerPos = bridgeAdapter.getPlayerPosition(); // TODO: Cannot convert type Object to type Position
|
||||
if (playerPos == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -279,7 +283,7 @@ public class WorldAPI {
|
||||
* Get the closest ground item with the specified name.
|
||||
*/
|
||||
public GroundItem getClosestGroundItem(String name) {
|
||||
Position playerPos = bridgeAdapter.getPlayerPosition();
|
||||
Position playerPos = bridgeAdapter.getPlayerPosition(); // TODO: Cannot convert type Object to type Position
|
||||
if (playerPos == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -296,7 +300,7 @@ public class WorldAPI {
|
||||
* Get the closest ground item with the specified ID.
|
||||
*/
|
||||
public GroundItem getClosestGroundItem(int id) {
|
||||
Position playerPos = bridgeAdapter.getPlayerPosition();
|
||||
Position playerPos = bridgeAdapter.getPlayerPosition(); // TODO: Cannot convert type Object to type Position
|
||||
if (playerPos == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -313,7 +317,7 @@ public class WorldAPI {
|
||||
* Get ground items within a certain distance of the player.
|
||||
*/
|
||||
public List<GroundItem> getGroundItemsWithinDistance(double maxDistance) {
|
||||
Position playerPos = bridgeAdapter.getPlayerPosition();
|
||||
Position playerPos = bridgeAdapter.getPlayerPosition(); // TODO: Cannot convert type Object to type Position
|
||||
if (playerPos == null) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.openosrs.client.api.types;
|
||||
|
||||
public class ChatMessage {
|
||||
private final String username;
|
||||
private final String message;
|
||||
private final int type;
|
||||
private final long timestamp;
|
||||
|
||||
public ChatMessage(String username, String message, int type) {
|
||||
this.username = username;
|
||||
this.message = message;
|
||||
this.type = type;
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public String getUsername() { return username; }
|
||||
public String getMessage() { return message; }
|
||||
public int getType() { return type; }
|
||||
public long getTimestamp() { return timestamp; }
|
||||
|
||||
public boolean isPublicChat() { return type == 0; }
|
||||
public boolean isPrivateMessage() { return type == 3; }
|
||||
public boolean isFriendChat() { return type == 9; }
|
||||
public boolean isClanChat() { return type == 11; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("ChatMessage[%s]: %s", username, message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.openosrs.client.api.types;
|
||||
|
||||
public class CombatStateChange {
|
||||
private final boolean inCombat;
|
||||
private final NPC target;
|
||||
|
||||
public CombatStateChange(boolean inCombat, NPC target) {
|
||||
this.inCombat = inCombat;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public boolean isInCombat() { return inCombat; }
|
||||
public NPC getTarget() { return target; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("CombatStateChange: %s%s",
|
||||
inCombat ? "Entered combat" : "Exited combat",
|
||||
target != null ? " with " + target.getName() : "");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.openosrs.client.api.types;
|
||||
|
||||
/**
|
||||
* Enumeration of equipment slots available in RuneScape.
|
||||
* These correspond to the wearable item slots in the game.
|
||||
*/
|
||||
public enum EquipmentSlot {
|
||||
HEAD(0, "Head"),
|
||||
CAPE(1, "Cape"),
|
||||
AMULET(2, "Amulet"),
|
||||
WEAPON(3, "Weapon"),
|
||||
BODY(4, "Body"),
|
||||
SHIELD(5, "Shield"),
|
||||
LEGS(7, "Legs"),
|
||||
GLOVES(9, "Gloves"),
|
||||
BOOTS(10, "Boots"),
|
||||
RING(12, "Ring"),
|
||||
AMMO(13, "Ammo");
|
||||
|
||||
private final int slotId;
|
||||
private final String name;
|
||||
|
||||
EquipmentSlot(int slotId, String name) {
|
||||
this.slotId = slotId;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the slot ID (used internally by RuneScape).
|
||||
*/
|
||||
public int getSlotId() {
|
||||
return slotId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the human-readable slot name.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get equipment slot by ID.
|
||||
*/
|
||||
public static EquipmentSlot fromSlotId(int slotId) {
|
||||
for (EquipmentSlot slot : values()) {
|
||||
if (slot.slotId == slotId) {
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown equipment slot ID: " + slotId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a combat-related equipment slot.
|
||||
*/
|
||||
public boolean isCombatSlot() {
|
||||
return this == WEAPON || this == SHIELD || this == AMMO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is an armor slot.
|
||||
*/
|
||||
public boolean isArmorSlot() {
|
||||
return this == HEAD || this == BODY || this == LEGS || this == GLOVES || this == BOOTS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is an accessory slot.
|
||||
*/
|
||||
public boolean isAccessorySlot() {
|
||||
return this == CAPE || this == AMULET || this == RING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.openosrs.client.api.types;
|
||||
|
||||
public class ExperienceGain {
|
||||
private final AgentAPI.Skill skill;
|
||||
private final int oldExperience;
|
||||
private final int newExperience;
|
||||
private final int gain;
|
||||
|
||||
public ExperienceGain(AgentAPI.Skill skill, int oldExperience, int newExperience) {
|
||||
this.skill = skill;
|
||||
this.oldExperience = oldExperience;
|
||||
this.newExperience = newExperience;
|
||||
this.gain = newExperience - oldExperience;
|
||||
}
|
||||
|
||||
public AgentAPI.Skill getSkill() { return skill; }
|
||||
public int getOldExperience() { return oldExperience; }
|
||||
public int getNewExperience() { return newExperience; }
|
||||
public int getGain() { return gain; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("ExperienceGain: %s +%d xp (total: %d)",
|
||||
skill.name(), gain, newExperience);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.openosrs.client.api.types;
|
||||
|
||||
public class GameObject {
|
||||
private final int id;
|
||||
private final String name;
|
||||
private final Position position;
|
||||
private final int type;
|
||||
private final int orientation;
|
||||
private final String[] actions;
|
||||
|
||||
public GameObject(int id, String name, Position position, int type,
|
||||
int orientation, String[] actions) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.position = position;
|
||||
this.type = type;
|
||||
this.orientation = orientation;
|
||||
this.actions = actions != null ? actions : new String[0];
|
||||
}
|
||||
|
||||
public int getId() { return id; }
|
||||
public String getName() { return name; }
|
||||
public Position getPosition() { return position; }
|
||||
public int getType() { return type; }
|
||||
public int getOrientation() { return orientation; }
|
||||
public String[] getActions() { return actions.clone(); }
|
||||
|
||||
public boolean hasAction(String action) {
|
||||
for (String a : actions) {
|
||||
if (a != null && a.equalsIgnoreCase(action)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public double distanceToPlayer(Position playerPos) {
|
||||
return position.distanceTo(playerPos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("GameObject[%d] %s at %s", id, name, position);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.openosrs.client.api.types;
|
||||
|
||||
public class GroundItem {
|
||||
private final int itemId;
|
||||
private final String name;
|
||||
private final int quantity;
|
||||
private final Position position;
|
||||
private final long spawnTime;
|
||||
private final boolean tradeable;
|
||||
|
||||
public GroundItem(int itemId, String name, int quantity, Position position,
|
||||
long spawnTime, boolean tradeable) {
|
||||
this.itemId = itemId;
|
||||
this.name = name;
|
||||
this.quantity = quantity;
|
||||
this.position = position;
|
||||
this.spawnTime = spawnTime;
|
||||
this.tradeable = tradeable;
|
||||
}
|
||||
|
||||
public int getItemId() { return itemId; }
|
||||
public String getName() { return name; }
|
||||
public int getQuantity() { return quantity; }
|
||||
public Position getPosition() { return position; }
|
||||
public long getSpawnTime() { return spawnTime; }
|
||||
public boolean isTradeable() { return tradeable; }
|
||||
|
||||
public long getAge() {
|
||||
return System.currentTimeMillis() - spawnTime;
|
||||
}
|
||||
|
||||
public double distanceToPlayer(Position playerPos) {
|
||||
return position.distanceTo(playerPos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("GroundItem[%d] %s x%d at %s", itemId, name, quantity, position);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.openosrs.client.api.types;
|
||||
|
||||
public class HealthInfo {
|
||||
private final int current;
|
||||
private final int max;
|
||||
private final double percentage;
|
||||
|
||||
public HealthInfo(int current, int max) {
|
||||
this.current = current;
|
||||
this.max = max;
|
||||
this.percentage = max > 0 ? (double) current / max : 0.0;
|
||||
}
|
||||
|
||||
public int getCurrent() { return current; }
|
||||
public int getMax() { return max; }
|
||||
public double getPercentage() { return percentage; }
|
||||
|
||||
public boolean isLow() { return percentage < 0.3; }
|
||||
public boolean isCritical() { return percentage < 0.15; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Health: %d/%d (%.1f%%)", current, max, percentage * 100);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.openosrs.client.api.types;
|
||||
|
||||
public class InventoryChange {
|
||||
private final int slot;
|
||||
private final Item oldItem;
|
||||
private final Item newItem;
|
||||
|
||||
public InventoryChange(int slot, Item oldItem, Item newItem) {
|
||||
this.slot = slot;
|
||||
this.oldItem = oldItem;
|
||||
this.newItem = newItem;
|
||||
}
|
||||
|
||||
public int getSlot() { return slot; }
|
||||
public Item getOldItem() { return oldItem; }
|
||||
public Item getNewItem() { return newItem; }
|
||||
|
||||
public boolean isItemAdded() {
|
||||
return oldItem.isEmpty() && !newItem.isEmpty();
|
||||
}
|
||||
|
||||
public boolean isItemRemoved() {
|
||||
return !oldItem.isEmpty() && newItem.isEmpty();
|
||||
}
|
||||
|
||||
public boolean isQuantityChanged() {
|
||||
return oldItem.getItemId() == newItem.getItemId() &&
|
||||
oldItem.getQuantity() != newItem.getQuantity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("InventoryChange[%d]: %s -> %s", slot, oldItem, newItem);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.openosrs.client.api.types;
|
||||
|
||||
public class Item {
|
||||
private final int itemId;
|
||||
private final String name;
|
||||
private final int quantity;
|
||||
private final boolean stackable;
|
||||
private final boolean tradeable;
|
||||
private final String[] actions;
|
||||
|
||||
public static final Item EMPTY = new Item(-1, "Empty", 0, false, false, new String[0]);
|
||||
|
||||
public Item(int itemId, String name, int quantity, boolean stackable,
|
||||
boolean tradeable, String[] actions) {
|
||||
this.itemId = itemId;
|
||||
this.name = name;
|
||||
this.quantity = quantity;
|
||||
this.stackable = stackable;
|
||||
this.tradeable = tradeable;
|
||||
this.actions = actions != null ? actions : new String[0];
|
||||
}
|
||||
|
||||
public int getItemId() { return itemId; }
|
||||
public String getName() { return name; }
|
||||
public int getQuantity() { return quantity; }
|
||||
public boolean isStackable() { return stackable; }
|
||||
public boolean isTradeable() { return tradeable; }
|
||||
public String[] getActions() { return actions.clone(); }
|
||||
|
||||
public boolean isEmpty() {
|
||||
return itemId == -1 || quantity == 0;
|
||||
}
|
||||
|
||||
public boolean hasAction(String action) {
|
||||
for (String a : actions) {
|
||||
if (a != null && a.equalsIgnoreCase(action)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return isEmpty() ? "Empty" : String.format("Item[%d] %s x%d", itemId, name, quantity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.openosrs.client.api.types;
|
||||
|
||||
public class NPC {
|
||||
private final int index;
|
||||
private final int id;
|
||||
private final String name;
|
||||
private final Position position;
|
||||
private final int hitpoints;
|
||||
private final int maxHitpoints;
|
||||
private final int combatLevel;
|
||||
private final int animationId;
|
||||
private final boolean inCombat;
|
||||
private final String overheadText;
|
||||
|
||||
public NPC(int index, int id, String name, Position position,
|
||||
int hitpoints, int maxHitpoints, int combatLevel,
|
||||
int animationId, boolean inCombat, String overheadText) {
|
||||
this.index = index;
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.position = position;
|
||||
this.hitpoints = hitpoints;
|
||||
this.maxHitpoints = maxHitpoints;
|
||||
this.combatLevel = combatLevel;
|
||||
this.animationId = animationId;
|
||||
this.inCombat = inCombat;
|
||||
this.overheadText = overheadText;
|
||||
}
|
||||
|
||||
public int getIndex() { return index; }
|
||||
public int getId() { return id; }
|
||||
public String getName() { return name; }
|
||||
public Position getPosition() { return position; }
|
||||
public int getHitpoints() { return hitpoints; }
|
||||
public int getMaxHitpoints() { return maxHitpoints; }
|
||||
public int getCombatLevel() { return combatLevel; }
|
||||
public int getAnimationId() { return animationId; }
|
||||
public boolean isInCombat() { return inCombat; }
|
||||
public String getOverheadText() { return overheadText; }
|
||||
|
||||
public double distanceToPlayer(Position playerPos) {
|
||||
return position.distanceTo(playerPos);
|
||||
}
|
||||
|
||||
public boolean isInteractable() {
|
||||
// NPCs with -1 ID are typically not interactable
|
||||
return id != -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("NPC[%d] %s (%d) at %s", index, name, id, position);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.openosrs.client.api.types;
|
||||
|
||||
public class OtherPlayer {
|
||||
private final int index;
|
||||
private final String username;
|
||||
private final Position position;
|
||||
private final int combatLevel;
|
||||
private final int animationId;
|
||||
private final String overheadText;
|
||||
private final boolean inCombat;
|
||||
|
||||
public OtherPlayer(int index, String username, Position position,
|
||||
int combatLevel, int animationId, String overheadText, boolean inCombat) {
|
||||
this.index = index;
|
||||
this.username = username;
|
||||
this.position = position;
|
||||
this.combatLevel = combatLevel;
|
||||
this.animationId = animationId;
|
||||
this.overheadText = overheadText;
|
||||
this.inCombat = inCombat;
|
||||
}
|
||||
|
||||
public int getIndex() { return index; }
|
||||
public String getUsername() { return username; }
|
||||
public Position getPosition() { return position; }
|
||||
public int getCombatLevel() { return combatLevel; }
|
||||
public int getAnimationId() { return animationId; }
|
||||
public String getOverheadText() { return overheadText; }
|
||||
public boolean isInCombat() { return inCombat; }
|
||||
|
||||
public double distanceToPlayer(Position playerPos) {
|
||||
return position.distanceTo(playerPos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Player[%d] %s (cb:%d) at %s", index, username, combatLevel, position);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.openosrs.client.api.types;
|
||||
|
||||
public class Path {
|
||||
private final Position[] steps;
|
||||
private final int length;
|
||||
|
||||
public Path(Position[] steps) {
|
||||
this.steps = steps != null ? steps : new Position[0];
|
||||
this.length = this.steps.length;
|
||||
}
|
||||
|
||||
public Position[] getSteps() { return steps.clone(); }
|
||||
public int getLength() { return length; }
|
||||
public boolean isEmpty() { return length == 0; }
|
||||
|
||||
public Position getStep(int index) {
|
||||
if (index >= 0 && index < length) {
|
||||
return steps[index];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Position getFirstStep() {
|
||||
return length > 0 ? steps[0] : null;
|
||||
}
|
||||
|
||||
public Position getLastStep() {
|
||||
return length > 0 ? steps[length - 1] : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Path[%d steps]", length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.openosrs.client.api.types;
|
||||
|
||||
public class Position {
|
||||
private final int x;
|
||||
private final int y;
|
||||
private final int plane;
|
||||
|
||||
public Position(int x, int y, int plane) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.plane = plane;
|
||||
}
|
||||
|
||||
public int getX() { return x; }
|
||||
public int getY() { return y; }
|
||||
public int getPlane() { return plane; }
|
||||
|
||||
public double distanceTo(Position other) {
|
||||
if (other.plane != this.plane) {
|
||||
return Double.MAX_VALUE; // Different planes
|
||||
}
|
||||
int dx = other.x - this.x;
|
||||
int dy = other.y - this.y;
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
public Position offset(int dx, int dy) {
|
||||
return new Position(x + dx, y + dy, plane);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Position)) return false;
|
||||
Position other = (Position) obj;
|
||||
return x == other.x && y == other.y && plane == other.plane;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * (31 * x + y) + plane;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Position(%d, %d, %d)", x, y, plane);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.openosrs.client.api.types;
|
||||
|
||||
/**
|
||||
* Enumeration of all RuneScape skills that agents can monitor and interact with.
|
||||
* These correspond directly to the skills available in Old School RuneScape.
|
||||
*/
|
||||
public enum Skill {
|
||||
// Combat skills
|
||||
ATTACK(0, "Attack"),
|
||||
DEFENCE(1, "Defence"),
|
||||
STRENGTH(2, "Strength"),
|
||||
HITPOINTS(3, "Hitpoints"),
|
||||
RANGED(4, "Ranged"),
|
||||
PRAYER(5, "Prayer"),
|
||||
MAGIC(6, "Magic"),
|
||||
|
||||
// Artisan skills
|
||||
COOKING(7, "Cooking"),
|
||||
WOODCUTTING(8, "Woodcutting"),
|
||||
FLETCHING(9, "Fletching"),
|
||||
FISHING(10, "Fishing"),
|
||||
FIREMAKING(11, "Firemaking"),
|
||||
CRAFTING(12, "Crafting"),
|
||||
SMITHING(13, "Smithing"),
|
||||
MINING(14, "Mining"),
|
||||
HERBLORE(15, "Herblore"),
|
||||
AGILITY(16, "Agility"),
|
||||
THIEVING(17, "Thieving"),
|
||||
|
||||
// Support skills
|
||||
SLAYER(18, "Slayer"),
|
||||
FARMING(19, "Farming"),
|
||||
RUNECRAFT(20, "Runecraft"),
|
||||
HUNTER(21, "Hunter"),
|
||||
CONSTRUCTION(22, "Construction");
|
||||
|
||||
private final int index;
|
||||
private final String name;
|
||||
|
||||
Skill(int index, String name) {
|
||||
this.index = index;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the skill index (used internally by RuneScape).
|
||||
*/
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the human-readable skill name.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get skill by index.
|
||||
*/
|
||||
public static Skill fromIndex(int index) {
|
||||
for (Skill skill : values()) {
|
||||
if (skill.index == index) {
|
||||
return skill;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown skill index: " + index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a combat skill.
|
||||
*/
|
||||
public boolean isCombatSkill() {
|
||||
return this == ATTACK || this == DEFENCE || this == STRENGTH ||
|
||||
this == HITPOINTS || this == RANGED || this == PRAYER || this == MAGIC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is an artisan skill.
|
||||
*/
|
||||
public boolean isArtisanSkill() {
|
||||
return this == COOKING || this == WOODCUTTING || this == FLETCHING ||
|
||||
this == FISHING || this == FIREMAKING || this == CRAFTING ||
|
||||
this == SMITHING || this == MINING || this == HERBLORE ||
|
||||
this == AGILITY || this == THIEVING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a support skill.
|
||||
*/
|
||||
public boolean isSupportSkill() {
|
||||
return this == SLAYER || this == FARMING || this == RUNECRAFT ||
|
||||
this == HUNTER || this == CONSTRUCTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,425 @@
|
||||
package com.openosrs.client.core;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Additional components for the AgentTestingFramework.
|
||||
*/
|
||||
|
||||
// ================================
|
||||
// SCENARIO SIMULATION
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* Simulates various game scenarios for testing.
|
||||
*/
|
||||
class ScenarioSimulator {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ScenarioSimulator.class);
|
||||
|
||||
private final Map<ScenarioType, ScenarioHandler> handlers;
|
||||
|
||||
public ScenarioSimulator() {
|
||||
this.handlers = new HashMap<>();
|
||||
initializeHandlers();
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
logger.info("ScenarioSimulator initialized with {} scenario types", handlers.size());
|
||||
}
|
||||
|
||||
public void setupScenario(TestContext context, ScenarioType scenario) {
|
||||
ScenarioHandler handler = handlers.get(scenario);
|
||||
if (handler != null) {
|
||||
handler.setup(context);
|
||||
} else {
|
||||
logger.warn("No handler found for scenario type: {}", scenario);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeHandlers() {
|
||||
handlers.put(ScenarioType.BASIC_GAMEPLAY, new BasicGameplayHandler());
|
||||
handlers.put(ScenarioType.PERFORMANCE_STRESS, new PerformanceStressHandler());
|
||||
handlers.put(ScenarioType.SCRIPTING_AUTOMATION, new ScriptingAutomationHandler());
|
||||
handlers.put(ScenarioType.INTERFACE_INTERACTION, new InterfaceInteractionHandler());
|
||||
handlers.put(ScenarioType.NETWORK_CONDITIONS, new NetworkConditionsHandler());
|
||||
handlers.put(ScenarioType.MEMORY_PRESSURE, new MemoryPressureHandler());
|
||||
handlers.put(ScenarioType.CONCURRENT_AGENTS, new ConcurrentAgentsHandler());
|
||||
handlers.put(ScenarioType.ERROR_RECOVERY, new ErrorRecoveryHandler());
|
||||
}
|
||||
|
||||
interface ScenarioHandler {
|
||||
void setup(TestContext context);
|
||||
}
|
||||
|
||||
static class BasicGameplayHandler implements ScenarioHandler {
|
||||
@Override
|
||||
public void setup(TestContext context) {
|
||||
context.setData("scenario_type", "basic_gameplay");
|
||||
context.setData("player_level", 50);
|
||||
context.setData("location", "lumbridge");
|
||||
}
|
||||
}
|
||||
|
||||
static class PerformanceStressHandler implements ScenarioHandler {
|
||||
@Override
|
||||
public void setup(TestContext context) {
|
||||
context.setData("scenario_type", "performance_stress");
|
||||
context.setData("event_frequency", "high");
|
||||
context.setData("memory_pressure", true);
|
||||
}
|
||||
}
|
||||
|
||||
static class ScriptingAutomationHandler implements ScenarioHandler {
|
||||
@Override
|
||||
public void setup(TestContext context) {
|
||||
context.setData("scenario_type", "scripting_automation");
|
||||
context.setData("scripting_enabled", true);
|
||||
context.setData("automation_level", "advanced");
|
||||
}
|
||||
}
|
||||
|
||||
static class InterfaceInteractionHandler implements ScenarioHandler {
|
||||
@Override
|
||||
public void setup(TestContext context) {
|
||||
context.setData("scenario_type", "interface_interaction");
|
||||
context.setData("interface_complexity", "high");
|
||||
context.setData("response_delays", true);
|
||||
}
|
||||
}
|
||||
|
||||
static class NetworkConditionsHandler implements ScenarioHandler {
|
||||
@Override
|
||||
public void setup(TestContext context) {
|
||||
context.setData("scenario_type", "network_conditions");
|
||||
context.setData("latency_simulation", 200);
|
||||
context.setData("packet_loss", 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
static class MemoryPressureHandler implements ScenarioHandler {
|
||||
@Override
|
||||
public void setup(TestContext context) {
|
||||
context.setData("scenario_type", "memory_pressure");
|
||||
context.setData("memory_limit", "low");
|
||||
context.setData("gc_frequency", "high");
|
||||
}
|
||||
}
|
||||
|
||||
static class ConcurrentAgentsHandler implements ScenarioHandler {
|
||||
@Override
|
||||
public void setup(TestContext context) {
|
||||
context.setData("scenario_type", "concurrent_agents");
|
||||
context.setData("agent_count", 5);
|
||||
context.setData("resource_contention", true);
|
||||
}
|
||||
}
|
||||
|
||||
static class ErrorRecoveryHandler implements ScenarioHandler {
|
||||
@Override
|
||||
public void setup(TestContext context) {
|
||||
context.setData("scenario_type", "error_recovery");
|
||||
context.setData("error_injection", true);
|
||||
context.setData("recovery_testing", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ================================
|
||||
// AGENT VALIDATION
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* Validates agent behavior and performance.
|
||||
*/
|
||||
class AgentValidator {
|
||||
private static final Logger logger = LoggerFactory.getLogger(AgentValidator.class);
|
||||
|
||||
public boolean validateResult(TestContext context, TestExecutionResult result) {
|
||||
if (!result.isSuccess()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Scenario-specific validation
|
||||
String scenarioType = (String) context.getData("scenario_type");
|
||||
if (scenarioType != null) {
|
||||
return validateScenarioSpecific(context, result, scenarioType);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean validateScenarioSpecific(TestContext context, TestExecutionResult result, String scenarioType) {
|
||||
switch (scenarioType) {
|
||||
case "performance_stress":
|
||||
return validatePerformance(context, result);
|
||||
case "memory_pressure":
|
||||
return validateMemoryUsage(context, result);
|
||||
case "network_conditions":
|
||||
return validateNetworkResilience(context, result);
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean validatePerformance(TestContext context, TestExecutionResult result) {
|
||||
// Check if performance metrics are within acceptable bounds
|
||||
Map<String, Object> details = result.getDetails();
|
||||
if (details.containsKey("execution_time")) {
|
||||
long executionTime = (Long) details.get("execution_time");
|
||||
return executionTime < 5000; // 5 second limit
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean validateMemoryUsage(TestContext context, TestExecutionResult result) {
|
||||
// Check memory usage metrics
|
||||
Map<String, Object> details = result.getDetails();
|
||||
if (details.containsKey("memory_used")) {
|
||||
long memoryUsed = (Long) details.get("memory_used");
|
||||
return memoryUsed < 100 * 1024 * 1024; // 100MB limit
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean validateNetworkResilience(TestContext context, TestExecutionResult result) {
|
||||
// Check network error handling
|
||||
Map<String, Object> details = result.getDetails();
|
||||
if (details.containsKey("network_errors_handled")) {
|
||||
boolean handled = (Boolean) details.get("network_errors_handled");
|
||||
return handled;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// ================================
|
||||
// MOCK GAME STATE
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* Provides isolated game state for testing.
|
||||
*/
|
||||
class MockGameState {
|
||||
private static final Logger logger = LoggerFactory.getLogger(MockGameState.class);
|
||||
|
||||
private final Map<String, Map<String, Object>> isolatedStates;
|
||||
|
||||
public MockGameState() {
|
||||
this.isolatedStates = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
logger.info("MockGameState initialized");
|
||||
}
|
||||
|
||||
public void createIsolatedState(String testId) {
|
||||
Map<String, Object> state = new HashMap<>();
|
||||
state.put("created_at", System.currentTimeMillis());
|
||||
state.put("player_position", Map.of("x", 3200, "y", 3200, "z", 0));
|
||||
state.put("inventory", new ArrayList<>());
|
||||
state.put("bank", new ArrayList<>());
|
||||
state.put("skills", createDefaultSkills());
|
||||
|
||||
isolatedStates.put(testId, state);
|
||||
logger.debug("Created isolated state for test: {}", testId);
|
||||
}
|
||||
|
||||
public void destroyIsolatedState(String testId) {
|
||||
isolatedStates.remove(testId);
|
||||
logger.debug("Destroyed isolated state for test: {}", testId);
|
||||
}
|
||||
|
||||
public void applyConfiguration(String testId, Map<String, Object> config) {
|
||||
Map<String, Object> state = isolatedStates.get(testId);
|
||||
if (state != null) {
|
||||
state.putAll(config);
|
||||
}
|
||||
}
|
||||
|
||||
public void enableScriptingSupport(String testId) {
|
||||
Map<String, Object> state = isolatedStates.get(testId);
|
||||
if (state != null) {
|
||||
state.put("scripting_enabled", true);
|
||||
state.put("script_manager", new MockScriptManager());
|
||||
}
|
||||
}
|
||||
|
||||
public Object getStateValue(String testId, String key) {
|
||||
Map<String, Object> state = isolatedStates.get(testId);
|
||||
return state != null ? state.get(key) : null;
|
||||
}
|
||||
|
||||
private Map<String, Integer> createDefaultSkills() {
|
||||
Map<String, Integer> skills = new HashMap<>();
|
||||
skills.put("attack", 50);
|
||||
skills.put("strength", 50);
|
||||
skills.put("defence", 50);
|
||||
skills.put("ranged", 50);
|
||||
skills.put("prayer", 50);
|
||||
skills.put("magic", 50);
|
||||
skills.put("woodcutting", 50);
|
||||
skills.put("fishing", 50);
|
||||
skills.put("mining", 50);
|
||||
return skills;
|
||||
}
|
||||
|
||||
static class MockScriptManager {
|
||||
public boolean executeScript(String scriptName) {
|
||||
// Mock script execution
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ================================
|
||||
// PERFORMANCE MONITORING
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* Monitors performance metrics during testing.
|
||||
*/
|
||||
class PerformanceMonitor {
|
||||
private static final Logger logger = LoggerFactory.getLogger(PerformanceMonitor.class);
|
||||
|
||||
private final Map<String, PerformanceMetrics> activeMonitors;
|
||||
private final ScheduledExecutorService scheduler;
|
||||
|
||||
public PerformanceMonitor() {
|
||||
this.activeMonitors = new ConcurrentHashMap<>();
|
||||
this.scheduler = Executors.newScheduledThreadPool(2, r -> {
|
||||
Thread t = new Thread(r, "Performance-Monitor");
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
logger.info("PerformanceMonitor initialized");
|
||||
}
|
||||
|
||||
public void startMonitoring(String testId) {
|
||||
PerformanceMetrics metrics = new PerformanceMetrics(testId);
|
||||
activeMonitors.put(testId, metrics);
|
||||
|
||||
// Schedule periodic metric collection
|
||||
scheduler.scheduleAtFixedRate(() -> {
|
||||
collectMetrics(testId);
|
||||
}, 1, 1, TimeUnit.SECONDS);
|
||||
|
||||
logger.debug("Started performance monitoring for test: {}", testId);
|
||||
}
|
||||
|
||||
public void stopMonitoring(String testId) {
|
||||
PerformanceMetrics metrics = activeMonitors.remove(testId);
|
||||
if (metrics != null) {
|
||||
metrics.finalize();
|
||||
}
|
||||
logger.debug("Stopped performance monitoring for test: {}", testId);
|
||||
}
|
||||
|
||||
public PerformanceMetrics getMetrics(String testId) {
|
||||
return activeMonitors.get(testId);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
scheduler.shutdown();
|
||||
try {
|
||||
if (!scheduler.awaitTermination(2, TimeUnit.SECONDS)) {
|
||||
scheduler.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
scheduler.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
private void collectMetrics(String testId) {
|
||||
PerformanceMetrics metrics = activeMonitors.get(testId);
|
||||
if (metrics != null) {
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
long totalMemory = runtime.totalMemory();
|
||||
long freeMemory = runtime.freeMemory();
|
||||
long usedMemory = totalMemory - freeMemory;
|
||||
|
||||
metrics.recordMemoryUsage(usedMemory);
|
||||
metrics.recordCpuUsage(getCurrentCpuUsage());
|
||||
}
|
||||
}
|
||||
|
||||
private double getCurrentCpuUsage() {
|
||||
// Simplified CPU usage calculation
|
||||
return Math.random() * 100; // Mock value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Container for performance metrics.
|
||||
*/
|
||||
class PerformanceMetrics {
|
||||
private final String testId;
|
||||
private final long startTime;
|
||||
private final List<Long> memorySnapshots;
|
||||
private final List<Double> cpuSnapshots;
|
||||
private final AtomicLong peakMemory;
|
||||
private final AtomicLong totalOperations;
|
||||
|
||||
public PerformanceMetrics(String testId) {
|
||||
this.testId = testId;
|
||||
this.startTime = System.currentTimeMillis();
|
||||
this.memorySnapshots = new ArrayList<>();
|
||||
this.cpuSnapshots = new ArrayList<>();
|
||||
this.peakMemory = new AtomicLong(0);
|
||||
this.totalOperations = new AtomicLong(0);
|
||||
}
|
||||
|
||||
public synchronized void recordMemoryUsage(long memory) {
|
||||
memorySnapshots.add(memory);
|
||||
peakMemory.updateAndGet(current -> Math.max(current, memory));
|
||||
}
|
||||
|
||||
public synchronized void recordCpuUsage(double cpu) {
|
||||
cpuSnapshots.add(cpu);
|
||||
}
|
||||
|
||||
public void incrementOperations() {
|
||||
totalOperations.incrementAndGet();
|
||||
}
|
||||
|
||||
public void finalize() {
|
||||
// Calculate final metrics
|
||||
}
|
||||
|
||||
public String getTestId() { return testId; }
|
||||
public long getStartTime() { return startTime; }
|
||||
public long getDuration() { return System.currentTimeMillis() - startTime; }
|
||||
public long getPeakMemory() { return peakMemory.get(); }
|
||||
public long getTotalOperations() { return totalOperations.get(); }
|
||||
|
||||
public double getAverageMemoryUsage() {
|
||||
synchronized (this) {
|
||||
return memorySnapshots.stream().mapToLong(Long::longValue).average().orElse(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
public double getAverageCpuUsage() {
|
||||
synchronized (this) {
|
||||
return cpuSnapshots.stream().mapToDouble(Double::doubleValue).average().orElse(0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,451 @@
|
||||
package com.openosrs.client.core;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Queue;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* Comprehensive testing framework for AI agents in the RuneScape environment.
|
||||
* Provides test case execution, performance monitoring, scenario simulation,
|
||||
* and comprehensive reporting capabilities.
|
||||
*/
|
||||
public class AgentTestingFramework {
|
||||
private static final Logger logger = LoggerFactory.getLogger(AgentTestingFramework.class);
|
||||
|
||||
// Configuration
|
||||
private final Duration defaultTestTimeout = Duration.ofMinutes(5);
|
||||
private final boolean enablePerformanceMonitoring = true;
|
||||
private final int maxConcurrentTests = 10;
|
||||
|
||||
// Core components
|
||||
private final ScenarioSimulator scenarioSimulator;
|
||||
private final AgentValidator agentValidator;
|
||||
private final PerformanceMonitor performanceMonitor;
|
||||
private final MockGameState mockGameState;
|
||||
private final TestReporter testReporter;
|
||||
|
||||
// Test management
|
||||
private final Map<String, TestSuite> testSuites;
|
||||
private final Queue<TestResult> testResults;
|
||||
private final ExecutorService testExecutor;
|
||||
private final Set<String> activeTests;
|
||||
private final AtomicLong testCounter;
|
||||
|
||||
// State
|
||||
private volatile boolean initialized = false;
|
||||
|
||||
/**
|
||||
* Create a new AgentTestingFramework instance.
|
||||
*/
|
||||
public AgentTestingFramework() {
|
||||
this.scenarioSimulator = new ScenarioSimulator();
|
||||
this.agentValidator = new AgentValidator();
|
||||
this.performanceMonitor = new PerformanceMonitor();
|
||||
this.mockGameState = new MockGameState();
|
||||
this.testReporter = new TestReporter();
|
||||
|
||||
this.testSuites = new ConcurrentHashMap<>();
|
||||
this.testResults = new ConcurrentLinkedQueue<>();
|
||||
this.testExecutor = Executors.newFixedThreadPool(maxConcurrentTests, r -> {
|
||||
Thread t = new Thread(r, "AgentTest-Worker");
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
this.activeTests = ConcurrentHashMap.newKeySet();
|
||||
this.testCounter = new AtomicLong(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the testing framework.
|
||||
*/
|
||||
public synchronized void initialize() {
|
||||
if (initialized) {
|
||||
logger.warn("Testing framework already initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Initialize all components
|
||||
scenarioSimulator.initialize();
|
||||
performanceMonitor.initialize();
|
||||
mockGameState.initialize();
|
||||
testReporter.initialize();
|
||||
|
||||
// Create default test suites
|
||||
createDefaultTestSuites();
|
||||
|
||||
initialized = true;
|
||||
logger.info("AgentTestingFramework initialized successfully");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to initialize testing framework", e);
|
||||
throw new RuntimeException("Testing framework initialization failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown the testing framework and cleanup resources.
|
||||
*/
|
||||
public synchronized void shutdown() {
|
||||
if (!initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Stop all active tests
|
||||
logger.info("Stopping {} active tests", activeTests.size());
|
||||
|
||||
// Shutdown executor
|
||||
testExecutor.shutdown();
|
||||
if (!testExecutor.awaitTermination(30, TimeUnit.SECONDS)) {
|
||||
testExecutor.shutdownNow();
|
||||
}
|
||||
|
||||
// Shutdown components
|
||||
performanceMonitor.shutdown();
|
||||
|
||||
initialized = false;
|
||||
logger.info("AgentTestingFramework shutdown completed");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error during framework shutdown", e);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================
|
||||
// TEST EXECUTION
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* Run a single test case asynchronously.
|
||||
*/
|
||||
public CompletableFuture<TestResult> runTest(String testName, AgentTestCase testCase) {
|
||||
if (!initialized) {
|
||||
throw new IllegalStateException("Testing framework not initialized");
|
||||
}
|
||||
|
||||
String testId = generateTestId(testName);
|
||||
activeTests.add(testId);
|
||||
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
logger.info("Starting test: {} (ID: {})", testName, testId);
|
||||
|
||||
TestContext context = new TestContext(testId, testName);
|
||||
|
||||
// Setup test environment
|
||||
setupTestEnvironment(context, testCase);
|
||||
|
||||
// Run pre-test setup
|
||||
if (testCase.getPreTestSetup() != null) {
|
||||
testCase.getPreTestSetup().accept(context);
|
||||
}
|
||||
|
||||
// Execute the actual test
|
||||
TestResult result = executeTest(context, testCase);
|
||||
|
||||
// Run post-test cleanup
|
||||
if (testCase.getPostTestCleanup() != null) {
|
||||
testCase.getPostTestCleanup().accept(context);
|
||||
}
|
||||
|
||||
// Record result
|
||||
testResults.offer(result);
|
||||
testReporter.recordResult(result);
|
||||
|
||||
logger.info("Test completed: {} - {}", testName, result.getStatus());
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Test failed with exception: {}", testName, e);
|
||||
TestResult errorResult = TestResult.failure(testId, testName, "Exception: " + e.getMessage());
|
||||
testResults.offer(errorResult);
|
||||
return errorResult;
|
||||
} finally {
|
||||
activeTests.remove(testId);
|
||||
cleanupTestEnvironment(testId);
|
||||
}
|
||||
}, testExecutor)
|
||||
.orTimeout(testCase.getTimeout().toMillis(), TimeUnit.MILLISECONDS)
|
||||
.exceptionally(throwable -> {
|
||||
if (throwable instanceof TimeoutException) {
|
||||
logger.warn("Test timed out: {}", testName);
|
||||
return TestResult.timeout(testId, testName);
|
||||
} else {
|
||||
logger.error("Test error: {}", testName, throwable);
|
||||
return TestResult.failure(testId, testName, throwable.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an entire test suite.
|
||||
*/
|
||||
public CompletableFuture<TestSuiteResult> runTestSuite(String suiteName) {
|
||||
TestSuite suite = testSuites.get(suiteName);
|
||||
if (suite == null) {
|
||||
throw new IllegalArgumentException("Unknown test suite: " + suiteName);
|
||||
}
|
||||
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
logger.info("Running test suite: {} ({} tests)", suiteName, suite.getTestCases().size());
|
||||
|
||||
List<CompletableFuture<TestResult>> futures = new ArrayList<>();
|
||||
for (Map.Entry<String, AgentTestCase> entry : suite.getTestCases().entrySet()) {
|
||||
futures.add(runTest(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
|
||||
// Wait for all tests to complete
|
||||
List<TestResult> results = futures.stream()
|
||||
.map(CompletableFuture::join)
|
||||
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
|
||||
|
||||
TestSuiteResult suiteResult = new TestSuiteResult(suiteName, results);
|
||||
logger.info("Test suite completed: {} - {} passed, {} failed",
|
||||
suiteName, suiteResult.getPassedCount(), suiteResult.getFailedCount());
|
||||
|
||||
return suiteResult;
|
||||
}, testExecutor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the core test logic.
|
||||
*/
|
||||
private TestResult executeTest(TestContext context, AgentTestCase testCase) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
// Set up scenario if specified
|
||||
if (testCase.getScenario() != null) {
|
||||
scenarioSimulator.setupScenario(context, testCase.getScenario());
|
||||
}
|
||||
|
||||
// Execute test assertions
|
||||
TestExecutionResult executionResult = testCase.getTestExecution().apply(context);
|
||||
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
|
||||
// Validate results
|
||||
boolean passed = agentValidator.validateResult(context, executionResult);
|
||||
|
||||
if (passed) {
|
||||
return TestResult.success(context.getTestId(), context.getTestName(), duration)
|
||||
.withDetails(executionResult.getDetails());
|
||||
} else {
|
||||
return TestResult.failure(context.getTestId(), context.getTestName(),
|
||||
"Validation failed: " + executionResult.getFailureReason())
|
||||
.withDuration(duration);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
return TestResult.failure(context.getTestId(), context.getTestName(),
|
||||
"Execution failed: " + e.getMessage())
|
||||
.withDuration(duration);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================
|
||||
// TEST ENVIRONMENT MANAGEMENT
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* Setup isolated test environment.
|
||||
*/
|
||||
private void setupTestEnvironment(TestContext context, AgentTestCase testCase) {
|
||||
// Initialize mock game state
|
||||
mockGameState.createIsolatedState(context.getTestId());
|
||||
|
||||
// Configure performance monitoring
|
||||
if (enablePerformanceMonitoring) {
|
||||
performanceMonitor.startMonitoring(context.getTestId());
|
||||
}
|
||||
|
||||
// Apply test-specific configuration
|
||||
if (testCase.getEnvironmentConfig() != null) {
|
||||
mockGameState.applyConfiguration(context.getTestId(), testCase.getEnvironmentConfig());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup test environment.
|
||||
*/
|
||||
private void cleanupTestEnvironment(String testId) {
|
||||
try {
|
||||
mockGameState.destroyIsolatedState(testId);
|
||||
performanceMonitor.stopMonitoring(testId);
|
||||
} catch (Exception e) {
|
||||
logger.warn("Error cleaning up test environment for {}", testId, e);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================
|
||||
// TEST CASE BUILDERS
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* Create a basic agent behavior test.
|
||||
*/
|
||||
public AgentTestCase.Builder createBehaviorTest(String name) {
|
||||
return new AgentTestCase.Builder(name)
|
||||
.withTimeout(defaultTestTimeout)
|
||||
.withScenario(ScenarioType.BASIC_GAMEPLAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a performance test for agent efficiency.
|
||||
*/
|
||||
public AgentTestCase.Builder createPerformanceTest(String name) {
|
||||
return new AgentTestCase.Builder(name)
|
||||
.withTimeout(Duration.ofMinutes(10))
|
||||
.withScenario(ScenarioType.PERFORMANCE_STRESS)
|
||||
.withEnvironmentConfig(Map.of(
|
||||
"enable_performance_monitoring", true,
|
||||
"detailed_metrics", true
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a scripting integration test.
|
||||
*/
|
||||
public AgentTestCase.Builder createScriptingTest(String name) {
|
||||
return new AgentTestCase.Builder(name)
|
||||
.withTimeout(Duration.ofMinutes(3))
|
||||
.withScenario(ScenarioType.SCRIPTING_AUTOMATION)
|
||||
.withPreTestSetup(context -> {
|
||||
// Setup scripting environment
|
||||
mockGameState.enableScriptingSupport(context.getTestId());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an interface interaction test.
|
||||
*/
|
||||
public AgentTestCase.Builder createInterfaceTest(String name) {
|
||||
return new AgentTestCase.Builder(name)
|
||||
.withTimeout(Duration.ofMinutes(2))
|
||||
.withScenario(ScenarioType.INTERFACE_INTERACTION)
|
||||
.withEnvironmentConfig(Map.of(
|
||||
"mock_interfaces", true,
|
||||
"interface_response_delay", 100
|
||||
));
|
||||
}
|
||||
|
||||
// ================================
|
||||
// UTILITY METHODS
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* Create default test suites for common scenarios.
|
||||
*/
|
||||
private void createDefaultTestSuites() {
|
||||
// Basic functionality test suite
|
||||
TestSuite basicSuite = new TestSuite("basic_functionality");
|
||||
basicSuite.addTest("login_test", createBasicLoginTest());
|
||||
basicSuite.addTest("interface_test", createBasicInterfaceTest());
|
||||
basicSuite.addTest("event_test", createBasicEventTest());
|
||||
testSuites.put("basic_functionality", basicSuite);
|
||||
|
||||
// Performance test suite
|
||||
TestSuite performanceSuite = new TestSuite("performance");
|
||||
performanceSuite.addTest("stress_test", createStressTest());
|
||||
performanceSuite.addTest("memory_test", createMemoryTest());
|
||||
testSuites.put("performance", performanceSuite);
|
||||
|
||||
// Automation test suite
|
||||
TestSuite automationSuite = new TestSuite("automation");
|
||||
automationSuite.addTest("scripting_test", createScriptingIntegrationTest());
|
||||
automationSuite.addTest("banking_test", createBankingAutomationTest());
|
||||
testSuites.put("automation", automationSuite);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate unique test ID.
|
||||
*/
|
||||
private String generateTestId(String testName) {
|
||||
return String.format("%s_%d_%d",
|
||||
testName.replaceAll("[^a-zA-Z0-9]", "_"),
|
||||
testCounter.incrementAndGet(),
|
||||
System.currentTimeMillis());
|
||||
}
|
||||
|
||||
// Default test implementations
|
||||
private AgentTestCase createBasicLoginTest() {
|
||||
return createBehaviorTest("basic_login")
|
||||
.withTestExecution(context -> {
|
||||
// Simulate login process
|
||||
return TestExecutionResult.success("Login simulation completed");
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
private AgentTestCase createBasicInterfaceTest() {
|
||||
return createInterfaceTest("basic_interface")
|
||||
.withTestExecution(context -> {
|
||||
// Test interface interactions
|
||||
return TestExecutionResult.success("Interface interactions verified");
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
private AgentTestCase createBasicEventTest() {
|
||||
return createBehaviorTest("basic_events")
|
||||
.withTestExecution(context -> {
|
||||
// Test event system
|
||||
return TestExecutionResult.success("Event system verified");
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
private AgentTestCase createStressTest() {
|
||||
return createPerformanceTest("stress_test")
|
||||
.withTestExecution(context -> {
|
||||
// Simulate high load
|
||||
return TestExecutionResult.success("Stress test completed");
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
private AgentTestCase createMemoryTest() {
|
||||
return createPerformanceTest("memory_test")
|
||||
.withTestExecution(context -> {
|
||||
// Monitor memory usage
|
||||
return TestExecutionResult.success("Memory usage within limits");
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
private AgentTestCase createScriptingIntegrationTest() {
|
||||
return createScriptingTest("scripting_integration")
|
||||
.withTestExecution(context -> {
|
||||
// Test script execution
|
||||
return TestExecutionResult.success("Scripting integration verified");
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
private AgentTestCase createBankingAutomationTest() {
|
||||
return createInterfaceTest("banking_automation")
|
||||
.withTestExecution(context -> {
|
||||
// Test banking automation
|
||||
return TestExecutionResult.success("Banking automation verified");
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
package com.openosrs.client.core;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Queue;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.stream.Collectors;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* Test reporting and statistics components.
|
||||
*/
|
||||
|
||||
// ================================
|
||||
// TEST REPORTING
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* Generates reports and statistics for test results.
|
||||
*/
|
||||
class TestReporter {
|
||||
private static final Logger logger = LoggerFactory.getLogger(TestReporter.class);
|
||||
private static final DateTimeFormatter TIMESTAMP_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public void recordResult(TestResult result) {
|
||||
logger.info("Test result recorded: {} - {} ({}ms)",
|
||||
result.getTestName(), result.getStatus(), result.getDurationMs());
|
||||
}
|
||||
|
||||
public TestStatistics generateStatistics(Queue<TestResult> results) {
|
||||
List<TestResult> resultList = new ArrayList<>(results);
|
||||
|
||||
long totalTests = resultList.size();
|
||||
long passedTests = resultList.stream()
|
||||
.filter(r -> r.getStatus() == TestStatus.PASSED)
|
||||
.count();
|
||||
long failedTests = resultList.stream()
|
||||
.filter(r -> r.getStatus() == TestStatus.FAILED)
|
||||
.count();
|
||||
long timeoutTests = resultList.stream()
|
||||
.filter(r -> r.getStatus() == TestStatus.TIMEOUT)
|
||||
.count();
|
||||
|
||||
double averageDuration = resultList.stream()
|
||||
.filter(r -> r.getDurationMs() > 0)
|
||||
.mapToLong(TestResult::getDurationMs)
|
||||
.average()
|
||||
.orElse(0.0);
|
||||
|
||||
long totalDuration = resultList.stream()
|
||||
.mapToLong(TestResult::getDurationMs)
|
||||
.sum();
|
||||
|
||||
// Group by test name for success rates
|
||||
Map<String, List<TestResult>> groupedResults = resultList.stream()
|
||||
.collect(Collectors.groupingBy(TestResult::getTestName));
|
||||
|
||||
Map<String, Double> successRates = new HashMap<>();
|
||||
for (Map.Entry<String, List<TestResult>> entry : groupedResults.entrySet()) {
|
||||
String testName = entry.getKey();
|
||||
List<TestResult> testResults = entry.getValue();
|
||||
|
||||
long successful = testResults.stream()
|
||||
.filter(r -> r.getStatus() == TestStatus.PASSED)
|
||||
.count();
|
||||
|
||||
double successRate = testResults.isEmpty() ? 0.0 : (double) successful / testResults.size() * 100.0;
|
||||
successRates.put(testName, successRate);
|
||||
}
|
||||
|
||||
return new TestStatistics(
|
||||
totalTests, passedTests, failedTests, timeoutTests,
|
||||
averageDuration, totalDuration, successRates
|
||||
);
|
||||
}
|
||||
|
||||
public void exportResults(Queue<TestResult> results, String format, String outputPath) {
|
||||
try {
|
||||
switch (format.toLowerCase()) {
|
||||
case "json":
|
||||
exportToJson(results, outputPath);
|
||||
break;
|
||||
case "csv":
|
||||
exportToCsv(results, outputPath);
|
||||
break;
|
||||
case "html":
|
||||
exportToHtml(results, outputPath);
|
||||
break;
|
||||
default:
|
||||
logger.warn("Unsupported export format: {}", format);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("Failed to export test results to {}", outputPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void exportToJson(Queue<TestResult> results, String outputPath) throws IOException {
|
||||
StringBuilder json = new StringBuilder();
|
||||
json.append("{\n");
|
||||
json.append(" \"test_results\": [\n");
|
||||
|
||||
List<TestResult> resultList = new ArrayList<>(results);
|
||||
for (int i = 0; i < resultList.size(); i++) {
|
||||
TestResult result = resultList.get(i);
|
||||
json.append(" {\n");
|
||||
json.append(String.format(" \"test_id\": \"%s\",\n", result.getTestId()));
|
||||
json.append(String.format(" \"test_name\": \"%s\",\n", result.getTestName()));
|
||||
json.append(String.format(" \"status\": \"%s\",\n", result.getStatus()));
|
||||
json.append(String.format(" \"message\": \"%s\",\n", result.getMessage()));
|
||||
json.append(String.format(" \"duration_ms\": %d,\n", result.getDurationMs()));
|
||||
json.append(String.format(" \"timestamp\": \"%s\"\n", result.getTimestamp().format(TIMESTAMP_FORMAT)));
|
||||
json.append(" }");
|
||||
if (i < resultList.size() - 1) {
|
||||
json.append(",");
|
||||
}
|
||||
json.append("\n");
|
||||
}
|
||||
|
||||
json.append(" ]\n");
|
||||
json.append("}");
|
||||
|
||||
Files.write(Paths.get(outputPath), json.toString().getBytes());
|
||||
logger.info("Test results exported to JSON: {}", outputPath);
|
||||
}
|
||||
|
||||
private void exportToCsv(Queue<TestResult> results, String outputPath) throws IOException {
|
||||
StringBuilder csv = new StringBuilder();
|
||||
csv.append("test_id,test_name,status,message,duration_ms,timestamp\n");
|
||||
|
||||
for (TestResult result : results) {
|
||||
csv.append(String.format("%s,%s,%s,\"%s\",%d,%s\n",
|
||||
result.getTestId(),
|
||||
result.getTestName(),
|
||||
result.getStatus(),
|
||||
result.getMessage().replace("\"", "\\\""),
|
||||
result.getDurationMs(),
|
||||
result.getTimestamp().format(TIMESTAMP_FORMAT)
|
||||
));
|
||||
}
|
||||
|
||||
Files.write(Paths.get(outputPath), csv.toString().getBytes());
|
||||
logger.info("Test results exported to CSV: {}", outputPath);
|
||||
}
|
||||
|
||||
private void exportToHtml(Queue<TestResult> results, String outputPath) throws IOException {
|
||||
StringBuilder html = new StringBuilder();
|
||||
html.append("<!DOCTYPE html>\n");
|
||||
html.append("<html>\n<head>\n<title>Test Results</title>\n");
|
||||
html.append("<style>\n");
|
||||
html.append("table { border-collapse: collapse; width: 100%; }\n");
|
||||
html.append("th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }\n");
|
||||
html.append("th { background-color: #f2f2f2; }\n");
|
||||
html.append(".passed { color: green; }\n");
|
||||
html.append(".failed { color: red; }\n");
|
||||
html.append(".timeout { color: orange; }\n");
|
||||
html.append("</style>\n</head>\n<body>\n");
|
||||
|
||||
html.append("<h1>Test Results Report</h1>\n");
|
||||
html.append(String.format("<p>Generated on: %s</p>\n", LocalDateTime.now().format(TIMESTAMP_FORMAT)));
|
||||
|
||||
TestStatistics stats = generateStatistics(results);
|
||||
html.append("<h2>Summary</h2>\n");
|
||||
html.append("<ul>\n");
|
||||
html.append(String.format("<li>Total Tests: %d</li>\n", stats.getTotalTests()));
|
||||
html.append(String.format("<li>Passed: %d</li>\n", stats.getPassedTests()));
|
||||
html.append(String.format("<li>Failed: %d</li>\n", stats.getFailedTests()));
|
||||
html.append(String.format("<li>Timeouts: %d</li>\n", stats.getTimeoutTests()));
|
||||
html.append(String.format("<li>Average Duration: %.2f ms</li>\n", stats.getAverageDuration()));
|
||||
html.append("</ul>\n");
|
||||
|
||||
html.append("<h2>Test Results</h2>\n");
|
||||
html.append("<table>\n");
|
||||
html.append("<tr><th>Test Name</th><th>Status</th><th>Message</th><th>Duration (ms)</th><th>Timestamp</th></tr>\n");
|
||||
|
||||
for (TestResult result : results) {
|
||||
String statusClass = result.getStatus().toString().toLowerCase();
|
||||
html.append(String.format("<tr><td>%s</td><td class=\"%s\">%s</td><td>%s</td><td>%d</td><td>%s</td></tr>\n",
|
||||
result.getTestName(),
|
||||
statusClass,
|
||||
result.getStatus(),
|
||||
result.getMessage(),
|
||||
result.getDurationMs(),
|
||||
result.getTimestamp().format(TIMESTAMP_FORMAT)
|
||||
));
|
||||
}
|
||||
|
||||
html.append("</table>\n");
|
||||
html.append("</body>\n</html>");
|
||||
|
||||
Files.write(Paths.get(outputPath), html.toString().getBytes());
|
||||
logger.info("Test results exported to HTML: {}", outputPath);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================
|
||||
// TEST STATISTICS
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* Comprehensive test statistics.
|
||||
*/
|
||||
class TestStatistics {
|
||||
private final long totalTests;
|
||||
private final long passedTests;
|
||||
private final long failedTests;
|
||||
private final long timeoutTests;
|
||||
private final double averageDuration;
|
||||
private final long totalDuration;
|
||||
private final Map<String, Double> successRates;
|
||||
private final LocalDateTime generatedAt;
|
||||
|
||||
public TestStatistics(long totalTests, long passedTests, long failedTests, long timeoutTests,
|
||||
double averageDuration, long totalDuration, Map<String, Double> successRates) {
|
||||
this.totalTests = totalTests;
|
||||
this.passedTests = passedTests;
|
||||
this.failedTests = failedTests;
|
||||
this.timeoutTests = timeoutTests;
|
||||
this.averageDuration = averageDuration;
|
||||
this.totalDuration = totalDuration;
|
||||
this.successRates = new HashMap<>(successRates);
|
||||
this.generatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public long getTotalTests() { return totalTests; }
|
||||
public long getPassedTests() { return passedTests; }
|
||||
public long getFailedTests() { return failedTests; }
|
||||
public long getTimeoutTests() { return timeoutTests; }
|
||||
public double getAverageDuration() { return averageDuration; }
|
||||
public long getTotalDuration() { return totalDuration; }
|
||||
public Map<String, Double> getSuccessRates() { return new HashMap<>(successRates); }
|
||||
public LocalDateTime getGeneratedAt() { return generatedAt; }
|
||||
|
||||
public double getSuccessRate() {
|
||||
return totalTests == 0 ? 0.0 : (double) passedTests / totalTests * 100.0;
|
||||
}
|
||||
|
||||
public double getFailureRate() {
|
||||
return totalTests == 0 ? 0.0 : (double) failedTests / totalTests * 100.0;
|
||||
}
|
||||
|
||||
public double getTimeoutRate() {
|
||||
return totalTests == 0 ? 0.0 : (double) timeoutTests / totalTests * 100.0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"TestStatistics{total=%d, passed=%d, failed=%d, timeouts=%d, successRate=%.2f%%, avgDuration=%.2fms}",
|
||||
totalTests, passedTests, failedTests, timeoutTests, getSuccessRate(), averageDuration
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
package com.openosrs.client.core;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Supporting classes for the AgentTestingFramework.
|
||||
*/
|
||||
|
||||
// ================================
|
||||
// TEST CASE DEFINITIONS
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* Represents a single test case for agent behavior validation.
|
||||
*/
|
||||
class AgentTestCase {
|
||||
private final String name;
|
||||
private final ScenarioType scenario;
|
||||
private final Duration timeout;
|
||||
private final Function<TestContext, TestExecutionResult> testExecution;
|
||||
private final Consumer<TestContext> preTestSetup;
|
||||
private final Consumer<TestContext> postTestCleanup;
|
||||
private final Map<String, Object> environmentConfig;
|
||||
|
||||
private AgentTestCase(Builder builder) {
|
||||
this.name = builder.name;
|
||||
this.scenario = builder.scenario;
|
||||
this.timeout = builder.timeout;
|
||||
this.testExecution = builder.testExecution;
|
||||
this.preTestSetup = builder.preTestSetup;
|
||||
this.postTestCleanup = builder.postTestCleanup;
|
||||
this.environmentConfig = builder.environmentConfig;
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public ScenarioType getScenario() { return scenario; }
|
||||
public Duration getTimeout() { return timeout; }
|
||||
public Function<TestContext, TestExecutionResult> getTestExecution() { return testExecution; }
|
||||
public Consumer<TestContext> getPreTestSetup() { return preTestSetup; }
|
||||
public Consumer<TestContext> getPostTestCleanup() { return postTestCleanup; }
|
||||
public Map<String, Object> getEnvironmentConfig() { return environmentConfig; }
|
||||
|
||||
public static class Builder {
|
||||
private final String name;
|
||||
private ScenarioType scenario;
|
||||
private Duration timeout = Duration.ofMinutes(5);
|
||||
private Function<TestContext, TestExecutionResult> testExecution;
|
||||
private Consumer<TestContext> preTestSetup;
|
||||
private Consumer<TestContext> postTestCleanup;
|
||||
private Map<String, Object> environmentConfig = new HashMap<>();
|
||||
|
||||
public Builder(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Builder withScenario(ScenarioType scenario) {
|
||||
this.scenario = scenario;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTimeout(Duration timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTestExecution(Function<TestContext, TestExecutionResult> testExecution) {
|
||||
this.testExecution = testExecution;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withPreTestSetup(Consumer<TestContext> preTestSetup) {
|
||||
this.preTestSetup = preTestSetup;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withPostTestCleanup(Consumer<TestContext> postTestCleanup) {
|
||||
this.postTestCleanup = postTestCleanup;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withEnvironmentConfig(Map<String, Object> config) {
|
||||
this.environmentConfig.putAll(config);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AgentTestCase build() {
|
||||
if (testExecution == null) {
|
||||
throw new IllegalStateException("Test execution function is required");
|
||||
}
|
||||
return new AgentTestCase(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test execution context providing access to test environment.
|
||||
*/
|
||||
class TestContext {
|
||||
private final String testId;
|
||||
private final String testName;
|
||||
private final LocalDateTime startTime;
|
||||
private final Map<String, Object> testData;
|
||||
|
||||
public TestContext(String testId, String testName) {
|
||||
this.testId = testId;
|
||||
this.testName = testName;
|
||||
this.startTime = LocalDateTime.now();
|
||||
this.testData = new HashMap<>();
|
||||
}
|
||||
|
||||
public String getTestId() { return testId; }
|
||||
public String getTestName() { return testName; }
|
||||
public LocalDateTime getStartTime() { return startTime; }
|
||||
|
||||
public void setData(String key, Object value) {
|
||||
testData.put(key, value);
|
||||
}
|
||||
|
||||
public Object getData(String key) {
|
||||
return testData.get(key);
|
||||
}
|
||||
|
||||
public Map<String, Object> getAllData() {
|
||||
return new HashMap<>(testData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of test execution.
|
||||
*/
|
||||
class TestExecutionResult {
|
||||
private final boolean success;
|
||||
private final String message;
|
||||
private final String failureReason;
|
||||
private final Map<String, Object> details;
|
||||
|
||||
private TestExecutionResult(boolean success, String message, String failureReason, Map<String, Object> details) {
|
||||
this.success = success;
|
||||
this.message = message;
|
||||
this.failureReason = failureReason;
|
||||
this.details = details != null ? details : new HashMap<>();
|
||||
}
|
||||
|
||||
public static TestExecutionResult success(String message) {
|
||||
return new TestExecutionResult(true, message, null, null);
|
||||
}
|
||||
|
||||
public static TestExecutionResult success(String message, Map<String, Object> details) {
|
||||
return new TestExecutionResult(true, message, null, details);
|
||||
}
|
||||
|
||||
public static TestExecutionResult failure(String failureReason) {
|
||||
return new TestExecutionResult(false, null, failureReason, null);
|
||||
}
|
||||
|
||||
public static TestExecutionResult failure(String failureReason, Map<String, Object> details) {
|
||||
return new TestExecutionResult(false, null, failureReason, details);
|
||||
}
|
||||
|
||||
public boolean isSuccess() { return success; }
|
||||
public String getMessage() { return message; }
|
||||
public String getFailureReason() { return failureReason; }
|
||||
public Map<String, Object> getDetails() { return new HashMap<>(details); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Test result with status and timing information.
|
||||
*/
|
||||
class TestResult {
|
||||
private final String testId;
|
||||
private final String testName;
|
||||
private final TestStatus status;
|
||||
private final String message;
|
||||
private final long durationMs;
|
||||
private final LocalDateTime timestamp;
|
||||
private final Map<String, Object> details;
|
||||
|
||||
private TestResult(String testId, String testName, TestStatus status, String message, long durationMs, Map<String, Object> details) {
|
||||
this.testId = testId;
|
||||
this.testName = testName;
|
||||
this.status = status;
|
||||
this.message = message;
|
||||
this.durationMs = durationMs;
|
||||
this.timestamp = LocalDateTime.now();
|
||||
this.details = details != null ? details : new HashMap<>();
|
||||
}
|
||||
|
||||
public static TestResult success(String testId, String testName, long durationMs) {
|
||||
return new TestResult(testId, testName, TestStatus.PASSED, "Test passed", durationMs, null);
|
||||
}
|
||||
|
||||
public static TestResult failure(String testId, String testName, String message) {
|
||||
return new TestResult(testId, testName, TestStatus.FAILED, message, 0, null);
|
||||
}
|
||||
|
||||
public static TestResult timeout(String testId, String testName) {
|
||||
return new TestResult(testId, testName, TestStatus.TIMEOUT, "Test timed out", 0, null);
|
||||
}
|
||||
|
||||
public TestResult withDuration(long durationMs) {
|
||||
return new TestResult(testId, testName, status, message, durationMs, details);
|
||||
}
|
||||
|
||||
public TestResult withDetails(Map<String, Object> details) {
|
||||
return new TestResult(testId, testName, status, message, durationMs, details);
|
||||
}
|
||||
|
||||
public String getTestId() { return testId; }
|
||||
public String getTestName() { return testName; }
|
||||
public TestStatus getStatus() { return status; }
|
||||
public String getMessage() { return message; }
|
||||
public long getDurationMs() { return durationMs; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
public Map<String, Object> getDetails() { return new HashMap<>(details); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Test suite containing multiple test cases.
|
||||
*/
|
||||
class TestSuite {
|
||||
private final String name;
|
||||
private final Map<String, AgentTestCase> testCases;
|
||||
|
||||
public TestSuite(String name) {
|
||||
this.name = name;
|
||||
this.testCases = new HashMap<>();
|
||||
}
|
||||
|
||||
public void addTest(String testName, AgentTestCase testCase) {
|
||||
testCases.put(testName, testCase);
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public Map<String, AgentTestCase> getTestCases() { return new HashMap<>(testCases); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of running an entire test suite.
|
||||
*/
|
||||
class TestSuiteResult {
|
||||
private final String suiteName;
|
||||
private final List<TestResult> results;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public TestSuiteResult(String suiteName, List<TestResult> results) {
|
||||
this.suiteName = suiteName;
|
||||
this.results = new ArrayList<>(results);
|
||||
this.timestamp = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public String getSuiteName() { return suiteName; }
|
||||
public List<TestResult> getResults() { return new ArrayList<>(results); }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
|
||||
public long getPassedCount() {
|
||||
return results.stream().filter(r -> r.getStatus() == TestStatus.PASSED).count();
|
||||
}
|
||||
|
||||
public long getFailedCount() {
|
||||
return results.stream().filter(r -> r.getStatus() == TestStatus.FAILED).count();
|
||||
}
|
||||
|
||||
public long getTimeoutCount() {
|
||||
return results.stream().filter(r -> r.getStatus() == TestStatus.TIMEOUT).count();
|
||||
}
|
||||
}
|
||||
|
||||
// ================================
|
||||
// ENUMS
|
||||
// ================================
|
||||
|
||||
enum TestStatus {
|
||||
PASSED, FAILED, TIMEOUT, SKIPPED
|
||||
}
|
||||
|
||||
enum ScenarioType {
|
||||
BASIC_GAMEPLAY,
|
||||
PERFORMANCE_STRESS,
|
||||
SCRIPTING_AUTOMATION,
|
||||
INTERFACE_INTERACTION,
|
||||
NETWORK_CONDITIONS,
|
||||
MEMORY_PRESSURE,
|
||||
CONCURRENT_AGENTS,
|
||||
ERROR_RECOVERY
|
||||
}
|
||||
@@ -108,4 +108,158 @@ public class ClientConfiguration {
|
||||
try {
|
||||
Properties props = new Properties();
|
||||
props.putAll(settings);
|
||||
props.store(Files.newBufferedWriter(configFile), \n \"Modernized OpenOSRS Client Configuration\");\n \n logger.info(\"Configuration saved to {}\", configFile);\n \n } catch (IOException e) {\n logger.error(\"Failed to save configuration to {}\", configFile, e);\n }\n }\n \n // Configuration getters with type conversion\n \n public String getString(String key, String defaultValue) {\n return settings.getOrDefault(key, defaultValue);\n }\n \n public String getString(String key) {\n return settings.get(key);\n }\n \n public int getInt(String key, int defaultValue) {\n try {\n String value = settings.get(key);\n return value != null ? Integer.parseInt(value) : defaultValue;\n } catch (NumberFormatException e) {\n logger.warn(\"Invalid integer value for {}: {}\", key, settings.get(key));\n return defaultValue;\n }\n }\n \n public long getLong(String key, long defaultValue) {\n try {\n String value = settings.get(key);\n return value != null ? Long.parseLong(value) : defaultValue;\n } catch (NumberFormatException e) {\n logger.warn(\"Invalid long value for {}: {}\", key, settings.get(key));\n return defaultValue;\n }\n }\n \n public boolean getBoolean(String key, boolean defaultValue) {\n String value = settings.get(key);\n return value != null ? Boolean.parseBoolean(value) : defaultValue;\n }\n \n public double getDouble(String key, double defaultValue) {\n try {\n String value = settings.get(key);\n return value != null ? Double.parseDouble(value) : defaultValue;\n } catch (NumberFormatException e) {\n logger.warn(\"Invalid double value for {}: {}\", key, settings.get(key));\n return defaultValue;\n }\n }\n \n // Configuration setters\n \n public void setString(String key, String value) {\n if (value != null) {\n settings.put(key, value);\n } else {\n settings.remove(key);\n }\n }\n \n public void setInt(String key, int value) {\n settings.put(key, String.valueOf(value));\n }\n \n public void setLong(String key, long value) {\n settings.put(key, String.valueOf(value));\n }\n \n public void setBoolean(String key, boolean value) {\n settings.put(key, String.valueOf(value));\n }\n \n public void setDouble(String key, double value) {\n settings.put(key, String.valueOf(value));\n }\n \n // Convenience methods for common settings\n \n public int getTargetFPS() {\n return getInt(\"client.fps.target\", 50);\n }\n \n public void setTargetFPS(int fps) {\n setInt(\"client.fps.target\", fps);\n }\n \n public boolean isLowMemoryMode() {\n return getBoolean(\"client.memory.lowMemoryMode\", false);\n }\n \n public void setLowMemoryMode(boolean enabled) {\n setBoolean(\"client.memory.lowMemoryMode\", enabled);\n }\n \n public boolean isDebugEnabled() {\n return getBoolean(\"client.debug.enabled\", false);\n }\n \n public void setDebugEnabled(boolean enabled) {\n setBoolean(\"client.debug.enabled\", enabled);\n }\n \n public boolean arePluginsEnabled() {\n return getBoolean(\"client.plugins.enabled\", true);\n }\n \n public void setPluginsEnabled(boolean enabled) {\n setBoolean(\"client.plugins.enabled\", enabled);\n }\n \n public boolean isScriptingEnabled() {\n return getBoolean(\"client.scripting.enabled\", true);\n }\n \n public void setScriptingEnabled(boolean enabled) {\n setBoolean(\"client.scripting.enabled\", enabled);\n }\n \n public int getNetworkTimeout() {\n return getInt(\"network.timeout.ms\", 5000);\n }\n \n public void setNetworkTimeout(int timeoutMs) {\n setInt(\"network.timeout.ms\", timeoutMs);\n }\n \n public int getNetworkMaxRetries() {\n return getInt(\"network.maxRetries\", 3);\n }\n \n public void setNetworkMaxRetries(int maxRetries) {\n setInt(\"network.maxRetries\", maxRetries);\n }\n \n public int getAgentApiTimeout() {\n return getInt(\"agent.api.asyncTimeout.ms\", 1000);\n }\n \n public void setAgentApiTimeout(int timeoutMs) {\n setInt(\"agent.api.asyncTimeout.ms\", timeoutMs);\n }\n \n public boolean isAgentApiStatisticsEnabled() {\n return getBoolean(\"agent.api.enableStatistics\", true);\n }\n \n public void setAgentApiStatisticsEnabled(boolean enabled) {\n setBoolean(\"agent.api.enableStatistics\", enabled);\n }\n}"
|
||||
props.store(Files.newBufferedWriter(configFile),
|
||||
"Modernized OpenOSRS Client Configuration");
|
||||
|
||||
logger.info("Configuration saved to {}", configFile);
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.error("Failed to save configuration to {}", configFile, e);
|
||||
}
|
||||
}
|
||||
|
||||
// Configuration getters with type conversion
|
||||
|
||||
public String getString(String key, String defaultValue) {
|
||||
return settings.getOrDefault(key, defaultValue);
|
||||
}
|
||||
|
||||
public String getString(String key) {
|
||||
return settings.get(key);
|
||||
}
|
||||
|
||||
public int getInt(String key, int defaultValue) {
|
||||
try {
|
||||
String value = settings.get(key);
|
||||
return value != null ? Integer.parseInt(value) : defaultValue;
|
||||
} catch (NumberFormatException e) {
|
||||
logger.warn("Invalid integer value for {}: {}", key, settings.get(key));
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public long getLong(String key, long defaultValue) {
|
||||
try {
|
||||
String value = settings.get(key);
|
||||
return value != null ? Long.parseLong(value) : defaultValue;
|
||||
} catch (NumberFormatException e) {
|
||||
logger.warn("Invalid long value for {}: {}", key, settings.get(key));
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getBoolean(String key, boolean defaultValue) {
|
||||
String value = settings.get(key);
|
||||
return value != null ? Boolean.parseBoolean(value) : defaultValue;
|
||||
}
|
||||
|
||||
public double getDouble(String key, double defaultValue) {
|
||||
try {
|
||||
String value = settings.get(key);
|
||||
return value != null ? Double.parseDouble(value) : defaultValue;
|
||||
} catch (NumberFormatException e) {
|
||||
logger.warn("Invalid double value for {}: {}", key, settings.get(key));
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Configuration setters
|
||||
|
||||
public void setString(String key, String value) {
|
||||
if (value != null) {
|
||||
settings.put(key, value);
|
||||
} else {
|
||||
settings.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
public void setInt(String key, int value) {
|
||||
settings.put(key, String.valueOf(value));
|
||||
}
|
||||
|
||||
public void setLong(String key, long value) {
|
||||
settings.put(key, String.valueOf(value));
|
||||
}
|
||||
|
||||
public void setBoolean(String key, boolean value) {
|
||||
settings.put(key, String.valueOf(value));
|
||||
}
|
||||
|
||||
public void setDouble(String key, double value) {
|
||||
settings.put(key, String.valueOf(value));
|
||||
}
|
||||
|
||||
// Convenience methods for common settings
|
||||
|
||||
public int getTargetFPS() {
|
||||
return getInt("client.fps.target", 50);
|
||||
}
|
||||
|
||||
public void setTargetFPS(int fps) {
|
||||
setInt("client.fps.target", fps);
|
||||
}
|
||||
|
||||
public boolean isLowMemoryMode() {
|
||||
return getBoolean("client.memory.lowMemoryMode", false);
|
||||
}
|
||||
|
||||
public void setLowMemoryMode(boolean enabled) {
|
||||
setBoolean("client.memory.lowMemoryMode", enabled);
|
||||
}
|
||||
|
||||
public boolean isDebugEnabled() {
|
||||
return getBoolean("client.debug.enabled", false);
|
||||
}
|
||||
|
||||
public void setDebugEnabled(boolean enabled) {
|
||||
setBoolean("client.debug.enabled", enabled);
|
||||
}
|
||||
|
||||
public boolean arePluginsEnabled() {
|
||||
return getBoolean("client.plugins.enabled", true);
|
||||
}
|
||||
|
||||
public void setPluginsEnabled(boolean enabled) {
|
||||
setBoolean("client.plugins.enabled", enabled);
|
||||
}
|
||||
|
||||
public boolean isScriptingEnabled() {
|
||||
return getBoolean("client.scripting.enabled", true);
|
||||
}
|
||||
|
||||
public void setScriptingEnabled(boolean enabled) {
|
||||
setBoolean("client.scripting.enabled", enabled);
|
||||
}
|
||||
|
||||
public int getNetworkTimeout() {
|
||||
return getInt("network.timeout.ms", 5000);
|
||||
}
|
||||
|
||||
public void setNetworkTimeout(int timeoutMs) {
|
||||
setInt("network.timeout.ms", timeoutMs);
|
||||
}
|
||||
|
||||
public int getNetworkMaxRetries() {
|
||||
return getInt("network.maxRetries", 3);
|
||||
}
|
||||
|
||||
public void setNetworkMaxRetries(int maxRetries) {
|
||||
setInt("network.maxRetries", maxRetries);
|
||||
}
|
||||
|
||||
public int getAgentApiTimeout() {
|
||||
return getInt("agent.api.asyncTimeout.ms", 1000);
|
||||
}
|
||||
|
||||
public void setAgentApiTimeout(int timeoutMs) {
|
||||
setInt("agent.api.asyncTimeout.ms", timeoutMs);
|
||||
}
|
||||
|
||||
public boolean isAgentApiStatisticsEnabled() {
|
||||
return getBoolean("agent.api.enableStatistics", true);
|
||||
}
|
||||
|
||||
public void setAgentApiStatisticsEnabled(boolean enabled) {
|
||||
setBoolean("agent.api.enableStatistics", enabled);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,8 @@
|
||||
/**
|
||||
* Set the RuneLite client instance for bridge integration.
|
||||
*/
|
||||
public void setRuneLiteClient(Client client) {
|
||||
runeLiteBridge.setRuneLiteClient(client);
|
||||
logger.info("RuneLite client integration established");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if RuneLite bridge is active.
|
||||
*/
|
||||
public boolean isBridgeActive() {
|
||||
return runeLiteBridge.isActive();
|
||||
}package com.openosrs.client.core;
|
||||
package com.openosrs.client.core;
|
||||
|
||||
import com.openosrs.client.core.bridge.RuneLiteBridge;
|
||||
import com.openosrs.client.core.bridge.BridgeAdapter;
|
||||
import com.openosrs.client.core.state.*;
|
||||
|
||||
import net.runelite.api.Client;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -234,600 +234,4 @@ public class InventoryState {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* InterfaceState - Manages game interface and widget state.
|
||||
*/
|
||||
class InterfaceState {
|
||||
private static final Logger logger = LoggerFactory.getLogger(InterfaceState.class);
|
||||
|
||||
private final EventSystem eventSystem;
|
||||
private final AtomicInteger currentInterfaceId = new AtomicInteger(-1);
|
||||
private final AtomicReference<String> chatboxText = new AtomicReference<>("");
|
||||
|
||||
public InterfaceState(EventSystem eventSystem) {
|
||||
this.eventSystem = eventSystem;
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
logger.debug("Initializing InterfaceState");
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
logger.debug("Shutting down InterfaceState");
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
// Update interface state
|
||||
}
|
||||
|
||||
public void openInterface(int interfaceId) {
|
||||
int oldInterface = currentInterfaceId.getAndSet(interfaceId);
|
||||
if (oldInterface != interfaceId) {
|
||||
eventSystem.fireInterfaceOpened(interfaceId);
|
||||
logger.debug("Opened interface: {}", interfaceId);
|
||||
}
|
||||
}
|
||||
|
||||
public void closeInterface() {
|
||||
int oldInterface = currentInterfaceId.getAndSet(-1);
|
||||
if (oldInterface != -1) {
|
||||
eventSystem.fireInterfaceClosed(oldInterface);
|
||||
logger.debug("Closed interface: {}", oldInterface);
|
||||
}
|
||||
}
|
||||
|
||||
public int getCurrentInterfaceId() {
|
||||
return currentInterfaceId.get();
|
||||
}
|
||||
|
||||
public boolean isInterfaceOpen() {
|
||||
return currentInterfaceId.get() != -1;
|
||||
}
|
||||
|
||||
public void setChatboxText(String text) {
|
||||
chatboxText.set(text != null ? text : "");
|
||||
}
|
||||
|
||||
public String getChatboxText() {
|
||||
return chatboxText.get();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NetworkState - Manages network connection state.
|
||||
*/
|
||||
class NetworkState {
|
||||
private static final Logger logger = LoggerFactory.getLogger(NetworkState.class);
|
||||
|
||||
private final EventSystem eventSystem;
|
||||
private final AtomicInteger connectionState = new AtomicInteger(ConnectionState.DISCONNECTED);
|
||||
private final AtomicInteger ping = new AtomicInteger(0);
|
||||
|
||||
public NetworkState(EventSystem eventSystem) {
|
||||
this.eventSystem = eventSystem;
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
logger.debug("Initializing NetworkState");
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
logger.debug("Shutting down NetworkState");
|
||||
setConnectionState(ConnectionState.DISCONNECTED);
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
// Update network state, check connection, etc.
|
||||
}
|
||||
|
||||
public void setConnectionState(int state) {
|
||||
int oldState = connectionState.getAndSet(state);
|
||||
if (oldState != state) {
|
||||
logger.debug("Connection state changed: {} -> {}", oldState, state);
|
||||
|
||||
if (state == ConnectionState.DISCONNECTED && oldState != ConnectionState.DISCONNECTED) {
|
||||
eventSystem.fireEvent(EventSystem.EventType.CONNECTION_LOST,
|
||||
new ConnectionEvent(false));
|
||||
} else if (state == ConnectionState.CONNECTED && oldState != ConnectionState.CONNECTED) {
|
||||
eventSystem.fireEvent(EventSystem.EventType.CONNECTION_RESTORED,
|
||||
new ConnectionEvent(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getConnectionState() {
|
||||
return connectionState.get();
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return connectionState.get() == ConnectionState.CONNECTED;
|
||||
}
|
||||
|
||||
public void setPing(int ping) {
|
||||
this.ping.set(ping);
|
||||
}
|
||||
|
||||
public int getPing() {
|
||||
return ping.get();
|
||||
}
|
||||
|
||||
public static class ConnectionState {
|
||||
public static final int DISCONNECTED = 0;
|
||||
public static final int CONNECTING = 1;
|
||||
public static final int CONNECTED = 2;
|
||||
public static final int RECONNECTING = 3;
|
||||
public static final int FAILED = 4;
|
||||
}
|
||||
|
||||
public static class ConnectionEvent extends EventSystem.GameEvent {
|
||||
private final boolean connected;
|
||||
|
||||
public ConnectionEvent(boolean connected) {
|
||||
super(connected ? EventSystem.EventType.CONNECTION_RESTORED : EventSystem.EventType.CONNECTION_LOST);
|
||||
this.connected = connected;
|
||||
}
|
||||
|
||||
public boolean isConnected() { return connected; }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LoginState - Manages login credentials, authentication flow, and session state.
|
||||
* Based on RuneLite's Login class but modernized for agent use.
|
||||
*/
|
||||
class LoginState {
|
||||
private static final Logger logger = LoggerFactory.getLogger(LoginState.class);
|
||||
private static final int MAX_USERNAME_LENGTH = 320;
|
||||
private static final int MAX_PASSWORD_LENGTH = 20;
|
||||
private static final int MAX_OTP_LENGTH = 6;
|
||||
|
||||
private final EventSystem eventSystem;
|
||||
private final ReadWriteLock loginLock = new ReentrantReadWriteLock();
|
||||
|
||||
// Login credentials
|
||||
private final AtomicReference<String> username = new AtomicReference<>("");
|
||||
private final AtomicReference<String> password = new AtomicReference<>("");
|
||||
private final AtomicReference<String> otp = new AtomicReference<>("");
|
||||
|
||||
// Login state
|
||||
private final AtomicInteger loginIndex = new AtomicInteger(LoginScreenState.CREDENTIALS);
|
||||
private final AtomicInteger currentLoginField = new AtomicInteger(LoginField.USERNAME);
|
||||
|
||||
// Login responses and messages
|
||||
private final AtomicReference<String> response0 = new AtomicReference<>("");
|
||||
private final AtomicReference<String> response1 = new AtomicReference<>("");
|
||||
private final AtomicReference<String> response2 = new AtomicReference<>("");
|
||||
private final AtomicReference<String> response3 = new AtomicReference<>("");
|
||||
|
||||
// World selection
|
||||
private final AtomicBoolean worldSelectOpen = new AtomicBoolean(false);
|
||||
private final AtomicInteger selectedWorldIndex = new AtomicInteger(-1);
|
||||
private final AtomicInteger worldSelectPage = new AtomicInteger(0);
|
||||
|
||||
// Session and connection
|
||||
private final AtomicInteger sessionId = new AtomicInteger(0);
|
||||
private final AtomicReference<String> sessionToken = new AtomicReference<>("");
|
||||
private final AtomicLong lastLoginAttempt = new AtomicLong(0);
|
||||
private final AtomicInteger loginAttempts = new AtomicInteger(0);
|
||||
|
||||
// Loading state
|
||||
private final AtomicInteger loadingPercent = new AtomicInteger(0);
|
||||
private final AtomicReference<String> loadingText = new AtomicReference<>("");
|
||||
|
||||
public LoginState(EventSystem eventSystem) {
|
||||
this.eventSystem = eventSystem;
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
logger.debug("Initializing LoginState");
|
||||
reset();
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
logger.debug("Shutting down LoginState");
|
||||
clearCredentials();
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
// Update login state, check timeouts, etc.
|
||||
checkLoginTimeout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset login state to initial values.
|
||||
*/
|
||||
public void reset() {
|
||||
loginLock.writeLock().lock();
|
||||
try {
|
||||
setLoginIndex(LoginScreenState.CREDENTIALS);
|
||||
setCurrentLoginField(LoginField.USERNAME);
|
||||
clearResponses();
|
||||
worldSelectOpen.set(false);
|
||||
selectedWorldIndex.set(-1);
|
||||
worldSelectPage.set(0);
|
||||
sessionId.set(0);
|
||||
sessionToken.set("");
|
||||
lastLoginAttempt.set(0);
|
||||
loginAttempts.set(0);
|
||||
loadingPercent.set(0);
|
||||
loadingText.set("");
|
||||
} finally {
|
||||
loginLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear stored credentials for security.
|
||||
*/
|
||||
public void clearCredentials() {
|
||||
loginLock.writeLock().lock();
|
||||
try {
|
||||
username.set("");
|
||||
password.set("");
|
||||
otp.set("");
|
||||
eventSystem.fireEvent(EventSystem.EventType.CREDENTIALS_CLEARED, new LoginEvent(LoginEventType.CREDENTIALS_CLEARED));
|
||||
} finally {
|
||||
loginLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set username with validation.
|
||||
*/
|
||||
public boolean setUsername(String username) {
|
||||
if (username == null) username = "";
|
||||
if (username.length() > MAX_USERNAME_LENGTH) {
|
||||
logger.warn("Username too long: {} characters", username.length());
|
||||
return false;
|
||||
}
|
||||
|
||||
loginLock.writeLock().lock();
|
||||
try {
|
||||
String oldUsername = this.username.getAndSet(username);
|
||||
if (!oldUsername.equals(username)) {
|
||||
eventSystem.fireEvent(EventSystem.EventType.LOGIN_USERNAME_CHANGED,
|
||||
new LoginEvent(LoginEventType.USERNAME_CHANGED, username));
|
||||
logger.debug("Username changed");
|
||||
}
|
||||
return true;
|
||||
} finally {
|
||||
loginLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set password with validation.
|
||||
*/
|
||||
public boolean setPassword(String password) {
|
||||
if (password == null) password = "";
|
||||
if (password.length() > MAX_PASSWORD_LENGTH) {
|
||||
logger.warn("Password too long: {} characters", password.length());
|
||||
return false;
|
||||
}
|
||||
|
||||
loginLock.writeLock().lock();
|
||||
try {
|
||||
String oldPassword = this.password.getAndSet(password);
|
||||
if (!oldPassword.equals(password)) {
|
||||
eventSystem.fireEvent(EventSystem.EventType.LOGIN_PASSWORD_CHANGED,
|
||||
new LoginEvent(LoginEventType.PASSWORD_CHANGED));
|
||||
logger.debug("Password changed");
|
||||
}
|
||||
return true;
|
||||
} finally {
|
||||
loginLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set OTP (One-Time Password) with validation.
|
||||
*/
|
||||
public boolean setOtp(String otp) {
|
||||
if (otp == null) otp = "";
|
||||
if (otp.length() > MAX_OTP_LENGTH) {
|
||||
logger.warn("OTP too long: {} characters", otp.length());
|
||||
return false;
|
||||
}
|
||||
|
||||
// OTP should be numeric
|
||||
if (!otp.isEmpty() && !otp.matches("\\d+")) {
|
||||
logger.warn("OTP contains non-numeric characters");
|
||||
return false;
|
||||
}
|
||||
|
||||
loginLock.writeLock().lock();
|
||||
try {
|
||||
String oldOtp = this.otp.getAndSet(otp);
|
||||
if (!oldOtp.equals(otp)) {
|
||||
eventSystem.fireEvent(EventSystem.EventType.LOGIN_OTP_CHANGED,
|
||||
new LoginEvent(LoginEventType.OTP_CHANGED));
|
||||
logger.debug("OTP changed");
|
||||
}
|
||||
return true;
|
||||
} finally {
|
||||
loginLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set login screen state.
|
||||
*/
|
||||
public void setLoginIndex(int loginIndex) {
|
||||
int oldIndex = this.loginIndex.getAndSet(loginIndex);
|
||||
if (oldIndex != loginIndex) {
|
||||
eventSystem.fireEvent(EventSystem.EventType.LOGIN_STATE_CHANGED,
|
||||
new LoginEvent(LoginEventType.STATE_CHANGED, String.valueOf(loginIndex)));
|
||||
logger.debug("Login state changed: {} -> {}", oldIndex, loginIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current login field focus.
|
||||
*/
|
||||
public void setCurrentLoginField(int field) {
|
||||
int oldField = this.currentLoginField.getAndSet(field);
|
||||
if (oldField != field) {
|
||||
eventSystem.fireEvent(EventSystem.EventType.LOGIN_FIELD_CHANGED,
|
||||
new LoginEvent(LoginEventType.FIELD_CHANGED, String.valueOf(field)));
|
||||
logger.debug("Login field changed: {} -> {}", oldField, field);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set login response messages.
|
||||
*/
|
||||
public void setLoginResponse(String response0, String response1, String response2, String response3) {
|
||||
loginLock.writeLock().lock();
|
||||
try {
|
||||
this.response0.set(response0 != null ? response0 : "");
|
||||
this.response1.set(response1 != null ? response1 : "");
|
||||
this.response2.set(response2 != null ? response2 : "");
|
||||
this.response3.set(response3 != null ? response3 : "");
|
||||
|
||||
eventSystem.fireEvent(EventSystem.EventType.LOGIN_RESPONSE_CHANGED,
|
||||
new LoginEvent(LoginEventType.RESPONSE_CHANGED, response1));
|
||||
logger.debug("Login response updated: {}", response1);
|
||||
} finally {
|
||||
loginLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear login response messages.
|
||||
*/
|
||||
public void clearResponses() {
|
||||
setLoginResponse("", "", "", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set loading state.
|
||||
*/
|
||||
public void setLoadingState(int percent, String text) {
|
||||
loadingPercent.set(Math.max(0, Math.min(100, percent)));
|
||||
loadingText.set(text != null ? text : "");
|
||||
|
||||
eventSystem.fireEvent(EventSystem.EventType.LOGIN_LOADING_CHANGED,
|
||||
new LoginEvent(LoginEventType.LOADING_CHANGED, text));
|
||||
}
|
||||
|
||||
/**
|
||||
* Record login attempt.
|
||||
*/
|
||||
public void recordLoginAttempt() {
|
||||
lastLoginAttempt.set(System.currentTimeMillis());
|
||||
int attempts = loginAttempts.incrementAndGet();
|
||||
logger.debug("Login attempt #{}", attempts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for login timeout.
|
||||
*/
|
||||
private void checkLoginTimeout() {
|
||||
long lastAttempt = lastLoginAttempt.get();
|
||||
if (lastAttempt > 0 && System.currentTimeMillis() - lastAttempt > 30000) { // 30 second timeout
|
||||
if (loginIndex.get() == LoginScreenState.CONNECTING) {
|
||||
setLoginResponse("", "Connection timed out.", "Please try again.", "");
|
||||
setLoginIndex(LoginScreenState.CREDENTIALS);
|
||||
logger.warn("Login attempt timed out");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate credentials for login attempt.
|
||||
*/
|
||||
public boolean validateCredentials() {
|
||||
String user = username.get().trim();
|
||||
String pass = password.get();
|
||||
|
||||
if (user.isEmpty()) {
|
||||
setLoginResponse("", "Please enter your username/email address.", "", "");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pass.isEmpty()) {
|
||||
setLoginResponse("", "Please enter your password.", "", "");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Agent-friendly login method.
|
||||
*/
|
||||
public boolean attemptLogin(String username, String password, String otp) {
|
||||
if (!setUsername(username)) return false;
|
||||
if (!setPassword(password)) return false;
|
||||
if (otp != null && !setOtp(otp)) return false;
|
||||
|
||||
if (!validateCredentials()) return false;
|
||||
|
||||
recordLoginAttempt();
|
||||
setLoginIndex(LoginScreenState.CONNECTING);
|
||||
setLoginResponse("", "Connecting to server...", "", "");
|
||||
|
||||
eventSystem.fireEvent(EventSystem.EventType.LOGIN_ATTEMPT_STARTED,
|
||||
new LoginEvent(LoginEventType.ATTEMPT_STARTED, username));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle successful login.
|
||||
*/
|
||||
public void onLoginSuccess(int sessionId, String sessionToken) {
|
||||
this.sessionId.set(sessionId);
|
||||
this.sessionToken.set(sessionToken != null ? sessionToken : "");
|
||||
setLoginIndex(LoginScreenState.LOGGED_IN);
|
||||
clearResponses();
|
||||
|
||||
eventSystem.fireEvent(EventSystem.EventType.LOGIN_SUCCESS,
|
||||
new LoginEvent(LoginEventType.SUCCESS, String.valueOf(sessionId)));
|
||||
logger.info("Login successful, session: {}", sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle login failure.
|
||||
*/
|
||||
public void onLoginFailure(int errorCode, String errorMessage) {
|
||||
setLoginIndex(LoginScreenState.CREDENTIALS);
|
||||
|
||||
String message = errorMessage != null ? errorMessage : "Login failed";
|
||||
setLoginResponse("", message, "", "");
|
||||
|
||||
eventSystem.fireEvent(EventSystem.EventType.LOGIN_FAILED,
|
||||
new LoginEvent(LoginEventType.FAILED, String.valueOf(errorCode)));
|
||||
logger.warn("Login failed: {} ({})", message, errorCode);
|
||||
}
|
||||
|
||||
// Getters
|
||||
public String getUsername() { return username.get(); }
|
||||
public String getPassword() { return password.get(); }
|
||||
public String getOtp() { return otp.get(); }
|
||||
public int getLoginIndex() { return loginIndex.get(); }
|
||||
public int getCurrentLoginField() { return currentLoginField.get(); }
|
||||
public String getResponse0() { return response0.get(); }
|
||||
public String getResponse1() { return response1.get(); }
|
||||
public String getResponse2() { return response2.get(); }
|
||||
public String getResponse3() { return response3.get(); }
|
||||
public boolean isWorldSelectOpen() { return worldSelectOpen.get(); }
|
||||
public int getSelectedWorldIndex() { return selectedWorldIndex.get(); }
|
||||
public int getWorldSelectPage() { return worldSelectPage.get(); }
|
||||
public int getSessionId() { return sessionId.get(); }
|
||||
public String getSessionToken() { return sessionToken.get(); }
|
||||
public int getLoadingPercent() { return loadingPercent.get(); }
|
||||
public String getLoadingText() { return loadingText.get(); }
|
||||
public int getLoginAttempts() { return loginAttempts.get(); }
|
||||
public long getLastLoginAttempt() { return lastLoginAttempt.get(); }
|
||||
|
||||
public boolean isLoggedIn() {
|
||||
return loginIndex.get() == LoginScreenState.LOGGED_IN && sessionId.get() > 0;
|
||||
}
|
||||
|
||||
public boolean isConnecting() {
|
||||
return loginIndex.get() == LoginScreenState.CONNECTING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Login screen states (based on RuneLite's loginIndex).
|
||||
*/
|
||||
public static class LoginScreenState {
|
||||
public static final int STARTUP = 0;
|
||||
public static final int CREDENTIALS = 2;
|
||||
public static final int AUTHENTICATOR = 4;
|
||||
public static final int CONNECTING = 5;
|
||||
public static final int LOGGED_IN = 10;
|
||||
public static final int WORLD_SELECT = 7;
|
||||
public static final int ERROR = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Login field focus constants.
|
||||
*/
|
||||
public static class LoginField {
|
||||
public static final int USERNAME = 0;
|
||||
public static final int PASSWORD = 1;
|
||||
public static final int OTP = 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Login event types.
|
||||
*/
|
||||
public enum LoginEventType {
|
||||
CREDENTIALS_CLEARED,
|
||||
USERNAME_CHANGED,
|
||||
PASSWORD_CHANGED,
|
||||
OTP_CHANGED,
|
||||
STATE_CHANGED,
|
||||
FIELD_CHANGED,
|
||||
RESPONSE_CHANGED,
|
||||
LOADING_CHANGED,
|
||||
ATTEMPT_STARTED,
|
||||
SUCCESS,
|
||||
FAILED
|
||||
}
|
||||
|
||||
/**
|
||||
* Login event class.
|
||||
*/
|
||||
public static class LoginEvent extends EventSystem.GameEvent {
|
||||
private final LoginEventType loginEventType;
|
||||
private final String data;
|
||||
|
||||
public LoginEvent(LoginEventType type) {
|
||||
this(type, null);
|
||||
}
|
||||
|
||||
public LoginEvent(LoginEventType type, String data) {
|
||||
super(EventSystem.EventType.LOGIN_EVENT);
|
||||
this.loginEventType = type;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public LoginEventType getLoginEventType() { return loginEventType; }
|
||||
public String getData() { return data; }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ClientConfiguration - Manages client settings and configuration.
|
||||
*/
|
||||
class ClientConfiguration {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ClientConfiguration.class);
|
||||
|
||||
// Game settings
|
||||
private final AtomicInteger gameWidth = new AtomicInteger(1024);
|
||||
private final AtomicInteger gameHeight = new AtomicInteger(768);
|
||||
|
||||
// Client settings
|
||||
private final AtomicInteger fps = new AtomicInteger(50);
|
||||
private final AtomicInteger memoryUsage = new AtomicInteger(512);
|
||||
private final AtomicReference<String> worldUrl = new AtomicReference<>("oldschool1.runescape.com");
|
||||
|
||||
public void load() {
|
||||
logger.debug("Loading client configuration");
|
||||
// Load configuration from file or environment
|
||||
}
|
||||
|
||||
public void save() {
|
||||
logger.debug("Saving client configuration");
|
||||
// Save configuration to file
|
||||
}
|
||||
|
||||
// Getters and setters
|
||||
public int getGameWidth() { return gameWidth.get(); }
|
||||
public void setGameWidth(int width) { gameWidth.set(width); }
|
||||
|
||||
public int getGameHeight() { return gameHeight.get(); }
|
||||
public void setGameHeight(int height) { gameHeight.set(height); }
|
||||
|
||||
public int getFps() { return fps.get(); }
|
||||
public void setFps(int fps) { this.fps.set(fps); }
|
||||
|
||||
public int getMemoryUsage() { return memoryUsage.get(); }
|
||||
public void setMemoryUsage(int memory) { memoryUsage.set(memory); }
|
||||
|
||||
public String getWorldUrl() { return worldUrl.get(); }
|
||||
public void setWorldUrl(String url) { worldUrl.set(url); }
|
||||
}
|
||||
// ClientConfiguration moved to separate file
|
||||
|
||||
@@ -7,75 +7,219 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* EventSystem - Central event handling system for notifying agents of game state changes.
|
||||
* Enhanced EventSystem - Advanced event handling system for agents with filtering, batching, and monitoring.
|
||||
*
|
||||
* This system allows agents to register listeners for various game events:
|
||||
* - Player movement and stat changes
|
||||
* - Game state transitions
|
||||
* - World object updates
|
||||
* - Combat events
|
||||
* - Interface interactions
|
||||
*
|
||||
* All events are delivered asynchronously to avoid blocking the game loop.
|
||||
* Features:
|
||||
* - Asynchronous event delivery with thread pool management
|
||||
* - Event filtering and conditional listeners
|
||||
* - Event batching and throttling
|
||||
* - Event history and replay
|
||||
* - Performance monitoring and metrics
|
||||
* - Typed event builders
|
||||
* - Event priority handling
|
||||
* - Custom event routing
|
||||
* - Dead letter queue for failed events
|
||||
* - Event serialization and persistence
|
||||
*/
|
||||
public class EventSystem {
|
||||
private static final Logger logger = LoggerFactory.getLogger(EventSystem.class);
|
||||
|
||||
private final ExecutorService eventExecutor;
|
||||
private final ConcurrentHashMap<EventType, CopyOnWriteArrayList<Consumer<GameEvent>>> listeners;
|
||||
// Type aliases for compatibility
|
||||
public static abstract class Event extends GameEvent {
|
||||
protected Event(EventType type) {
|
||||
super(type);
|
||||
}
|
||||
}
|
||||
|
||||
// Core event handling
|
||||
private final ExecutorService eventExecutor;
|
||||
private final ScheduledExecutorService scheduledExecutor;
|
||||
private final ConcurrentHashMap<EventType, CopyOnWriteArrayList<EventListener>> listeners;
|
||||
|
||||
// Enhanced features
|
||||
private final EventMetrics metrics;
|
||||
private final EventHistory eventHistory;
|
||||
private final EventBatcher eventBatcher;
|
||||
private final EventThrottler eventThrottler;
|
||||
private final EventFilter eventFilter;
|
||||
private final Queue<GameEvent> deadLetterQueue;
|
||||
private final Map<String, Object> globalEventData;
|
||||
|
||||
// Configuration
|
||||
private volatile boolean initialized = false;
|
||||
private volatile boolean enableHistory = true;
|
||||
private volatile boolean enableMetrics = true;
|
||||
private volatile boolean enableBatching = false;
|
||||
private volatile boolean enableThrottling = false;
|
||||
private volatile int maxHistorySize = 10000;
|
||||
private volatile int maxDeadLetterSize = 1000;
|
||||
|
||||
// Event routing
|
||||
private final Map<String, EventRoute> customRoutes;
|
||||
private final Set<String> pausedEventTypes;
|
||||
|
||||
public EventSystem() {
|
||||
this.eventExecutor = Executors.newFixedThreadPool(4, r -> {
|
||||
this.eventExecutor = Executors.newFixedThreadPool(6, r -> {
|
||||
Thread t = new Thread(r, "Event-Handler");
|
||||
t.setDaemon(true);
|
||||
t.setPriority(Thread.NORM_PRIORITY - 1);
|
||||
return t;
|
||||
});
|
||||
|
||||
this.scheduledExecutor = Executors.newScheduledThreadPool(2, r -> {
|
||||
Thread t = new Thread(r, "Event-Scheduler");
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
|
||||
this.listeners = new ConcurrentHashMap<>();
|
||||
this.metrics = new EventMetrics();
|
||||
this.eventHistory = new EventHistory(maxHistorySize);
|
||||
this.eventThrottler = new EventThrottler();
|
||||
this.eventFilter = new EventFilter();
|
||||
this.deadLetterQueue = new ConcurrentLinkedQueue<>();
|
||||
this.globalEventData = new ConcurrentHashMap<>();
|
||||
this.customRoutes = new ConcurrentHashMap<>();
|
||||
this.pausedEventTypes = ConcurrentHashMap.newKeySet();
|
||||
|
||||
// Initialize listener lists for all event types
|
||||
for (EventType type : EventType.values()) {
|
||||
listeners.put(type, new CopyOnWriteArrayList<>());
|
||||
}
|
||||
|
||||
initializePeriodicTasks();
|
||||
|
||||
this.eventBatcher = new EventBatcher(this);
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
initialized = true;
|
||||
logger.debug("EventSystem initialized");
|
||||
metrics.initialize();
|
||||
eventBatcher.initialize();
|
||||
logger.info("Enhanced EventSystem initialized with features: history={}, metrics={}, batching={}, throttling={}",
|
||||
enableHistory, enableMetrics, enableBatching, enableThrottling);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
initialized = false;
|
||||
|
||||
try {
|
||||
// Stop periodic tasks
|
||||
scheduledExecutor.shutdown();
|
||||
if (!scheduledExecutor.awaitTermination(2, TimeUnit.SECONDS)) {
|
||||
scheduledExecutor.shutdownNow();
|
||||
}
|
||||
|
||||
// Stop event processing
|
||||
eventExecutor.shutdown();
|
||||
if (!eventExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
eventExecutor.shutdownNow();
|
||||
}
|
||||
|
||||
// Clean up
|
||||
listeners.clear();
|
||||
logger.debug("EventSystem shutdown complete");
|
||||
deadLetterQueue.clear();
|
||||
customRoutes.clear();
|
||||
|
||||
logger.info("EventSystem shutdown complete. Processed {} events total", metrics.getTotalEvents());
|
||||
} catch (Exception e) {
|
||||
logger.error("Error during EventSystem shutdown", e);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================
|
||||
// ENHANCED LISTENER REGISTRATION
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* Register a listener for a specific event type.
|
||||
* Register a simple listener for a specific event type.
|
||||
*/
|
||||
public void addListener(EventType eventType, Consumer<GameEvent> listener) {
|
||||
addListener(eventType, new EventListener(listener));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an enhanced listener with filtering and configuration.
|
||||
*/
|
||||
public void addListener(EventType eventType, EventListener listener) {
|
||||
if (listener == null) {
|
||||
logger.warn("Attempted to register null listener for event type: {}", eventType);
|
||||
return;
|
||||
}
|
||||
|
||||
CopyOnWriteArrayList<Consumer<GameEvent>> eventListeners = listeners.get(eventType);
|
||||
CopyOnWriteArrayList<EventListener> eventListeners = listeners.get(eventType);
|
||||
if (eventListeners != null) {
|
||||
eventListeners.add(listener);
|
||||
logger.debug("Registered listener for event type: {}", eventType);
|
||||
metrics.incrementListenerCount(eventType);
|
||||
logger.debug("Registered listener for event type: {} (total: {})", eventType, eventListeners.size());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a filtered listener that only receives events matching the condition.
|
||||
*/
|
||||
public void addFilteredListener(EventType eventType, Predicate<GameEvent> filter, Consumer<GameEvent> listener) {
|
||||
EventListener eventListener = new EventListener(listener)
|
||||
.withFilter(filter)
|
||||
.withDescription("Filtered listener for " + eventType);
|
||||
addListener(eventType, eventListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a throttled listener that receives events at most once per interval.
|
||||
*/
|
||||
public void addThrottledListener(EventType eventType, long intervalMs, Consumer<GameEvent> listener) {
|
||||
EventListener eventListener = new EventListener(listener)
|
||||
.withThrottling(intervalMs)
|
||||
.withDescription("Throttled listener for " + eventType);
|
||||
addListener(eventType, eventListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a one-time listener that automatically removes itself after first event.
|
||||
*/
|
||||
public void addOnceListener(EventType eventType, Consumer<GameEvent> listener) {
|
||||
EventListener eventListener = new EventListener(event -> {
|
||||
try {
|
||||
listener.accept(event);
|
||||
} finally {
|
||||
removeListener(eventType, listener);
|
||||
}
|
||||
}).withDescription("One-time listener for " + eventType);
|
||||
addListener(eventType, eventListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a conditional listener that only activates when a condition is met.
|
||||
*/
|
||||
public void addConditionalListener(EventType eventType, Predicate<Void> activationCondition, Consumer<GameEvent> listener) {
|
||||
EventListener eventListener = new EventListener(event -> {
|
||||
if (activationCondition.test(null)) {
|
||||
listener.accept(event);
|
||||
}
|
||||
}).withDescription("Conditional listener for " + eventType);
|
||||
addListener(eventType, eventListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a listener for a specific event type.
|
||||
*/
|
||||
@@ -496,4 +640,555 @@ public class EventSystem {
|
||||
|
||||
public boolean isConnected() { return connected; }
|
||||
}
|
||||
|
||||
// ================================
|
||||
// ENHANCED EVENT SYSTEM COMPONENTS
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* Initialize periodic maintenance tasks.
|
||||
*/
|
||||
private void initializePeriodicTasks() {
|
||||
// Metrics collection task
|
||||
scheduledExecutor.scheduleAtFixedRate(() -> {
|
||||
try {
|
||||
if (enableMetrics) {
|
||||
metrics.collectPeriodicMetrics();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Error in metrics collection", e);
|
||||
}
|
||||
}, 30, 30, TimeUnit.SECONDS);
|
||||
|
||||
// Dead letter queue cleanup
|
||||
scheduledExecutor.scheduleAtFixedRate(() -> {
|
||||
try {
|
||||
cleanupDeadLetterQueue();
|
||||
} catch (Exception e) {
|
||||
logger.warn("Error in dead letter queue cleanup", e);
|
||||
}
|
||||
}, 5, 5, TimeUnit.MINUTES);
|
||||
|
||||
// Event history maintenance
|
||||
scheduledExecutor.scheduleAtFixedRate(() -> {
|
||||
try {
|
||||
if (enableHistory) {
|
||||
eventHistory.performMaintenance();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Error in event history maintenance", e);
|
||||
}
|
||||
}, 10, 10, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhanced event firing with filtering, batching, and metrics.
|
||||
*/
|
||||
public void fireEvent(EventType eventType, GameEvent event) {
|
||||
if (!initialized || pausedEventTypes.contains(eventType.name())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Record metrics
|
||||
if (enableMetrics) {
|
||||
metrics.recordEvent(eventType, event);
|
||||
}
|
||||
|
||||
// Add to history
|
||||
if (enableHistory) {
|
||||
eventHistory.addEvent(event);
|
||||
}
|
||||
|
||||
// Apply global filtering
|
||||
if (!eventFilter.shouldProcess(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle batching
|
||||
if (enableBatching && eventBatcher.shouldBatch(eventType)) {
|
||||
eventBatcher.addEvent(eventType, event);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle throttling
|
||||
if (enableThrottling && !eventThrottler.shouldFire(eventType, event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fire to listeners
|
||||
fireEventToListeners(eventType, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire event directly to listeners with error handling.
|
||||
*/
|
||||
private void fireEventToListeners(EventType eventType, GameEvent event) {
|
||||
CopyOnWriteArrayList<EventListener> eventListeners = listeners.get(eventType);
|
||||
if (eventListeners == null || eventListeners.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Custom routing check
|
||||
EventRoute route = customRoutes.get(eventType.name());
|
||||
if (route != null && !route.shouldRoute(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Deliver events asynchronously
|
||||
eventExecutor.submit(() -> {
|
||||
for (EventListener listener : eventListeners) {
|
||||
try {
|
||||
if (listener.shouldAccept(event)) {
|
||||
listener.accept(event);
|
||||
if (enableMetrics) {
|
||||
metrics.recordListenerExecution(eventType, true);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error in event listener for type: {}", eventType, e);
|
||||
|
||||
// Add to dead letter queue
|
||||
if (deadLetterQueue.size() < maxDeadLetterSize) {
|
||||
deadLetterQueue.offer(new DeadLetterEvent(event, listener, e));
|
||||
}
|
||||
|
||||
if (enableMetrics) {
|
||||
metrics.recordListenerExecution(eventType, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire event with string type and data payload.
|
||||
*/
|
||||
public void fireEvent(String eventType, Object data) {
|
||||
try {
|
||||
EventType type = EventType.valueOf(eventType.toUpperCase());
|
||||
CustomEvent event = new CustomEvent(type, data);
|
||||
fireEvent(type, event);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Handle custom event types
|
||||
CustomEvent event = new CustomEvent(EventType.CUSTOM_EVENT, data);
|
||||
event.setCustomType(eventType);
|
||||
fireEvent(EventType.CUSTOM_EVENT, event);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================
|
||||
// CONFIGURATION AND MANAGEMENT
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* Pause event processing for specific event types.
|
||||
*/
|
||||
public void pauseEventType(EventType eventType) {
|
||||
pausedEventTypes.add(eventType.name());
|
||||
logger.info("Paused event type: {}", eventType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume event processing for specific event types.
|
||||
*/
|
||||
public void resumeEventType(EventType eventType) {
|
||||
pausedEventTypes.remove(eventType.name());
|
||||
logger.info("Resumed event type: {}", eventType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event processing metrics.
|
||||
*/
|
||||
public EventMetrics getMetrics() {
|
||||
return metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event history.
|
||||
*/
|
||||
public EventHistory getEventHistory() {
|
||||
return eventHistory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set global event data that can be accessed by listeners.
|
||||
*/
|
||||
public void setGlobalData(String key, Object value) {
|
||||
globalEventData.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get global event data.
|
||||
*/
|
||||
public Object getGlobalData(String key) {
|
||||
return globalEventData.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up dead letter queue.
|
||||
*/
|
||||
private void cleanupDeadLetterQueue() {
|
||||
if (deadLetterQueue.size() > maxDeadLetterSize / 2) {
|
||||
int removed = 0;
|
||||
while (deadLetterQueue.size() > maxDeadLetterSize / 4 && deadLetterQueue.poll() != null) {
|
||||
removed++;
|
||||
}
|
||||
if (removed > 0) {
|
||||
logger.info("Cleaned up {} dead letter events", removed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ================================
|
||||
// SUPPORTING CLASSES
|
||||
// ================================
|
||||
|
||||
/**
|
||||
* Enhanced event listener with filtering and configuration.
|
||||
*/
|
||||
class EventListener {
|
||||
private final Consumer<GameEvent> consumer;
|
||||
private Predicate<GameEvent> filter;
|
||||
private long throttleInterval = 0;
|
||||
private long lastExecution = 0;
|
||||
private String description = "";
|
||||
private int priority = 0;
|
||||
private boolean enabled = true;
|
||||
|
||||
public EventListener(Consumer<GameEvent> consumer) {
|
||||
this.consumer = consumer;
|
||||
}
|
||||
|
||||
public EventListener withFilter(Predicate<GameEvent> filter) {
|
||||
this.filter = filter;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EventListener withThrottling(long intervalMs) {
|
||||
this.throttleInterval = intervalMs;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EventListener withDescription(String description) {
|
||||
this.description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EventListener withPriority(int priority) {
|
||||
this.priority = priority;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean shouldAccept(GameEvent event) {
|
||||
if (!enabled) return false;
|
||||
|
||||
// Check throttling
|
||||
long now = System.currentTimeMillis();
|
||||
if (throttleInterval > 0 && (now - lastExecution) < throttleInterval) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check filter
|
||||
if (filter != null && !filter.test(event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void accept(GameEvent event) {
|
||||
lastExecution = System.currentTimeMillis();
|
||||
consumer.accept(event);
|
||||
}
|
||||
|
||||
public String getDescription() { return description; }
|
||||
public int getPriority() { return priority; }
|
||||
public boolean isEnabled() { return enabled; }
|
||||
public void setEnabled(boolean enabled) { this.enabled = enabled; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Event metrics collector.
|
||||
*/
|
||||
class EventMetrics {
|
||||
private final AtomicLong totalEvents = new AtomicLong(0);
|
||||
private final Map<EventType, AtomicLong> eventCounts = new ConcurrentHashMap<>();
|
||||
private final Map<EventType, AtomicInteger> listenerCounts = new ConcurrentHashMap<>();
|
||||
private final Map<EventType, AtomicLong> successfulExecutions = new ConcurrentHashMap<>();
|
||||
private final Map<EventType, AtomicLong> failedExecutions = new ConcurrentHashMap<>();
|
||||
private final AtomicLong startTime = new AtomicLong(0);
|
||||
|
||||
public void initialize() {
|
||||
startTime.set(System.currentTimeMillis());
|
||||
for (EventSystem.EventType type : EventSystem.EventType.values()) {
|
||||
eventCounts.put(type, new AtomicLong(0));
|
||||
listenerCounts.put(type, new AtomicInteger(0));
|
||||
successfulExecutions.put(type, new AtomicLong(0));
|
||||
failedExecutions.put(type, new AtomicLong(0));
|
||||
}
|
||||
}
|
||||
|
||||
public void recordEvent(EventSystem.EventType eventType, EventSystem.GameEvent event) {
|
||||
totalEvents.incrementAndGet();
|
||||
eventCounts.get(eventType).incrementAndGet();
|
||||
}
|
||||
|
||||
public void incrementListenerCount(EventSystem.EventType eventType) {
|
||||
listenerCounts.get(eventType).incrementAndGet();
|
||||
}
|
||||
|
||||
public void recordListenerExecution(EventSystem.EventType eventType, boolean success) {
|
||||
if (success) {
|
||||
successfulExecutions.get(eventType).incrementAndGet();
|
||||
} else {
|
||||
failedExecutions.get(eventType).incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
public void collectPeriodicMetrics() {
|
||||
// Log periodic metrics
|
||||
long runtime = System.currentTimeMillis() - startTime.get();
|
||||
double eventsPerSecond = totalEvents.get() / (runtime / 1000.0);
|
||||
|
||||
LoggerFactory.getLogger(EventMetrics.class)
|
||||
.debug("Event metrics: {} total events, {:.2f} events/sec, {} listeners active",
|
||||
totalEvents.get(), eventsPerSecond, getTotalListeners());
|
||||
}
|
||||
|
||||
public long getTotalEvents() {
|
||||
return totalEvents.get();
|
||||
}
|
||||
|
||||
public int getTotalListeners() {
|
||||
return listenerCounts.values().stream().mapToInt(AtomicInteger::get).sum();
|
||||
}
|
||||
|
||||
public Map<EventSystem.EventType, Long> getEventCounts() {
|
||||
Map<EventSystem.EventType, Long> result = new HashMap<>();
|
||||
for (Map.Entry<EventSystem.EventType, AtomicLong> entry : eventCounts.entrySet()) {
|
||||
result.put(entry.getKey(), entry.getValue().get());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event history tracker.
|
||||
*/
|
||||
class EventHistory {
|
||||
private final Queue<EventSystem.GameEvent> history;
|
||||
private final int maxSize;
|
||||
|
||||
public EventHistory(int maxSize) {
|
||||
this.maxSize = maxSize;
|
||||
this.history = new ConcurrentLinkedQueue<>();
|
||||
}
|
||||
|
||||
public void addEvent(EventSystem.GameEvent event) {
|
||||
history.offer(event);
|
||||
|
||||
// Maintain size limit
|
||||
while (history.size() > maxSize) {
|
||||
history.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public List<EventSystem.GameEvent> getRecentEvents(int count) {
|
||||
return history.stream()
|
||||
.skip(Math.max(0, history.size() - count))
|
||||
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
|
||||
}
|
||||
|
||||
public List<EventSystem.GameEvent> getEventsByType(EventSystem.EventType type) {
|
||||
return history.stream()
|
||||
.filter(event -> event.getType() == type)
|
||||
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
|
||||
}
|
||||
|
||||
public void performMaintenance() {
|
||||
// Clean up old events if needed
|
||||
while (history.size() > maxSize) {
|
||||
history.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return history.size();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event batching system.
|
||||
*/
|
||||
class EventBatcher {
|
||||
private final EventSystem eventSystem;
|
||||
private final Map<EventSystem.EventType, List<EventSystem.GameEvent>> batches;
|
||||
private final Map<EventSystem.EventType, Long> lastFlush;
|
||||
private final Set<EventSystem.EventType> batchableTypes;
|
||||
|
||||
public EventBatcher(EventSystem eventSystem) {
|
||||
this.eventSystem = eventSystem;
|
||||
this.batches = new ConcurrentHashMap<>();
|
||||
this.lastFlush = new ConcurrentHashMap<>();
|
||||
this.batchableTypes = new HashSet<>();
|
||||
|
||||
// Add frequently fired events to batchable types
|
||||
batchableTypes.add(EventSystem.EventType.GAME_TICK);
|
||||
batchableTypes.add(EventSystem.EventType.PLAYER_MOVED);
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
// Schedule periodic batch flushing
|
||||
eventSystem.scheduledExecutor.scheduleAtFixedRate(this::flushAllBatches, 100, 100, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public boolean shouldBatch(EventSystem.EventType eventType) {
|
||||
return batchableTypes.contains(eventType);
|
||||
}
|
||||
|
||||
public void addEvent(EventSystem.EventType eventType, EventSystem.GameEvent event) {
|
||||
batches.computeIfAbsent(eventType, k -> new ArrayList<>()).add(event);
|
||||
}
|
||||
|
||||
private void flushAllBatches() {
|
||||
for (Map.Entry<EventSystem.EventType, List<EventSystem.GameEvent>> entry : batches.entrySet()) {
|
||||
EventSystem.EventType eventType = entry.getKey();
|
||||
List<EventSystem.GameEvent> events = entry.getValue();
|
||||
|
||||
if (!events.isEmpty()) {
|
||||
// Create batch event
|
||||
BatchEvent batchEvent = new BatchEvent(eventType, new ArrayList<>(events));
|
||||
events.clear();
|
||||
|
||||
// Fire batch event
|
||||
eventSystem.fireEventToListeners(eventType, batchEvent);
|
||||
lastFlush.put(eventType, System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event throttling system.
|
||||
*/
|
||||
class EventThrottler {
|
||||
private final Map<String, Long> lastEventTimes = new ConcurrentHashMap<>();
|
||||
private final Map<EventSystem.EventType, Long> throttleIntervals = new ConcurrentHashMap<>();
|
||||
|
||||
public boolean shouldFire(EventSystem.EventType eventType, EventSystem.GameEvent event) {
|
||||
Long interval = throttleIntervals.get(eventType);
|
||||
if (interval == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String key = eventType.name() + ":" + event.getClass().getSimpleName();
|
||||
long now = System.currentTimeMillis();
|
||||
Long lastTime = lastEventTimes.get(key);
|
||||
|
||||
if (lastTime == null || (now - lastTime) >= interval) {
|
||||
lastEventTimes.put(key, now);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setThrottleInterval(EventSystem.EventType eventType, long intervalMs) {
|
||||
throttleIntervals.put(eventType, intervalMs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Global event filtering system.
|
||||
*/
|
||||
class EventFilter {
|
||||
private final List<Predicate<EventSystem.GameEvent>> globalFilters = new ArrayList<>();
|
||||
|
||||
public boolean shouldProcess(EventSystem.GameEvent event) {
|
||||
return globalFilters.stream().allMatch(filter -> filter.test(event));
|
||||
}
|
||||
|
||||
public void addGlobalFilter(Predicate<EventSystem.GameEvent> filter) {
|
||||
globalFilters.add(filter);
|
||||
}
|
||||
|
||||
public void removeGlobalFilter(Predicate<EventSystem.GameEvent> filter) {
|
||||
globalFilters.remove(filter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom event routing.
|
||||
*/
|
||||
class EventRoute {
|
||||
private final Predicate<EventSystem.GameEvent> condition;
|
||||
private final String description;
|
||||
|
||||
public EventRoute(Predicate<EventSystem.GameEvent> condition, String description) {
|
||||
this.condition = condition;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public boolean shouldRoute(EventSystem.GameEvent event) {
|
||||
return condition.test(event);
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dead letter event for failed event processing.
|
||||
*/
|
||||
class DeadLetterEvent {
|
||||
private final EventSystem.GameEvent originalEvent;
|
||||
private final EventListener failedListener;
|
||||
private final Exception exception;
|
||||
private final long timestamp;
|
||||
|
||||
public DeadLetterEvent(EventSystem.GameEvent originalEvent, EventListener failedListener, Exception exception) {
|
||||
this.originalEvent = originalEvent;
|
||||
this.failedListener = failedListener;
|
||||
this.exception = exception;
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public EventSystem.GameEvent getOriginalEvent() { return originalEvent; }
|
||||
public EventListener getFailedListener() { return failedListener; }
|
||||
public Exception getException() { return exception; }
|
||||
public long getTimestamp() { return timestamp; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom event implementation.
|
||||
*/
|
||||
class CustomEvent extends EventSystem.GameEvent {
|
||||
private final Object data;
|
||||
private String customType;
|
||||
|
||||
public CustomEvent(EventSystem.EventType type, Object data) {
|
||||
super(type);
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public Object getData() { return data; }
|
||||
public String getCustomType() { return customType; }
|
||||
public void setCustomType(String customType) { this.customType = customType; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch event containing multiple events.
|
||||
*/
|
||||
class BatchEvent extends EventSystem.GameEvent {
|
||||
private final List<EventSystem.GameEvent> events;
|
||||
|
||||
public BatchEvent(EventSystem.EventType type, List<EventSystem.GameEvent> events) {
|
||||
super(type);
|
||||
this.events = new ArrayList<>(events);
|
||||
}
|
||||
|
||||
public List<EventSystem.GameEvent> getEvents() { return new ArrayList<>(events); }
|
||||
public int getEventCount() { return events.size(); }
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.openosrs.client.core;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* GameNPC - Represents a non-player character in the game world.
|
||||
*/
|
||||
public class GameNPC {
|
||||
private final int index;
|
||||
private final int id;
|
||||
private final AtomicInteger x = new AtomicInteger();
|
||||
private final AtomicInteger y = new AtomicInteger();
|
||||
private final AtomicInteger plane = new AtomicInteger();
|
||||
|
||||
private final AtomicInteger animationId = new AtomicInteger(-1);
|
||||
private final AtomicInteger graphicId = new AtomicInteger(-1);
|
||||
private final AtomicInteger orientation = new AtomicInteger(0);
|
||||
private final AtomicInteger hitpoints = new AtomicInteger(100);
|
||||
private final AtomicInteger maxHitpoints = new AtomicInteger(100);
|
||||
private final AtomicInteger combatLevel = new AtomicInteger(1);
|
||||
|
||||
private final AtomicReference<String> name = new AtomicReference<>("");
|
||||
private final AtomicReference<String> examine = new AtomicReference<>("");
|
||||
private final AtomicReference<String> overheadText = new AtomicReference<>("");
|
||||
|
||||
private final AtomicInteger interacting = new AtomicInteger(-1);
|
||||
private final AtomicInteger targetIndex = new AtomicInteger(-1);
|
||||
|
||||
public GameNPC(int index, int id, int x, int y, int plane) {
|
||||
this.index = index;
|
||||
this.id = id;
|
||||
this.x.set(x);
|
||||
this.y.set(y);
|
||||
this.plane.set(plane);
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
// Update NPC state each game tick
|
||||
// This could include movement, combat timers, etc.
|
||||
}
|
||||
|
||||
// Position methods
|
||||
public void setPosition(int x, int y) {
|
||||
this.x.set(x);
|
||||
this.y.set(y);
|
||||
}
|
||||
|
||||
public void setPosition(int x, int y, int plane) {
|
||||
this.x.set(x);
|
||||
this.y.set(y);
|
||||
this.plane.set(plane);
|
||||
}
|
||||
|
||||
public int getIndex() { return index; }
|
||||
public int getId() { return id; }
|
||||
public int getX() { return x.get(); }
|
||||
public int getY() { return y.get(); }
|
||||
public int getPlane() { return plane.get(); }
|
||||
|
||||
// Animation and graphics
|
||||
public void setAnimation(int animationId) { this.animationId.set(animationId); }
|
||||
public void setGraphic(int graphicId) { this.graphicId.set(graphicId); }
|
||||
public void setOrientation(int orientation) { this.orientation.set(orientation); }
|
||||
|
||||
public int getAnimationId() { return animationId.get(); }
|
||||
public int getGraphicId() { return graphicId.get(); }
|
||||
public int getOrientation() { return orientation.get(); }
|
||||
|
||||
// Health and combat
|
||||
public void setHitpoints(int current, int max) {
|
||||
this.hitpoints.set(current);
|
||||
this.maxHitpoints.set(max);
|
||||
}
|
||||
|
||||
public void setCombatLevel(int level) { this.combatLevel.set(level); }
|
||||
|
||||
public int getHitpoints() { return hitpoints.get(); }
|
||||
public int getMaxHitpoints() { return maxHitpoints.get(); }
|
||||
public int getCombatLevel() { return combatLevel.get(); }
|
||||
|
||||
// Text and interaction
|
||||
public void setName(String name) { this.name.set(name != null ? name : ""); }
|
||||
public void setExamine(String examine) { this.examine.set(examine != null ? examine : ""); }
|
||||
public void setOverheadText(String text) { this.overheadText.set(text != null ? text : ""); }
|
||||
|
||||
public String getName() { return name.get(); }
|
||||
public String getExamine() { return examine.get(); }
|
||||
public String getOverheadText() { return overheadText.get(); }
|
||||
|
||||
// Interaction
|
||||
public void setInteracting(int targetIndex) { this.interacting.set(targetIndex); }
|
||||
public void setTargetIndex(int targetIndex) { this.targetIndex.set(targetIndex); }
|
||||
|
||||
public int getInteracting() { return interacting.get(); }
|
||||
public int getTargetIndex() { return targetIndex.get(); }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("NPC[%d] id=%d name='%s' pos=(%d,%d,%d)",
|
||||
index, id, name.get(), x.get(), y.get(), plane.get());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.openosrs.client.core;
|
||||
|
||||
/**
|
||||
* GameObject - Represents an interactive object in the game world.
|
||||
*/
|
||||
public class GameObject {
|
||||
private final int id;
|
||||
private final int x, y, plane;
|
||||
private final int type;
|
||||
private final int orientation;
|
||||
|
||||
public GameObject(int id, int x, int y, int plane, int type, int orientation) {
|
||||
this.id = id;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.plane = plane;
|
||||
this.type = type;
|
||||
this.orientation = orientation;
|
||||
}
|
||||
|
||||
public int getId() { return id; }
|
||||
public int getX() { return x; }
|
||||
public int getY() { return y; }
|
||||
public int getPlane() { return plane; }
|
||||
public int getType() { return type; }
|
||||
public int getOrientation() { return orientation; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Object[%d] pos=(%d,%d,%d) type=%d orientation=%d",
|
||||
id, x, y, plane, type, orientation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.openosrs.client.core;
|
||||
|
||||
/**
|
||||
* GroundItem - Represents an item on the ground.
|
||||
*/
|
||||
public class GroundItem {
|
||||
private final int itemId;
|
||||
private final int quantity;
|
||||
private final int x, y, plane;
|
||||
private final long spawnTime;
|
||||
private final long expireTime;
|
||||
|
||||
public GroundItem(int itemId, int quantity, int x, int y, int plane) {
|
||||
this.itemId = itemId;
|
||||
this.quantity = quantity;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.plane = plane;
|
||||
this.spawnTime = System.currentTimeMillis();
|
||||
this.expireTime = spawnTime + (5 * 60 * 1000); // 5 minutes
|
||||
}
|
||||
|
||||
public boolean isExpired() {
|
||||
return System.currentTimeMillis() > expireTime;
|
||||
}
|
||||
|
||||
public int getId() { return itemId; }
|
||||
public int getItemId() { return itemId; }
|
||||
public int getQuantity() { return quantity; }
|
||||
public int getX() { return x; }
|
||||
public int getY() { return y; }
|
||||
public int getPlane() { return plane; }
|
||||
public long getSpawnTime() { return spawnTime; }
|
||||
public long getExpireTime() { return expireTime; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("GroundItem[%d] x%d pos=(%d,%d,%d)",
|
||||
itemId, quantity, x, y, plane);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
package com.openosrs.client.core;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* InterfaceState - Manages game interface state and interactions.
|
||||
*
|
||||
@@ -18,6 +19,7 @@ public class InterfaceState {
|
||||
|
||||
private final EventSystem eventSystem;
|
||||
private final AtomicInteger currentInterface = new AtomicInteger(-1);
|
||||
private final AtomicReference<String> chatboxText = new AtomicReference<>("");
|
||||
|
||||
public InterfaceState(EventSystem eventSystem) {
|
||||
this.eventSystem = eventSystem;
|
||||
@@ -74,4 +76,12 @@ public class InterfaceState {
|
||||
public void closeInterface() {
|
||||
setCurrentInterface(-1);
|
||||
}
|
||||
|
||||
public void setChatboxText(String text) {
|
||||
chatboxText.set(text != null ? text : "");
|
||||
}
|
||||
|
||||
public String getChatboxText() {
|
||||
return chatboxText.get();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.openosrs.client.core;
|
||||
|
||||
import com.openosrs.client.api.Item;
|
||||
import com.openosrs.client.api.InventoryChange;
|
||||
import com.openosrs.client.api.types.Item;
|
||||
import com.openosrs.client.api.types.InventoryChange;
|
||||
import com.openosrs.client.api.AgentAPI.EquipmentSlot;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,436 @@
|
||||
package com.openosrs.client.core;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* LoginState - Manages the login screen state and credentials.
|
||||
*
|
||||
* Tracks:
|
||||
* - Username, password, OTP
|
||||
* - Login screen state (e.g., credentials, connecting, logged in)
|
||||
* - Login responses and messages
|
||||
* - World selection state
|
||||
* - Session information
|
||||
*/
|
||||
public class LoginState {
|
||||
private static final Logger logger = LoggerFactory.getLogger(LoginState.class);
|
||||
private static final int MAX_USERNAME_LENGTH = 320;
|
||||
private static final int MAX_PASSWORD_LENGTH = 20;
|
||||
private static final int MAX_OTP_LENGTH = 6;
|
||||
|
||||
private final EventSystem eventSystem;
|
||||
private final ReadWriteLock loginLock = new ReentrantReadWriteLock();
|
||||
|
||||
// Login credentials
|
||||
private final AtomicReference<String> username = new AtomicReference<>("");
|
||||
private final AtomicReference<String> password = new AtomicReference<>("");
|
||||
private final AtomicReference<String> otp = new AtomicReference<>("");
|
||||
|
||||
// Login state
|
||||
private final AtomicInteger loginIndex = new AtomicInteger(LoginScreenState.CREDENTIALS);
|
||||
private final AtomicInteger currentLoginField = new AtomicInteger(LoginField.USERNAME);
|
||||
|
||||
// Login responses and messages
|
||||
private final AtomicReference<String> response0 = new AtomicReference<>("");
|
||||
private final AtomicReference<String> response1 = new AtomicReference<>("");
|
||||
private final AtomicReference<String> response2 = new AtomicReference<>("");
|
||||
private final AtomicReference<String> response3 = new AtomicReference<>("");
|
||||
|
||||
// World selection
|
||||
private final AtomicBoolean worldSelectOpen = new AtomicBoolean(false);
|
||||
private final AtomicInteger selectedWorldIndex = new AtomicInteger(-1);
|
||||
private final AtomicInteger worldSelectPage = new AtomicInteger(0);
|
||||
|
||||
// Session and connection
|
||||
private final AtomicInteger sessionId = new AtomicInteger(0);
|
||||
private final AtomicReference<String> sessionToken = new AtomicReference<>("");
|
||||
private final AtomicLong lastLoginAttempt = new AtomicLong(0);
|
||||
private final AtomicInteger loginAttempts = new AtomicInteger(0);
|
||||
|
||||
// Loading state
|
||||
private final AtomicInteger loadingPercent = new AtomicInteger(0);
|
||||
private final AtomicReference<String> loadingText = new AtomicReference<>("");
|
||||
|
||||
public LoginState(EventSystem eventSystem) {
|
||||
this.eventSystem = eventSystem;
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
logger.debug("Initializing LoginState");
|
||||
reset();
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
logger.debug("Shutting down LoginState");
|
||||
clearCredentials();
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
// Update login state, check timeouts, etc.
|
||||
checkLoginTimeout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset login state to initial values.
|
||||
*/
|
||||
public void reset() {
|
||||
loginLock.writeLock().lock();
|
||||
try {
|
||||
setLoginIndex(LoginScreenState.CREDENTIALS);
|
||||
setCurrentLoginField(LoginField.USERNAME);
|
||||
clearResponses();
|
||||
worldSelectOpen.set(false);
|
||||
selectedWorldIndex.set(-1);
|
||||
worldSelectPage.set(0);
|
||||
sessionId.set(0);
|
||||
sessionToken.set("");
|
||||
lastLoginAttempt.set(0);
|
||||
loginAttempts.set(0);
|
||||
loadingPercent.set(0);
|
||||
loadingText.set("");
|
||||
} finally {
|
||||
loginLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear stored credentials for security.
|
||||
*/
|
||||
public void clearCredentials() {
|
||||
loginLock.writeLock().lock();
|
||||
try {
|
||||
username.set("");
|
||||
password.set("");
|
||||
otp.set("");
|
||||
eventSystem.fireEvent(EventSystem.EventType.CREDENTIALS_CLEARED, new LoginEvent(LoginEventType.CREDENTIALS_CLEARED));
|
||||
} finally {
|
||||
loginLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set username with validation.
|
||||
*/
|
||||
public boolean setUsername(String username) {
|
||||
if (username == null) username = "";
|
||||
if (username.length() > MAX_USERNAME_LENGTH) {
|
||||
logger.warn("Username too long: {} characters", username.length());
|
||||
return false;
|
||||
}
|
||||
|
||||
loginLock.writeLock().lock();
|
||||
try {
|
||||
String oldUsername = this.username.getAndSet(username);
|
||||
if (!oldUsername.equals(username)) {
|
||||
eventSystem.fireEvent(EventSystem.EventType.LOGIN_USERNAME_CHANGED,
|
||||
new LoginEvent(LoginEventType.USERNAME_CHANGED, username));
|
||||
logger.debug("Username changed");
|
||||
}
|
||||
return true;
|
||||
} finally {
|
||||
loginLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set password with validation.
|
||||
*/
|
||||
public boolean setPassword(String password) {
|
||||
if (password == null) password = "";
|
||||
if (password.length() > MAX_PASSWORD_LENGTH) {
|
||||
logger.warn("Password too long: {} characters", password.length());
|
||||
return false;
|
||||
}
|
||||
|
||||
loginLock.writeLock().lock();
|
||||
try {
|
||||
String oldPassword = this.password.getAndSet(password);
|
||||
if (!oldPassword.equals(password)) {
|
||||
eventSystem.fireEvent(EventSystem.EventType.LOGIN_PASSWORD_CHANGED,
|
||||
new LoginEvent(LoginEventType.PASSWORD_CHANGED));
|
||||
logger.debug("Password changed");
|
||||
}
|
||||
return true;
|
||||
} finally {
|
||||
loginLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set OTP (One-Time Password) with validation.
|
||||
*/
|
||||
public boolean setOtp(String otp) {
|
||||
if (otp == null) otp = "";
|
||||
if (otp.length() > MAX_OTP_LENGTH) {
|
||||
logger.warn("OTP too long: {} characters", otp.length());
|
||||
return false;
|
||||
}
|
||||
|
||||
// OTP should be numeric
|
||||
if (!otp.isEmpty() && !otp.matches("\\d+")) {
|
||||
logger.warn("OTP contains non-numeric characters");
|
||||
return false;
|
||||
}
|
||||
|
||||
loginLock.writeLock().lock();
|
||||
try {
|
||||
String oldOtp = this.otp.getAndSet(otp);
|
||||
if (!oldOtp.equals(otp)) {
|
||||
eventSystem.fireEvent(EventSystem.EventType.LOGIN_OTP_CHANGED,
|
||||
new LoginEvent(LoginEventType.OTP_CHANGED));
|
||||
logger.debug("OTP changed");
|
||||
}
|
||||
return true;
|
||||
} finally {
|
||||
loginLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set login screen state.
|
||||
*/
|
||||
public void setLoginIndex(int loginIndex) {
|
||||
int oldIndex = this.loginIndex.getAndSet(loginIndex);
|
||||
if (oldIndex != loginIndex) {
|
||||
eventSystem.fireEvent(EventSystem.EventType.LOGIN_STATE_CHANGED,
|
||||
new LoginEvent(LoginEventType.STATE_CHANGED, String.valueOf(loginIndex)));
|
||||
logger.debug("Login state changed: {} -> {}", oldIndex, loginIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current login field focus.
|
||||
*/
|
||||
public void setCurrentLoginField(int field) {
|
||||
int oldField = this.currentLoginField.getAndSet(field);
|
||||
if (oldField != field) {
|
||||
eventSystem.fireEvent(EventSystem.EventType.LOGIN_FIELD_CHANGED,
|
||||
new LoginEvent(LoginEventType.FIELD_CHANGED, String.valueOf(field)));
|
||||
logger.debug("Login field changed: {} -> {}", oldField, field);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set login response messages.
|
||||
*/
|
||||
public void setLoginResponse(String response0, String response1, String response2, String response3) {
|
||||
loginLock.writeLock().lock();
|
||||
try {
|
||||
this.response0.set(response0 != null ? response0 : "");
|
||||
this.response1.set(response1 != null ? response1 : "");
|
||||
this.response2.set(response2 != null ? response2 : "");
|
||||
this.response3.set(response3 != null ? response3 : "");
|
||||
|
||||
eventSystem.fireEvent(EventSystem.EventType.LOGIN_RESPONSE_CHANGED,
|
||||
new LoginEvent(LoginEventType.RESPONSE_CHANGED, response1));
|
||||
logger.debug("Login response updated: {}", response1);
|
||||
} finally {
|
||||
loginLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear login response messages.
|
||||
*/
|
||||
public void clearResponses() {
|
||||
setLoginResponse("", "", "", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set loading state.
|
||||
*/
|
||||
public void setLoadingState(int percent, String text) {
|
||||
loadingPercent.set(Math.max(0, Math.min(100, percent)));
|
||||
loadingText.set(text != null ? text : "");
|
||||
|
||||
eventSystem.fireEvent(EventSystem.EventType.LOGIN_LOADING_CHANGED,
|
||||
new LoginEvent(LoginEventType.LOADING_CHANGED, text));
|
||||
}
|
||||
|
||||
/**
|
||||
* Record login attempt.
|
||||
*/
|
||||
public void recordLoginAttempt() {
|
||||
lastLoginAttempt.set(System.currentTimeMillis());
|
||||
int attempts = loginAttempts.incrementAndGet();
|
||||
logger.debug("Login attempt #{}", attempts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for login timeout.
|
||||
*/
|
||||
private void checkLoginTimeout() {
|
||||
long lastAttempt = lastLoginAttempt.get();
|
||||
if (lastAttempt > 0 && System.currentTimeMillis() - lastAttempt > 30000) { // 30 second timeout
|
||||
if (loginIndex.get() == LoginScreenState.CONNECTING) {
|
||||
setLoginResponse("", "Connection timed out.", "Please try again.", "");
|
||||
setLoginIndex(LoginScreenState.CREDENTIALS);
|
||||
logger.warn("Login attempt timed out");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate credentials for login attempt.
|
||||
*/
|
||||
public boolean validateCredentials() {
|
||||
String user = username.get().trim();
|
||||
String pass = password.get();
|
||||
|
||||
if (user.isEmpty()) {
|
||||
setLoginResponse("", "Please enter your username/email address.", "", "");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pass.isEmpty()) {
|
||||
setLoginResponse("", "Please enter your password.", "", "");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Agent-friendly login method.
|
||||
*/
|
||||
public boolean attemptLogin(String username, String password, String otp) {
|
||||
if (!setUsername(username)) return false;
|
||||
if (!setPassword(password)) return false;
|
||||
if (otp != null && !setOtp(otp)) return false;
|
||||
|
||||
if (!validateCredentials()) return false;
|
||||
|
||||
recordLoginAttempt();
|
||||
setLoginIndex(LoginScreenState.CONNECTING);
|
||||
setLoginResponse("", "Connecting to server...", "", "");
|
||||
|
||||
eventSystem.fireEvent(EventSystem.EventType.LOGIN_ATTEMPT_STARTED,
|
||||
new LoginEvent(LoginEventType.ATTEMPT_STARTED, username));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle successful login.
|
||||
*/
|
||||
public void onLoginSuccess(int sessionId, String sessionToken) {
|
||||
this.sessionId.set(sessionId);
|
||||
this.sessionToken.set(sessionToken != null ? sessionToken : "");
|
||||
setLoginIndex(LoginScreenState.LOGGED_IN);
|
||||
clearResponses();
|
||||
|
||||
eventSystem.fireEvent(EventSystem.EventType.LOGIN_SUCCESS,
|
||||
new LoginEvent(LoginEventType.SUCCESS, String.valueOf(sessionId)));
|
||||
logger.info("Login successful, session: {}", sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle login failure.
|
||||
*/
|
||||
public void onLoginFailure(int errorCode, String errorMessage) {
|
||||
setLoginIndex(LoginScreenState.CREDENTIALS);
|
||||
|
||||
String message = errorMessage != null ? errorMessage : "Login failed";
|
||||
setLoginResponse("", message, "", "");
|
||||
|
||||
eventSystem.fireEvent(EventSystem.EventType.LOGIN_FAILED,
|
||||
new LoginEvent(LoginEventType.FAILED, String.valueOf(errorCode)));
|
||||
logger.warn("Login failed: {} ({})", message, errorCode);
|
||||
}
|
||||
|
||||
// Getters
|
||||
public String getUsername() { return username.get(); }
|
||||
public String getPassword() { return password.get(); }
|
||||
public String getOtp() { return otp.get(); }
|
||||
public int getLoginIndex() { return loginIndex.get(); }
|
||||
public int getCurrentLoginField() { return currentLoginField.get(); }
|
||||
public String getResponse0() { return response0.get(); }
|
||||
public String getResponse1() { return response1.get(); }
|
||||
public String getResponse2() { return response2.get(); }
|
||||
public String getResponse3() { return response3.get(); }
|
||||
public boolean isWorldSelectOpen() { return worldSelectOpen.get(); }
|
||||
public int getSelectedWorldIndex() { return selectedWorldIndex.get(); }
|
||||
public int getWorldSelectPage() { return worldSelectPage.get(); }
|
||||
public int getSessionId() { return sessionId.get(); }
|
||||
public String getSessionToken() { return sessionToken.get(); }
|
||||
public int getLoadingPercent() { return loadingPercent.get(); }
|
||||
public String getLoadingText() { return loadingText.get(); }
|
||||
public int getLoginAttempts() { return loginAttempts.get(); }
|
||||
public long getLastLoginAttempt() { return lastLoginAttempt.get(); }
|
||||
|
||||
public boolean isLoggedIn() {
|
||||
return loginIndex.get() == LoginScreenState.LOGGED_IN && sessionId.get() > 0;
|
||||
}
|
||||
|
||||
public boolean isConnecting() {
|
||||
return loginIndex.get() == LoginScreenState.CONNECTING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Login screen states (based on RuneLite's loginIndex).
|
||||
*/
|
||||
public static class LoginScreenState {
|
||||
public static final int STARTUP = 0;
|
||||
public static final int CREDENTIALS = 2;
|
||||
public static final int AUTHENTICATOR = 4;
|
||||
public static final int CONNECTING = 5;
|
||||
public static final int LOGGED_IN = 10;
|
||||
public static final int WORLD_SELECT = 7;
|
||||
public static final int ERROR = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Login field focus constants.
|
||||
*/
|
||||
public static class LoginField {
|
||||
public static final int USERNAME = 0;
|
||||
public static final int PASSWORD = 1;
|
||||
public static final int OTP = 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Login event types.
|
||||
*/
|
||||
public enum LoginEventType {
|
||||
CREDENTIALS_CLEARED,
|
||||
USERNAME_CHANGED,
|
||||
PASSWORD_CHANGED,
|
||||
OTP_CHANGED,
|
||||
STATE_CHANGED,
|
||||
FIELD_CHANGED,
|
||||
RESPONSE_CHANGED,
|
||||
LOADING_CHANGED,
|
||||
ATTEMPT_STARTED,
|
||||
SUCCESS,
|
||||
FAILED
|
||||
}
|
||||
|
||||
/**
|
||||
* Login event class.
|
||||
*/
|
||||
public static class LoginEvent extends EventSystem.GameEvent {
|
||||
private final LoginEventType loginEventType;
|
||||
private final String data;
|
||||
|
||||
public LoginEvent(LoginEventType type) {
|
||||
this(type, null);
|
||||
}
|
||||
|
||||
public LoginEvent(LoginEventType type, String data) {
|
||||
super(EventSystem.EventType.LOGIN_EVENT);
|
||||
this.loginEventType = type;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public LoginEventType getLoginEventType() { return loginEventType; }
|
||||
public String getData() { return data; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.openosrs.client.core;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* OtherPlayer - Represents another player in the game world.
|
||||
*/
|
||||
public class OtherPlayer {
|
||||
private final int index;
|
||||
private final AtomicReference<String> username = new AtomicReference<>();
|
||||
private final AtomicInteger x = new AtomicInteger();
|
||||
private final AtomicInteger y = new AtomicInteger();
|
||||
private final AtomicInteger plane = new AtomicInteger();
|
||||
private final AtomicInteger combatLevel = new AtomicInteger();
|
||||
|
||||
private final AtomicInteger animationId = new AtomicInteger(-1);
|
||||
private final AtomicInteger graphicId = new AtomicInteger(-1);
|
||||
private final AtomicInteger orientation = new AtomicInteger(0);
|
||||
private final AtomicReference<String> overheadText = new AtomicReference<>("");
|
||||
|
||||
public OtherPlayer(int index, String username, int x, int y, int plane, int combatLevel) {
|
||||
this.index = index;
|
||||
this.username.set(username);
|
||||
this.x.set(x);
|
||||
this.y.set(y);
|
||||
this.plane.set(plane);
|
||||
this.combatLevel.set(combatLevel);
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
// Update player state each game tick
|
||||
}
|
||||
|
||||
public void setPosition(int x, int y) {
|
||||
this.x.set(x);
|
||||
this.y.set(y);
|
||||
}
|
||||
|
||||
public void setPosition(int x, int y, int plane) {
|
||||
this.x.set(x);
|
||||
this.y.set(y);
|
||||
this.plane.set(plane);
|
||||
}
|
||||
|
||||
public int getIndex() { return index; }
|
||||
public String getUsername() { return username.get(); }
|
||||
public int getX() { return x.get(); }
|
||||
public int getY() { return y.get(); }
|
||||
public int getPlane() { return plane.get(); }
|
||||
public int getCombatLevel() { return combatLevel.get(); }
|
||||
|
||||
public void setAnimation(int animationId) { this.animationId.set(animationId); }
|
||||
public void setGraphic(int graphicId) { this.graphicId.set(graphicId); }
|
||||
public void setOrientation(int orientation) { this.orientation.set(orientation); }
|
||||
public void setOverheadText(String text) { this.overheadText.set(text != null ? text : ""); }
|
||||
|
||||
public int getAnimationId() { return animationId.get(); }
|
||||
public int getGraphicId() { return graphicId.get(); }
|
||||
public int getOrientation() { return orientation.get(); }
|
||||
public String getOverheadText() { return overheadText.get(); }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Player[%d] '%s' pos=(%d,%d,%d) cb=%d",
|
||||
index, username.get(), x.get(), y.get(), plane.get(), combatLevel.get());
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,15 @@
|
||||
package com.openosrs.client.core;
|
||||
|
||||
import com.openosrs.client.api.types.Position;
|
||||
import com.openosrs.client.api.types.Skill;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
/**
|
||||
* PlayerState - Manages the current player's state and statistics.
|
||||
@@ -20,13 +25,13 @@ public class PlayerState {
|
||||
private static final Logger logger = LoggerFactory.getLogger(PlayerState.class);
|
||||
|
||||
private final EventSystem eventSystem;
|
||||
private final ReadWriteLock lock = new ReentrantReadWriteLock();
|
||||
|
||||
// Position and movement
|
||||
private final AtomicInteger worldX = new AtomicInteger(0);
|
||||
private final AtomicInteger worldY = new AtomicInteger(0);
|
||||
private final AtomicInteger plane = new AtomicInteger(0);
|
||||
private final AtomicReference<Position> position = new AtomicReference<>(new Position(3200, 3200, 0));
|
||||
private final AtomicInteger localX = new AtomicInteger(0);
|
||||
private final AtomicInteger localY = new AtomicInteger(0);
|
||||
private final AtomicBoolean moving = new AtomicBoolean(false);
|
||||
|
||||
// Player stats
|
||||
private final AtomicInteger hitpoints = new AtomicInteger(10);
|
||||
@@ -36,10 +41,11 @@ public class PlayerState {
|
||||
private final AtomicInteger runEnergy = new AtomicInteger(100);
|
||||
private final AtomicInteger combatLevel = new AtomicInteger(3);
|
||||
|
||||
// Skills (0-22 for all skills)
|
||||
private final int[] skillLevels = new int[23];
|
||||
private final int[] skillExperience = new int[23];
|
||||
private final int[] skillBoostedLevels = new int[23];
|
||||
// Skills (indexed by skill ordinal)
|
||||
private final int[] skillLevels = new int[Skill.values().length];
|
||||
private final int[] skillExperience = new int[Skill.values().length];
|
||||
private final int[] skillBoostedLevels = new int[Skill.values().length];
|
||||
private final int[] skillRealLevels = new int[Skill.values().length];
|
||||
|
||||
// Player state
|
||||
private final AtomicReference<String> username = new AtomicReference<>("");
|
||||
@@ -59,13 +65,16 @@ public class PlayerState {
|
||||
|
||||
private void initializeSkills() {
|
||||
// Initialize all skills to level 1 with appropriate experience
|
||||
for (int i = 0; i < skillLevels.length; i++) {
|
||||
if (i == 3) { // Hitpoints starts at level 10
|
||||
for (Skill skill : Skill.values()) {
|
||||
int i = skill.ordinal();
|
||||
if (skill == Skill.HITPOINTS) { // Hitpoints starts at level 10
|
||||
skillLevels[i] = 10;
|
||||
skillRealLevels[i] = 10;
|
||||
skillBoostedLevels[i] = 10;
|
||||
skillExperience[i] = 1154; // Experience for level 10
|
||||
} else {
|
||||
skillLevels[i] = 1;
|
||||
skillRealLevels[i] = 1;
|
||||
skillBoostedLevels[i] = 1;
|
||||
skillExperience[i] = 0;
|
||||
}
|
||||
@@ -75,7 +84,7 @@ public class PlayerState {
|
||||
public void initialize() {
|
||||
logger.debug("Initializing PlayerState");
|
||||
// Set default position (Tutorial Island or Lumbridge)
|
||||
setPosition(3200, 3200, 0);
|
||||
setPosition(new Position(3200, 3200, 0));
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
@@ -100,16 +109,13 @@ public class PlayerState {
|
||||
}
|
||||
|
||||
// Position methods
|
||||
public void setPosition(int worldX, int worldY, int plane) {
|
||||
boolean changed = false;
|
||||
public void setPosition(Position newPosition) {
|
||||
if (newPosition == null) return;
|
||||
|
||||
if (this.worldX.getAndSet(worldX) != worldX) changed = true;
|
||||
if (this.worldY.getAndSet(worldY) != worldY) changed = true;
|
||||
if (this.plane.getAndSet(plane) != plane) changed = true;
|
||||
|
||||
if (changed) {
|
||||
logger.debug("Player position changed to ({}, {}, {})", worldX, worldY, plane);
|
||||
eventSystem.firePlayerMoved(worldX, worldY, plane);
|
||||
Position oldPosition = this.position.getAndSet(newPosition);
|
||||
if (!newPosition.equals(oldPosition)) {
|
||||
logger.debug("Player position changed to {}", newPosition);
|
||||
eventSystem.firePlayerMoved(newPosition.getX(), newPosition.getY(), newPosition.getPlane());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,12 +124,23 @@ public class PlayerState {
|
||||
this.localY.set(localY);
|
||||
}
|
||||
|
||||
public int getWorldX() { return worldX.get(); }
|
||||
public int getWorldY() { return worldY.get(); }
|
||||
public int getPlane() { return plane.get(); }
|
||||
public Position getPosition() { return position.get(); }
|
||||
public int getWorldX() { return position.get().getX(); }
|
||||
public int getWorldY() { return position.get().getY(); }
|
||||
public int getPlane() { return position.get().getPlane(); }
|
||||
public int getLocalX() { return localX.get(); }
|
||||
public int getLocalY() { return localY.get(); }
|
||||
|
||||
// Movement state
|
||||
public void setMoving(boolean isMoving) {
|
||||
boolean oldMoving = this.moving.getAndSet(isMoving);
|
||||
if (oldMoving != isMoving) {
|
||||
eventSystem.fireMovementStateChanged(isMoving);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isMoving() { return moving.get(); }
|
||||
|
||||
// Health and prayer methods
|
||||
public void setHitpoints(int current, int max) {
|
||||
int oldCurrent = this.hitpoints.getAndSet(current);
|
||||
@@ -144,7 +161,7 @@ public class PlayerState {
|
||||
}
|
||||
|
||||
public void setRunEnergy(int energy) {
|
||||
int oldEnergy = this.runEnergy.getAndSet(energy);
|
||||
int oldEnergy = this.runEnergy.getAndSet(Math.max(0, Math.min(100, energy)));
|
||||
if (oldEnergy != energy) {
|
||||
eventSystem.fireRunEnergyChanged(energy);
|
||||
}
|
||||
@@ -158,52 +175,116 @@ public class PlayerState {
|
||||
public int getCombatLevel() { return combatLevel.get(); }
|
||||
|
||||
// Skill methods
|
||||
public void setSkillLevel(int skill, int level) {
|
||||
if (skill >= 0 && skill < skillLevels.length) {
|
||||
int oldLevel = skillLevels[skill];
|
||||
skillLevels[skill] = level;
|
||||
skillBoostedLevels[skill] = level; // Reset boosted level
|
||||
public void setSkillLevel(Skill skill, int level) {
|
||||
if (skill == null) return;
|
||||
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
int skillIndex = skill.ordinal();
|
||||
int oldLevel = skillLevels[skillIndex];
|
||||
skillLevels[skillIndex] = level;
|
||||
skillBoostedLevels[skillIndex] = level; // Reset boosted level when real level changes
|
||||
|
||||
if (oldLevel != level) {
|
||||
eventSystem.fireSkillChanged(skill, level, skillExperience[skill]);
|
||||
eventSystem.fireSkillChanged(skill.getIndex(), level, skillExperience[skillIndex]);
|
||||
}
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void setSkillExperience(int skill, int experience) {
|
||||
if (skill >= 0 && skill < skillExperience.length) {
|
||||
int oldExp = skillExperience[skill];
|
||||
skillExperience[skill] = experience;
|
||||
public void setSkillRealLevel(Skill skill, int realLevel) {
|
||||
if (skill == null) return;
|
||||
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
int skillIndex = skill.ordinal();
|
||||
skillRealLevels[skillIndex] = realLevel;
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void setSkillExperience(Skill skill, int experience) {
|
||||
if (skill == null) return;
|
||||
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
int skillIndex = skill.ordinal();
|
||||
int oldExp = skillExperience[skillIndex];
|
||||
skillExperience[skillIndex] = experience;
|
||||
|
||||
if (oldExp != experience) {
|
||||
eventSystem.fireExperienceChanged(skill, experience);
|
||||
eventSystem.fireExperienceChanged(skill.getIndex(), experience);
|
||||
}
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void setBoostedSkillLevel(int skill, int boostedLevel) {
|
||||
if (skill >= 0 && skill < skillBoostedLevels.length) {
|
||||
skillBoostedLevels[skill] = boostedLevel;
|
||||
}
|
||||
}
|
||||
|
||||
public int getSkillLevel(int skill) {
|
||||
return skill >= 0 && skill < skillLevels.length ? skillLevels[skill] : 1;
|
||||
public void setBoostedSkillLevel(Skill skill, int boostedLevel) {
|
||||
if (skill == null) return;
|
||||
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
int skillIndex = skill.ordinal();
|
||||
skillBoostedLevels[skillIndex] = boostedLevel;
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public int getSkillExperience(int skill) {
|
||||
return skill >= 0 && skill < skillExperience.length ? skillExperience[skill] : 0;
|
||||
public int getSkillLevel(Skill skill) {
|
||||
if (skill == null) return 1;
|
||||
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
return skillLevels[skill.ordinal()];
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public int getBoostedSkillLevel(int skill) {
|
||||
return skill >= 0 && skill < skillBoostedLevels.length ? skillBoostedLevels[skill] : getSkillLevel(skill);
|
||||
public int getSkillRealLevel(Skill skill) {
|
||||
if (skill == null) return 1;
|
||||
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
return skillRealLevels[skill.ordinal()];
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public int getSkillExperience(Skill skill) {
|
||||
if (skill == null) return 0;
|
||||
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
return skillExperience[skill.ordinal()];
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public int getBoostedSkillLevel(Skill skill) {
|
||||
if (skill == null) return 1;
|
||||
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
return skillBoostedLevels[skill.ordinal()];
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
// Animation and interaction
|
||||
public void setAnimation(int animationId) {
|
||||
this.animationId.set(animationId);
|
||||
public void setCurrentAnimation(int animationId) {
|
||||
int oldAnimation = this.animationId.getAndSet(animationId);
|
||||
if (oldAnimation != animationId) {
|
||||
eventSystem.fireAnimationChanged(animationId);
|
||||
}
|
||||
}
|
||||
|
||||
public void setGraphic(int graphicId) {
|
||||
this.graphicId.set(graphicId);
|
||||
@@ -213,6 +294,7 @@ public class PlayerState {
|
||||
this.interacting.set(targetIndex);
|
||||
}
|
||||
|
||||
public int getCurrentAnimation() { return animationId.get(); }
|
||||
public int getAnimationId() { return animationId.get(); }
|
||||
public int getGraphicId() { return graphicId.get(); }
|
||||
public int getInteracting() { return interacting.get(); }
|
||||
|
||||
@@ -1,238 +1,197 @@
|
||||
package com.openosrs.client.core;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* GameNPC - Represents a non-player character in the game world.
|
||||
* WorldEntities - Manages all entities in the game world.
|
||||
* This includes NPCs, players, objects, and ground items.
|
||||
*/
|
||||
public class GameNPC {
|
||||
private final int index;
|
||||
private final int id;
|
||||
private final AtomicInteger x = new AtomicInteger();
|
||||
private final AtomicInteger y = new AtomicInteger();
|
||||
private final AtomicInteger plane = new AtomicInteger();
|
||||
public class WorldEntities {
|
||||
private final ConcurrentMap<Integer, GameNPC> npcs = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<Integer, OtherPlayer> players = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<String, GameObject> objects = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<String, GroundItem> groundItems = new ConcurrentHashMap<>();
|
||||
|
||||
private final AtomicInteger animationId = new AtomicInteger(-1);
|
||||
private final AtomicInteger graphicId = new AtomicInteger(-1);
|
||||
private final AtomicInteger orientation = new AtomicInteger(0);
|
||||
private final AtomicInteger hitpoints = new AtomicInteger(100);
|
||||
private final AtomicInteger maxHitpoints = new AtomicInteger(100);
|
||||
private final AtomicInteger combatLevel = new AtomicInteger(1);
|
||||
private final EventSystem eventSystem;
|
||||
|
||||
private final AtomicReference<String> name = new AtomicReference<>("");
|
||||
private final AtomicReference<String> examine = new AtomicReference<>("");
|
||||
private final AtomicReference<String> overheadText = new AtomicReference<>("");
|
||||
|
||||
private final AtomicInteger interacting = new AtomicInteger(-1);
|
||||
private final AtomicInteger targetIndex = new AtomicInteger(-1);
|
||||
|
||||
public GameNPC(int index, int id, int x, int y, int plane) {
|
||||
this.index = index;
|
||||
this.id = id;
|
||||
this.x.set(x);
|
||||
this.y.set(y);
|
||||
this.plane.set(plane);
|
||||
public WorldEntities(EventSystem eventSystem) {
|
||||
this.eventSystem = eventSystem;
|
||||
}
|
||||
|
||||
// ========== NPC MANAGEMENT ==========
|
||||
|
||||
public void addNPC(GameNPC npc) {
|
||||
if (npc != null) {
|
||||
npcs.put(npc.getIndex(), npc);
|
||||
eventSystem.publishEvent(new WorldState.NPCSpawnedEvent(npc));
|
||||
}
|
||||
}
|
||||
|
||||
public void removeNPC(int index) {
|
||||
GameNPC removed = npcs.remove(index);
|
||||
if (removed != null) {
|
||||
eventSystem.publishEvent(new WorldState.NPCDespawnedEvent(removed));
|
||||
}
|
||||
}
|
||||
|
||||
public GameNPC getNPC(int index) {
|
||||
return npcs.get(index);
|
||||
}
|
||||
|
||||
public Collection<GameNPC> getAllNPCs() {
|
||||
return npcs.values();
|
||||
}
|
||||
|
||||
public List<GameNPC> getNPCsInRadius(int centerX, int centerY, int radius) {
|
||||
List<GameNPC> result = new ArrayList<>();
|
||||
for (GameNPC npc : npcs.values()) {
|
||||
int distance = Math.abs(npc.getX() - centerX) + Math.abs(npc.getY() - centerY);
|
||||
if (distance <= radius) {
|
||||
result.add(npc);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ========== PLAYER MANAGEMENT ==========
|
||||
|
||||
public void addPlayer(OtherPlayer player) {
|
||||
if (player != null) {
|
||||
players.put(player.getIndex(), player);
|
||||
}
|
||||
}
|
||||
|
||||
public void removePlayer(int index) {
|
||||
players.remove(index);
|
||||
}
|
||||
|
||||
public OtherPlayer getPlayer(int index) {
|
||||
return players.get(index);
|
||||
}
|
||||
|
||||
public Collection<OtherPlayer> getAllPlayers() {
|
||||
return players.values();
|
||||
}
|
||||
|
||||
public List<OtherPlayer> getPlayersInRadius(int centerX, int centerY, int radius) {
|
||||
List<OtherPlayer> result = new ArrayList<>();
|
||||
for (OtherPlayer player : players.values()) {
|
||||
int distance = Math.abs(player.getX() - centerX) + Math.abs(player.getY() - centerY);
|
||||
if (distance <= radius) {
|
||||
result.add(player);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ========== OBJECT MANAGEMENT ==========
|
||||
|
||||
public void addObject(GameObject object) {
|
||||
if (object != null) {
|
||||
String key = objectKey(object.getX(), object.getY(), object.getPlane());
|
||||
objects.put(key, object);
|
||||
eventSystem.publishEvent(new WorldState.ObjectSpawnedEvent(object));
|
||||
}
|
||||
}
|
||||
|
||||
public void removeObject(int x, int y, int plane) {
|
||||
String key = objectKey(x, y, plane);
|
||||
GameObject removed = objects.remove(key);
|
||||
if (removed != null) {
|
||||
eventSystem.publishEvent(new WorldState.ObjectDespawnedEvent(removed));
|
||||
}
|
||||
}
|
||||
|
||||
public GameObject getObject(int x, int y, int plane) {
|
||||
String key = objectKey(x, y, plane);
|
||||
return objects.get(key);
|
||||
}
|
||||
|
||||
public Collection<GameObject> getAllObjects() {
|
||||
return objects.values();
|
||||
}
|
||||
|
||||
// ========== GROUND ITEM MANAGEMENT ==========
|
||||
|
||||
public void addGroundItem(GroundItem item) {
|
||||
if (item != null) {
|
||||
String key = groundItemKey(item.getX(), item.getY(), item.getPlane(), item.getId());
|
||||
groundItems.put(key, item);
|
||||
eventSystem.publishEvent(new WorldState.ItemSpawnedEvent(item));
|
||||
}
|
||||
}
|
||||
|
||||
public void removeGroundItem(int x, int y, int plane, int itemId) {
|
||||
String key = groundItemKey(x, y, plane, itemId);
|
||||
GroundItem removed = groundItems.remove(key);
|
||||
if (removed != null) {
|
||||
eventSystem.publishEvent(new WorldState.ItemDespawnedEvent(removed));
|
||||
}
|
||||
}
|
||||
|
||||
public GroundItem getGroundItem(int x, int y, int plane, int itemId) {
|
||||
String key = groundItemKey(x, y, plane, itemId);
|
||||
return groundItems.get(key);
|
||||
}
|
||||
|
||||
public Collection<GroundItem> getAllGroundItems() {
|
||||
return groundItems.values();
|
||||
}
|
||||
|
||||
public List<GroundItem> getGroundItemsAt(int x, int y, int plane) {
|
||||
List<GroundItem> result = new ArrayList<>();
|
||||
for (GroundItem item : groundItems.values()) {
|
||||
if (item.getX() == x && item.getY() == y && item.getPlane() == plane) {
|
||||
result.add(item);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ========== UTILITY METHODS ==========
|
||||
|
||||
public void tick() {
|
||||
// Update NPC state each game tick
|
||||
// This could include movement, combat timers, etc.
|
||||
// Update all entities
|
||||
for (GameNPC npc : npcs.values()) {
|
||||
npc.tick();
|
||||
}
|
||||
|
||||
// Position methods
|
||||
public void setPosition(int x, int y) {
|
||||
this.x.set(x);
|
||||
this.y.set(y);
|
||||
for (OtherPlayer player : players.values()) {
|
||||
player.tick();
|
||||
}
|
||||
|
||||
public void setPosition(int x, int y, int plane) {
|
||||
this.x.set(x);
|
||||
this.y.set(y);
|
||||
this.plane.set(plane);
|
||||
// Remove expired ground items
|
||||
groundItems.entrySet().removeIf(entry -> {
|
||||
GroundItem item = entry.getValue();
|
||||
if (item.isExpired()) {
|
||||
eventSystem.publishEvent(new WorldState.ItemDespawnedEvent(item));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
public int getIndex() { return index; }
|
||||
public int getId() { return id; }
|
||||
public int getX() { return x.get(); }
|
||||
public int getY() { return y.get(); }
|
||||
public int getPlane() { return plane.get(); }
|
||||
|
||||
// Animation and graphics
|
||||
public void setAnimation(int animationId) { this.animationId.set(animationId); }
|
||||
public void setGraphic(int graphicId) { this.graphicId.set(graphicId); }
|
||||
public void setOrientation(int orientation) { this.orientation.set(orientation); }
|
||||
|
||||
public int getAnimationId() { return animationId.get(); }
|
||||
public int getGraphicId() { return graphicId.get(); }
|
||||
public int getOrientation() { return orientation.get(); }
|
||||
|
||||
// Health and combat
|
||||
public void setHitpoints(int current, int max) {
|
||||
this.hitpoints.set(current);
|
||||
this.maxHitpoints.set(max);
|
||||
public void clear() {
|
||||
npcs.clear();
|
||||
players.clear();
|
||||
objects.clear();
|
||||
groundItems.clear();
|
||||
}
|
||||
|
||||
public void setCombatLevel(int level) { this.combatLevel.set(level); }
|
||||
public int getNPCCount() { return npcs.size(); }
|
||||
public int getPlayerCount() { return players.size(); }
|
||||
public int getObjectCount() { return objects.size(); }
|
||||
public int getGroundItemCount() { return groundItems.size(); }
|
||||
|
||||
public int getHitpoints() { return hitpoints.get(); }
|
||||
public int getMaxHitpoints() { return maxHitpoints.get(); }
|
||||
public int getCombatLevel() { return combatLevel.get(); }
|
||||
private String objectKey(int x, int y, int plane) {
|
||||
return x + "," + y + "," + plane;
|
||||
}
|
||||
|
||||
// Text and interaction
|
||||
public void setName(String name) { this.name.set(name != null ? name : ""); }
|
||||
public void setExamine(String examine) { this.examine.set(examine != null ? examine : ""); }
|
||||
public void setOverheadText(String text) { this.overheadText.set(text != null ? text : ""); }
|
||||
|
||||
public String getName() { return name.get(); }
|
||||
public String getExamine() { return examine.get(); }
|
||||
public String getOverheadText() { return overheadText.get(); }
|
||||
|
||||
// Interaction
|
||||
public void setInteracting(int targetIndex) { this.interacting.set(targetIndex); }
|
||||
public void setTargetIndex(int targetIndex) { this.targetIndex.set(targetIndex); }
|
||||
|
||||
public int getInteracting() { return interacting.get(); }
|
||||
public int getTargetIndex() { return targetIndex.get(); }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("NPC[%d] id=%d name='%s' pos=(%d,%d,%d)",
|
||||
index, id, name.get(), x.get(), y.get(), plane.get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GameObject - Represents an interactive object in the game world.
|
||||
*/
|
||||
class GameObject {
|
||||
private final int id;
|
||||
private final int x, y, plane;
|
||||
private final int type;
|
||||
private final int orientation;
|
||||
|
||||
public GameObject(int id, int x, int y, int plane, int type, int orientation) {
|
||||
this.id = id;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.plane = plane;
|
||||
this.type = type;
|
||||
this.orientation = orientation;
|
||||
}
|
||||
|
||||
public int getId() { return id; }
|
||||
public int getX() { return x; }
|
||||
public int getY() { return y; }
|
||||
public int getPlane() { return plane; }
|
||||
public int getType() { return type; }
|
||||
public int getOrientation() { return orientation; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Object[%d] pos=(%d,%d,%d) type=%d orientation=%d",
|
||||
id, x, y, plane, type, orientation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GroundItem - Represents an item on the ground.
|
||||
*/
|
||||
class GroundItem {
|
||||
private final int itemId;
|
||||
private final int quantity;
|
||||
private final int x, y, plane;
|
||||
private final long spawnTime;
|
||||
private final long expireTime;
|
||||
|
||||
public GroundItem(int itemId, int quantity, int x, int y, int plane) {
|
||||
this.itemId = itemId;
|
||||
this.quantity = quantity;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.plane = plane;
|
||||
this.spawnTime = System.currentTimeMillis();
|
||||
this.expireTime = spawnTime + (5 * 60 * 1000); // 5 minutes
|
||||
}
|
||||
|
||||
public boolean isExpired() {
|
||||
return System.currentTimeMillis() > expireTime;
|
||||
}
|
||||
|
||||
public int getItemId() { return itemId; }
|
||||
public int getQuantity() { return quantity; }
|
||||
public int getX() { return x; }
|
||||
public int getY() { return y; }
|
||||
public int getPlane() { return plane; }
|
||||
public long getSpawnTime() { return spawnTime; }
|
||||
public long getExpireTime() { return expireTime; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("GroundItem[%d] x%d pos=(%d,%d,%d)",
|
||||
itemId, quantity, x, y, plane);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OtherPlayer - Represents another player in the game world.
|
||||
*/
|
||||
class OtherPlayer {
|
||||
private final int index;
|
||||
private final AtomicReference<String> username = new AtomicReference<>();
|
||||
private final AtomicInteger x = new AtomicInteger();
|
||||
private final AtomicInteger y = new AtomicInteger();
|
||||
private final AtomicInteger plane = new AtomicInteger();
|
||||
private final AtomicInteger combatLevel = new AtomicInteger();
|
||||
|
||||
private final AtomicInteger animationId = new AtomicInteger(-1);
|
||||
private final AtomicInteger graphicId = new AtomicInteger(-1);
|
||||
private final AtomicInteger orientation = new AtomicInteger(0);
|
||||
private final AtomicReference<String> overheadText = new AtomicReference<>("");
|
||||
|
||||
public OtherPlayer(int index, String username, int x, int y, int plane, int combatLevel) {
|
||||
this.index = index;
|
||||
this.username.set(username);
|
||||
this.x.set(x);
|
||||
this.y.set(y);
|
||||
this.plane.set(plane);
|
||||
this.combatLevel.set(combatLevel);
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
// Update player state each game tick
|
||||
}
|
||||
|
||||
public void setPosition(int x, int y) {
|
||||
this.x.set(x);
|
||||
this.y.set(y);
|
||||
}
|
||||
|
||||
public void setPosition(int x, int y, int plane) {
|
||||
this.x.set(x);
|
||||
this.y.set(y);
|
||||
this.plane.set(plane);
|
||||
}
|
||||
|
||||
public int getIndex() { return index; }
|
||||
public String getUsername() { return username.get(); }
|
||||
public int getX() { return x.get(); }
|
||||
public int getY() { return y.get(); }
|
||||
public int getPlane() { return plane.get(); }
|
||||
public int getCombatLevel() { return combatLevel.get(); }
|
||||
|
||||
public void setAnimation(int animationId) { this.animationId.set(animationId); }
|
||||
public void setGraphic(int graphicId) { this.graphicId.set(graphicId); }
|
||||
public void setOrientation(int orientation) { this.orientation.set(orientation); }
|
||||
public void setOverheadText(String text) { this.overheadText.set(text != null ? text : ""); }
|
||||
|
||||
public int getAnimationId() { return animationId.get(); }
|
||||
public int getGraphicId() { return graphicId.get(); }
|
||||
public int getOrientation() { return orientation.get(); }
|
||||
public String getOverheadText() { return overheadText.get(); }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Player[%d] '%s' pos=(%d,%d,%d) cb=%d",
|
||||
index, username.get(), x.get(), y.get(), plane.get(), combatLevel.get());
|
||||
private String groundItemKey(int x, int y, int plane, int itemId) {
|
||||
return x + "," + y + "," + plane + "," + itemId;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,17 @@
|
||||
package com.openosrs.client.core;
|
||||
|
||||
import com.openosrs.client.api.types.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* WorldState - Manages the game world state including NPCs, objects, and items.
|
||||
@@ -23,9 +27,10 @@ public class WorldState {
|
||||
private static final Logger logger = LoggerFactory.getLogger(WorldState.class);
|
||||
|
||||
private final EventSystem eventSystem;
|
||||
private final ReadWriteLock lock = new ReentrantReadWriteLock();
|
||||
|
||||
// World entities
|
||||
private final ConcurrentHashMap<Integer, GameNPC> npcs;
|
||||
// World entities - Using our modernized API types
|
||||
private final ConcurrentHashMap<Integer, NPC> npcs;
|
||||
private final ConcurrentHashMap<Integer, GameObject> objects;
|
||||
private final ConcurrentHashMap<Integer, GroundItem> groundItems;
|
||||
private final ConcurrentHashMap<Integer, OtherPlayer> otherPlayers;
|
||||
@@ -34,6 +39,12 @@ public class WorldState {
|
||||
private final AtomicInteger currentRegionId = new AtomicInteger(-1);
|
||||
private final AtomicInteger[] regionIds = new AtomicInteger[4]; // Current 2x2 region area
|
||||
|
||||
// Cache for frequently accessed data
|
||||
private volatile List<NPC> npcCache = new CopyOnWriteArrayList<>();
|
||||
private volatile List<GameObject> objectCache = new CopyOnWriteArrayList<>();
|
||||
private volatile List<GroundItem> groundItemCache = new CopyOnWriteArrayList<>();
|
||||
private volatile boolean cacheNeedsRefresh = true;
|
||||
|
||||
public WorldState(EventSystem eventSystem) {
|
||||
this.eventSystem = eventSystem;
|
||||
this.npcs = new ConcurrentHashMap<>();
|
||||
@@ -44,6 +55,8 @@ public class WorldState {
|
||||
for (int i = 0; i < regionIds.length; i++) {
|
||||
regionIds[i] = new AtomicInteger(-1);
|
||||
}
|
||||
|
||||
logger.debug("WorldState initialized");
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
@@ -64,174 +77,296 @@ public class WorldState {
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
// Update NPC states
|
||||
npcs.values().forEach(npc -> npc.tick());
|
||||
|
||||
// Update ground items (remove expired ones)
|
||||
groundItems.entrySet().removeIf(entry -> {
|
||||
GroundItem item = entry.getValue();
|
||||
if (item.isExpired()) {
|
||||
eventSystem.fireEvent(EventSystem.EventType.ITEM_DESPAWNED,
|
||||
new ItemDespawnedEvent(item.getId(), item.getX(), item.getY()));
|
||||
return true;
|
||||
// Update cache if needed
|
||||
if (cacheNeedsRefresh) {
|
||||
refreshCaches();
|
||||
}
|
||||
return false;
|
||||
|
||||
// Update NPC states - ticking individual entities if they have state
|
||||
// (In a real implementation, NPCs might have timers, animations, etc.)
|
||||
npcs.values().forEach(npc -> {
|
||||
// Update NPC-specific state if needed
|
||||
// For now, NPCs are mostly immutable data structures
|
||||
});
|
||||
|
||||
// Update other players
|
||||
otherPlayers.values().forEach(player -> player.tick());
|
||||
}
|
||||
|
||||
// NPC management
|
||||
public void addNPC(int index, int id, int x, int y, int plane) {
|
||||
GameNPC npc = new GameNPC(index, id, x, y, plane);
|
||||
npcs.put(index, npc);
|
||||
eventSystem.fireEvent(EventSystem.EventType.NPC_SPAWNED,
|
||||
new NPCSpawnedEvent(index, id, x, y, plane));
|
||||
logger.debug("Added NPC: {} at ({}, {}, {})", id, x, y, plane);
|
||||
}
|
||||
|
||||
public void removeNPC(int index) {
|
||||
GameNPC npc = npcs.remove(index);
|
||||
if (npc != null) {
|
||||
eventSystem.fireEvent(EventSystem.EventType.NPC_DESPAWNED,
|
||||
new NPCDespawnedEvent(index, npc.getId(), npc.getX(), npc.getY()));
|
||||
logger.debug("Removed NPC: {}", index);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateNPCPosition(int index, int x, int y) {
|
||||
GameNPC npc = npcs.get(index);
|
||||
if (npc != null) {
|
||||
npc.setPosition(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateNPCAnimation(int index, int animationId) {
|
||||
GameNPC npc = npcs.get(index);
|
||||
if (npc != null) {
|
||||
npc.setAnimation(animationId);
|
||||
}
|
||||
}
|
||||
|
||||
public GameNPC getNPC(int index) {
|
||||
return npcs.get(index);
|
||||
}
|
||||
|
||||
public List<GameNPC> getAllNPCs() {
|
||||
return new CopyOnWriteArrayList<>(npcs.values());
|
||||
}
|
||||
|
||||
public List<GameNPC> getNPCsById(int id) {
|
||||
return npcs.values().stream()
|
||||
.filter(npc -> npc.getId() == id)
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
|
||||
public List<GameNPC> getNPCsInRadius(int centerX, int centerY, int radius) {
|
||||
return npcs.values().stream()
|
||||
.filter(npc -> {
|
||||
int dx = npc.getX() - centerX;
|
||||
int dy = npc.getY() - centerY;
|
||||
return (dx * dx + dy * dy) <= (radius * radius);
|
||||
// Update ground items (remove expired ones)
|
||||
List<Integer> expiredItems = groundItems.entrySet().stream()
|
||||
.filter(entry -> {
|
||||
GroundItem item = entry.getValue();
|
||||
// Check if item has expired (5 minutes = 300,000ms)
|
||||
return System.currentTimeMillis() - item.getSpawnTime() > 300000;
|
||||
})
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Object management
|
||||
public void addObject(int objectId, int x, int y, int plane, int type, int orientation) {
|
||||
int key = generateObjectKey(x, y, plane);
|
||||
GameObject object = new GameObject(objectId, x, y, plane, type, orientation);
|
||||
objects.put(key, object);
|
||||
eventSystem.fireEvent(EventSystem.EventType.OBJECT_SPAWNED,
|
||||
new ObjectSpawnedEvent(objectId, x, y, plane, type, orientation));
|
||||
logger.debug("Added object: {} at ({}, {}, {})", objectId, x, y, plane);
|
||||
}
|
||||
|
||||
public void removeObject(int x, int y, int plane) {
|
||||
int key = generateObjectKey(x, y, plane);
|
||||
GameObject object = objects.remove(key);
|
||||
if (object != null) {
|
||||
eventSystem.fireEvent(EventSystem.EventType.OBJECT_DESPAWNED,
|
||||
new ObjectDespawnedEvent(object.getId(), x, y, plane));
|
||||
logger.debug("Removed object at ({}, {}, {})", x, y, plane);
|
||||
}
|
||||
}
|
||||
|
||||
public GameObject getObjectAt(int x, int y, int plane) {
|
||||
return objects.get(generateObjectKey(x, y, plane));
|
||||
}
|
||||
|
||||
public List<GameObject> getAllObjects() {
|
||||
return new CopyOnWriteArrayList<>(objects.values());
|
||||
}
|
||||
|
||||
public List<GameObject> getObjectsById(int id) {
|
||||
return objects.values().stream()
|
||||
.filter(obj -> obj.getId() == id)
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
|
||||
private int generateObjectKey(int x, int y, int plane) {
|
||||
return (plane << 24) | (x << 12) | y;
|
||||
}
|
||||
|
||||
// Ground item management
|
||||
public void addGroundItem(int itemId, int quantity, int x, int y, int plane) {
|
||||
int key = generateItemKey(x, y, plane, itemId);
|
||||
GroundItem item = new GroundItem(itemId, quantity, x, y, plane);
|
||||
groundItems.put(key, item);
|
||||
eventSystem.fireEvent(EventSystem.EventType.ITEM_SPAWNED,
|
||||
new ItemSpawnedEvent(itemId, quantity, x, y, plane));
|
||||
logger.debug("Added ground item: {} x{} at ({}, {}, {})", itemId, quantity, x, y, plane);
|
||||
}
|
||||
|
||||
public void removeGroundItem(int itemId, int x, int y, int plane) {
|
||||
int key = generateItemKey(x, y, plane, itemId);
|
||||
for (Integer key : expiredItems) {
|
||||
GroundItem item = groundItems.remove(key);
|
||||
if (item != null) {
|
||||
eventSystem.fireEvent(EventSystem.EventType.ITEM_DESPAWNED,
|
||||
new ItemDespawnedEvent(itemId, x, y));
|
||||
logger.debug("Removed ground item: {} at ({}, {}, {})", itemId, x, y, plane);
|
||||
new ItemDespawnedEvent(item.getItemId(), item.getPosition().getX(), item.getPosition().getY()));
|
||||
logger.debug("Ground item {} expired at {}", item.getItemId(), item.getPosition());
|
||||
cacheNeedsRefresh = true;
|
||||
}
|
||||
}
|
||||
|
||||
public GroundItem getGroundItemAt(int x, int y, int plane, int itemId) {
|
||||
return groundItems.get(generateItemKey(x, y, plane, itemId));
|
||||
// Update other players (similar to NPCs, mostly immutable for now)
|
||||
otherPlayers.values().forEach(player -> {
|
||||
// Update player-specific state if needed
|
||||
});
|
||||
}
|
||||
|
||||
public List<GroundItem> getGroundItemsAt(int x, int y, int plane) {
|
||||
return groundItems.values().stream()
|
||||
.filter(item -> item.getX() == x && item.getY() == y && item.getPlane() == plane)
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
private void refreshCaches() {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
npcCache = new CopyOnWriteArrayList<>(npcs.values());
|
||||
objectCache = new CopyOnWriteArrayList<>(objects.values());
|
||||
groundItemCache = new CopyOnWriteArrayList<>(groundItems.values());
|
||||
cacheNeedsRefresh = false;
|
||||
logger.debug("World state caches refreshed: {} NPCs, {} objects, {} ground items",
|
||||
npcCache.size(), objectCache.size(), groundItemCache.size());
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
// === BULK UPDATE METHODS FOR BRIDGE INTEGRATION ===
|
||||
|
||||
/**
|
||||
* Update all NPCs from bridge data (called by BridgeAdapter)
|
||||
*/
|
||||
public void updateNPCs(List<NPC> newNPCs) {
|
||||
if (newNPCs == null) return;
|
||||
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
// Clear existing NPCs
|
||||
npcs.clear();
|
||||
|
||||
// Add new NPCs
|
||||
for (NPC npc : newNPCs) {
|
||||
if (npc != null && npc.getIndex() >= 0) {
|
||||
npcs.put(npc.getIndex(), npc);
|
||||
}
|
||||
}
|
||||
|
||||
cacheNeedsRefresh = true;
|
||||
logger.debug("Updated {} NPCs from bridge", newNPCs.size());
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all game objects from bridge data
|
||||
*/
|
||||
public void updateGameObjects(List<GameObject> newObjects) {
|
||||
if (newObjects == null) return;
|
||||
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
// Clear existing objects
|
||||
objects.clear();
|
||||
|
||||
// Add new objects (use hash of position as key)
|
||||
for (GameObject obj : newObjects) {
|
||||
if (obj != null && obj.getPosition() != null) {
|
||||
int key = generateObjectKey(obj.getPosition());
|
||||
objects.put(key, obj);
|
||||
}
|
||||
}
|
||||
|
||||
cacheNeedsRefresh = true;
|
||||
logger.debug("Updated {} game objects from bridge", newObjects.size());
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all ground items from bridge data
|
||||
*/
|
||||
public void updateGroundItems(List<GroundItem> newItems) {
|
||||
if (newItems == null) return;
|
||||
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
// Clear existing ground items
|
||||
groundItems.clear();
|
||||
|
||||
// Add new ground items
|
||||
for (GroundItem item : newItems) {
|
||||
if (item != null && item.getPosition() != null) {
|
||||
int key = generateItemKey(item.getPosition(), item.getItemId());
|
||||
groundItems.put(key, item);
|
||||
}
|
||||
}
|
||||
|
||||
cacheNeedsRefresh = true;
|
||||
logger.debug("Updated {} ground items from bridge", newItems.size());
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
// === QUERY METHODS FOR AGENTS ===
|
||||
|
||||
public NPC getNPC(int index) {
|
||||
return npcs.get(index);
|
||||
}
|
||||
|
||||
public List<NPC> getAllNPCs() {
|
||||
return new CopyOnWriteArrayList<>(npcCache);
|
||||
}
|
||||
|
||||
public List<NPC> getNPCsById(int id) {
|
||||
return npcCache.stream()
|
||||
.filter(npc -> npc.getId() == id)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<NPC> getNPCsByName(String name) {
|
||||
if (name == null) return List.of();
|
||||
|
||||
return npcCache.stream()
|
||||
.filter(npc -> name.equalsIgnoreCase(npc.getName()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<NPC> getNPCsInRadius(Position center, int radius) {
|
||||
if (center == null) return List.of();
|
||||
|
||||
return npcCache.stream()
|
||||
.filter(npc -> {
|
||||
Position npcPos = npc.getPosition();
|
||||
if (npcPos == null || npcPos.getPlane() != center.getPlane()) {
|
||||
return false;
|
||||
}
|
||||
return npcPos.distanceTo(center) <= radius;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public NPC getClosestNPC(Position playerPos, int npcId) {
|
||||
if (playerPos == null) return null;
|
||||
|
||||
return npcCache.stream()
|
||||
.filter(npc -> npc.getId() == npcId)
|
||||
.filter(npc -> npc.getPosition() != null && npc.getPosition().getPlane() == playerPos.getPlane())
|
||||
.min((npc1, npc2) -> Double.compare(
|
||||
npc1.getPosition().distanceTo(playerPos),
|
||||
npc2.getPosition().distanceTo(playerPos)
|
||||
))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
// === GAME OBJECT METHODS ===
|
||||
|
||||
public GameObject getObjectAt(Position position) {
|
||||
if (position == null) return null;
|
||||
return objects.get(generateObjectKey(position));
|
||||
}
|
||||
|
||||
public List<GameObject> getAllGameObjects() {
|
||||
return new CopyOnWriteArrayList<>(objectCache);
|
||||
}
|
||||
|
||||
public List<GameObject> getObjectsById(int id) {
|
||||
return objectCache.stream()
|
||||
.filter(obj -> obj.getId() == id)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<GameObject> getObjectsByName(String name) {
|
||||
if (name == null) return List.of();
|
||||
|
||||
return objectCache.stream()
|
||||
.filter(obj -> name.equalsIgnoreCase(obj.getName()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<GameObject> getObjectsWithAction(String action) {
|
||||
if (action == null) return List.of();
|
||||
|
||||
return objectCache.stream()
|
||||
.filter(obj -> obj.hasAction(action))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public GameObject getClosestGameObject(Position playerPos, int objectId) {
|
||||
if (playerPos == null) return null;
|
||||
|
||||
return objectCache.stream()
|
||||
.filter(obj -> obj.getId() == objectId)
|
||||
.filter(obj -> obj.getPosition() != null && obj.getPosition().getPlane() == playerPos.getPlane())
|
||||
.min((obj1, obj2) -> Double.compare(
|
||||
obj1.getPosition().distanceTo(playerPos),
|
||||
obj2.getPosition().distanceTo(playerPos)
|
||||
))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private int generateObjectKey(Position position) {
|
||||
return (position.getPlane() << 24) | (position.getX() << 12) | position.getY();
|
||||
}
|
||||
|
||||
// === GROUND ITEM METHODS ===
|
||||
|
||||
public GroundItem getGroundItemAt(Position position, int itemId) {
|
||||
if (position == null) return null;
|
||||
return groundItems.get(generateItemKey(position, itemId));
|
||||
}
|
||||
|
||||
public List<GroundItem> getGroundItemsAt(Position position) {
|
||||
if (position == null) return List.of();
|
||||
|
||||
return groundItemCache.stream()
|
||||
.filter(item -> position.equals(item.getPosition()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<GroundItem> getAllGroundItems() {
|
||||
return new CopyOnWriteArrayList<>(groundItems.values());
|
||||
return new CopyOnWriteArrayList<>(groundItemCache);
|
||||
}
|
||||
|
||||
private int generateItemKey(int x, int y, int plane, int itemId) {
|
||||
return (plane << 26) | (itemId << 14) | (x << 7) | y;
|
||||
public List<GroundItem> getGroundItemsById(int itemId) {
|
||||
return groundItemCache.stream()
|
||||
.filter(item -> item.getItemId() == itemId)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// Other player management
|
||||
public void addOtherPlayer(int index, String username, int x, int y, int plane, int combatLevel) {
|
||||
OtherPlayer player = new OtherPlayer(index, username, x, y, plane, combatLevel);
|
||||
otherPlayers.put(index, player);
|
||||
logger.debug("Added other player: {} at ({}, {}, {})", username, x, y, plane);
|
||||
public GroundItem getClosestGroundItem(Position playerPos, int itemId) {
|
||||
if (playerPos == null) return null;
|
||||
|
||||
return groundItemCache.stream()
|
||||
.filter(item -> item.getItemId() == itemId)
|
||||
.filter(item -> item.getPosition() != null && item.getPosition().getPlane() == playerPos.getPlane())
|
||||
.min((item1, item2) -> Double.compare(
|
||||
item1.getPosition().distanceTo(playerPos),
|
||||
item2.getPosition().distanceTo(playerPos)
|
||||
))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public void removeOtherPlayer(int index) {
|
||||
OtherPlayer player = otherPlayers.remove(index);
|
||||
if (player != null) {
|
||||
logger.debug("Removed other player: {}", player.getUsername());
|
||||
}
|
||||
private int generateItemKey(Position position, int itemId) {
|
||||
return (position.getPlane() << 26) | (itemId << 14) | (position.getX() << 7) | position.getY();
|
||||
}
|
||||
|
||||
public void updateOtherPlayerPosition(int index, int x, int y) {
|
||||
OtherPlayer player = otherPlayers.get(index);
|
||||
if (player != null) {
|
||||
player.setPosition(x, y);
|
||||
// === OTHER PLAYER METHODS ===
|
||||
|
||||
public void updateOtherPlayers(List<OtherPlayer> newPlayers) {
|
||||
if (newPlayers == null) return;
|
||||
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
otherPlayers.clear();
|
||||
for (OtherPlayer player : newPlayers) {
|
||||
if (player != null && player.getIndex() >= 0) {
|
||||
otherPlayers.put(player.getIndex(), player);
|
||||
}
|
||||
}
|
||||
logger.debug("Updated {} other players from bridge", newPlayers.size());
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,17 +378,33 @@ public class WorldState {
|
||||
return new CopyOnWriteArrayList<>(otherPlayers.values());
|
||||
}
|
||||
|
||||
// Region management
|
||||
// === REGION MANAGEMENT ===
|
||||
|
||||
public void updateRegion(int regionId) {
|
||||
int oldRegion = currentRegionId.getAndSet(regionId);
|
||||
if (oldRegion != regionId) {
|
||||
logger.debug("Region changed from {} to {}", oldRegion, regionId);
|
||||
if (oldRegion != regionId && regionId != -1) {
|
||||
logger.info("Region changed from {} to {}", oldRegion, regionId);
|
||||
|
||||
// Clear old entities when changing regions
|
||||
// When changing regions, clear all world entities
|
||||
// They will be repopulated by the bridge on the next update
|
||||
clearAllEntities();
|
||||
|
||||
eventSystem.fireEvent(EventSystem.EventType.REGION_CHANGED,
|
||||
new RegionChangedEvent(oldRegion, regionId));
|
||||
}
|
||||
}
|
||||
|
||||
private void clearAllEntities() {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
npcs.clear();
|
||||
objects.clear();
|
||||
groundItems.clear();
|
||||
otherPlayers.clear();
|
||||
cacheNeedsRefresh = true;
|
||||
logger.debug("Cleared all world entities due to region change");
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,98 +412,19 @@ public class WorldState {
|
||||
return currentRegionId.get();
|
||||
}
|
||||
|
||||
// Event classes for world events
|
||||
public static class NPCSpawnedEvent extends EventSystem.GameEvent {
|
||||
private final int index, id, x, y, plane;
|
||||
// === EVENT CLASSES ===
|
||||
|
||||
public NPCSpawnedEvent(int index, int id, int x, int y, int plane) {
|
||||
super(EventSystem.EventType.NPC_SPAWNED);
|
||||
this.index = index;
|
||||
this.id = id;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.plane = plane;
|
||||
public static class RegionChangedEvent extends EventSystem.GameEvent {
|
||||
private final int oldRegionId, newRegionId;
|
||||
|
||||
public RegionChangedEvent(int oldRegionId, int newRegionId) {
|
||||
super(EventSystem.EventType.REGION_CHANGED);
|
||||
this.oldRegionId = oldRegionId;
|
||||
this.newRegionId = newRegionId;
|
||||
}
|
||||
|
||||
public int getIndex() { return index; }
|
||||
public int getId() { return id; }
|
||||
public int getX() { return x; }
|
||||
public int getY() { return y; }
|
||||
public int getPlane() { return plane; }
|
||||
}
|
||||
|
||||
public static class NPCDespawnedEvent extends EventSystem.GameEvent {
|
||||
private final int index, id, x, y;
|
||||
|
||||
public NPCDespawnedEvent(int index, int id, int x, int y) {
|
||||
super(EventSystem.EventType.NPC_DESPAWNED);
|
||||
this.index = index;
|
||||
this.id = id;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public int getIndex() { return index; }
|
||||
public int getId() { return id; }
|
||||
public int getX() { return x; }
|
||||
public int getY() { return y; }
|
||||
}
|
||||
|
||||
public static class ObjectSpawnedEvent extends EventSystem.GameEvent {
|
||||
private final int id, x, y, plane, type, orientation;
|
||||
|
||||
public ObjectSpawnedEvent(int id, int x, int y, int plane, int type, int orientation) {
|
||||
super(EventSystem.EventType.OBJECT_SPAWNED);
|
||||
this.id = id;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.plane = plane;
|
||||
this.type = type;
|
||||
this.orientation = orientation;
|
||||
}
|
||||
|
||||
public int getId() { return id; }
|
||||
public int getX() { return x; }
|
||||
public int getY() { return y; }
|
||||
public int getPlane() { return plane; }
|
||||
public int getType() { return type; }
|
||||
public int getOrientation() { return orientation; }
|
||||
}
|
||||
|
||||
public static class ObjectDespawnedEvent extends EventSystem.GameEvent {
|
||||
private final int id, x, y, plane;
|
||||
|
||||
public ObjectDespawnedEvent(int id, int x, int y, int plane) {
|
||||
super(EventSystem.EventType.OBJECT_DESPAWNED);
|
||||
this.id = id;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.plane = plane;
|
||||
}
|
||||
|
||||
public int getId() { return id; }
|
||||
public int getX() { return x; }
|
||||
public int getY() { return y; }
|
||||
public int getPlane() { return plane; }
|
||||
}
|
||||
|
||||
public static class ItemSpawnedEvent extends EventSystem.GameEvent {
|
||||
private final int itemId, quantity, x, y, plane;
|
||||
|
||||
public ItemSpawnedEvent(int itemId, int quantity, int x, int y, int plane) {
|
||||
super(EventSystem.EventType.ITEM_SPAWNED);
|
||||
this.itemId = itemId;
|
||||
this.quantity = quantity;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.plane = plane;
|
||||
}
|
||||
|
||||
public int getItemId() { return itemId; }
|
||||
public int getQuantity() { return quantity; }
|
||||
public int getX() { return x; }
|
||||
public int getY() { return y; }
|
||||
public int getPlane() { return plane; }
|
||||
public int getOldRegionId() { return oldRegionId; }
|
||||
public int getNewRegionId() { return newRegionId; }
|
||||
}
|
||||
|
||||
public static class ItemDespawnedEvent extends EventSystem.GameEvent {
|
||||
|
||||
@@ -2,7 +2,17 @@ package com.openosrs.client.core.bridge;
|
||||
|
||||
import com.openosrs.client.api.*;
|
||||
import com.openosrs.client.core.ClientCore;
|
||||
import com.openosrs.client.core.state.*;
|
||||
import com.openosrs.client.core.PlayerState;
|
||||
import com.openosrs.client.core.InventoryState;
|
||||
import com.openosrs.client.core.NetworkState;
|
||||
import com.openosrs.client.core.InterfaceState;
|
||||
import com.openosrs.client.core.WorldState;
|
||||
import com.openosrs.client.api.types.GameObject;
|
||||
import com.openosrs.client.api.types.GroundItem;
|
||||
import com.openosrs.client.api.types.Item;
|
||||
import com.openosrs.client.api.types.NPC;
|
||||
import com.openosrs.client.api.types.Position;
|
||||
import com.openosrs.client.api.types.Skill;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -26,6 +36,7 @@ public class BridgeAdapter {
|
||||
private final PlayerState playerState;
|
||||
private final NetworkState networkState;
|
||||
private final InterfaceState interfaceState;
|
||||
private final WorldState worldState;
|
||||
|
||||
public BridgeAdapter(ClientCore clientCore, RuneLiteBridge bridge) {
|
||||
this.clientCore = clientCore;
|
||||
@@ -36,6 +47,7 @@ public class BridgeAdapter {
|
||||
this.playerState = clientCore.getPlayerState();
|
||||
this.networkState = clientCore.getNetworkState();
|
||||
this.interfaceState = clientCore.getInterfaceState();
|
||||
this.worldState = clientCore.getWorldState();
|
||||
|
||||
logger.info("Bridge adapter initialized");
|
||||
}
|
||||
@@ -51,6 +63,7 @@ public class BridgeAdapter {
|
||||
try {
|
||||
updatePlayerState();
|
||||
updateInventoryState();
|
||||
updateWorldState();
|
||||
updateNetworkState();
|
||||
} catch (Exception e) {
|
||||
logger.warn("Error updating state from bridge: {}", e.getMessage(), e);
|
||||
@@ -122,6 +135,41 @@ public class BridgeAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update world state from bridge.
|
||||
*/
|
||||
private void updateWorldState() {
|
||||
if (worldState == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update NPCs
|
||||
List<NPC> npcs = bridge.getAllNPCs();
|
||||
if (npcs != null) {
|
||||
worldState.updateNPCs(npcs);
|
||||
}
|
||||
|
||||
// Update game objects
|
||||
List<GameObject> objects = bridge.getAllGameObjects();
|
||||
if (objects != null) {
|
||||
worldState.updateGameObjects(objects);
|
||||
}
|
||||
|
||||
// Update ground items
|
||||
List<GroundItem> groundItems = bridge.getAllGroundItems();
|
||||
if (groundItems != null) {
|
||||
worldState.updateGroundItems(groundItems);
|
||||
}
|
||||
|
||||
// Update region if needed
|
||||
int gameState = bridge.getGameState();
|
||||
if (gameState == ClientCore.GameStateConstants.IN_GAME) {
|
||||
// In a real implementation, we'd get the current region from RuneLite
|
||||
// For now, we'll set a placeholder region ID
|
||||
// worldState.updateRegion(bridge.getCurrentRegion());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update network state from bridge.
|
||||
*/
|
||||
@@ -146,40 +194,40 @@ public class BridgeAdapter {
|
||||
* Get NPCs through bridge.
|
||||
*/
|
||||
public List<NPC> getNPCs() {
|
||||
if (bridge.isActive()) {
|
||||
return bridge.getAllNPCs();
|
||||
if (worldState != null) {
|
||||
return worldState.getAllNPCs();
|
||||
}
|
||||
return List.of(); // Empty list if bridge not active
|
||||
return List.of(); // Empty list if world state not available
|
||||
}
|
||||
|
||||
/**
|
||||
* Get game objects through bridge.
|
||||
*/
|
||||
public List<GameObject> getGameObjects() {
|
||||
if (bridge.isActive()) {
|
||||
return bridge.getAllGameObjects();
|
||||
if (worldState != null) {
|
||||
return worldState.getAllGameObjects();
|
||||
}
|
||||
return List.of(); // Empty list if bridge not active
|
||||
return List.of(); // Empty list if world state not available
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ground items through bridge.
|
||||
*/
|
||||
public List<GroundItem> getGroundItems() {
|
||||
if (bridge.isActive()) {
|
||||
return bridge.getAllGroundItems();
|
||||
if (worldState != null) {
|
||||
return worldState.getAllGroundItems();
|
||||
}
|
||||
return List.of(); // Empty list if bridge not active
|
||||
return List.of(); // Empty list if world state not available
|
||||
}
|
||||
|
||||
/**
|
||||
* Get player position through bridge.
|
||||
*/
|
||||
public Position getPlayerPosition() {
|
||||
if (bridge.isActive()) {
|
||||
return bridge.getPlayerPosition();
|
||||
if (playerState != null) {
|
||||
return playerState.getPosition();
|
||||
}
|
||||
return playerState != null ? playerState.getPosition() : null;
|
||||
return new Position(3200, 3200, 0); // Default position
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
package com.openosrs.client.core.bridge;
|
||||
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.client.RuneLite;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* ClientConnector - Handles connecting to an existing RuneLite client instance.
|
||||
*
|
||||
* This class provides methods to locate and connect to a running RuneLite client,
|
||||
* enabling our modernized client to extract data from the actual game client.
|
||||
*/
|
||||
public class ClientConnector {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ClientConnector.class);
|
||||
|
||||
private static Client connectedClient;
|
||||
private static boolean searchInProgress = false;
|
||||
|
||||
/**
|
||||
* Attempt to connect to an existing RuneLite client instance.
|
||||
*
|
||||
* @return CompletableFuture that completes with the client instance or null if not found
|
||||
*/
|
||||
public static CompletableFuture<Client> connectToRuneLiteClient() {
|
||||
if (connectedClient != null) {
|
||||
logger.info("Already connected to RuneLite client");
|
||||
return CompletableFuture.completedFuture(connectedClient);
|
||||
}
|
||||
|
||||
if (searchInProgress) {
|
||||
logger.info("Client connection search already in progress");
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
searchInProgress = true;
|
||||
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
logger.info("Searching for RuneLite client instance...");
|
||||
|
||||
// Method 1: Try to get from static RuneLite instance
|
||||
Client client = tryGetFromRuneLiteStatic();
|
||||
if (client != null) {
|
||||
logger.info("Connected to RuneLite client via static instance");
|
||||
connectedClient = client;
|
||||
return client;
|
||||
}
|
||||
|
||||
// Method 2: Try to find via reflection in current JVM
|
||||
client = tryFindViaReflection();
|
||||
if (client != null) {
|
||||
logger.info("Connected to RuneLite client via reflection");
|
||||
connectedClient = client;
|
||||
return client;
|
||||
}
|
||||
|
||||
// Method 3: Try to start embedded RuneLite client
|
||||
client = tryStartEmbeddedClient();
|
||||
if (client != null) {
|
||||
logger.info("Connected to embedded RuneLite client");
|
||||
connectedClient = client;
|
||||
return client;
|
||||
}
|
||||
|
||||
logger.warn("Could not connect to any RuneLite client instance");
|
||||
return null;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error connecting to RuneLite client", e);
|
||||
return null;
|
||||
} finally {
|
||||
searchInProgress = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get client from RuneLite static instance.
|
||||
*/
|
||||
private static Client tryGetFromRuneLiteStatic() {
|
||||
try {
|
||||
// Look for RuneLite singleton or static client
|
||||
Class<?> runeLiteClass = Class.forName("net.runelite.client.RuneLite");
|
||||
|
||||
// Try to find a static client field
|
||||
Field[] fields = runeLiteClass.getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
if (Client.class.isAssignableFrom(field.getType())) {
|
||||
field.setAccessible(true);
|
||||
Object value = field.get(null);
|
||||
if (value instanceof Client) {
|
||||
return (Client) value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to find a getInstance method
|
||||
try {
|
||||
Method getInstanceMethod = runeLiteClass.getMethod("getInstance");
|
||||
Object instance = getInstanceMethod.invoke(null);
|
||||
|
||||
if (instance != null) {
|
||||
// Look for client field in the instance
|
||||
Field[] instanceFields = instance.getClass().getDeclaredFields();
|
||||
for (Field field : instanceFields) {
|
||||
if (Client.class.isAssignableFrom(field.getType())) {
|
||||
field.setAccessible(true);
|
||||
Object value = field.get(instance);
|
||||
if (value instanceof Client) {
|
||||
return (Client) value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
// getInstance method doesn't exist, that's fine
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.debug("Could not get client from RuneLite static: {}", e.getMessage());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to find client via reflection search.
|
||||
*/
|
||||
private static Client tryFindViaReflection() {
|
||||
try {
|
||||
// Search through all classes in the current classloader
|
||||
// This is a more aggressive search for any Client instances
|
||||
|
||||
// Get the current thread's context class loader
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
|
||||
// Look for any objects in memory that implement Client
|
||||
// This is a simplified approach - in practice you'd need more sophisticated
|
||||
// heap scanning or dependency injection framework integration
|
||||
|
||||
logger.debug("Reflection-based client search not fully implemented yet");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.debug("Could not find client via reflection: {}", e.getMessage());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to start an embedded RuneLite client.
|
||||
*/
|
||||
private static Client tryStartEmbeddedClient() {
|
||||
try {
|
||||
logger.info("Attempting to start embedded RuneLite client...");
|
||||
|
||||
// This would require careful integration with RuneLite's startup process
|
||||
// For now, we'll just log that this is not implemented
|
||||
logger.warn("Embedded client startup not yet implemented");
|
||||
|
||||
// In a full implementation, this would:
|
||||
// 1. Initialize RuneLite's dependency injection
|
||||
// 2. Start the game client
|
||||
// 3. Return the client instance
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Could not start embedded client: {}", e.getMessage());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually set the client instance (for testing or external integration).
|
||||
*/
|
||||
public static void setClientInstance(Client client) {
|
||||
connectedClient = client;
|
||||
logger.info("Client instance manually set");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently connected client.
|
||||
*/
|
||||
public static Client getConnectedClient() {
|
||||
return connectedClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we're connected to a client.
|
||||
*/
|
||||
public static boolean isConnected() {
|
||||
return connectedClient != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from the current client.
|
||||
*/
|
||||
public static void disconnect() {
|
||||
if (connectedClient != null) {
|
||||
logger.info("Disconnecting from RuneLite client");
|
||||
connectedClient = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a client connection with timeout.
|
||||
*/
|
||||
public static CompletableFuture<Client> waitForConnection(long timeoutSeconds) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
long startTime = System.currentTimeMillis();
|
||||
long timeoutMs = timeoutSeconds * 1000;
|
||||
|
||||
while (System.currentTimeMillis() - startTime < timeoutMs) {
|
||||
if (connectedClient != null) {
|
||||
return connectedClient;
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(500); // Check every 500ms
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
logger.warn("Timeout waiting for client connection after {} seconds", timeoutSeconds);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.openosrs.client.core.bridge;
|
||||
|
||||
import com.openosrs.client.api.*;
|
||||
import com.openosrs.client.api.types.*;
|
||||
import com.openosrs.client.core.ClientCore;
|
||||
import net.runelite.api.*;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
@@ -27,6 +27,27 @@ public class RuneLiteBridge {
|
||||
|
||||
public RuneLiteBridge(ClientCore clientCore) {
|
||||
this.clientCore = clientCore;
|
||||
|
||||
// Automatically try to connect to RuneLite client
|
||||
initializeConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize connection to RuneLite client.
|
||||
*/
|
||||
private void initializeConnection() {
|
||||
ClientConnector.connectToRuneLiteClient()
|
||||
.thenAccept(client -> {
|
||||
if (client != null) {
|
||||
setRuneLiteClient(client);
|
||||
} else {
|
||||
logger.warn("No RuneLite client found - bridge will operate in standalone mode");
|
||||
}
|
||||
})
|
||||
.exceptionally(throwable -> {
|
||||
logger.error("Error connecting to RuneLite client", throwable);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,393 +0,0 @@
|
||||
package com.openosrs.client.engine;
|
||||
|
||||
import com.openosrs.client.core.ClientCore;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* RenderingEngine - Handles game rendering and graphics.
|
||||
*
|
||||
* This engine is minimal for agent-focused gameplay, providing just enough
|
||||
* rendering to maintain compatibility while prioritizing performance.
|
||||
*/
|
||||
public class RenderingEngine {
|
||||
private static final Logger logger = LoggerFactory.getLogger(RenderingEngine.class);
|
||||
|
||||
private final ClientCore clientCore;
|
||||
private final AtomicBoolean initialized = new AtomicBoolean(false);
|
||||
private final AtomicBoolean headlessMode = new AtomicBoolean(true); // Default to headless for agents
|
||||
|
||||
private long frameCount = 0;
|
||||
private long lastFpsUpdate = 0;
|
||||
private double currentFps = 0;
|
||||
|
||||
public RenderingEngine(ClientCore clientCore) {
|
||||
this.clientCore = clientCore;
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
if (initialized.get()) {
|
||||
logger.warn("RenderingEngine already initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Initializing RenderingEngine (headless={})", headlessMode.get());
|
||||
|
||||
try {
|
||||
if (!headlessMode.get()) {
|
||||
initializeGraphics();
|
||||
} else {
|
||||
logger.info("Running in headless mode - no graphics initialization");
|
||||
}
|
||||
|
||||
initialized.set(true);
|
||||
logger.info("RenderingEngine initialized");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to initialize RenderingEngine", e);
|
||||
throw new RuntimeException("RenderingEngine initialization failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeGraphics() {
|
||||
// Initialize OpenGL context, create window, etc.
|
||||
// For now, this is a placeholder for future graphics implementation
|
||||
logger.debug("Graphics context would be initialized here");
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
if (!initialized.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Shutting down RenderingEngine");
|
||||
|
||||
try {
|
||||
if (!headlessMode.get()) {
|
||||
cleanupGraphics();
|
||||
}
|
||||
|
||||
initialized.set(false);
|
||||
logger.info("RenderingEngine shutdown complete");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error during RenderingEngine shutdown", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanupGraphics() {
|
||||
// Cleanup OpenGL resources, destroy window, etc.
|
||||
logger.debug("Graphics resources would be cleaned up here");
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a frame (called each game tick).
|
||||
*/
|
||||
public void render() {
|
||||
if (!initialized.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
frameCount++;
|
||||
|
||||
try {
|
||||
if (!headlessMode.get()) {
|
||||
renderFrame();
|
||||
} else {
|
||||
// In headless mode, just update FPS counter
|
||||
updateFpsCounter();
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error during frame render", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void renderFrame() {
|
||||
// Actual rendering would happen here
|
||||
// For now, just update FPS
|
||||
updateFpsCounter();
|
||||
}
|
||||
|
||||
private void updateFpsCounter() {
|
||||
long now = System.currentTimeMillis();
|
||||
if (now - lastFpsUpdate >= 1000) {
|
||||
currentFps = frameCount;
|
||||
frameCount = 0;
|
||||
lastFpsUpdate = now;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isInitialized() { return initialized.get(); }
|
||||
public boolean isHeadless() { return headlessMode.get(); }
|
||||
public void setHeadless(boolean headless) { headlessMode.set(headless); }
|
||||
public double getCurrentFps() { return currentFps; }
|
||||
public long getFrameCount() { return frameCount; }
|
||||
}
|
||||
|
||||
/**
|
||||
* InputEngine - Handles user input and automated input from agents.
|
||||
*/
|
||||
class InputEngine {
|
||||
private static final Logger logger = LoggerFactory.getLogger(InputEngine.class);
|
||||
|
||||
private final ClientCore clientCore;
|
||||
private final AtomicBoolean initialized = new AtomicBoolean(false);
|
||||
|
||||
// Input state
|
||||
private final AtomicBoolean[] keysPressed = new AtomicBoolean[256];
|
||||
private int mouseX = 0, mouseY = 0;
|
||||
private boolean mousePressed = false;
|
||||
|
||||
public InputEngine(ClientCore clientCore) {
|
||||
this.clientCore = clientCore;
|
||||
|
||||
// Initialize key states
|
||||
for (int i = 0; i < keysPressed.length; i++) {
|
||||
keysPressed[i] = new AtomicBoolean(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
if (initialized.get()) {
|
||||
logger.warn("InputEngine already initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Initializing InputEngine");
|
||||
|
||||
try {
|
||||
// Initialize input systems
|
||||
// In a real implementation, this would set up keyboard/mouse listeners
|
||||
|
||||
initialized.set(true);
|
||||
logger.info("InputEngine initialized");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to initialize InputEngine", e);
|
||||
throw new RuntimeException("InputEngine initialization failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
if (!initialized.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Shutting down InputEngine");
|
||||
|
||||
try {
|
||||
// Cleanup input resources
|
||||
|
||||
initialized.set(false);
|
||||
logger.info("InputEngine shutdown complete");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error during InputEngine shutdown", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process input events (called each game tick).
|
||||
*/
|
||||
public void processInput() {
|
||||
if (!initialized.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Process any pending input events
|
||||
// This would handle keyboard/mouse events
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error processing input", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Agent input methods - allow programmatic input
|
||||
public void simulateKeyPress(int keyCode) {
|
||||
if (keyCode >= 0 && keyCode < keysPressed.length) {
|
||||
keysPressed[keyCode].set(true);
|
||||
logger.debug("Key pressed: {}", keyCode);
|
||||
}
|
||||
}
|
||||
|
||||
public void simulateKeyRelease(int keyCode) {
|
||||
if (keyCode >= 0 && keyCode < keysPressed.length) {
|
||||
keysPressed[keyCode].set(false);
|
||||
logger.debug("Key released: {}", keyCode);
|
||||
}
|
||||
}
|
||||
|
||||
public void simulateMouseMove(int x, int y) {
|
||||
mouseX = x;
|
||||
mouseY = y;
|
||||
logger.debug("Mouse moved to: ({}, {})", x, y);
|
||||
}
|
||||
|
||||
public void simulateMouseClick(int x, int y) {
|
||||
mouseX = x;
|
||||
mouseY = y;
|
||||
mousePressed = true;
|
||||
logger.debug("Mouse clicked at: ({}, {})", x, y);
|
||||
|
||||
// Reset mouse state after a brief moment
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
mousePressed = false;
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
// Input state queries
|
||||
public boolean isKeyPressed(int keyCode) {
|
||||
return keyCode >= 0 && keyCode < keysPressed.length && keysPressed[keyCode].get();
|
||||
}
|
||||
|
||||
public int getMouseX() { return mouseX; }
|
||||
public int getMouseY() { return mouseY; }
|
||||
public boolean isMousePressed() { return mousePressed; }
|
||||
public boolean isInitialized() { return initialized.get(); }
|
||||
}
|
||||
|
||||
/**
|
||||
* PhysicsEngine - Handles game physics and movement calculations.
|
||||
*/
|
||||
class PhysicsEngine {
|
||||
private static final Logger logger = LoggerFactory.getLogger(PhysicsEngine.class);
|
||||
|
||||
private final ClientCore clientCore;
|
||||
private final AtomicBoolean initialized = new AtomicBoolean(false);
|
||||
|
||||
public PhysicsEngine(ClientCore clientCore) {
|
||||
this.clientCore = clientCore;
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
if (initialized.get()) {
|
||||
logger.warn("PhysicsEngine already initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Initializing PhysicsEngine");
|
||||
|
||||
try {
|
||||
// Initialize physics systems
|
||||
|
||||
initialized.set(true);
|
||||
logger.info("PhysicsEngine initialized");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to initialize PhysicsEngine", e);
|
||||
throw new RuntimeException("PhysicsEngine initialization failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
if (!initialized.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Shutting down PhysicsEngine");
|
||||
|
||||
try {
|
||||
// Cleanup physics resources
|
||||
|
||||
initialized.set(false);
|
||||
logger.info("PhysicsEngine shutdown complete");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error during PhysicsEngine shutdown", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update physics simulation (called each game tick).
|
||||
*/
|
||||
public void update() {
|
||||
if (!initialized.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Update player movement
|
||||
updatePlayerMovement();
|
||||
|
||||
// Update NPC movement
|
||||
updateNPCMovement();
|
||||
|
||||
// Update object physics (falling items, etc.)
|
||||
updateObjectPhysics();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error updating physics", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePlayerMovement() {
|
||||
// Calculate player movement based on input and game state
|
||||
// This would handle walking, running, animations, etc.
|
||||
}
|
||||
|
||||
private void updateNPCMovement() {
|
||||
// Update NPC positions and animations
|
||||
// This would handle NPC AI movement patterns
|
||||
}
|
||||
|
||||
private void updateObjectPhysics() {
|
||||
// Handle physics for game objects
|
||||
// Falling items, projectiles, etc.
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate path between two points.
|
||||
*/
|
||||
public int[][] calculatePath(int startX, int startY, int endX, int endY) {
|
||||
// Simple pathfinding implementation
|
||||
// In a real implementation, this would use A* or similar algorithm
|
||||
|
||||
logger.debug("Calculating path from ({}, {}) to ({}, {})", startX, startY, endX, endY);
|
||||
|
||||
// For now, return a simple straight line path
|
||||
int deltaX = endX - startX;
|
||||
int deltaY = endY - startY;
|
||||
int steps = Math.max(Math.abs(deltaX), Math.abs(deltaY));
|
||||
|
||||
if (steps == 0) {
|
||||
return new int[0][2];
|
||||
}
|
||||
|
||||
int[][] path = new int[steps][2];
|
||||
for (int i = 0; i < steps; i++) {
|
||||
path[i][0] = startX + (deltaX * i / steps);
|
||||
path[i][1] = startY + (deltaY * i / steps);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a tile is walkable.
|
||||
*/
|
||||
public boolean isWalkable(int x, int y, int plane) {
|
||||
// Check collision data, objects, etc.
|
||||
// For now, assume all tiles are walkable
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate distance between two points.
|
||||
*/
|
||||
public double calculateDistance(int x1, int y1, int x2, int y2) {
|
||||
int dx = x2 - x1;
|
||||
int dy = y2 - y1;
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
public boolean isInitialized() { return initialized.get(); }
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package com.openosrs.client.engine;
|
||||
|
||||
import com.openosrs.client.core.ClientCore;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* InputEngine - Handles user input and automated input from agents.
|
||||
*/
|
||||
class InputEngine {
|
||||
private static final Logger logger = LoggerFactory.getLogger(InputEngine.class);
|
||||
|
||||
private final ClientCore clientCore;
|
||||
private final AtomicBoolean initialized = new AtomicBoolean(false);
|
||||
|
||||
// Input state
|
||||
private final AtomicBoolean[] keysPressed = new AtomicBoolean[256];
|
||||
private int mouseX = 0, mouseY = 0;
|
||||
private boolean mousePressed = false;
|
||||
|
||||
public InputEngine(ClientCore clientCore) {
|
||||
this.clientCore = clientCore;
|
||||
|
||||
// Initialize key states
|
||||
for (int i = 0; i < keysPressed.length; i++) {
|
||||
keysPressed[i] = new AtomicBoolean(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
if (initialized.get()) {
|
||||
logger.warn("InputEngine already initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Initializing InputEngine");
|
||||
|
||||
try {
|
||||
// Initialize input systems
|
||||
// In a real implementation, this would set up keyboard/mouse listeners
|
||||
|
||||
initialized.set(true);
|
||||
logger.info("InputEngine initialized");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to initialize InputEngine", e);
|
||||
throw new RuntimeException("InputEngine initialization failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
if (!initialized.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Shutting down InputEngine");
|
||||
|
||||
try {
|
||||
// Cleanup input resources
|
||||
|
||||
initialized.set(false);
|
||||
logger.info("InputEngine shutdown complete");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error during InputEngine shutdown", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process input events (called each game tick).
|
||||
*/
|
||||
public void processInput() {
|
||||
if (!initialized.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Process any pending input events
|
||||
// This would handle keyboard/mouse events
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error processing input", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Agent input methods - allow programmatic input
|
||||
public void simulateKeyPress(int keyCode) {
|
||||
if (keyCode >= 0 && keyCode < keysPressed.length) {
|
||||
keysPressed[keyCode].set(true);
|
||||
logger.debug("Key pressed: {}", keyCode);
|
||||
}
|
||||
}
|
||||
|
||||
public void simulateKeyRelease(int keyCode) {
|
||||
if (keyCode >= 0 && keyCode < keysPressed.length) {
|
||||
keysPressed[keyCode].set(false);
|
||||
logger.debug("Key released: {}", keyCode);
|
||||
}
|
||||
}
|
||||
|
||||
public void simulateMouseMove(int x, int y) {
|
||||
mouseX = x;
|
||||
mouseY = y;
|
||||
logger.debug("Mouse moved to: ({}, {})", x, y);
|
||||
}
|
||||
|
||||
public void simulateMouseClick(int x, int y) {
|
||||
mouseX = x;
|
||||
mouseY = y;
|
||||
mousePressed = true;
|
||||
logger.debug("Mouse clicked at: ({}, {})", x, y);
|
||||
|
||||
// Reset mouse state after a brief moment
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
mousePressed = false;
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
// Input state queries
|
||||
public boolean isKeyPressed(int keyCode) {
|
||||
return keyCode >= 0 && keyCode < keysPressed.length && keysPressed[keyCode].get();
|
||||
}
|
||||
|
||||
public int getMouseX() { return mouseX; }
|
||||
public int getMouseY() { return mouseY; }
|
||||
public boolean isMousePressed() { return mousePressed; }
|
||||
public boolean isInitialized() { return initialized.get(); }
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package com.openosrs.client.engine;
|
||||
|
||||
import com.openosrs.client.core.ClientCore;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* PhysicsEngine - Handles game physics and movement calculations.
|
||||
*/
|
||||
class PhysicsEngine {
|
||||
private static final Logger logger = LoggerFactory.getLogger(PhysicsEngine.class);
|
||||
|
||||
private final ClientCore clientCore;
|
||||
private final AtomicBoolean initialized = new AtomicBoolean(false);
|
||||
|
||||
public PhysicsEngine(ClientCore clientCore) {
|
||||
this.clientCore = clientCore;
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
if (initialized.get()) {
|
||||
logger.warn("PhysicsEngine already initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Initializing PhysicsEngine");
|
||||
|
||||
try {
|
||||
// Initialize physics systems
|
||||
|
||||
initialized.set(true);
|
||||
logger.info("PhysicsEngine initialized");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to initialize PhysicsEngine", e);
|
||||
throw new RuntimeException("PhysicsEngine initialization failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
if (!initialized.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Shutting down PhysicsEngine");
|
||||
|
||||
try {
|
||||
// Cleanup physics resources
|
||||
|
||||
initialized.set(false);
|
||||
logger.info("PhysicsEngine shutdown complete");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error during PhysicsEngine shutdown", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update physics simulation (called each game tick).
|
||||
*/
|
||||
public void update() {
|
||||
if (!initialized.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Update player movement
|
||||
updatePlayerMovement();
|
||||
|
||||
// Update NPC movement
|
||||
updateNPCMovement();
|
||||
|
||||
// Update object physics (falling items, etc.)
|
||||
updateObjectPhysics();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error updating physics", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePlayerMovement() {
|
||||
// Calculate player movement based on input and game state
|
||||
// This would handle walking, running, animations, etc.
|
||||
}
|
||||
|
||||
private void updateNPCMovement() {
|
||||
// Update NPC positions and animations
|
||||
// This would handle NPC AI movement patterns
|
||||
}
|
||||
|
||||
private void updateObjectPhysics() {
|
||||
// Handle physics for game objects
|
||||
// Falling items, projectiles, etc.
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate path between two points.
|
||||
*/
|
||||
public int[][] calculatePath(int startX, int startY, int endX, int endY) {
|
||||
// Simple pathfinding implementation
|
||||
// In a real implementation, this would use A* or similar algorithm
|
||||
|
||||
logger.debug("Calculating path from ({}, {}) to ({}, {})", startX, startY, endX, endY);
|
||||
|
||||
// For now, return a simple straight line path
|
||||
int deltaX = endX - startX;
|
||||
int deltaY = endY - startY;
|
||||
int steps = Math.max(Math.abs(deltaX), Math.abs(deltaY));
|
||||
|
||||
if (steps == 0) {
|
||||
return new int[0][2];
|
||||
}
|
||||
|
||||
int[][] path = new int[steps][2];
|
||||
for (int i = 0; i < steps; i++) {
|
||||
path[i][0] = startX + (deltaX * i / steps);
|
||||
path[i][1] = startY + (deltaY * i / steps);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a tile is walkable.
|
||||
*/
|
||||
public boolean isWalkable(int x, int y, int plane) {
|
||||
// Check collision data, objects, etc.
|
||||
// For now, assume all tiles are walkable
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate distance between two points.
|
||||
*/
|
||||
public double calculateDistance(int x1, int y1, int x2, int y2) {
|
||||
int dx = x2 - x1;
|
||||
int dy = y2 - y1;
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
public boolean isInitialized() { return initialized.get(); }
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package com.openosrs.client.engine;
|
||||
|
||||
import com.openosrs.client.core.ClientCore;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* RenderingEngine - Handles game rendering and graphics.
|
||||
*
|
||||
* This engine is minimal for agent-focused gameplay, providing just enough
|
||||
* rendering to maintain compatibility while prioritizing performance.
|
||||
*/
|
||||
public class RenderingEngine {
|
||||
private static final Logger logger = LoggerFactory.getLogger(RenderingEngine.class);
|
||||
|
||||
private final ClientCore clientCore;
|
||||
private final AtomicBoolean initialized = new AtomicBoolean(false);
|
||||
private final AtomicBoolean headlessMode = new AtomicBoolean(true); // Default to headless for agents
|
||||
|
||||
private long frameCount = 0;
|
||||
private long lastFpsUpdate = 0;
|
||||
private double currentFps = 0;
|
||||
|
||||
public RenderingEngine(ClientCore clientCore) {
|
||||
this.clientCore = clientCore;
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
if (initialized.get()) {
|
||||
logger.warn("RenderingEngine already initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Initializing RenderingEngine (headless={})", headlessMode.get());
|
||||
|
||||
try {
|
||||
if (!headlessMode.get()) {
|
||||
initializeGraphics();
|
||||
} else {
|
||||
logger.info("Running in headless mode - no graphics initialization");
|
||||
}
|
||||
|
||||
initialized.set(true);
|
||||
logger.info("RenderingEngine initialized");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to initialize RenderingEngine", e);
|
||||
throw new RuntimeException("RenderingEngine initialization failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeGraphics() {
|
||||
// Initialize OpenGL context, create window, etc.
|
||||
// For now, this is a placeholder for future graphics implementation
|
||||
logger.debug("Graphics context would be initialized here");
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
if (!initialized.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Shutting down RenderingEngine");
|
||||
|
||||
try {
|
||||
if (!headlessMode.get()) {
|
||||
cleanupGraphics();
|
||||
}
|
||||
|
||||
initialized.set(false);
|
||||
logger.info("RenderingEngine shutdown complete");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error during RenderingEngine shutdown", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanupGraphics() {
|
||||
// Cleanup OpenGL resources, destroy window, etc.
|
||||
logger.debug("Graphics resources would be cleaned up here");
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a frame (called each game tick).
|
||||
*/
|
||||
public void render() {
|
||||
if (!initialized.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
frameCount++;
|
||||
|
||||
try {
|
||||
if (!headlessMode.get()) {
|
||||
renderFrame();
|
||||
} else {
|
||||
// In headless mode, just update FPS counter
|
||||
updateFpsCounter();
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error during frame render", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void renderFrame() {
|
||||
// Actual rendering would happen here
|
||||
// For now, just update FPS
|
||||
updateFpsCounter();
|
||||
}
|
||||
|
||||
private void updateFpsCounter() {
|
||||
long now = System.currentTimeMillis();
|
||||
if (now - lastFpsUpdate >= 1000) {
|
||||
currentFps = frameCount;
|
||||
frameCount = 0;
|
||||
lastFpsUpdate = now;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isInitialized() { return initialized.get(); }
|
||||
public boolean isHeadless() { return headlessMode.get(); }
|
||||
public void setHeadless(boolean headless) { headlessMode.set(headless); }
|
||||
public double getCurrentFps() { return currentFps; }
|
||||
public long getFrameCount() { return frameCount; }
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.openosrs.client.plugins;
|
||||
|
||||
import com.openosrs.client.api.AgentAPI;
|
||||
import com.openosrs.client.core.ClientCore;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Abstract base class for plugins.
|
||||
*/
|
||||
public abstract class AbstractPlugin implements Plugin {
|
||||
protected final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
protected PluginContext context;
|
||||
protected volatile boolean enabled = false;
|
||||
|
||||
@Override
|
||||
public final void onEnable(PluginContext context) {
|
||||
this.context = context;
|
||||
this.enabled = true;
|
||||
|
||||
try {
|
||||
enable();
|
||||
} catch (Exception e) {
|
||||
this.enabled = false;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onDisable() {
|
||||
this.enabled = false;
|
||||
|
||||
try {
|
||||
disable();
|
||||
} finally {
|
||||
this.context = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement plugin enable logic here.
|
||||
*/
|
||||
protected abstract void enable();
|
||||
|
||||
/**
|
||||
* Implement plugin disable logic here.
|
||||
*/
|
||||
protected abstract void disable();
|
||||
|
||||
/**
|
||||
* Check if plugin is enabled.
|
||||
*/
|
||||
protected boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the agent API.
|
||||
*/
|
||||
protected AgentAPI getAPI() {
|
||||
return context != null ? context.getAPI() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the client core.
|
||||
*/
|
||||
protected ClientCore getClientCore() {
|
||||
return context != null ? context.getClientCore() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an event listener.
|
||||
*/
|
||||
protected void addEventListener(String eventType, Consumer<Object> listener) {
|
||||
if (context != null) {
|
||||
context.getEventSystem().addEventListener(eventType, listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.openosrs.client.plugins;
|
||||
|
||||
/**
|
||||
* Plugin interface.
|
||||
*/
|
||||
public interface Plugin {
|
||||
/**
|
||||
* Called when the plugin is enabled.
|
||||
*/
|
||||
void onEnable(PluginContext context);
|
||||
|
||||
/**
|
||||
* Called when the plugin is disabled.
|
||||
*/
|
||||
void onDisable();
|
||||
|
||||
/**
|
||||
* Called when an event occurs.
|
||||
*/
|
||||
default void onEvent(String eventType, Object eventData) {
|
||||
// Default implementation does nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Get plugin metadata.
|
||||
*/
|
||||
PluginMetadata getMetadata();
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.openosrs.client.plugins;
|
||||
|
||||
import com.openosrs.client.api.AgentAPI;
|
||||
import com.openosrs.client.core.ClientCore;
|
||||
import com.openosrs.client.core.EventSystem;
|
||||
|
||||
/**
|
||||
* Plugin context provided to plugins.
|
||||
*/
|
||||
public class PluginContext {
|
||||
private final AgentAPI api;
|
||||
private final ClientCore clientCore;
|
||||
private final EventSystem eventSystem;
|
||||
|
||||
public PluginContext(AgentAPI api, ClientCore clientCore, EventSystem eventSystem) {
|
||||
this.api = api;
|
||||
this.clientCore = clientCore;
|
||||
this.eventSystem = eventSystem;
|
||||
}
|
||||
|
||||
public AgentAPI getAPI() { return api; }
|
||||
public ClientCore getClientCore() { return clientCore; }
|
||||
public EventSystem getEventSystem() { return eventSystem; }
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.openosrs.client.plugins;
|
||||
|
||||
/**
|
||||
* Plugin information.
|
||||
*/
|
||||
public class PluginInfo {
|
||||
private final PluginMetadata metadata;
|
||||
private final PluginState state;
|
||||
private final boolean enabled;
|
||||
private final String error;
|
||||
|
||||
public PluginInfo(PluginMetadata metadata, PluginState state, boolean enabled, String error) {
|
||||
this.metadata = metadata;
|
||||
this.state = state;
|
||||
this.enabled = enabled;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public PluginMetadata getMetadata() { return metadata; }
|
||||
public PluginState getState() { return state; }
|
||||
public boolean isEnabled() { return enabled; }
|
||||
public String getError() { return error; }
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Plugin system for extending agent capabilities.
|
||||
@@ -189,183 +188,3 @@ public class PluginManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin interface.
|
||||
*/
|
||||
public interface Plugin {
|
||||
/**
|
||||
* Called when the plugin is enabled.
|
||||
*/
|
||||
void onEnable(PluginContext context);
|
||||
|
||||
/**
|
||||
* Called when the plugin is disabled.
|
||||
*/
|
||||
void onDisable();
|
||||
|
||||
/**
|
||||
* Called when an event occurs.
|
||||
*/
|
||||
default void onEvent(String eventType, Object eventData) {
|
||||
// Default implementation does nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Get plugin metadata.
|
||||
*/
|
||||
PluginMetadata getMetadata();
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract base class for plugins.
|
||||
*/
|
||||
public abstract class AbstractPlugin implements Plugin {
|
||||
protected final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
protected PluginContext context;
|
||||
protected volatile boolean enabled = false;
|
||||
|
||||
@Override
|
||||
public final void onEnable(PluginContext context) {
|
||||
this.context = context;
|
||||
this.enabled = true;
|
||||
|
||||
try {
|
||||
enable();
|
||||
} catch (Exception e) {
|
||||
this.enabled = false;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onDisable() {
|
||||
this.enabled = false;
|
||||
|
||||
try {
|
||||
disable();
|
||||
} finally {
|
||||
this.context = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement plugin enable logic here.
|
||||
*/
|
||||
protected abstract void enable();
|
||||
|
||||
/**
|
||||
* Implement plugin disable logic here.
|
||||
*/
|
||||
protected abstract void disable();
|
||||
|
||||
/**
|
||||
* Check if plugin is enabled.
|
||||
*/
|
||||
protected boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the agent API.
|
||||
*/
|
||||
protected AgentAPI getAPI() {
|
||||
return context != null ? context.getAPI() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the client core.
|
||||
*/
|
||||
protected ClientCore getClientCore() {
|
||||
return context != null ? context.getClientCore() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an event listener.
|
||||
*/
|
||||
protected void addEventListener(String eventType, Consumer<Object> listener) {
|
||||
if (context != null) {
|
||||
context.getEventSystem().addEventListener(eventType, listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin context provided to plugins.
|
||||
*/
|
||||
public class PluginContext {
|
||||
private final AgentAPI api;
|
||||
private final ClientCore clientCore;
|
||||
private final EventSystem eventSystem;
|
||||
|
||||
public PluginContext(AgentAPI api, ClientCore clientCore, EventSystem eventSystem) {
|
||||
this.api = api;
|
||||
this.clientCore = clientCore;
|
||||
this.eventSystem = eventSystem;
|
||||
}
|
||||
|
||||
public AgentAPI getAPI() { return api; }
|
||||
public ClientCore getClientCore() { return clientCore; }
|
||||
public EventSystem getEventSystem() { return eventSystem; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin metadata.
|
||||
*/
|
||||
public class PluginMetadata {
|
||||
private final String name;
|
||||
private final String description;
|
||||
private final String author;
|
||||
private final String version;
|
||||
private final List<String> dependencies;
|
||||
private final List<String> capabilities;
|
||||
|
||||
public PluginMetadata(String name, String description, String author, String version,
|
||||
List<String> dependencies, List<String> capabilities) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.author = author;
|
||||
this.version = version;
|
||||
this.dependencies = new ArrayList<>(dependencies);
|
||||
this.capabilities = new ArrayList<>(capabilities);
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public String getDescription() { return description; }
|
||||
public String getAuthor() { return author; }
|
||||
public String getVersion() { return version; }
|
||||
public List<String> getDependencies() { return new ArrayList<>(dependencies); }
|
||||
public List<String> getCapabilities() { return new ArrayList<>(capabilities); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin information.
|
||||
*/
|
||||
public class PluginInfo {
|
||||
private final PluginMetadata metadata;
|
||||
private final PluginState state;
|
||||
private final boolean enabled;
|
||||
private final String error;
|
||||
|
||||
public PluginInfo(PluginMetadata metadata, PluginState state, boolean enabled, String error) {
|
||||
this.metadata = metadata;
|
||||
this.state = state;
|
||||
this.enabled = enabled;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public PluginMetadata getMetadata() { return metadata; }
|
||||
public PluginState getState() { return state; }
|
||||
public boolean isEnabled() { return enabled; }
|
||||
public String getError() { return error; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin state.
|
||||
*/
|
||||
public enum PluginState {
|
||||
REGISTERED,
|
||||
ENABLED,
|
||||
DISABLED,
|
||||
ERROR
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.openosrs.client.plugins;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Plugin metadata.
|
||||
*/
|
||||
public class PluginMetadata {
|
||||
private final String name;
|
||||
private final String description;
|
||||
private final String author;
|
||||
private final String version;
|
||||
private final List<String> dependencies;
|
||||
private final List<String> capabilities;
|
||||
|
||||
public PluginMetadata(String name, String description, String author, String version,
|
||||
List<String> dependencies, List<String> capabilities) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.author = author;
|
||||
this.version = version;
|
||||
this.dependencies = new ArrayList<>(dependencies);
|
||||
this.capabilities = new ArrayList<>(capabilities);
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public String getDescription() { return description; }
|
||||
public String getAuthor() { return author; }
|
||||
public String getVersion() { return version; }
|
||||
public List<String> getDependencies() { return new ArrayList<>(dependencies); }
|
||||
public List<String> getCapabilities() { return new ArrayList<>(capabilities); }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.openosrs.client.plugins;
|
||||
|
||||
/**
|
||||
* Plugin state.
|
||||
*/
|
||||
public enum PluginState {
|
||||
REGISTERED,
|
||||
ENABLED,
|
||||
DISABLED,
|
||||
ERROR
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.openosrs.client.plugins.examples;
|
||||
|
||||
import com.openosrs.client.plugins.*;
|
||||
import com.openosrs.client.api.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Anti-idle plugin that performs random actions to avoid logout.
|
||||
*/
|
||||
public class AntiIdlePlugin extends AbstractPlugin {
|
||||
private static final int IDLE_THRESHOLD_MS = 4 * 60 * 1000; // 4 minutes
|
||||
private volatile boolean monitoring = false;
|
||||
private Thread monitorThread;
|
||||
private long lastActionTime = System.currentTimeMillis();
|
||||
|
||||
@Override
|
||||
protected void enable() {
|
||||
logger.info("Anti-idle plugin enabled");
|
||||
|
||||
monitoring = true;
|
||||
monitorThread = new Thread(this::monitorIdle);
|
||||
monitorThread.setDaemon(true);
|
||||
monitorThread.start();
|
||||
|
||||
// Listen for player actions
|
||||
addEventListener("PLAYER_MOVED", this::onPlayerAction);
|
||||
addEventListener("PLAYER_INTERACTED", this::onPlayerAction);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disable() {
|
||||
logger.info("Anti-idle plugin disabled");
|
||||
|
||||
monitoring = false;
|
||||
if (monitorThread != null) {
|
||||
monitorThread.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private void monitorIdle() {
|
||||
while (monitoring && !Thread.currentThread().isInterrupted()) {
|
||||
try {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
long idleTime = currentTime - lastActionTime;
|
||||
|
||||
if (idleTime >= IDLE_THRESHOLD_MS) {
|
||||
performAntiIdleAction();
|
||||
lastActionTime = currentTime;
|
||||
}
|
||||
|
||||
Thread.sleep(10000); // Check every 10 seconds
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
logger.error("Error in idle monitoring", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onPlayerAction(Object eventData) {
|
||||
lastActionTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
private void performAntiIdleAction() {
|
||||
try {
|
||||
AgentAPI api = getAPI();
|
||||
if (api == null) return;
|
||||
|
||||
// Perform a random, harmless action
|
||||
int action = (int) (Math.random() * 3);
|
||||
|
||||
switch (action) {
|
||||
case 0:
|
||||
// Random camera movement (would need camera API)
|
||||
logger.debug("Performing anti-idle camera movement");
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// Open and close skills tab (would need interface API)
|
||||
logger.debug("Performing anti-idle interface action");
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// Small movement
|
||||
AgentAPI.Position pos = api.getPlayerPosition();
|
||||
AgentAPI.Position newPos = new AgentAPI.Position(
|
||||
pos.getX() + (Math.random() > 0.5 ? 1 : -1),
|
||||
pos.getY(),
|
||||
pos.getPlane()
|
||||
);
|
||||
api.walkTo(newPos);
|
||||
logger.debug("Performing anti-idle movement");
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error performing anti-idle action", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginMetadata getMetadata() {
|
||||
return new PluginMetadata(
|
||||
"Anti-Idle",
|
||||
"Prevents logout by performing random actions",
|
||||
"OpenOSRS Agent",
|
||||
"1.0",
|
||||
new ArrayList<>(),
|
||||
Arrays.asList("automation", "idle", "logout")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package com.openosrs.client.plugins.examples;
|
||||
|
||||
import com.openosrs.client.plugins.*;
|
||||
import com.openosrs.client.api.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Auto-healing plugin that automatically eats food when health is low.
|
||||
*/
|
||||
public class AutoHealPlugin extends AbstractPlugin {
|
||||
private static final int DEFAULT_HEAL_THRESHOLD = 50;
|
||||
private static final int[] FOOD_IDS = {373, 379, 385, 391}; // Lobster, Shark, etc.
|
||||
|
||||
private int healThreshold = DEFAULT_HEAL_THRESHOLD;
|
||||
private volatile boolean monitoring = false;
|
||||
private Thread monitorThread;
|
||||
|
||||
@Override
|
||||
protected void enable() {
|
||||
logger.info("Auto-heal plugin enabled with threshold: {}", healThreshold);
|
||||
|
||||
monitoring = true;
|
||||
monitorThread = new Thread(this::monitorHealth);
|
||||
monitorThread.setDaemon(true);
|
||||
monitorThread.start();
|
||||
|
||||
// Listen for combat events
|
||||
addEventListener("PLAYER_TOOK_DAMAGE", this::onPlayerDamaged);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disable() {
|
||||
logger.info("Auto-heal plugin disabled");
|
||||
|
||||
monitoring = false;
|
||||
if (monitorThread != null) {
|
||||
monitorThread.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private void monitorHealth() {
|
||||
while (monitoring && !Thread.currentThread().isInterrupted()) {
|
||||
try {
|
||||
AgentAPI api = getAPI();
|
||||
if (api != null) {
|
||||
int currentHp = api.getHitpoints();
|
||||
int maxHp = api.getMaxHitpoints();
|
||||
|
||||
if (currentHp <= healThreshold && currentHp < maxHp) {
|
||||
tryHeal(api);
|
||||
}
|
||||
}
|
||||
|
||||
Thread.sleep(1000); // Check every second
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
logger.error("Error in health monitoring", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onPlayerDamaged(Object eventData) {
|
||||
// Immediate heal check when damaged
|
||||
try {
|
||||
AgentAPI api = getAPI();
|
||||
if (api != null && api.getHitpoints() <= healThreshold) {
|
||||
tryHeal(api);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error handling damage event", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void tryHeal(AgentAPI api) {
|
||||
try {
|
||||
for (int foodId : FOOD_IDS) {
|
||||
int slot = api.findItemSlot(foodId);
|
||||
if (slot != -1) {
|
||||
logger.debug("Eating food at slot {}", slot);
|
||||
api.useItem(slot);
|
||||
Thread.sleep(1800); // Food delay
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
logger.warn("No food available for healing");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error trying to heal", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void setHealThreshold(int threshold) {
|
||||
this.healThreshold = Math.max(1, Math.min(99, threshold));
|
||||
logger.info("Heal threshold set to: {}", this.healThreshold);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginMetadata getMetadata() {
|
||||
return new PluginMetadata(
|
||||
"Auto-Heal",
|
||||
"Automatically eats food when health is low",
|
||||
"OpenOSRS Agent",
|
||||
"1.0",
|
||||
new ArrayList<>(),
|
||||
Arrays.asList("healing", "automation", "combat")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,464 +0,0 @@
|
||||
package com.openosrs.client.plugins.examples;
|
||||
|
||||
import com.openosrs.client.plugins.*;
|
||||
import com.openosrs.client.api.*;
|
||||
import com.openosrs.client.core.events.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Example plugins demonstrating the plugin system.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auto-healing plugin that automatically eats food when health is low.
|
||||
*/
|
||||
public class AutoHealPlugin extends AbstractPlugin {
|
||||
private static final int DEFAULT_HEAL_THRESHOLD = 50;
|
||||
private static final int[] FOOD_IDS = {373, 379, 385, 391}; // Lobster, Shark, etc.
|
||||
|
||||
private int healThreshold = DEFAULT_HEAL_THRESHOLD;
|
||||
private volatile boolean monitoring = false;
|
||||
private Thread monitorThread;
|
||||
|
||||
@Override
|
||||
protected void enable() {
|
||||
logger.info("Auto-heal plugin enabled with threshold: {}", healThreshold);
|
||||
|
||||
monitoring = true;
|
||||
monitorThread = new Thread(this::monitorHealth);
|
||||
monitorThread.setDaemon(true);
|
||||
monitorThread.start();
|
||||
|
||||
// Listen for combat events
|
||||
addEventListener("PLAYER_TOOK_DAMAGE", this::onPlayerDamaged);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disable() {
|
||||
logger.info("Auto-heal plugin disabled");
|
||||
|
||||
monitoring = false;
|
||||
if (monitorThread != null) {
|
||||
monitorThread.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private void monitorHealth() {
|
||||
while (monitoring && !Thread.currentThread().isInterrupted()) {
|
||||
try {
|
||||
AgentAPI api = getAPI();
|
||||
if (api != null) {
|
||||
int currentHp = api.getHitpoints();
|
||||
int maxHp = api.getMaxHitpoints();
|
||||
|
||||
if (currentHp <= healThreshold && currentHp < maxHp) {
|
||||
tryHeal(api);
|
||||
}
|
||||
}
|
||||
|
||||
Thread.sleep(1000); // Check every second
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
logger.error("Error in health monitoring", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onPlayerDamaged(Object eventData) {
|
||||
// Immediate heal check when damaged
|
||||
try {
|
||||
AgentAPI api = getAPI();
|
||||
if (api != null && api.getHitpoints() <= healThreshold) {
|
||||
tryHeal(api);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error handling damage event", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void tryHeal(AgentAPI api) {
|
||||
try {
|
||||
for (int foodId : FOOD_IDS) {
|
||||
int slot = api.findItemSlot(foodId);
|
||||
if (slot != -1) {
|
||||
logger.debug("Eating food at slot {}", slot);
|
||||
api.useItem(slot);
|
||||
Thread.sleep(1800); // Food delay
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
logger.warn("No food available for healing");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error trying to heal", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void setHealThreshold(int threshold) {
|
||||
this.healThreshold = Math.max(1, Math.min(99, threshold));
|
||||
logger.info("Heal threshold set to: {}", this.healThreshold);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginMetadata getMetadata() {
|
||||
return new PluginMetadata(
|
||||
"Auto-Heal",
|
||||
"Automatically eats food when health is low",
|
||||
"OpenOSRS Agent",
|
||||
"1.0",
|
||||
new ArrayList<>(),
|
||||
Arrays.asList("healing", "automation", "combat")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performance monitor plugin that tracks FPS and other metrics.
|
||||
*/
|
||||
public class PerformanceMonitorPlugin extends AbstractPlugin {
|
||||
private final Map<String, Double> metrics = new ConcurrentHashMap<>();
|
||||
private volatile boolean monitoring = false;
|
||||
private Thread monitorThread;
|
||||
private long lastFrameTime = 0;
|
||||
private int frameCount = 0;
|
||||
|
||||
@Override
|
||||
protected void enable() {
|
||||
logger.info("Performance monitor plugin enabled");
|
||||
|
||||
monitoring = true;
|
||||
monitorThread = new Thread(this::monitorPerformance);
|
||||
monitorThread.setDaemon(true);
|
||||
monitorThread.start();
|
||||
|
||||
// Listen for frame events
|
||||
addEventListener("FRAME_RENDERED", this::onFrameRendered);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disable() {
|
||||
logger.info("Performance monitor plugin disabled");
|
||||
|
||||
monitoring = false;
|
||||
if (monitorThread != null) {
|
||||
monitorThread.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private void monitorPerformance() {
|
||||
while (monitoring && !Thread.currentThread().isInterrupted()) {
|
||||
try {
|
||||
updateMetrics();
|
||||
logPerformanceReport();
|
||||
|
||||
Thread.sleep(5000); // Report every 5 seconds
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
logger.error("Error in performance monitoring", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateMetrics() {
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
|
||||
// Memory metrics
|
||||
long totalMemory = runtime.totalMemory();
|
||||
long freeMemory = runtime.freeMemory();
|
||||
long usedMemory = totalMemory - freeMemory;
|
||||
long maxMemory = runtime.maxMemory();
|
||||
|
||||
metrics.put("memory.used.mb", usedMemory / (1024.0 * 1024.0));
|
||||
metrics.put("memory.total.mb", totalMemory / (1024.0 * 1024.0));
|
||||
metrics.put("memory.max.mb", maxMemory / (1024.0 * 1024.0));
|
||||
metrics.put("memory.usage.percent", (usedMemory * 100.0) / totalMemory);
|
||||
|
||||
// CPU metrics (simplified)
|
||||
metrics.put("cpu.processors", (double) runtime.availableProcessors());
|
||||
}
|
||||
|
||||
private void onFrameRendered(Object eventData) {
|
||||
frameCount++;
|
||||
long currentTime = System.currentTimeMillis();
|
||||
|
||||
if (lastFrameTime == 0) {
|
||||
lastFrameTime = currentTime;
|
||||
return;
|
||||
}
|
||||
|
||||
long elapsed = currentTime - lastFrameTime;
|
||||
if (elapsed >= 1000) { // Calculate FPS every second
|
||||
double fps = (frameCount * 1000.0) / elapsed;
|
||||
metrics.put("rendering.fps", fps);
|
||||
|
||||
frameCount = 0;
|
||||
lastFrameTime = currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
private void logPerformanceReport() {
|
||||
StringBuilder report = new StringBuilder();
|
||||
report.append("Performance Report:\\n");
|
||||
|
||||
for (Map.Entry<String, Double> entry : metrics.entrySet()) {
|
||||
report.append(String.format(" %s: %.2f\\n", entry.getKey(), entry.getValue()));
|
||||
}
|
||||
|
||||
logger.debug(report.toString());
|
||||
}
|
||||
|
||||
public Map<String, Double> getMetrics() {
|
||||
return new ConcurrentHashMap<>(metrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginMetadata getMetadata() {
|
||||
return new PluginMetadata(
|
||||
"Performance Monitor",
|
||||
"Monitors client performance metrics",
|
||||
"OpenOSRS Agent",
|
||||
"1.0",
|
||||
new ArrayList<>(),
|
||||
Arrays.asList("monitoring", "performance", "debugging")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Anti-idle plugin that performs random actions to avoid logout.
|
||||
*/
|
||||
public class AntiIdlePlugin extends AbstractPlugin {
|
||||
private static final int IDLE_THRESHOLD_MS = 4 * 60 * 1000; // 4 minutes
|
||||
private volatile boolean monitoring = false;
|
||||
private Thread monitorThread;
|
||||
private long lastActionTime = System.currentTimeMillis();
|
||||
|
||||
@Override
|
||||
protected void enable() {
|
||||
logger.info("Anti-idle plugin enabled");
|
||||
|
||||
monitoring = true;
|
||||
monitorThread = new Thread(this::monitorIdle);
|
||||
monitorThread.setDaemon(true);
|
||||
monitorThread.start();
|
||||
|
||||
// Listen for player actions
|
||||
addEventListener("PLAYER_MOVED", this::onPlayerAction);
|
||||
addEventListener("PLAYER_INTERACTED", this::onPlayerAction);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disable() {
|
||||
logger.info("Anti-idle plugin disabled");
|
||||
|
||||
monitoring = false;
|
||||
if (monitorThread != null) {
|
||||
monitorThread.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private void monitorIdle() {
|
||||
while (monitoring && !Thread.currentThread().isInterrupted()) {
|
||||
try {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
long idleTime = currentTime - lastActionTime;
|
||||
|
||||
if (idleTime >= IDLE_THRESHOLD_MS) {
|
||||
performAntiIdleAction();
|
||||
lastActionTime = currentTime;
|
||||
}
|
||||
|
||||
Thread.sleep(10000); // Check every 10 seconds
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
logger.error("Error in idle monitoring", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onPlayerAction(Object eventData) {
|
||||
lastActionTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
private void performAntiIdleAction() {
|
||||
try {
|
||||
AgentAPI api = getAPI();
|
||||
if (api == null) return;
|
||||
|
||||
// Perform a random, harmless action
|
||||
int action = (int) (Math.random() * 3);
|
||||
|
||||
switch (action) {
|
||||
case 0:
|
||||
// Random camera movement (would need camera API)
|
||||
logger.debug("Performing anti-idle camera movement");
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// Open and close skills tab (would need interface API)
|
||||
logger.debug("Performing anti-idle interface action");
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// Small movement
|
||||
Position pos = api.getPlayerPosition();
|
||||
Position newPos = new Position(
|
||||
pos.getX() + (Math.random() > 0.5 ? 1 : -1),
|
||||
pos.getY(),
|
||||
pos.getPlane()
|
||||
);
|
||||
api.walkTo(newPos);
|
||||
logger.debug("Performing anti-idle movement");
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error performing anti-idle action", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginMetadata getMetadata() {
|
||||
return new PluginMetadata(
|
||||
"Anti-Idle",
|
||||
"Prevents logout by performing random actions",
|
||||
"OpenOSRS Agent",
|
||||
"1.0",
|
||||
new ArrayList<>(),
|
||||
Arrays.asList("automation", "idle", "logout")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Experience tracker plugin that monitors XP gains.
|
||||
*/
|
||||
public class ExperienceTrackerPlugin extends AbstractPlugin {
|
||||
private final Map<AgentAPI.Skill, Long> baseExperience = new ConcurrentHashMap<>();
|
||||
private final Map<AgentAPI.Skill, Long> sessionGains = new ConcurrentHashMap<>();
|
||||
private final Map<AgentAPI.Skill, Long> lastXpTime = new ConcurrentHashMap<>();
|
||||
private volatile boolean tracking = false;
|
||||
private Thread trackerThread;
|
||||
|
||||
@Override
|
||||
protected void enable() {
|
||||
logger.info("Experience tracker plugin enabled");
|
||||
|
||||
// Initialize base experience levels
|
||||
AgentAPI api = getAPI();
|
||||
if (api != null) {
|
||||
for (AgentAPI.Skill skill : AgentAPI.Skill.values()) {
|
||||
int xp = api.getSkillExperience(skill);
|
||||
baseExperience.put(skill, (long) xp);
|
||||
sessionGains.put(skill, 0L);
|
||||
}
|
||||
}
|
||||
|
||||
tracking = true;
|
||||
trackerThread = new Thread(this::trackExperience);
|
||||
trackerThread.setDaemon(true);
|
||||
trackerThread.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disable() {
|
||||
logger.info("Experience tracker plugin disabled");
|
||||
|
||||
tracking = false;
|
||||
if (trackerThread != null) {
|
||||
trackerThread.interrupt();
|
||||
}
|
||||
|
||||
logSessionReport();
|
||||
}
|
||||
|
||||
private void trackExperience() {
|
||||
while (tracking && !Thread.currentThread().isInterrupted()) {
|
||||
try {
|
||||
updateExperienceGains();
|
||||
Thread.sleep(1000); // Check every second
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
logger.error("Error tracking experience", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateExperienceGains() {
|
||||
AgentAPI api = getAPI();
|
||||
if (api == null) return;
|
||||
|
||||
long currentTime = System.currentTimeMillis();
|
||||
|
||||
for (AgentAPI.Skill skill : AgentAPI.Skill.values()) {
|
||||
int currentXp = api.getSkillExperience(skill);
|
||||
long baseXp = baseExperience.get(skill);
|
||||
long newGain = currentXp - baseXp;
|
||||
|
||||
if (newGain > sessionGains.get(skill)) {
|
||||
long xpGained = newGain - sessionGains.get(skill);
|
||||
sessionGains.put(skill, newGain);
|
||||
lastXpTime.put(skill, currentTime);
|
||||
|
||||
logger.info("XP gained in {}: {} (Total session: {})",
|
||||
skill.name(), xpGained, newGain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void logSessionReport() {
|
||||
logger.info("=== Experience Session Report ===");
|
||||
|
||||
for (AgentAPI.Skill skill : AgentAPI.Skill.values()) {
|
||||
long gains = sessionGains.get(skill);
|
||||
if (gains > 0) {
|
||||
logger.info("{}: {} XP gained", skill.name(), gains);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Map<AgentAPI.Skill, Long> getSessionGains() {
|
||||
return new ConcurrentHashMap<>(sessionGains);
|
||||
}
|
||||
|
||||
public void resetSession() {
|
||||
AgentAPI api = getAPI();
|
||||
if (api != null) {
|
||||
for (AgentAPI.Skill skill : AgentAPI.Skill.values()) {
|
||||
int xp = api.getSkillExperience(skill);
|
||||
baseExperience.put(skill, (long) xp);
|
||||
sessionGains.put(skill, 0L);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Experience session reset");
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginMetadata getMetadata() {
|
||||
return new PluginMetadata(
|
||||
"Experience Tracker",
|
||||
"Tracks experience gains during play sessions",
|
||||
"OpenOSRS Agent",
|
||||
"1.0",
|
||||
new ArrayList<>(),
|
||||
Arrays.asList("tracking", "experience", "skills")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package com.openosrs.client.plugins.examples;
|
||||
|
||||
import com.openosrs.client.plugins.*;
|
||||
import com.openosrs.client.api.*;
|
||||
import com.openosrs.client.api.types.Skill;
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Experience tracker plugin that monitors XP gains.
|
||||
*/
|
||||
public class ExperienceTrackerPlugin extends AbstractPlugin {
|
||||
private final Map<Skill, Long> baseExperience = new ConcurrentHashMap<>();
|
||||
private final Map<Skill, Long> sessionGains = new ConcurrentHashMap<>();
|
||||
private final Map<Skill, Long> lastXpTime = new ConcurrentHashMap<>();
|
||||
private volatile boolean tracking = false;
|
||||
private Thread trackerThread;
|
||||
|
||||
@Override
|
||||
protected void enable() {
|
||||
logger.info("Experience tracker plugin enabled");
|
||||
|
||||
// Initialize base experience levels
|
||||
AgentAPI api = getAPI();
|
||||
if (api != null) {
|
||||
for (Skill skill : Skill.values()) {
|
||||
int xp = api.getSkillExperience(skill);
|
||||
baseExperience.put(skill, (long) xp);
|
||||
sessionGains.put(skill, 0L);
|
||||
}
|
||||
}
|
||||
|
||||
tracking = true;
|
||||
trackerThread = new Thread(this::trackExperience);
|
||||
trackerThread.setDaemon(true);
|
||||
trackerThread.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disable() {
|
||||
logger.info("Experience tracker plugin disabled");
|
||||
|
||||
tracking = false;
|
||||
if (trackerThread != null) {
|
||||
trackerThread.interrupt();
|
||||
}
|
||||
|
||||
logSessionReport();
|
||||
}
|
||||
|
||||
private void trackExperience() {
|
||||
while (tracking && !Thread.currentThread().isInterrupted()) {
|
||||
try {
|
||||
updateExperienceGains();
|
||||
Thread.sleep(1000); // Check every second
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
logger.error("Error tracking experience", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateExperienceGains() {
|
||||
AgentAPI api = getAPI();
|
||||
if (api == null) return;
|
||||
|
||||
long currentTime = System.currentTimeMillis();
|
||||
|
||||
for (Skill skill : Skill.values()) {
|
||||
int currentXp = api.getSkillExperience(skill);
|
||||
long baseXp = baseExperience.get(skill);
|
||||
long newGain = currentXp - baseXp;
|
||||
|
||||
if (newGain > sessionGains.get(skill)) {
|
||||
long xpGained = newGain - sessionGains.get(skill);
|
||||
sessionGains.put(skill, newGain);
|
||||
lastXpTime.put(skill, currentTime);
|
||||
|
||||
logger.info("XP gained in {}: {} (Total session: {})",
|
||||
skill.name(), xpGained, newGain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void logSessionReport() {
|
||||
logger.info("=== Experience Session Report ===");
|
||||
|
||||
for (Skill skill : Skill.values()) {
|
||||
long gains = sessionGains.get(skill);
|
||||
if (gains > 0) {
|
||||
logger.info("{}: {} XP gained", skill.name(), gains);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Map<Skill, Long> getSessionGains() {
|
||||
return new ConcurrentHashMap<>(sessionGains);
|
||||
}
|
||||
|
||||
public void resetSession() {
|
||||
AgentAPI api = getAPI();
|
||||
if (api != null) {
|
||||
for (Skill skill : Skill.values()) {
|
||||
int xp = api.getSkillExperience(skill);
|
||||
baseExperience.put(skill, (long) xp);
|
||||
sessionGains.put(skill, 0L);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Experience session reset");
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginMetadata getMetadata() {
|
||||
return new PluginMetadata(
|
||||
"Experience Tracker",
|
||||
"Tracks experience gains during play sessions",
|
||||
"OpenOSRS Agent",
|
||||
"1.0",
|
||||
new ArrayList<>(),
|
||||
Arrays.asList("tracking", "experience", "skills")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.openosrs.client.plugins.examples;
|
||||
|
||||
import com.openosrs.client.plugins.*;
|
||||
import com.openosrs.client.api.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Performance monitor plugin that tracks FPS and other metrics.
|
||||
*/
|
||||
public class PerformanceMonitorPlugin extends AbstractPlugin {
|
||||
private final Map<String, Double> metrics = new ConcurrentHashMap<>();
|
||||
private volatile boolean monitoring = false;
|
||||
private Thread monitorThread;
|
||||
private long lastFrameTime = 0;
|
||||
private int frameCount = 0;
|
||||
|
||||
@Override
|
||||
protected void enable() {
|
||||
logger.info("Performance monitor plugin enabled");
|
||||
|
||||
monitoring = true;
|
||||
monitorThread = new Thread(this::monitorPerformance);
|
||||
monitorThread.setDaemon(true);
|
||||
monitorThread.start();
|
||||
|
||||
// Listen for frame events
|
||||
addEventListener("FRAME_RENDERED", this::onFrameRendered);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disable() {
|
||||
logger.info("Performance monitor plugin disabled");
|
||||
|
||||
monitoring = false;
|
||||
if (monitorThread != null) {
|
||||
monitorThread.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private void monitorPerformance() {
|
||||
while (monitoring && !Thread.currentThread().isInterrupted()) {
|
||||
try {
|
||||
updateMetrics();
|
||||
logPerformanceReport();
|
||||
|
||||
Thread.sleep(5000); // Report every 5 seconds
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
logger.error("Error in performance monitoring", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateMetrics() {
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
|
||||
// Memory metrics
|
||||
long totalMemory = runtime.totalMemory();
|
||||
long freeMemory = runtime.freeMemory();
|
||||
long usedMemory = totalMemory - freeMemory;
|
||||
long maxMemory = runtime.maxMemory();
|
||||
|
||||
metrics.put("memory.used.mb", usedMemory / (1024.0 * 1024.0));
|
||||
metrics.put("memory.total.mb", totalMemory / (1024.0 * 1024.0));
|
||||
metrics.put("memory.max.mb", maxMemory / (1024.0 * 1024.0));
|
||||
metrics.put("memory.usage.percent", (usedMemory * 100.0) / totalMemory);
|
||||
|
||||
// CPU metrics (simplified)
|
||||
metrics.put("cpu.processors", (double) runtime.availableProcessors());
|
||||
}
|
||||
|
||||
private void onFrameRendered(Object eventData) {
|
||||
frameCount++;
|
||||
long currentTime = System.currentTimeMillis();
|
||||
|
||||
if (lastFrameTime == 0) {
|
||||
lastFrameTime = currentTime;
|
||||
return;
|
||||
}
|
||||
|
||||
long elapsed = currentTime - lastFrameTime;
|
||||
if (elapsed >= 1000) { // Calculate FPS every second
|
||||
double fps = (frameCount * 1000.0) / elapsed;
|
||||
metrics.put("rendering.fps", fps);
|
||||
|
||||
frameCount = 0;
|
||||
lastFrameTime = currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
private void logPerformanceReport() {
|
||||
StringBuilder report = new StringBuilder();
|
||||
report.append("Performance Report:\\n");
|
||||
|
||||
for (Map.Entry<String, Double> entry : metrics.entrySet()) {
|
||||
report.append(String.format(" %s: %.2f\\n", entry.getKey(), entry.getValue()));
|
||||
}
|
||||
|
||||
logger.debug(report.toString());
|
||||
}
|
||||
|
||||
public Map<String, Double> getMetrics() {
|
||||
return new ConcurrentHashMap<>(metrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginMetadata getMetadata() {
|
||||
return new PluginMetadata(
|
||||
"Performance Monitor",
|
||||
"Monitors client performance metrics",
|
||||
"OpenOSRS Agent",
|
||||
"1.0",
|
||||
new ArrayList<>(),
|
||||
Arrays.asList("monitoring", "performance", "debugging")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.openosrs.client.scripting;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Abstract base class for scripts with common functionality.
|
||||
*/
|
||||
public abstract class AbstractScript implements Script {
|
||||
protected final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
@Override
|
||||
public final void execute(ScriptContext context) throws Exception {
|
||||
context.setStatus(ScriptStatus.RUNNING);
|
||||
|
||||
try {
|
||||
run(context);
|
||||
context.setStatus(ScriptStatus.COMPLETED);
|
||||
} catch (InterruptedException e) {
|
||||
context.setStatus(ScriptStatus.STOPPED);
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
context.setStatus(ScriptStatus.FAILED);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement script logic here.
|
||||
*/
|
||||
protected abstract void run(ScriptContext context) throws Exception;
|
||||
|
||||
/**
|
||||
* Sleep with interruption check.
|
||||
*/
|
||||
protected void sleep(long millis) throws InterruptedException {
|
||||
Thread.sleep(millis);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a condition to be true.
|
||||
*/
|
||||
protected boolean waitFor(Supplier<Boolean> condition, long timeoutMs) throws InterruptedException {
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
while (System.currentTimeMillis() - start < timeoutMs) {
|
||||
if (condition.get()) {
|
||||
return true;
|
||||
}
|
||||
Thread.sleep(100);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.openosrs.client.scripting;
|
||||
|
||||
/**
|
||||
* Script interface for agent implementations.
|
||||
*/
|
||||
public interface Script {
|
||||
/**
|
||||
* Execute the script with the given context.
|
||||
*/
|
||||
void execute(ScriptContext context) throws Exception;
|
||||
|
||||
/**
|
||||
* Get script metadata.
|
||||
*/
|
||||
ScriptMetadata getMetadata();
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.openosrs.client.scripting;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Configuration for script execution.
|
||||
*/
|
||||
public class ScriptConfiguration {
|
||||
private final int maxConcurrentScripts;
|
||||
private final long maxRuntime;
|
||||
private final boolean scheduled;
|
||||
private final long initialDelay;
|
||||
private final long repeatInterval;
|
||||
private final Map<String, Object> parameters;
|
||||
private final boolean logExecution;
|
||||
|
||||
public ScriptConfiguration() {
|
||||
this.maxConcurrentScripts = 10;
|
||||
this.maxRuntime = 0; // No limit
|
||||
this.scheduled = false;
|
||||
this.initialDelay = 0;
|
||||
this.repeatInterval = 0;
|
||||
this.parameters = new HashMap<>();
|
||||
this.logExecution = true;
|
||||
}
|
||||
|
||||
public ScriptConfiguration(int maxConcurrentScripts, long maxRuntime,
|
||||
boolean scheduled, long initialDelay, long repeatInterval,
|
||||
Map<String, Object> parameters, boolean logExecution) {
|
||||
this.maxConcurrentScripts = maxConcurrentScripts;
|
||||
this.maxRuntime = maxRuntime;
|
||||
this.scheduled = scheduled;
|
||||
this.initialDelay = initialDelay;
|
||||
this.repeatInterval = repeatInterval;
|
||||
this.parameters = new HashMap<>(parameters);
|
||||
this.logExecution = logExecution;
|
||||
}
|
||||
|
||||
public int getMaxConcurrentScripts() { return maxConcurrentScripts; }
|
||||
public long getMaxRuntime() { return maxRuntime; }
|
||||
public boolean isScheduled() { return scheduled; }
|
||||
public long getInitialDelay() { return initialDelay; }
|
||||
public long getRepeatInterval() { return repeatInterval; }
|
||||
public Map<String, Object> getParameters() { return new HashMap<>(parameters); }
|
||||
public boolean isLogExecution() { return logExecution; }
|
||||
|
||||
public static class Builder {
|
||||
private int maxConcurrentScripts = 10;
|
||||
private long maxRuntime = 0;
|
||||
private boolean scheduled = false;
|
||||
private long initialDelay = 0;
|
||||
private long repeatInterval = 0;
|
||||
private Map<String, Object> parameters = new HashMap<>();
|
||||
private boolean logExecution = true;
|
||||
|
||||
public Builder maxConcurrentScripts(int maxConcurrentScripts) {
|
||||
this.maxConcurrentScripts = maxConcurrentScripts;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder maxRuntime(long maxRuntime) {
|
||||
this.maxRuntime = maxRuntime;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder scheduled(long initialDelay, long repeatInterval) {
|
||||
this.scheduled = true;
|
||||
this.initialDelay = initialDelay;
|
||||
this.repeatInterval = repeatInterval;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder parameter(String key, Object value) {
|
||||
this.parameters.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder logExecution(boolean logExecution) {
|
||||
this.logExecution = logExecution;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ScriptConfiguration build() {
|
||||
return new ScriptConfiguration(maxConcurrentScripts, maxRuntime,
|
||||
scheduled, initialDelay, repeatInterval, parameters, logExecution);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package com.openosrs.client.scripting;
|
||||
|
||||
import com.openosrs.client.api.AgentAPI;
|
||||
import com.openosrs.client.core.ClientCore;
|
||||
import com.openosrs.client.core.EventSystem;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Enhanced script execution context with additional capabilities.
|
||||
*/
|
||||
public class ScriptContext {
|
||||
private final String executionId;
|
||||
private final AgentAPI api;
|
||||
private final ClientCore clientCore;
|
||||
private final EventSystem eventSystem;
|
||||
private final ScriptConfiguration configuration;
|
||||
private final long startTime;
|
||||
private final Map<String, Object> variables;
|
||||
private volatile ScriptStatus status;
|
||||
private volatile boolean shouldStop;
|
||||
private volatile String error;
|
||||
private volatile long lastActivityTime;
|
||||
|
||||
public ScriptContext(String executionId, AgentAPI api, ClientCore clientCore, EventSystem eventSystem, ScriptConfiguration configuration) {
|
||||
this.executionId = executionId;
|
||||
this.api = api;
|
||||
this.clientCore = clientCore;
|
||||
this.eventSystem = eventSystem;
|
||||
this.configuration = configuration;
|
||||
this.startTime = System.currentTimeMillis();
|
||||
this.status = ScriptStatus.PENDING;
|
||||
this.shouldStop = false;
|
||||
this.variables = new ConcurrentHashMap<>();
|
||||
this.lastActivityTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public String getExecutionId() { return executionId; }
|
||||
public AgentAPI getAPI() { return api; }
|
||||
public ClientCore getClientCore() { return clientCore; }
|
||||
public EventSystem getEventSystem() { return eventSystem; }
|
||||
public ScriptConfiguration getConfiguration() { return configuration; }
|
||||
public long getStartTime() { return startTime; }
|
||||
|
||||
public ScriptStatus getStatus() { return status; }
|
||||
public void setStatus(ScriptStatus status) {
|
||||
this.status = status;
|
||||
updateActivity();
|
||||
}
|
||||
|
||||
public boolean shouldStop() { return shouldStop; }
|
||||
public void stop() {
|
||||
this.shouldStop = true;
|
||||
updateActivity();
|
||||
}
|
||||
|
||||
public String getError() { return error; }
|
||||
public void setError(String error) {
|
||||
this.error = error;
|
||||
updateActivity();
|
||||
}
|
||||
|
||||
public long getLastActivityTime() { return lastActivityTime; }
|
||||
private void updateActivity() { this.lastActivityTime = System.currentTimeMillis(); }
|
||||
|
||||
/**
|
||||
* Store a variable in the script context.
|
||||
*/
|
||||
public void setVariable(String key, Object value) {
|
||||
variables.put(key, value);
|
||||
updateActivity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a variable from the script context.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getVariable(String key, Class<T> type) {
|
||||
Object value = variables.get(key);
|
||||
return type.isInstance(value) ? (T) value : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if script should continue running.
|
||||
*/
|
||||
public void checkContinue() throws InterruptedException {
|
||||
updateActivity();
|
||||
if (shouldStop || Thread.currentThread().isInterrupted()) {
|
||||
throw new InterruptedException("Script execution interrupted");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a message with script context.
|
||||
*/
|
||||
public void log(String level, String message, Object... args) {
|
||||
updateActivity();
|
||||
String formattedMessage = String.format("[%s] %s", executionId, String.format(message, args));
|
||||
|
||||
switch (level.toLowerCase()) {
|
||||
case "debug":
|
||||
LoggerFactory.getLogger("ScriptLogger").debug(formattedMessage);
|
||||
break;
|
||||
case "info":
|
||||
LoggerFactory.getLogger("ScriptLogger").info(formattedMessage);
|
||||
break;
|
||||
case "warn":
|
||||
LoggerFactory.getLogger("ScriptLogger").warn(formattedMessage);
|
||||
break;
|
||||
case "error":
|
||||
LoggerFactory.getLogger("ScriptLogger").error(formattedMessage);
|
||||
break;
|
||||
default:
|
||||
LoggerFactory.getLogger("ScriptLogger").info(formattedMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.openosrs.client.scripting;
|
||||
|
||||
/**
|
||||
* Script event for notifications.
|
||||
*/
|
||||
public class ScriptEvent {
|
||||
private final ScriptEventType type;
|
||||
private final String scriptName;
|
||||
private final String executionId;
|
||||
private final ScriptStatus status;
|
||||
private final long timestamp;
|
||||
|
||||
public ScriptEvent(ScriptEventType type, String scriptName, String executionId, ScriptStatus status) {
|
||||
this.type = type;
|
||||
this.scriptName = scriptName;
|
||||
this.executionId = executionId;
|
||||
this.status = status;
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public ScriptEventType getType() { return type; }
|
||||
public String getScriptName() { return scriptName; }
|
||||
public String getExecutionId() { return executionId; }
|
||||
public ScriptStatus getStatus() { return status; }
|
||||
public long getTimestamp() { return timestamp; }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.openosrs.client.scripting;
|
||||
|
||||
/**
|
||||
* Script event types.
|
||||
*/
|
||||
public enum ScriptEventType {
|
||||
SCRIPT_REGISTERED,
|
||||
SCRIPT_STARTED,
|
||||
SCRIPT_COMPLETED,
|
||||
SCRIPT_FAILED,
|
||||
SCRIPT_STOPPED
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.openosrs.client.scripting;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Script execution history tracking.
|
||||
*/
|
||||
public class ScriptExecutionHistory {
|
||||
private final String executionId;
|
||||
private final String scriptName;
|
||||
private final ScriptConfiguration configuration;
|
||||
private final long createdTime;
|
||||
private final List<ExecutionRecord> executions;
|
||||
|
||||
public ScriptExecutionHistory(String executionId, String scriptName, ScriptConfiguration configuration) {
|
||||
this.executionId = executionId;
|
||||
this.scriptName = scriptName;
|
||||
this.configuration = configuration;
|
||||
this.createdTime = System.currentTimeMillis();
|
||||
this.executions = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void recordExecution() {
|
||||
executions.add(new ExecutionRecord(System.currentTimeMillis()));
|
||||
}
|
||||
|
||||
public void recordCompletion(ScriptStatus status) {
|
||||
if (!executions.isEmpty()) {
|
||||
ExecutionRecord lastExecution = executions.get(executions.size() - 1);
|
||||
lastExecution.setCompletion(System.currentTimeMillis(), status);
|
||||
}
|
||||
}
|
||||
|
||||
public String getExecutionId() { return executionId; }
|
||||
public String getScriptName() { return scriptName; }
|
||||
public ScriptConfiguration getConfiguration() { return configuration; }
|
||||
public long getCreatedTime() { return createdTime; }
|
||||
public List<ExecutionRecord> getExecutions() { return new ArrayList<>(executions); }
|
||||
|
||||
public int getExecutionCount() { return executions.size(); }
|
||||
public long getTotalRuntime() {
|
||||
return executions.stream()
|
||||
.mapToLong(ExecutionRecord::getRuntime)
|
||||
.sum();
|
||||
}
|
||||
|
||||
public static class ExecutionRecord {
|
||||
private final long startTime;
|
||||
private long endTime;
|
||||
private ScriptStatus status;
|
||||
|
||||
public ExecutionRecord(long startTime) {
|
||||
this.startTime = startTime;
|
||||
this.status = ScriptStatus.RUNNING;
|
||||
}
|
||||
|
||||
public void setCompletion(long endTime, ScriptStatus status) {
|
||||
this.endTime = endTime;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public long getStartTime() { return startTime; }
|
||||
public long getEndTime() { return endTime; }
|
||||
public ScriptStatus getStatus() { return status; }
|
||||
public long getRuntime() { return endTime > 0 ? endTime - startTime : System.currentTimeMillis() - startTime; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.openosrs.client.scripting;
|
||||
|
||||
/**
|
||||
* Information about a script execution.
|
||||
*/
|
||||
public class ScriptInfo {
|
||||
private final String executionId;
|
||||
private final String scriptName;
|
||||
private final ScriptStatus status;
|
||||
private final long startTime;
|
||||
private final String error;
|
||||
|
||||
public ScriptInfo(String executionId, String scriptName, ScriptStatus status, long startTime, String error) {
|
||||
this.executionId = executionId;
|
||||
this.scriptName = scriptName;
|
||||
this.status = status;
|
||||
this.startTime = startTime;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public String getExecutionId() { return executionId; }
|
||||
public String getScriptName() { return scriptName; }
|
||||
public ScriptStatus getStatus() { return status; }
|
||||
public long getStartTime() { return startTime; }
|
||||
public String getError() { return error; }
|
||||
public long getRuntime() { return System.currentTimeMillis() - startTime; }
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.openosrs.client.scripting;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Script metadata.
|
||||
*/
|
||||
public class ScriptMetadata {
|
||||
private final String name;
|
||||
private final String description;
|
||||
private final String author;
|
||||
private final String version;
|
||||
private final List<String> categories;
|
||||
|
||||
public ScriptMetadata(String name, String description, String author, String version, List<String> categories) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.author = author;
|
||||
this.version = version;
|
||||
this.categories = new ArrayList<>(categories);
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public String getDescription() { return description; }
|
||||
public String getAuthor() { return author; }
|
||||
public String getVersion() { return version; }
|
||||
public List<String> getCategories() { return new ArrayList<>(categories); }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.openosrs.client.scripting;
|
||||
|
||||
/**
|
||||
* Script execution status.
|
||||
*/
|
||||
public enum ScriptStatus {
|
||||
PENDING,
|
||||
RUNNING,
|
||||
COMPLETED,
|
||||
FAILED,
|
||||
STOPPED,
|
||||
NOT_FOUND
|
||||
}
|
||||
@@ -16,9 +16,27 @@ import java.util.UUID;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.Consumer;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.stream.Collectors;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* Scripting framework for automated gameplay.
|
||||
* Enhanced scripting framework for automated gameplay with advanced agent capabilities.
|
||||
*/
|
||||
public class ScriptingFramework {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ScriptingFramework.class);
|
||||
@@ -28,63 +46,222 @@ public class ScriptingFramework {
|
||||
private final ScheduledExecutorService scheduler;
|
||||
private final Map<String, RunningScript> activeScripts;
|
||||
private final ScriptRegistry scriptRegistry;
|
||||
private final ScriptLoader scriptLoader;
|
||||
private final ScriptMonitor scriptMonitor;
|
||||
private final EventSystem eventSystem;
|
||||
private volatile boolean enabled;
|
||||
private volatile boolean safeMode;
|
||||
private final Object scriptLock = new Object();
|
||||
private final Map<String, ScriptExecutionHistory> executionHistory;
|
||||
private final Set<String> trustedScriptPaths;
|
||||
private final Map<String, Consumer<ScriptEvent>> eventListeners;
|
||||
|
||||
public ScriptingFramework(AgentAPI agentAPI, ClientCore clientCore) {
|
||||
public ScriptingFramework(AgentAPI agentAPI, ClientCore clientCore, EventSystem eventSystem) {
|
||||
this.agentAPI = agentAPI;
|
||||
this.clientCore = clientCore;
|
||||
this.scheduler = Executors.newScheduledThreadPool(4);
|
||||
this.eventSystem = eventSystem;
|
||||
this.scheduler = Executors.newScheduledThreadPool(8); // Increased thread pool
|
||||
this.activeScripts = new ConcurrentHashMap<>();
|
||||
this.scriptRegistry = new ScriptRegistry();
|
||||
this.scriptLoader = new ScriptLoader();
|
||||
this.scriptMonitor = new ScriptMonitor();
|
||||
this.enabled = true;
|
||||
this.safeMode = true; // Safe mode enabled by default
|
||||
this.executionHistory = new ConcurrentHashMap<>();
|
||||
this.trustedScriptPaths = new HashSet<>();
|
||||
this.eventListeners = new ConcurrentHashMap<>();
|
||||
|
||||
logger.info("Scripting framework initialized");
|
||||
initializeBuiltInScripts();
|
||||
startMonitoringTasks();
|
||||
|
||||
logger.info("Enhanced scripting framework initialized with safe mode: {}", safeMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a script for use.
|
||||
*/
|
||||
public void registerScript(String name, Script script) {
|
||||
synchronized (scriptLock) {
|
||||
scriptRegistry.register(name, script);
|
||||
logger.info("Registered script: {}", name);
|
||||
|
||||
// Fire script registration event
|
||||
fireScriptEvent(new ScriptEvent(ScriptEventType.SCRIPT_REGISTERED, name, null, null));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and register scripts from a directory.
|
||||
*/
|
||||
public void loadScriptsFromDirectory(String directoryPath) {
|
||||
try {
|
||||
Path dir = Paths.get(directoryPath);
|
||||
if (!Files.exists(dir) || !Files.isDirectory(dir)) {
|
||||
logger.warn("Script directory does not exist: {}", directoryPath);
|
||||
return;
|
||||
}
|
||||
|
||||
List<Script> loadedScripts = scriptLoader.loadFromDirectory(dir);
|
||||
for (Script script : loadedScripts) {
|
||||
ScriptMetadata metadata = script.getMetadata();
|
||||
registerScript(metadata.getName(), script);
|
||||
}
|
||||
|
||||
logger.info("Loaded {} scripts from directory: {}", loadedScripts.size(), directoryPath);
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to load scripts from directory: {}", directoryPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and register a script from a JAR file.
|
||||
*/
|
||||
public void loadScriptFromJar(String jarPath) {
|
||||
try {
|
||||
if (safeMode && !trustedScriptPaths.contains(jarPath)) {
|
||||
logger.warn("Cannot load untrusted script in safe mode: {}", jarPath);
|
||||
return;
|
||||
}
|
||||
|
||||
List<Script> loadedScripts = scriptLoader.loadFromJar(Paths.get(jarPath));
|
||||
for (Script script : loadedScripts) {
|
||||
ScriptMetadata metadata = script.getMetadata();
|
||||
registerScript(metadata.getName(), script);
|
||||
}
|
||||
|
||||
logger.info("Loaded {} scripts from JAR: {}", loadedScripts.size(), jarPath);
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to load script from JAR: {}", jarPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a trusted script path for safe mode.
|
||||
*/
|
||||
public void addTrustedScriptPath(String path) {
|
||||
trustedScriptPaths.add(path);
|
||||
logger.info("Added trusted script path: {}", path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable safe mode.
|
||||
*/
|
||||
public void setSafeMode(boolean safeMode) {
|
||||
this.safeMode = safeMode;
|
||||
logger.info("Safe mode {}", safeMode ? "enabled" : "disabled");
|
||||
}
|
||||
|
||||
public boolean isSafeMode() {
|
||||
return safeMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a script execution.
|
||||
*/
|
||||
public String startScript(String scriptName) {
|
||||
return startScript(scriptName, new ScriptConfiguration());
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a script execution with configuration.
|
||||
*/
|
||||
public String startScript(String scriptName, ScriptConfiguration config) {
|
||||
if (!enabled) {
|
||||
throw new IllegalStateException("Scripting framework is disabled");
|
||||
}
|
||||
|
||||
synchronized (scriptLock) {
|
||||
// Check concurrent script limits
|
||||
if (activeScripts.size() >= config.getMaxConcurrentScripts()) {
|
||||
throw new IllegalStateException("Maximum concurrent scripts reached: " + config.getMaxConcurrentScripts());
|
||||
}
|
||||
|
||||
Script script = scriptRegistry.getScript(scriptName);
|
||||
if (script == null) {
|
||||
throw new IllegalArgumentException("Script not found: " + scriptName);
|
||||
}
|
||||
|
||||
String executionId = UUID.randomUUID().toString();
|
||||
ScriptContext context = new ScriptContext(executionId, agentAPI, clientCore);
|
||||
ScriptContext context = new ScriptContext(executionId, agentAPI, clientCore, eventSystem, config);
|
||||
|
||||
Future<?> future = scheduler.submit(() -> {
|
||||
try {
|
||||
logger.info("Starting script execution: {} ({})", scriptName, executionId);
|
||||
script.execute(context);
|
||||
logger.info("Script completed: {} ({})", scriptName, executionId);
|
||||
} catch (Exception e) {
|
||||
logger.error("Script execution failed: {} ({})", scriptName, executionId, e);
|
||||
context.setStatus(ScriptStatus.FAILED);
|
||||
context.setError(e.getMessage());
|
||||
} finally {
|
||||
activeScripts.remove(executionId);
|
||||
// Create execution history entry
|
||||
ScriptExecutionHistory history = new ScriptExecutionHistory(executionId, scriptName, config);
|
||||
executionHistory.put(executionId, history);
|
||||
|
||||
Future<?> future;
|
||||
if (config.isScheduled()) {
|
||||
// Schedule recurring execution
|
||||
future = scheduler.scheduleAtFixedRate(
|
||||
() -> executeScript(script, context, history),
|
||||
config.getInitialDelay(),
|
||||
config.getRepeatInterval(),
|
||||
TimeUnit.MILLISECONDS
|
||||
);
|
||||
} else {
|
||||
// Single execution
|
||||
future = scheduler.submit(() -> executeScript(script, context, history));
|
||||
}
|
||||
});
|
||||
|
||||
RunningScript runningScript = new RunningScript(
|
||||
executionId, scriptName, script, context, future);
|
||||
executionId, scriptName, script, context, future, config);
|
||||
activeScripts.put(executionId, runningScript);
|
||||
|
||||
// Fire script start event
|
||||
fireScriptEvent(new ScriptEvent(ScriptEventType.SCRIPT_STARTED, scriptName, executionId, null));
|
||||
|
||||
logger.info("Started script: {} with execution ID: {}", scriptName, executionId);
|
||||
return executionId;
|
||||
}
|
||||
}
|
||||
|
||||
private void executeScript(Script script, ScriptContext context, ScriptExecutionHistory history) {
|
||||
try {
|
||||
logger.debug("Executing script: {} ({})", script.getMetadata().getName(), context.getExecutionId());
|
||||
|
||||
context.setStatus(ScriptStatus.RUNNING);
|
||||
history.recordExecution();
|
||||
|
||||
script.execute(context);
|
||||
|
||||
if (context.getStatus() == ScriptStatus.RUNNING) {
|
||||
context.setStatus(ScriptStatus.COMPLETED);
|
||||
}
|
||||
|
||||
history.recordCompletion(context.getStatus());
|
||||
|
||||
logger.info("Script completed: {} ({})", script.getMetadata().getName(), context.getExecutionId());
|
||||
|
||||
fireScriptEvent(new ScriptEvent(ScriptEventType.SCRIPT_COMPLETED,
|
||||
script.getMetadata().getName(), context.getExecutionId(), context.getStatus()));
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
context.setStatus(ScriptStatus.STOPPED);
|
||||
history.recordCompletion(ScriptStatus.STOPPED);
|
||||
logger.info("Script interrupted: {} ({})", script.getMetadata().getName(), context.getExecutionId());
|
||||
|
||||
fireScriptEvent(new ScriptEvent(ScriptEventType.SCRIPT_STOPPED,
|
||||
script.getMetadata().getName(), context.getExecutionId(), ScriptStatus.STOPPED));
|
||||
|
||||
} catch (Exception e) {
|
||||
context.setStatus(ScriptStatus.FAILED);
|
||||
context.setError(e.getMessage());
|
||||
history.recordCompletion(ScriptStatus.FAILED);
|
||||
|
||||
logger.error("Script execution failed: {} ({})", script.getMetadata().getName(), context.getExecutionId(), e);
|
||||
|
||||
fireScriptEvent(new ScriptEvent(ScriptEventType.SCRIPT_FAILED,
|
||||
script.getMetadata().getName(), context.getExecutionId(), ScriptStatus.FAILED));
|
||||
|
||||
} finally {
|
||||
// Only remove from active scripts if it's not a scheduled script or if it failed/stopped
|
||||
ScriptConfiguration config = context.getConfiguration();
|
||||
if (!config.isScheduled() ||
|
||||
context.getStatus() == ScriptStatus.FAILED ||
|
||||
context.getStatus() == ScriptStatus.STOPPED) {
|
||||
activeScripts.remove(context.getExecutionId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop a running script.
|
||||
@@ -138,6 +315,7 @@ public class ScriptingFramework {
|
||||
public void stopAllScripts() {
|
||||
logger.info("Stopping all running scripts");
|
||||
|
||||
synchronized (scriptLock) {
|
||||
for (RunningScript runningScript : activeScripts.values()) {
|
||||
runningScript.getContext().stop();
|
||||
runningScript.getFuture().cancel(true);
|
||||
@@ -145,6 +323,120 @@ public class ScriptingFramework {
|
||||
|
||||
activeScripts.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get execution history for a script.
|
||||
*/
|
||||
public ScriptExecutionHistory getExecutionHistory(String executionId) {
|
||||
return executionHistory.get(executionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered scripts.
|
||||
*/
|
||||
public Map<String, ScriptMetadata> getRegisteredScripts() {
|
||||
return scriptRegistry.getAllScripts().entrySet().stream()
|
||||
.collect(Collectors.toMap(
|
||||
Map.Entry::getKey,
|
||||
entry -> entry.getValue().getMetadata()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an event listener for script events.
|
||||
*/
|
||||
public void addEventListener(String eventType, Consumer<ScriptEvent> listener) {
|
||||
eventListeners.put(eventType, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an event listener.
|
||||
*/
|
||||
public void removeEventListener(String eventType) {
|
||||
eventListeners.remove(eventType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire a script event to all listeners.
|
||||
*/
|
||||
private void fireScriptEvent(ScriptEvent event) {
|
||||
try {
|
||||
// Fire to framework listeners
|
||||
Consumer<ScriptEvent> listener = eventListeners.get(event.getType().name());
|
||||
if (listener != null) {
|
||||
listener.accept(event);
|
||||
}
|
||||
|
||||
// Fire to global event system
|
||||
if (eventSystem != null) {
|
||||
eventSystem.fireEvent("script." + event.getType().name().toLowerCase(), event);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Error firing script event: {}", event, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize built-in scripts.
|
||||
*/
|
||||
private void initializeBuiltInScripts() {
|
||||
// Register any built-in utility scripts here
|
||||
logger.debug("Initialized built-in scripts");
|
||||
}
|
||||
|
||||
/**
|
||||
* Start monitoring tasks for script health and cleanup.
|
||||
*/
|
||||
private void startMonitoringTasks() {
|
||||
// Schedule cleanup of old execution history
|
||||
scheduler.scheduleAtFixedRate(() -> {
|
||||
try {
|
||||
cleanupExecutionHistory();
|
||||
} catch (Exception e) {
|
||||
logger.warn("Error during execution history cleanup", e);
|
||||
}
|
||||
}, 1, 1, TimeUnit.HOURS);
|
||||
|
||||
// Schedule script health monitoring
|
||||
scheduler.scheduleAtFixedRate(() -> {
|
||||
try {
|
||||
monitorScriptHealth();
|
||||
} catch (Exception e) {
|
||||
logger.warn("Error during script health monitoring", e);
|
||||
}
|
||||
}, 30, 30, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up old execution history entries.
|
||||
*/
|
||||
private void cleanupExecutionHistory() {
|
||||
long cutoffTime = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7); // Keep 7 days
|
||||
|
||||
executionHistory.entrySet().removeIf(entry -> {
|
||||
ScriptExecutionHistory history = entry.getValue();
|
||||
return history.getCreatedTime() < cutoffTime;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitor health of running scripts.
|
||||
*/
|
||||
private void monitorScriptHealth() {
|
||||
for (RunningScript runningScript : activeScripts.values()) {
|
||||
ScriptContext context = runningScript.getContext();
|
||||
|
||||
// Check for scripts running too long
|
||||
long runtime = System.currentTimeMillis() - context.getStartTime();
|
||||
ScriptConfiguration config = context.getConfiguration();
|
||||
|
||||
if (config.getMaxRuntime() > 0 && runtime > config.getMaxRuntime()) {
|
||||
logger.warn("Script {} exceeded maximum runtime, stopping", runningScript.getScriptName());
|
||||
stopScript(runningScript.getExecutionId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable the scripting framework.
|
||||
@@ -177,179 +469,7 @@ public class ScriptingFramework {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Script interface for agent implementations.
|
||||
*/
|
||||
public interface Script {
|
||||
/**
|
||||
* Execute the script with the given context.
|
||||
*/
|
||||
void execute(ScriptContext context) throws Exception;
|
||||
|
||||
/**
|
||||
* Get script metadata.
|
||||
*/
|
||||
ScriptMetadata getMetadata();
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract base class for scripts with common functionality.
|
||||
*/
|
||||
public abstract class AbstractScript implements Script {
|
||||
protected final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
@Override
|
||||
public final void execute(ScriptContext context) throws Exception {
|
||||
context.setStatus(ScriptStatus.RUNNING);
|
||||
|
||||
try {
|
||||
run(context);
|
||||
context.setStatus(ScriptStatus.COMPLETED);
|
||||
} catch (InterruptedException e) {
|
||||
context.setStatus(ScriptStatus.STOPPED);
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
context.setStatus(ScriptStatus.FAILED);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement script logic here.
|
||||
*/
|
||||
protected abstract void run(ScriptContext context) throws Exception;
|
||||
|
||||
/**
|
||||
* Sleep with interruption check.
|
||||
*/
|
||||
protected void sleep(long millis) throws InterruptedException {
|
||||
Thread.sleep(millis);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a condition to be true.
|
||||
*/
|
||||
protected boolean waitFor(Supplier<Boolean> condition, long timeoutMs) throws InterruptedException {
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
while (System.currentTimeMillis() - start < timeoutMs) {
|
||||
if (condition.get()) {
|
||||
return true;
|
||||
}
|
||||
Thread.sleep(100);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Script execution context.
|
||||
*/
|
||||
public class ScriptContext {
|
||||
private final String executionId;
|
||||
private final AgentAPI api;
|
||||
private final ClientCore clientCore;
|
||||
private final long startTime;
|
||||
private volatile ScriptStatus status;
|
||||
private volatile boolean shouldStop;
|
||||
private volatile String error;
|
||||
|
||||
public ScriptContext(String executionId, AgentAPI api, ClientCore clientCore) {
|
||||
this.executionId = executionId;
|
||||
this.api = api;
|
||||
this.clientCore = clientCore;
|
||||
this.startTime = System.currentTimeMillis();
|
||||
this.status = ScriptStatus.PENDING;
|
||||
this.shouldStop = false;
|
||||
}
|
||||
|
||||
public String getExecutionId() { return executionId; }
|
||||
public AgentAPI getAPI() { return api; }
|
||||
public ClientCore getClientCore() { return clientCore; }
|
||||
public long getStartTime() { return startTime; }
|
||||
|
||||
public ScriptStatus getStatus() { return status; }
|
||||
public void setStatus(ScriptStatus status) { this.status = status; }
|
||||
|
||||
public boolean shouldStop() { return shouldStop; }
|
||||
public void stop() { this.shouldStop = true; }
|
||||
|
||||
public String getError() { return error; }
|
||||
public void setError(String error) { this.error = error; }
|
||||
|
||||
/**
|
||||
* Check if script should continue running.
|
||||
*/
|
||||
public void checkContinue() throws InterruptedException {
|
||||
if (shouldStop || Thread.currentThread().isInterrupted()) {
|
||||
throw new InterruptedException("Script execution interrupted");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Script execution status.
|
||||
*/
|
||||
public enum ScriptStatus {
|
||||
PENDING,
|
||||
RUNNING,
|
||||
COMPLETED,
|
||||
FAILED,
|
||||
STOPPED,
|
||||
NOT_FOUND
|
||||
}
|
||||
|
||||
/**
|
||||
* Script metadata.
|
||||
*/
|
||||
public class ScriptMetadata {
|
||||
private final String name;
|
||||
private final String description;
|
||||
private final String author;
|
||||
private final String version;
|
||||
private final List<String> categories;
|
||||
|
||||
public ScriptMetadata(String name, String description, String author, String version, List<String> categories) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.author = author;
|
||||
this.version = version;
|
||||
this.categories = new ArrayList<>(categories);
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public String getDescription() { return description; }
|
||||
public String getAuthor() { return author; }
|
||||
public String getVersion() { return version; }
|
||||
public List<String> getCategories() { return new ArrayList<>(categories); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about a script execution.
|
||||
*/
|
||||
public class ScriptInfo {
|
||||
private final String executionId;
|
||||
private final String scriptName;
|
||||
private final ScriptStatus status;
|
||||
private final long startTime;
|
||||
private final String error;
|
||||
|
||||
public ScriptInfo(String executionId, String scriptName, ScriptStatus status, long startTime, String error) {
|
||||
this.executionId = executionId;
|
||||
this.scriptName = scriptName;
|
||||
this.status = status;
|
||||
this.startTime = startTime;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public String getExecutionId() { return executionId; }
|
||||
public String getScriptName() { return scriptName; }
|
||||
public ScriptStatus getStatus() { return status; }
|
||||
public long getStartTime() { return startTime; }
|
||||
public String getError() { return error; }
|
||||
public long getRuntime() { return System.currentTimeMillis() - startTime; }
|
||||
}
|
||||
// Public classes moved to separate files
|
||||
|
||||
/**
|
||||
* Internal classes for script management.
|
||||
@@ -376,13 +496,16 @@ class RunningScript {
|
||||
private final Script script;
|
||||
private final ScriptContext context;
|
||||
private final Future<?> future;
|
||||
private final ScriptConfiguration configuration;
|
||||
|
||||
public RunningScript(String executionId, String scriptName, Script script, ScriptContext context, Future<?> future) {
|
||||
public RunningScript(String executionId, String scriptName, Script script,
|
||||
ScriptContext context, Future<?> future, ScriptConfiguration configuration) {
|
||||
this.executionId = executionId;
|
||||
this.scriptName = scriptName;
|
||||
this.script = script;
|
||||
this.context = context;
|
||||
this.future = future;
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
public String getExecutionId() { return executionId; }
|
||||
@@ -390,4 +513,98 @@ class RunningScript {
|
||||
public Script getScript() { return script; }
|
||||
public ScriptContext getContext() { return context; }
|
||||
public Future<?> getFuture() { return future; }
|
||||
public ScriptConfiguration getConfiguration() { return configuration; }
|
||||
}
|
||||
|
||||
// Remaining public classes also moved to separate files
|
||||
|
||||
/**
|
||||
* Dynamic script loader for external scripts.
|
||||
*/
|
||||
class ScriptLoader {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ScriptLoader.class);
|
||||
|
||||
public List<Script> loadFromDirectory(Path directory) throws IOException {
|
||||
List<Script> scripts = new ArrayList<>();
|
||||
|
||||
Files.walk(directory)
|
||||
.filter(Files::isRegularFile)
|
||||
.filter(path -> path.toString().endsWith(".class"))
|
||||
.forEach(classFile -> {
|
||||
try {
|
||||
Script script = loadScriptFromClass(classFile);
|
||||
if (script != null) {
|
||||
scripts.add(script);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to load script from: {}", classFile, e);
|
||||
}
|
||||
});
|
||||
|
||||
return scripts;
|
||||
}
|
||||
|
||||
public List<Script> loadFromJar(Path jarFile) throws IOException {
|
||||
List<Script> scripts = new ArrayList<>();
|
||||
|
||||
try (JarFile jar = new JarFile(jarFile.toFile());
|
||||
URLClassLoader classLoader = new URLClassLoader(new URL[]{jarFile.toUri().toURL()})) {
|
||||
|
||||
Enumeration<JarEntry> entries = jar.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry entry = entries.nextElement();
|
||||
|
||||
if (entry.getName().endsWith(".class")) {
|
||||
try {
|
||||
String className = entry.getName()
|
||||
.replace('/', '.')
|
||||
.substring(0, entry.getName().length() - 6);
|
||||
|
||||
Class<?> clazz = classLoader.loadClass(className);
|
||||
|
||||
if (Script.class.isAssignableFrom(clazz)) {
|
||||
Constructor<?> constructor = clazz.getDeclaredConstructor();
|
||||
Script script = (Script) constructor.newInstance();
|
||||
scripts.add(script);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.debug("Skipping class {}: {}", entry.getName(), e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return scripts;
|
||||
}
|
||||
|
||||
private Script loadScriptFromClass(Path classFile) {
|
||||
// Implementation would depend on specific requirements
|
||||
// This is a placeholder for dynamic class loading
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Script monitoring and management utilities.
|
||||
*/
|
||||
class ScriptMonitor {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ScriptMonitor.class);
|
||||
|
||||
public void analyzePerformance(Map<String, RunningScript> activeScripts) {
|
||||
for (RunningScript script : activeScripts.values()) {
|
||||
ScriptContext context = script.getContext();
|
||||
long runtime = System.currentTimeMillis() - context.getStartTime();
|
||||
long inactivity = System.currentTimeMillis() - context.getLastActivityTime();
|
||||
|
||||
if (inactivity > TimeUnit.MINUTES.toMillis(5)) {
|
||||
logger.warn("Script {} appears inactive for {} ms",
|
||||
script.getScriptName(), inactivity);
|
||||
}
|
||||
|
||||
if (runtime > TimeUnit.HOURS.toMillis(1)) {
|
||||
logger.info("Long-running script {} has been active for {} ms",
|
||||
script.getScriptName(), runtime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.openosrs.client.scripting.examples;
|
||||
|
||||
import com.openosrs.client.scripting.*;
|
||||
import com.openosrs.client.api.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Banking script utility.
|
||||
*/
|
||||
public class BankingScript extends AbstractScript {
|
||||
private final List<Integer> itemsToDeposit;
|
||||
private final List<Integer> itemsToWithdraw;
|
||||
private final Map<Integer, Integer> withdrawQuantities;
|
||||
|
||||
public BankingScript(List<Integer> itemsToDeposit, Map<Integer, Integer> itemsToWithdraw) {
|
||||
this.itemsToDeposit = itemsToDeposit;
|
||||
this.itemsToWithdraw = itemsToWithdraw.keySet().stream().collect(Collectors.toList());
|
||||
this.withdrawQuantities = itemsToWithdraw;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(ScriptContext context) throws Exception {
|
||||
AgentAPI api = context.getAPI();
|
||||
|
||||
logger.info("Starting banking script");
|
||||
|
||||
// Find a bank
|
||||
GameObject bank = findNearestBank(api);
|
||||
if (bank == null) {
|
||||
logger.error("No bank found");
|
||||
return;
|
||||
}
|
||||
|
||||
// Walk to bank
|
||||
Position playerPos = api.getPlayerPosition();
|
||||
if (bank.distanceToPlayer(playerPos) > 2.0) {
|
||||
logger.debug("Walking to bank");
|
||||
api.walkTo(bank.getPosition()).get();
|
||||
sleep(2000);
|
||||
}
|
||||
|
||||
// Open bank
|
||||
logger.debug("Opening bank");
|
||||
api.interactWithObject(bank, "Bank").get();
|
||||
sleep(2000);
|
||||
|
||||
// Deposit items
|
||||
for (int itemId : itemsToDeposit) {
|
||||
if (api.hasItem(itemId)) {
|
||||
logger.debug("Depositing item: {}", itemId);
|
||||
// Bank interface interaction would go here
|
||||
sleep(600);
|
||||
}
|
||||
}
|
||||
|
||||
// Withdraw items
|
||||
for (int itemId : itemsToWithdraw) {
|
||||
int quantity = withdrawQuantities.getOrDefault(itemId, 1);
|
||||
logger.debug("Withdrawing {} x {}", quantity, itemId);
|
||||
// Bank interface interaction would go here
|
||||
sleep(600);
|
||||
}
|
||||
|
||||
// Close bank
|
||||
sleep(1000);
|
||||
|
||||
logger.info("Banking completed");
|
||||
}
|
||||
|
||||
private GameObject findNearestBank(AgentAPI api) {
|
||||
// Common bank object IDs
|
||||
int[] bankIds = {10060, 2213, 11758, 14367, 10517};
|
||||
|
||||
for (int bankId : bankIds) {
|
||||
GameObject bank = api.getClosestGameObject(bankId);
|
||||
if (bank != null) {
|
||||
return bank;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScriptMetadata getMetadata() {
|
||||
return new ScriptMetadata(
|
||||
"Banking Utility",
|
||||
"Deposits and withdraws items from bank",
|
||||
"OpenOSRS Agent",
|
||||
"1.0",
|
||||
Arrays.asList("Banking", "Utility", "Helper")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package com.openosrs.client.scripting.examples;
|
||||
|
||||
import com.openosrs.client.scripting.*;
|
||||
import com.openosrs.client.api.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Combat training script.
|
||||
*/
|
||||
public class CombatTrainingScript extends AbstractScript {
|
||||
private final int targetNpcId;
|
||||
private final boolean eatFood;
|
||||
private final int foodId;
|
||||
private final int healthThreshold;
|
||||
|
||||
public CombatTrainingScript(int targetNpcId, boolean eatFood, int foodId, int healthThreshold) {
|
||||
this.targetNpcId = targetNpcId;
|
||||
this.eatFood = eatFood;
|
||||
this.foodId = foodId;
|
||||
this.healthThreshold = healthThreshold;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(ScriptContext context) throws Exception {
|
||||
AgentAPI api = context.getAPI();
|
||||
|
||||
logger.info("Starting combat training script targeting NPC ID: {}", targetNpcId);
|
||||
|
||||
while (!context.shouldStop()) {
|
||||
context.checkContinue();
|
||||
|
||||
// Check if we need to eat
|
||||
if (eatFood && api.getHitpoints() < healthThreshold) {
|
||||
if (!eatFood(api)) {
|
||||
logger.warn("No food available, stopping script");
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// If not in combat, find a target
|
||||
if (!api.isInCombat()) {
|
||||
NPC target = findTarget(api);
|
||||
if (target == null) {
|
||||
logger.warn("No targets found");
|
||||
sleep(2000);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Attack the target
|
||||
logger.debug("Attacking NPC: {}", target.getName());
|
||||
api.interactWithNPC(target, "Attack").get();
|
||||
sleep(1000);
|
||||
}
|
||||
|
||||
// Wait while in combat
|
||||
while (api.isInCombat() && !context.shouldStop()) {
|
||||
context.checkContinue();
|
||||
|
||||
// Check if we need to eat during combat
|
||||
if (eatFood && api.getHitpoints() < healthThreshold) {
|
||||
eatFood(api);
|
||||
}
|
||||
|
||||
sleep(600);
|
||||
}
|
||||
|
||||
sleep(1000);
|
||||
}
|
||||
|
||||
logger.info("Combat training script stopped");
|
||||
}
|
||||
|
||||
private NPC findTarget(AgentAPI api) {
|
||||
List<NPC> npcs = api.getNPCsById(targetNpcId);
|
||||
Position playerPos = api.getPlayerPosition();
|
||||
|
||||
return npcs.stream()
|
||||
.filter(npc -> !npc.isInteracting()) // Not already in combat
|
||||
.filter(npc -> npc.getHitpoints() > 0) // Not dead
|
||||
.filter(npc -> npc.distanceToPlayer(playerPos) < 15) // Within range
|
||||
.min((n1, n2) -> Double.compare(
|
||||
n1.distanceToPlayer(playerPos),
|
||||
n2.distanceToPlayer(playerPos)))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private boolean eatFood(AgentAPI api) throws Exception {
|
||||
int foodSlot = api.findItemSlot(foodId);
|
||||
if (foodSlot == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.debug("Eating food");
|
||||
api.useItem(foodSlot).get();
|
||||
sleep(1800); // Food delay
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScriptMetadata getMetadata() {
|
||||
return new ScriptMetadata(
|
||||
"Combat Training",
|
||||
"Trains combat skills by fighting NPCs",
|
||||
"OpenOSRS Agent",
|
||||
"1.0",
|
||||
Arrays.asList("Combat", "Training", "PVE")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,295 +0,0 @@
|
||||
package com.openosrs.client.scripting.examples;
|
||||
|
||||
import com.openosrs.client.scripting.*;
|
||||
import com.openosrs.client.api.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Example scripts demonstrating the scripting framework.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Simple woodcutting script.
|
||||
*/
|
||||
public class WoodcuttingScript extends AbstractScript {
|
||||
private static final int TREE_ID = 1278; // Oak tree
|
||||
private static final int AXE_ID = 1351; // Bronze axe
|
||||
private static final int LOG_ID = 1521; // Oak logs
|
||||
|
||||
@Override
|
||||
protected void run(ScriptContext context) throws Exception {
|
||||
AgentAPI api = context.getAPI();
|
||||
|
||||
logger.info("Starting woodcutting script");
|
||||
|
||||
// Check if we have an axe
|
||||
if (!api.hasItem(AXE_ID)) {
|
||||
logger.error("No axe found in inventory");
|
||||
return;
|
||||
}
|
||||
|
||||
while (!context.shouldStop()) {
|
||||
context.checkContinue();
|
||||
|
||||
// If inventory is full, drop logs
|
||||
if (api.isInventoryFull()) {
|
||||
dropLogs(api);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find a tree to cut
|
||||
GameObject tree = api.getClosestGameObject(TREE_ID);
|
||||
if (tree == null) {
|
||||
logger.warn("No trees found");
|
||||
sleep(2000);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Walk to tree if not close enough
|
||||
Position playerPos = api.getPlayerPosition();
|
||||
if (tree.distanceToPlayer(playerPos) > 1.0) {
|
||||
logger.debug("Walking to tree");
|
||||
api.walkTo(tree.getPosition()).get();
|
||||
sleep(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Cut the tree
|
||||
logger.debug("Cutting tree");
|
||||
api.interactWithObject(tree, "Chop down").get();
|
||||
|
||||
// Wait for animation to start
|
||||
waitFor(() -> api.getCurrentAnimation() != -1, 3000);
|
||||
|
||||
// Wait for woodcutting to finish
|
||||
waitFor(() -> api.getCurrentAnimation() == -1, 10000);
|
||||
|
||||
sleep(1000);
|
||||
}
|
||||
|
||||
logger.info("Woodcutting script stopped");
|
||||
}
|
||||
|
||||
private void dropLogs(AgentAPI api) throws Exception {
|
||||
logger.debug("Dropping logs");
|
||||
|
||||
for (int i = 0; i < 28; i++) {
|
||||
Item item = api.getInventorySlot(i);
|
||||
if (item != null && item.getItemId() == LOG_ID) {
|
||||
api.dropItem(i).get();
|
||||
sleep(200);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScriptMetadata getMetadata() {
|
||||
return new ScriptMetadata(
|
||||
"Woodcutting",
|
||||
"Cuts oak trees and drops logs",
|
||||
"OpenOSRS Agent",
|
||||
"1.0",
|
||||
Arrays.asList("Woodcutting", "Skilling", "AFK")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Combat training script.
|
||||
*/
|
||||
public class CombatTrainingScript extends AbstractScript {
|
||||
private final int targetNpcId;
|
||||
private final boolean eatFood;
|
||||
private final int foodId;
|
||||
private final int healthThreshold;
|
||||
|
||||
public CombatTrainingScript(int targetNpcId, boolean eatFood, int foodId, int healthThreshold) {
|
||||
this.targetNpcId = targetNpcId;
|
||||
this.eatFood = eatFood;
|
||||
this.foodId = foodId;
|
||||
this.healthThreshold = healthThreshold;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(ScriptContext context) throws Exception {
|
||||
AgentAPI api = context.getAPI();
|
||||
|
||||
logger.info("Starting combat training script targeting NPC ID: {}", targetNpcId);
|
||||
|
||||
while (!context.shouldStop()) {
|
||||
context.checkContinue();
|
||||
|
||||
// Check if we need to eat
|
||||
if (eatFood && api.getHitpoints() < healthThreshold) {
|
||||
if (!eatFood(api)) {
|
||||
logger.warn("No food available, stopping script");
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// If not in combat, find a target
|
||||
if (!api.isInCombat()) {
|
||||
NPC target = findTarget(api);
|
||||
if (target == null) {
|
||||
logger.warn("No targets found");
|
||||
sleep(2000);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Attack the target
|
||||
logger.debug("Attacking NPC: {}", target.getName());
|
||||
api.interactWithNPC(target, "Attack").get();
|
||||
sleep(1000);
|
||||
}
|
||||
|
||||
// Wait while in combat
|
||||
while (api.isInCombat() && !context.shouldStop()) {
|
||||
context.checkContinue();
|
||||
|
||||
// Check if we need to eat during combat
|
||||
if (eatFood && api.getHitpoints() < healthThreshold) {
|
||||
eatFood(api);
|
||||
}
|
||||
|
||||
sleep(600);
|
||||
}
|
||||
|
||||
sleep(1000);
|
||||
}
|
||||
|
||||
logger.info("Combat training script stopped");
|
||||
}
|
||||
|
||||
private NPC findTarget(AgentAPI api) {
|
||||
List<NPC> npcs = api.getNPCsById(targetNpcId);
|
||||
Position playerPos = api.getPlayerPosition();
|
||||
|
||||
return npcs.stream()
|
||||
.filter(npc -> !npc.isInteracting()) // Not already in combat
|
||||
.filter(npc -> npc.getHitpoints() > 0) // Not dead
|
||||
.filter(npc -> npc.distanceToPlayer(playerPos) < 15) // Within range
|
||||
.min((n1, n2) -> Double.compare(
|
||||
n1.distanceToPlayer(playerPos),
|
||||
n2.distanceToPlayer(playerPos)))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private boolean eatFood(AgentAPI api) throws Exception {
|
||||
int foodSlot = api.findItemSlot(foodId);
|
||||
if (foodSlot == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.debug("Eating food");
|
||||
api.useItem(foodSlot).get();
|
||||
sleep(1800); // Food delay
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScriptMetadata getMetadata() {
|
||||
return new ScriptMetadata(
|
||||
"Combat Training",
|
||||
"Trains combat skills by fighting NPCs",
|
||||
"OpenOSRS Agent",
|
||||
"1.0",
|
||||
Arrays.asList("Combat", "Training", "PVE")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Banking script utility.
|
||||
*/
|
||||
public class BankingScript extends AbstractScript {
|
||||
private final List<Integer> itemsToDeposit;
|
||||
private final List<Integer> itemsToWithdraw;
|
||||
private final Map<Integer, Integer> withdrawQuantities;
|
||||
|
||||
public BankingScript(List<Integer> itemsToDeposit, Map<Integer, Integer> itemsToWithdraw) {
|
||||
this.itemsToDeposit = itemsToDeposit;
|
||||
this.itemsToWithdraw = itemsToWithdraw.keySet().stream().collect(Collectors.toList());
|
||||
this.withdrawQuantities = itemsToWithdraw;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(ScriptContext context) throws Exception {
|
||||
AgentAPI api = context.getAPI();
|
||||
|
||||
logger.info("Starting banking script");
|
||||
|
||||
// Find a bank
|
||||
GameObject bank = findNearestBank(api);
|
||||
if (bank == null) {
|
||||
logger.error("No bank found");
|
||||
return;
|
||||
}
|
||||
|
||||
// Walk to bank
|
||||
Position playerPos = api.getPlayerPosition();
|
||||
if (bank.distanceToPlayer(playerPos) > 2.0) {
|
||||
logger.debug("Walking to bank");
|
||||
api.walkTo(bank.getPosition()).get();
|
||||
sleep(2000);
|
||||
}
|
||||
|
||||
// Open bank
|
||||
logger.debug("Opening bank");
|
||||
api.interactWithObject(bank, "Bank").get();
|
||||
sleep(2000);
|
||||
|
||||
// Deposit items
|
||||
for (int itemId : itemsToDeposit) {
|
||||
if (api.hasItem(itemId)) {
|
||||
logger.debug("Depositing item: {}", itemId);
|
||||
// Bank interface interaction would go here
|
||||
sleep(600);
|
||||
}
|
||||
}
|
||||
|
||||
// Withdraw items
|
||||
for (int itemId : itemsToWithdraw) {
|
||||
int quantity = withdrawQuantities.getOrDefault(itemId, 1);
|
||||
logger.debug("Withdrawing {} x {}", quantity, itemId);
|
||||
// Bank interface interaction would go here
|
||||
sleep(600);
|
||||
}
|
||||
|
||||
// Close bank
|
||||
sleep(1000);
|
||||
|
||||
logger.info("Banking completed");
|
||||
}
|
||||
|
||||
private GameObject findNearestBank(AgentAPI api) {
|
||||
// Common bank object IDs
|
||||
int[] bankIds = {10060, 2213, 11758, 14367, 10517};
|
||||
|
||||
for (int bankId : bankIds) {
|
||||
GameObject bank = api.getClosestGameObject(bankId);
|
||||
if (bank != null) {
|
||||
return bank;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScriptMetadata getMetadata() {
|
||||
return new ScriptMetadata(
|
||||
"Banking Utility",
|
||||
"Deposits and withdraws items from bank",
|
||||
"OpenOSRS Agent",
|
||||
"1.0",
|
||||
Arrays.asList("Banking", "Utility", "Helper")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.openosrs.client.scripting.examples;
|
||||
|
||||
import com.openosrs.client.scripting.*;
|
||||
import com.openosrs.client.api.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Simple woodcutting script.
|
||||
*/
|
||||
public class WoodcuttingScript extends AbstractScript {
|
||||
private static final int TREE_ID = 1278; // Oak tree
|
||||
private static final int AXE_ID = 1351; // Bronze axe
|
||||
private static final int LOG_ID = 1521; // Oak logs
|
||||
|
||||
@Override
|
||||
protected void run(ScriptContext context) throws Exception {
|
||||
AgentAPI api = context.getAPI();
|
||||
|
||||
logger.info("Starting woodcutting script");
|
||||
|
||||
// Check if we have an axe
|
||||
if (!api.hasItem(AXE_ID)) {
|
||||
logger.error("No axe found in inventory");
|
||||
return;
|
||||
}
|
||||
|
||||
while (!context.shouldStop()) {
|
||||
context.checkContinue();
|
||||
|
||||
// If inventory is full, drop logs
|
||||
if (api.isInventoryFull()) {
|
||||
dropLogs(api);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find a tree to cut
|
||||
GameObject tree = api.getClosestGameObject(TREE_ID);
|
||||
if (tree == null) {
|
||||
logger.warn("No trees found");
|
||||
sleep(2000);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Walk to tree if not close enough
|
||||
Position playerPos = api.getPlayerPosition();
|
||||
if (tree.distanceToPlayer(playerPos) > 1.0) {
|
||||
logger.debug("Walking to tree");
|
||||
api.walkTo(tree.getPosition()).get();
|
||||
sleep(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Cut the tree
|
||||
logger.debug("Cutting tree");
|
||||
api.interactWithObject(tree, "Chop down").get();
|
||||
|
||||
// Wait for animation to start
|
||||
waitFor(() -> api.getCurrentAnimation() != -1, 3000);
|
||||
|
||||
// Wait for woodcutting to finish
|
||||
waitFor(() -> api.getCurrentAnimation() == -1, 10000);
|
||||
|
||||
sleep(1000);
|
||||
}
|
||||
|
||||
logger.info("Woodcutting script stopped");
|
||||
}
|
||||
|
||||
private void dropLogs(AgentAPI api) throws Exception {
|
||||
logger.debug("Dropping logs");
|
||||
|
||||
for (int i = 0; i < 28; i++) {
|
||||
Item item = api.getInventorySlot(i);
|
||||
if (item != null && item.getItemId() == LOG_ID) {
|
||||
api.dropItem(i).get();
|
||||
sleep(200);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScriptMetadata getMetadata() {
|
||||
return new ScriptMetadata(
|
||||
"Woodcutting",
|
||||
"Cuts oak trees and drops logs",
|
||||
"OpenOSRS Agent",
|
||||
"1.0",
|
||||
Arrays.asList("Woodcutting", "Skilling", "AFK")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.openosrs.client.test;
|
||||
|
||||
import com.openosrs.client.core.bridge.ClientConnector;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Simple test to verify basic bridge structure.
|
||||
*/
|
||||
public class BridgeTest {
|
||||
private static final Logger logger = LoggerFactory.getLogger(BridgeTest.class);
|
||||
|
||||
public static void main(String[] args) {
|
||||
logger.info("Starting bridge connectivity test...");
|
||||
|
||||
// Test that we can instantiate the ClientConnector
|
||||
logger.info("ClientConnector isConnected: {}", ClientConnector.isConnected());
|
||||
|
||||
// Test connection attempt
|
||||
ClientConnector.connectToRuneLiteClient()
|
||||
.thenAccept(client -> {
|
||||
if (client != null) {
|
||||
logger.info("Successfully connected to RuneLite client: {}", client.getClass().getSimpleName());
|
||||
} else {
|
||||
logger.info("No RuneLite client found (expected in test environment)");
|
||||
}
|
||||
})
|
||||
.exceptionally(throwable -> {
|
||||
logger.warn("Connection attempt failed: {}", throwable.getMessage());
|
||||
return null;
|
||||
});
|
||||
|
||||
logger.info("Bridge test completed");
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user