- Joined
- Nov 17, 2014
- Messages
- 4,906
- Thread Author
- #1
Introduction
The problem: Patterns
Even simple bots like bankstanding bots can generate detectable patterns very easily, for example your everyday bankstanding bot....
Well that's correct, but one essential thing is missing in this construct: the playstyle of a legit player.
How much attention are you paying when doing bankstanding tasks? Do you watch movies concurrently? How efficient are you? Do you use your keyboard a lot? Are you always hovering the next thing to click on, or are you just moving your mouse when needed? How much attention are you paying while waiting for something ingame?
All those questions are questions about your personal playstyle, and if every user would answer them, you would be surprised of how different the set of answers can be from user to user.
Now watch your bot play for a while and you will notice a certain playstyle the bot comes up with.
The big problem? Every. Single. User. has that very playstyle when using your bot.
And yes, you may be using random delay timeouts here and there, but no, since every user has the same intervals of possible random values, they still represent the same playstyle.
The solution: PlayerSense
PlayerSense is an API used to simulate different playstyles for every user and every RuneScape account, which ideally leads to a prevention of patterns across all RuneMate users.
Technically, PlayerSense is a persistent, instanced key->value map, where an instance is an account you have added to RuneMate Spectre. That means you are feeding it a random value for each account the user runs it with, this way you end up with a playstyle in form of a map.
For example, a colloquial mapping of keys (questions) to values (answers, different for each player) for your PlayerSense:
There already are existing PlayerSense keys you can use, which especially high-level methods of the API make frequent use of (JDocs).
Now that we have cleared things up, it's time to get practical. In the second chapter of this thread I will show you an example implementation of custom PlayerSense keys you can use in your bots.
Implementation
A basic understanding of enums is recommended
Preparing a set of keys
We start of with an empty class, which will later contain our keys and a few methods used to manage them.
In that class, we now create an enum for the keys, just as in the API.
Now that we have the keys we want to use in the bot later on, it's time to make them PlayerSense conform. When taking a look at the documentation, we can see that methods like put and get require a String value as key, and furthermore we need suppliers for each key to feed the PlayerSense database with.
As you can see, the names all have a certain prefix, you should do that in order to prevent collisions with other bot authors.
We have our keys prepared, now it's time to feed them to PlayerSense. In order to do that, we will make a method called initializeKeys() in the class we created.
To feed the values properly and don't overwrite them everytime, it's important to know that PlayerSense.get(String) will return null if the key has no associated value yet.
The added method will go over all the keys we have, and if the current key has not been initialized yet, it will do so by putting a random value generated by the key's value supplier into PlayerSense.
We are essentially done with the CustomPlayerSense class. Accessing the values may be done with calling PlayerSense.getAsDouble(CustomPlayerSense.Key.ACTIVENESS_FACTOR_WHILE_WAITING.getKey()) for example, and as you may notice, that is pretty long and will waste a lot of space in your code.
To make accessing the values a bit less ugly, we will add convenience methods to the Key enum.
Now, accessing a value will be as short as
CustomPlayerSense.Key.ACTIVENESS_FACTOR_WHILE_WAITING.getAsDouble(). That's still fairly long but as short as it gets.
That's basically it for the class, but since the PlayerSense database will be different for each account in RuneMate Spectre, we need to call the initializeKeys() method whenever a bot of yours is started.
Calling the method in the overridden onStart method will do the job.
Using PlayerSense in your code
Congratulations, you have prepared your bot to be capable of simulating tons of different playstyles, but the main aspect of using PlayerSense is actually utilizing the API in your bot logic.
This task will eat sharks in order to heal, although if the health of the player is extremely low (which can, and should, also be depending on PlayerSense), the bot will maybe spam click the food to simulate a very stressful player.
Conclusion
PlayerSense should be used wherever you can, the more places you use it the better. Make the ranges of possible random values large enough to allow a change in the bot's behavior.
Also, you should have the courage to not only use it to deviate some values a little, use it for fairly large things as well, for example changing the entire way of prioritizing monsters to attack, or which transportation system to use.
Thanks for reading this guide, which turned out larger than I thought.
The problem: Patterns
Even simple bots like bankstanding bots can generate detectable patterns very easily, for example your everyday bankstanding bot....
- Opens the bank
- Withdraws item A
- Withdraws item B
- Closes the bank
- Combines A with B
- Does nothing until the operation is complete
Well that's correct, but one essential thing is missing in this construct: the playstyle of a legit player.
How much attention are you paying when doing bankstanding tasks? Do you watch movies concurrently? How efficient are you? Do you use your keyboard a lot? Are you always hovering the next thing to click on, or are you just moving your mouse when needed? How much attention are you paying while waiting for something ingame?
All those questions are questions about your personal playstyle, and if every user would answer them, you would be surprised of how different the set of answers can be from user to user.
Now watch your bot play for a while and you will notice a certain playstyle the bot comes up with.
The big problem? Every. Single. User. has that very playstyle when using your bot.
And yes, you may be using random delay timeouts here and there, but no, since every user has the same intervals of possible random values, they still represent the same playstyle.
The solution: PlayerSense
PlayerSense is an API used to simulate different playstyles for every user and every RuneScape account, which ideally leads to a prevention of patterns across all RuneMate users.
Technically, PlayerSense is a persistent, instanced key->value map, where an instance is an account you have added to RuneMate Spectre. That means you are feeding it a random value for each account the user runs it with, this way you end up with a playstyle in form of a map.
For example, a colloquial mapping of keys (questions) to values (answers, different for each player) for your PlayerSense:
- How likely is it for you to play at maximum efficiency? 82%
- In what order do you usually drop your items? In a top-to-bottom order
- How frequently are you using hotkeys? 32% of the time
- How good are your reflexes in terms of delay? About 212 ms
There already are existing PlayerSense keys you can use, which especially high-level methods of the API make frequent use of (JDocs).
Now that we have cleared things up, it's time to get practical. In the second chapter of this thread I will show you an example implementation of custom PlayerSense keys you can use in your bots.
Implementation
A basic understanding of enums is recommended
Preparing a set of keys
We start of with an empty class, which will later contain our keys and a few methods used to manage them.
Code:
public class CustomPlayerSense {
}
In that class, we now create an enum for the keys, just as in the API.
Code:
public class CustomPlayerSense {
public enum Key {
ACTIVENESS_FACTOR_WHILE_WAITING,
SPAM_CLICK_COUNT,
REACION_TIME,
SPAM_CLICK_TO_HEAL
}
}
Now that we have the keys we want to use in the bot later on, it's time to make them PlayerSense conform. When taking a look at the documentation, we can see that methods like put and get require a String value as key, and furthermore we need suppliers for each key to feed the PlayerSense database with.
Code:
public class CustomPlayerSense {
public enum Key {
ACTIVENESS_FACTOR_WHILE_WAITING("prime_activeness_factor", () -> Random.nextDouble(0.2, 0.8)),
SPAM_CLICK_COUNT("prime_spam_click_count", () -> Random.nextInt(2, 6)),
REACION_TIME("prime_reaction_time", () -> Random.nextLong(160, 260)),
SPAM_CLICK_TO_HEAL("prime_spam_healing", () -> Random.nextBoolean());
private final String name;
private final Supplier supplier;
Key(String name, Supplier supplier) {
this.name = name;
this.supplier = supplier;
}
public String getKey() {
return name;
}
}
}
As you can see, the names all have a certain prefix, you should do that in order to prevent collisions with other bot authors.
We have our keys prepared, now it's time to feed them to PlayerSense. In order to do that, we will make a method called initializeKeys() in the class we created.
To feed the values properly and don't overwrite them everytime, it's important to know that PlayerSense.get(String) will return null if the key has no associated value yet.
Code:
public class CustomPlayerSense {
public static void initializeKeys() {
for (Key key : Key.values()) {
if (PlayerSense.get(key.name) == null) {
PlayerSense.put(key.name, key.supplier.get());
}
}
}
public enum Key {
ACTIVENESS_FACTOR_WHILE_WAITING("prime_activeness_factor", () -> Random.nextDouble(0.2, 0.8)),
SPAM_CLICK_COUNT("prime_spam_click_count", () -> Random.nextInt(2, 6)),
REACION_TIME("prime_reaction_time", () -> Random.nextLong(160, 260)),
SPAM_CLICK_TO_HEAL("prime_spam_healing", () -> Random.nextBoolean());
private final String name;
private final Supplier supplier;
Key(String name, Supplier supplier) {
this.name = name;
this.supplier = supplier;
}
public String getKey() {
return name;
}
}
}
We are essentially done with the CustomPlayerSense class. Accessing the values may be done with calling PlayerSense.getAsDouble(CustomPlayerSense.Key.ACTIVENESS_FACTOR_WHILE_WAITING.getKey()) for example, and as you may notice, that is pretty long and will waste a lot of space in your code.
To make accessing the values a bit less ugly, we will add convenience methods to the Key enum.
Code:
public class CustomPlayerSense {
public static void initializeKeys() {
for (Key key : Key.values()) {
if (PlayerSense.get(key.name) == null) {
PlayerSense.put(key.name, key.supplier.get());
}
}
}
public enum Key {
ACTIVENESS_FACTOR_WHILE_WAITING("prime_activeness_factor", () -> Random.nextDouble(0.2, 0.8)),
SPAM_CLICK_COUNT("prime_spam_click_count", () -> Random.nextInt(2, 6)),
REACION_TIME("prime_reaction_time", () -> Random.nextLong(160, 260)),
SPAM_CLICK_TO_HEAL("prime_spam_healing", () -> Random.nextBoolean());
private final String name;
private final Supplier supplier;
Key(String name, Supplier supplier) {
this.name = name;
this.supplier = supplier;
}
public String getKey() {
return name;
}
public Integer getAsInteger() {
return PlayerSense.getAsInteger(name);
}
public Double getAsDouble() {
return PlayerSense.getAsDouble(name);
}
public Long getAsLong() {
return PlayerSense.getAsLong(name);
}
public Boolean getAsBoolean() {
return PlayerSense.getAsBoolean(name);
}
}
}
CustomPlayerSense.Key.ACTIVENESS_FACTOR_WHILE_WAITING.getAsDouble(). That's still fairly long but as short as it gets.
That's basically it for the class, but since the PlayerSense database will be different for each account in RuneMate Spectre, we need to call the initializeKeys() method whenever a bot of yours is started.
Code:
public class YourMainClass extends TreeBot {
@Override
public void onStart(String... strings) {
CustomPlayerSense.initializeKeys();
}
}
Using PlayerSense in your code
Congratulations, you have prepared your bot to be capable of simulating tons of different playstyles, but the main aspect of using PlayerSense is actually utilizing the API in your bot logic.
Code:
@Override
public void execute() {
final SpriteItem food = Inventory.newQuery().actions("Eat").names("Shark").results().first();
if (food != null) {
if (isHealthDangerouslyLow() && CustomPlayerSense.Key.SPAM_CLICK_TO_HEAL.getAsBoolean()) {
final int clicks = CustomPlayerSense.Key.SPAM_CLICK_COUNT.getAsInteger();
for (int i = 0; i < clicks && food.isValid(); i++) {
food.click();
}
} else {
food.interact("Eat");
}
}
}
This task will eat sharks in order to heal, although if the health of the player is extremely low (which can, and should, also be depending on PlayerSense), the bot will maybe spam click the food to simulate a very stressful player.
Conclusion
PlayerSense should be used wherever you can, the more places you use it the better. Make the ranges of possible random values large enough to allow a change in the bot's behavior.
Also, you should have the courage to not only use it to deviate some values a little, use it for fairly large things as well, for example changing the entire way of prioritizing monsters to attack, or which transportation system to use.
Thanks for reading this guide, which turned out larger than I thought.
Last edited: