Merge branch 'webwalking_improvements_200524' into 'master'

Webwalking + AIO AIO improvements

See merge request open-runescape-classic/idlersc!104
This commit is contained in:
Ellie committed 2024-06-16 22:18:05 +00:00
commit a26341fc4d
16 files changed
+347 -75

No files matched your search

+1 -1
View File
@@ -717,7 +717,7 @@ public class Main {
pathwalkerButton = new JButton("PathWalker");
// all the buttons on the sidepanel.
takeScreenshotButton = new JButton("Screenshot");
showIdButton = new JButton("Show ID's");
showIdButton = new JButton("Show IDs");
openDebuggerButton = new JButton("Debugger");
resetXpButton = new JButton("Reset XP");
+77 -22
View File
@@ -177,7 +177,8 @@ public class Controller {
long startXp = getTotalXp();
// System.out.println("Stack trace for what called sleepUntilGainedXp:");
// StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
// StackTraceElement[] stackTraceElements =
// Thread.currentThread().getStackTrace();
// for (StackTraceElement element : stackTraceElements) {
// System.out.println(element.toString());
// }
@@ -212,6 +213,21 @@ public class Controller {
return result;
}
/**
* Returns the total level by summing up the player level for each stat.
*
* @return the total level as an int value
*/
public int getTotalLevel() {
int result = 0;
for (int statIndex = 0; statIndex < getStatCount(); statIndex++) {
result += getBaseStat(statIndex);
}
return result;
}
/**
* Whether or not the client is loaded.
*
@@ -743,6 +759,7 @@ public class Controller {
x - mud.getMidRegionBaseX(),
y - mud.getMidRegionBaseZ(),
false);
if (x == currentX() && y == currentY()) return;
if (!sleepUntilMoving(1200)) {
Main.log("Ok so basically, the region we want to walk to isn't loaded.");
Main.log("So I'm going to brute force a path to our dest, then walk half way");
@@ -2482,6 +2499,23 @@ public class Controller {
return true;
}
/**
* Talks to the specified npc by ID.
*
* @param npcId int
* @param waitUntilInDialog boolean -- whether to wait until the dialog is opened before returning
* @return true -- true if request to talk sent, false if we couldn't find the NPC or we timed out
* waiting for the dialog
*/
public boolean talkToNpcId(int npcId, boolean waitUntilInDialog) {
ORSCharacter npc = getNearestNpcById(npcId, false);
if (!talkToNpc(npc.serverIndex)) return false;
if (waitUntilInDialog) {
if (!sleepUntil(() -> isInOptionMenu())) return false;
}
return true;
}
/**
* Whether or not the bank window is currently open.
*
@@ -2503,11 +2537,10 @@ public class Controller {
* @return guaranteed to not be null.
*/
public List<Item> getBankItems() {
List<Item> bankItems = new ArrayList<>();
if (!isInBank()) {
return bankItems;
return new ArrayList<>();
}
ArrayList<Item> bankItems = new ArrayList<>();
ArrayList<Object> _bankItems =
(ArrayList<Object>) reflector.getObjectMemberFromSuperclass(mud.getBank(), "bankItems");
@@ -2538,7 +2571,7 @@ public class Controller {
* Retrieves the amount of the item in the bank.
*
* @param itemId int
* @return int -- returns -1 if bank not open.
* @return int -- returns -1 if bank was not opened this session.
*/
public int getBankItemCount(int itemId) {
if (!this.isInBank()) return -1;
@@ -2556,6 +2589,25 @@ public class Controller {
return 0;
}
/**
* Retrieves the estimated value of everything in your inventory and bank
*
* @return -1 if the bank has not been opened this session, otherwise the sum of all item values
*/
public long getAccountValue() {
long value = 0;
Map<Integer, Integer> bankItems = getDebuggerBank();
if (bankItems == null) return -1;
for (int itemId : bankItems.keySet()) {
ItemDef item = EntityHandler.getItemDef(itemId, false);
value += item.basePrice * getDebuggerBank().get(itemId);
}
for (Item item : getInventoryItems()) {
value += item.getItemDef().basePrice * item.getAmount();
}
return value;
}
/**
* Whether or not the specified item ID is in the bank.
*
@@ -4732,22 +4784,26 @@ public class Controller {
private void walkToActionSource(
mudclient mud, int startX, int startZ, int destX, int destZ, boolean walkToEntity) {
System.out.println(
"Controller walkToActionSource with "
+ startX
+ ", "
+ startZ
+ ", "
+ destX
+ ", "
+ destZ
+ ", "
+ walkToEntity);
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
for (StackTraceElement element : stackTraceElements) {
// System.out.println(element.toString());
}
// Uncomment to debug
/*
* System.out.println(
* "Controller walkToActionSource with "
* + startX
* + ", "
* + startZ
* + ", "
* + destX
* + ", "
* + destZ
* + ", "
* + walkToEntity);
*
* StackTraceElement[] stackTraceElements =
* Thread.currentThread().getStackTrace();
* for (StackTraceElement element : stackTraceElements) {
* System.out.println(element.toString());
* }
*/
reflector.mudInvoker(mud, "walkToActionSource", startX, startZ, destX, destZ, walkToEntity);
}
@@ -6404,7 +6460,6 @@ public class Controller {
}
public void hideWelcomeScreen() {
System.out.println("Attempting to hide login screen...");
mud.setShowDialogMessage(false);
}
@@ -398,7 +398,6 @@ public class PaintBuilder {
if (text != null && color != 0)
c.drawCenteredString(text, x, y, color, r.fontSize);
break;
}
// Draws a row with multiple item sprites and strings for each
@@ -414,11 +413,7 @@ public class PaintBuilder {
cumulativeSpacing += c.getItemSpriteScaledWidth(id, scale) + r.spriteSpacing;
int spriteX = r.borderXOffset + cumulativeSpacing;
int spriteY =
pY
+ rowsY
+ cumulativeRowHeight
+ (int) (c.getItemSpriteScaledWidth(id, scale) / 2);
int spriteY = pY + rowsY + cumulativeRowHeight;
int stringX = pX + r.borderXOffset + stringXOffset + cumulativeSpacing;
int stringY = pY + spriteY + r.stringYOffset + 11;
@@ -351,6 +351,91 @@ public class CustomLabelHandlers {
.sleepUntil(() -> Main.getController().getObjectAtCoord(xCoord, yCoord) == 59, 6000);
}
public static boolean brimhavenArdyBoat() {
// Only gets considered if we have 60gp, to prevent getting stuck.
boolean comingFromArdy =
Main.getController().getNearestNpcById(NpcId.CAPTAIN_BARNABY.getId(), false) != null;
if (comingFromArdy) {
// Talk to Barnaby
Main.getController().talkToNpcId(NpcId.CAPTAIN_BARNABY.getId(), true);
Main.getController().optionAnswer(1);
} else {
// Talk to Customs Official
Main.getController().talkToNpcId(NpcId.CUSTOMS_OFFICIAL.getId(), true);
Main.log("Answering..");
Main.getController().optionAnswer(0); // Can I board this ship?
Main.getController().sleepUntil(() -> !Main.getController().isInOptionMenu());
Main.getController().sleepUntil(() -> Main.getController().isInOptionMenu());
Main.getController().optionAnswer(1); // Search away, I have nothing to hide
Main.getController().sleepUntil(() -> !Main.getController().isInOptionMenu());
Main.getController().sleepUntil(() -> Main.getController().isInOptionMenu());
Main.getController().optionAnswer(0); // Ok
}
return Main.getController()
.sleepUntil(
() ->
comingFromArdy
? Main.getController().getNearestNpcById(NpcId.CAPTAIN_BARNABY.getId(), false)
== null
: Main.getController().getNearestNpcById(NpcId.CUSTOMS_OFFICIAL.getId(), false)
== null);
}
public static boolean portSarimKaramjaBoat() {
// Only gets considered if we have 60gp, to prevent getting stuck.
boolean comingFromKaramja =
Main.getController().getNearestNpcById(NpcId.CUSTOMS_OFFICER.getId(), false) != null;
if (comingFromKaramja) {
// Talk to Customs Officer
Main.getController().talkToNpcId(NpcId.CUSTOMS_OFFICER.getId(), true);
Main.log("Answering..");
Main.getController().optionAnswer(0); // Can I board this ship?
Main.getController().sleepUntil(() -> !Main.getController().isInOptionMenu());
Main.getController().sleepUntil(() -> Main.getController().isInOptionMenu());
Main.getController().optionAnswer(1); // Search away, I have nothing to hide
Main.getController().sleepUntil(() -> !Main.getController().isInOptionMenu());
Main.getController().sleepUntil(() -> Main.getController().isInOptionMenu());
Main.getController().optionAnswer(0); // Ok
} else {
Main.log("Talking to Npc..");
// Talk to Seaman Lorris
Main.getController().talkToNpcId(NpcId.SEAMAN_LORRIS.getId(), true);
Main.getController().sleepUntil(() -> !Main.getController().isInOptionMenu());
Main.getController().sleepUntil(() -> Main.getController().isInOptionMenu());
Main.getController().optionAnswer(1);
}
return Main.getController()
.sleepUntil(
() ->
comingFromKaramja
? Main.getController().getNearestNpcById(NpcId.CUSTOMS_OFFICER.getId(), false)
== null
: Main.getController().getNearestNpcById(NpcId.SEAMAN_LORRIS.getId(), false)
== null);
}
public static boolean brimhavenKaramjaGate() {
boolean goingEast = Main.getController().currentX() >= 435;
// Interact with the gate
Main.getController().atObject(434, 682);
// Sleep until we're on the other side of the gate
return Main.getController()
.sleepUntil(
() ->
goingEast
? Main.getController().currentX() < 435
: Main.getController().currentX() >= 435);
}
public static boolean lummyEastChickenGate() {
// Check if the gate is open; if object at 114, 608 == 59, gate is open and we
// just walk through
if (Main.getController().getObjectAtCoord(114, 608) == 59) return true;
// Interact with the gate
Main.getController().atObject(114, 608);
return true;
}
public static boolean wizardTowerBasement() {
boolean goingDown = Main.getController().currentY() <= 1000;
int ladderId = goingDown ? 6 : 5;
@@ -205,6 +205,9 @@ public class WebWalker {
|| Main.getController().getInventoryItemCount(ItemId.COINS.getId()) >= 10;
case "miningGuildDoor":
return Main.getController().getCurrentStat(Main.getController().getStatId("Mining")) >= 60;
case "portSarimKaramjaBoat":
case "brimhavenKaramjaGate":
return Main.getController().getInventoryItemCount(ItemId.COINS.getId()) >= 60;
default:
return true;
}
@@ -262,6 +265,14 @@ public class WebWalker {
return CustomLabelHandlers.varrockPalaceFence();
case "varrockEastDigsiteGate":
return CustomLabelHandlers.varrockEastDigsiteGate();
case "brimhavenArdyBoat":
return CustomLabelHandlers.brimhavenArdyBoat();
case "portSarimKaramjaBoat":
return CustomLabelHandlers.portSarimKaramjaBoat();
case "brimhavenKaramjaGate":
return CustomLabelHandlers.brimhavenKaramjaGate();
case "lummyEastChickenGate":
return CustomLabelHandlers.lummyEastChickenGate();
case "wizardTowerDoor":
return CustomLabelHandlers.wizardTowerDoor();
case "wizardTowerBasement":
@@ -15,22 +15,22 @@ public class WebwalkTester extends IdleScript {
private boolean scriptStarted = false;
private Integer currentDestX = null;
private Integer currentDestY = null;
private String[] initialParameters;
public int start(String[] parameters) {
if (!scriptStarted) {
if (!startup(parameters)) stop();
}
Main.getController().hideWelcomeScreen();
if (currentDestX == null && currentDestY == null) {
if (coordinates.isEmpty()) {
Main.getController().log("All destinations reached!");
stop();
return 0;
} else {
currentDestX = coordinates.poll();
currentDestY = coordinates.poll();
Main.getController().log("New destination: " + currentDestX + ", " + currentDestY);
Main.getController().log("All destinations reached! Restarting loop.");
startup(initialParameters); // Restart the loop by reinitializing coordinates
}
currentDestX = coordinates.poll();
currentDestY = coordinates.poll();
Main.getController().log("New destination: " + currentDestX + ", " + currentDestY);
}
if (Main.getController().currentX() == currentDestX
@@ -48,6 +48,7 @@ public class WebwalkTester extends IdleScript {
private boolean startup(String[] parameters) {
scriptStarted = true;
initialParameters = parameters;
if (parameters.length != 1) {
Main.getController()
.log("Invalid parameters. Please pass in coordinates as comma-separated integers.");
@@ -19,12 +19,12 @@ public class AIOAIO extends IdleScript {
*/
public static AIOAIO_State state = new AIOAIO_State();
public static final String VERSION = "1.15.2";
public static final String VERSION = "1.18.1";
public int start(String[] parameters) {
paintBuilder.start(4, 4, 240);
if (!state.guiSetup) {
state.guiSetup = true;
Main.getController().hideWelcomeScreen();
if (parameters.length >= 1 && parameters[0].equals("nogui")) {
state.startPressed = true;
} else {
@@ -40,7 +40,7 @@ public class AIOAIO extends IdleScript {
return 1500;
} catch (Throwable t) {
t.printStackTrace();
Main.log("A straight JVM error occured! " + t.getMessage());
Main.log("A straight JVM error occured! Check stdout for details.");
Main.log("I don't know _why_ this happens! Just gonna sleep 20s and ignore it lol");
return 20000;
}
@@ -49,6 +49,9 @@ public class AIOAIO extends IdleScript {
}
private int loop() {
if (!state.postLoginSetup && Main.getController().isLoggedIn()) {
loginSetup();
}
if (System.currentTimeMillis() >= state.endTime) {
state.currentSkill = state.botConfig.getRandomEnabledSkill();
state.currentTask = state.currentSkill.getRandomEnabledTask();
@@ -62,22 +65,18 @@ public class AIOAIO extends IdleScript {
state.endTime = System.currentTimeMillis() + 600_000;
state.taskStartup = true;
}
AIOAIO_Script_Utils.checkAccountValue();
return state.currentTask.getAction().get();
}
private void loginSetup() {
Main.getController().hideWelcomeScreen();
state.initLevel = Main.getController().getTotalLevel();
state.postLoginSetup = true;
}
@Override
public void paintInterrupt() {
Main.getController().drawString("@red@AIOAIO v" + AIOAIO.VERSION, 6, 21, 0xFFFFFF, 1);
String currentSkillText =
"Current Skill: " + (state.currentSkill != null ? state.currentSkill.getName() : "None");
String currentTaskText =
"Current Task: " + (state.currentTask != null ? state.currentTask.getName() : "None");
Main.getController().drawString("@red@" + currentSkillText, 6, 35, 0xFFFFFF, 1);
Main.getController().drawString("@red@AIOAIO status: " + state.status, 6, 49, 0xFFFFFF, 1);
Main.getController().drawString("@red@" + currentTaskText, 6, 63, 0xFFFFFF, 1);
long timeRemaining = state.endTime - System.currentTimeMillis();
String timeRemainingText =
"Time remaining: " + (timeRemaining > 0 ? timeRemaining / 1000 + " seconds" : "None");
Main.getController().drawString("@red@" + timeRemainingText, 6, 77, 0xFFFFFF, 1);
AIOAIO_Paint.paint(paintBuilder, rowBuilder);
}
}
@@ -0,0 +1,108 @@
package scripting.idlescript.other.AIOAIO;
import bot.Main;
import controller.PaintBuilder.PaintBuilder;
import controller.PaintBuilder.RowBuilder;
import models.entities.ItemId;
public class AIOAIO_Paint {
public static void paint(PaintBuilder paintBuilder, RowBuilder rowBuilder) {
paintBuilder.setBorderColor(0xBD93F9);
paintBuilder.setBackgroundColor(0x282A36, 125);
paintBuilder.setTitleCenteredSingleColor(
AIOAIO.state.currentSkill != null ? AIOAIO.state.currentSkill.getName() : "Initializing",
0xcfdf1f,
6);
paintBuilder.addRow(
rowBuilder.centeredSingleStringRow(
AIOAIO.state.currentTask != null
? AIOAIO.state.currentTask.getName() + taskTimeRemaining()
: "",
0xffffff,
1));
paintBuilder.addRow(rowBuilder.centeredSingleStringRow(AIOAIO.state.status, 0xffffff, 1));
paintBuilder.addRow(
rowBuilder.multiItemSpriteRow(
new int[] {
ItemId.CASKET.getId(),
ItemId.EYE_PATCH.getId(),
ItemId.COINS.getId(),
ItemId.EYE_PATCH.getId()
},
new int[] {100, 25, 140, 25},
new String[] {
paintBuilder.stringFormatInt(Main.getController().getTotalLevel()),
"(+ " + (Main.getController().getTotalLevel() - AIOAIO.state.initLevel) + ")",
formatValue(AIOAIO.state.lastCheckedBankValue),
"(+ "
+ formatValue(AIOAIO.state.lastCheckedBankValue - AIOAIO.state.initBankValue)
+ ")"
},
new int[] {
0xcfdf1f, 0x00ff00,
0xcfdf1f, 0x00ff00,
},
0,
22,
0,
0));
paintBuilder.addRow(
rowBuilder.centeredSingleStringRow("AIO AIO v" + AIOAIO.VERSION, 0xBD93F9, 1));
paintBuilder.draw();
}
private static String taskTimeRemaining() {
long timeRemaining = AIOAIO.state.endTime - System.currentTimeMillis();
if (timeRemaining <= 0) {
return " 00:00";
}
long totalSeconds = timeRemaining / 1000;
long hours = totalSeconds / 3600;
long minutes = (totalSeconds % 3600) / 60;
long seconds = totalSeconds % 60;
if (hours > 0) {
return String.format(" %d:%02d:%02d", hours, minutes, seconds);
} else {
return String.format(" %02d:%02d", minutes, seconds);
}
}
/**
* Convert a number to a string that looks nice for cash -1 is returned as ? Everything else is
* returned with 3 digits and an accompanying K/M/B I.e 126 is returned as 126 5896 is 5.9K 123456
* is 123K 18203515 is 18.2M
*
* @param value long
* @return String Nicely string represented value of the number
*/
private static String formatValue(long value) {
if (value == -1) return "?";
if (value < 1000) {
return String.valueOf(value);
} else if (value < 1000000) {
// Values in thousands with a single decimal place
double thousands = value / 1000.0;
if (thousands >= 100) {
return String.format("%.0fK", thousands);
} else {
return String.format("%.1fK", thousands);
}
} else if (value < 1000000000) {
// Values in millions with a single decimal place
double millions = value / 1000000.0;
if (millions >= 100) {
return String.format("%.0fM", millions);
} else {
return String.format("%.1fM", millions);
}
} else {
// Values in billions with a single decimal place
double billions = value / 1000000000.0;
return String.format("%.1fB", billions);
}
}
}
@@ -119,4 +119,14 @@ public class AIOAIO_Script_Utils {
// Main.getController().log("Dist to bank: " + dist);
return dist;
}
static long lastBankCheckTime = 0;
public static void checkAccountValue() {
// Kinda sorta not really expensive, so only do it every 10s
if (System.currentTimeMillis() - lastBankCheckTime < 10000) return;
AIOAIO.state.lastCheckedBankValue = Main.getController().getAccountValue();
if (AIOAIO.state.initBankValue == -1)
AIOAIO.state.initBankValue = AIOAIO.state.lastCheckedBankValue;
}
}
@@ -2,8 +2,8 @@ package scripting.idlescript.other.AIOAIO.core;
public class AIOAIO_State {
/**
* This class exists to not clutter up AIOAIO.java tbh Try to keep things isolated! The bot should
* never get into a confusing state
* This class exists to not clutter up AIOAIO.java tbh. Try to keep things isolated! The bot
* should never get into a confusing state.
*/
// --- Global Management State --- \\
@@ -11,6 +11,7 @@ public class AIOAIO_State {
public AIOAIO_Task currentTask;
public AIOAIO_Config botConfig = new AIOAIO_Config();
public boolean postLoginSetup = false; // Whether we completed any setup needed on first login
public boolean guiSetup = false;
public boolean startPressed = false;
public boolean taskStartup =
@@ -26,4 +27,9 @@ public class AIOAIO_State {
// --- Mining state variables --- \\
public boolean hasPickInBank = true; // Bot will set this to false if needed
// --- Paint State Variables --- \\
public int initLevel = -1; // What total level we started at
public long initBankValue = -1;
public long lastCheckedBankValue = -1;
}
@@ -32,6 +32,7 @@ public class Fish {
else if (!hasFishingTool()) return getToolFromBank();
else if (c.getInventoryItemCount() >= 30)
AIOAIO_Script_Utils.towardsDepositAll(fishTool().getId(), ItemId.FEATHER.getId());
else if (c.isInCombat()) return findFishingSpot(); // Get out of combat
else if (c.isBatching()) return 250; // Wait to finish fishing
else if (c.getNearestReachableObjectById(getFishingSpotId(), true) != null) return fish();
else return findFishingSpot();
@@ -26,11 +26,8 @@ public class AlKharidMan {
else if (Combat_Utils.needToEat()) Combat_Utils.runAndEat();
else if (c.isInCombat()) {
Thieving_Utils.leaveCombat();
} else if (c.getNearestNpcById(NpcId.MAN_ALKHARID.getId(), false) == null) findAlkharidMen();
else {
Thieving_Utils.theiveNpc(NpcId.MAN_ALKHARID);
return 680;
}
} else if (Thieving_Utils.getNpc(NpcId.MAN_ALKHARID) == null) findAlkharidMen();
else return Thieving_Utils.theiveNpc(NpcId.MAN_ALKHARID);
return 50;
}
@@ -31,12 +31,8 @@ public class AlKharidWarrior {
else if (Combat_Utils.needToEat()) Combat_Utils.runAndEat();
else if (c.isInCombat()) {
Thieving_Utils.leaveCombat();
} else if (c.getNearestNpcById(NpcId.ALKHARID_WARRIOR.getId(), false) == null)
findAlkharidWarriors();
else {
Thieving_Utils.theiveNpc(NpcId.ALKHARID_WARRIOR);
return 680;
}
} else if (Thieving_Utils.getNpc(NpcId.ALKHARID_WARRIOR) == null) findAlkharidWarriors();
else return Thieving_Utils.theiveNpc(NpcId.ALKHARID_WARRIOR);
return 50;
}
@@ -32,11 +32,8 @@ public class LumbridgeFarmer {
else if (Combat_Utils.needToEat()) Combat_Utils.runAndEat();
else if (c.isInCombat()) {
Thieving_Utils.leaveCombat();
} else if (c.getNearestNpcById(NpcId.FARMER.getId(), false) == null) findLummyFarmer();
else {
Thieving_Utils.theiveNpc(NpcId.FARMER);
return 680;
}
} else if (Thieving_Utils.getNpc(NpcId.FARMER) == null) findLummyFarmer();
else return Thieving_Utils.theiveNpc(NpcId.FARMER);
return 50;
}
@@ -1,6 +1,7 @@
package scripting.idlescript.other.AIOAIO.thieving;
import bot.Main;
import controller.Controller;
import models.entities.ItemId;
import models.entities.NpcId;
import orsc.ORSCharacter;
@@ -18,9 +19,22 @@ public class Thieving_Utils {
Main.getController().sleep(600);
}
public static ORSCharacter getNpc(NpcId npcId) {
Controller c = Main.getController();
return c.getNearestNPCByLambda(
n ->
n.npcId == npcId.getId()
&& !c.isNpcInCombat(n.serverIndex)
&& c.isReachable(
c.getNpcCoordsByServerIndex(n.serverIndex)[0],
c.getNpcCoordsByServerIndex(n.serverIndex)[1],
true));
}
public static int theiveNpc(NpcId npcId) {
if (Main.getController().isBatching()) return 600;
AIOAIO.state.status = ("@yel@Thieving " + npcId.name());
ORSCharacter npc = Main.getController().getNearestNpcById(npcId.getId(), false);
ORSCharacter npc = getNpc(npcId);
if (npc == null) {
AIOAIO.state.status = ("@yel@Can't find a " + npcId.name() + " :c");
return 50;
@@ -31,11 +31,8 @@ public class VarrockGuard {
else if (Combat_Utils.needToEat()) Combat_Utils.runAndEat();
else if (c.isInCombat()) {
Thieving_Utils.leaveCombat();
} else if (c.getNearestNpcById(NpcId.GUARD.getId(), false) == null) findVarrockGuards();
else {
Thieving_Utils.theiveNpc(NpcId.GUARD);
return 680;
}
} else if (Thieving_Utils.getNpc(NpcId.GUARD) == null) findVarrockGuards();
else return Thieving_Utils.theiveNpc(NpcId.GUARD);
return 50;
}